/* /Components/Account/TeamMemberTable.razor.rz.scp.css */
/* The grid renders its own chrome. All that is needed here is a gap between the email and the
   "you" pill that follows it, since both sit in one cell. */
[b-sqvu5y335s] .team-email {
    margin-right: var(--cp-space-2);
}
/* /Components/AppPageHeader.razor.rz.scp.css */
.app-page-header[b-20ruj5caqp] {
	width: 100%;
	/* Same width and gutter as .app-content in MainLayout.razor.css, from the same tokens, so the
	   heading always sits directly above the page content rather than beside it. The scroll area
	   around that column reserves its scrollbar on both sides (scrollbar-gutter in
	   MainLayout.razor.css), which is what keeps this header — outside the scroll area — from
	   sitting half a scrollbar to the right of the content it heads. */
	max-width: var(--cp-content-max);
	padding: 0 var(--cp-content-gutter);
	/* Title on the left, whatever the page offers on the right. Baselines rather than centres: the
	   buttons should sit on the heading's line, not float in the middle of a two-line title block. */
	display: flex;
	flex-direction: row;
	align-items: flex-end;
	justify-content: space-between;
	gap: var(--cp-space-4);
	flex-wrap: wrap;
}

.app-page-header-titles[b-20ruj5caqp] {
	min-width: 0;
}

.app-page-header-actions[b-20ruj5caqp] {
	display: flex;
	flex-direction: row;
	align-items: center;
	gap: var(--cp-space-2);
	flex-wrap: wrap;
}

@media screen and (max-width: 634px) {
	.app-page-header[b-20ruj5caqp] {
		padding: 0 var(--cp-space-4);
		margin-top: 0;
	}
}
/* /Components/Common/Address/AddressLookup.razor.rz.scp.css */
/* Address lookup. Every colour, radius, shadow and measurement comes through the --cp-* facade —
   no DevExpress variable, no Bootstrap variable, no hex (CLAUDE.md, ADR-0008), and spacing off the
   --cp-space-* rungs rather than whatever value happened to look right.

   The box is hand-built rather than a DevExpress editor (see the note at the top of the markup), so
   it is built the way one is: the shell carries the border, the radius and the focus underline, and
   the input inside it is transparent and borderless. The numbers come from --cp-control-*, which
   the facade reads off the editor's own variables at its default size — so this box and the
   DxTextBox editors it feeds are the same height and take focus the same way, and a DevExpress
   theme or size change moves both together instead of leaving this one behind. */

/* Positioning context for the dropdown, and the element the caller's CssClass lands on. */
.address-lookup[b-1yvuk4dpv1] {
    position: relative;
}

/* The editor shell: what a Dx editor's outer element is. Bordered on three sides with a subdued
   underline on the fourth, which is the Fluent editor's resting state. */
.address-lookup__field[b-1yvuk4dpv1] {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--cp-space-2);
    padding: var(--cp-control-padding-y) var(--cp-control-padding-x);
    border: var(--cp-control-border-width) solid var(--cp-color-border);
    border-bottom-color: var(--cp-control-underline);
    border-radius: var(--cp-control-radius);
    background: var(--cp-control-bg);
}

.address-lookup__field:hover[b-1yvuk4dpv1] {
    border-color: var(--cp-control-border-hover);
    border-bottom-color: var(--cp-control-underline-hover);
}

/* Focus is the editor's own: a thicker brand line along the bottom edge, drawn over the border so
   the box does not change height when it is taken. No outline ring — the DevExpress editors beside
   it do not paint one, and one box answering focus differently from its neighbours reads as a
   different kind of control. */
.address-lookup__field:focus-within[b-1yvuk4dpv1]::after {
    content: "";
    position: absolute;
    left: calc(-1 * var(--cp-control-border-width));
    right: calc(-1 * var(--cp-control-border-width));
    bottom: calc(-1 * var(--cp-control-border-width));
    height: max(var(--cp-control-underline-focus-size), var(--cp-control-radius));
    border-bottom: var(--cp-control-underline-focus-size) solid var(--cp-control-underline-focus);
    border-bottom-left-radius: var(--cp-control-radius);
    border-bottom-right-radius: var(--cp-control-radius);
    pointer-events: none;
}

/* In flow rather than absolutely positioned, so the text starts after it without the input needing
   padding that has to be kept in step with the icon's width. */
.address-lookup__icon[b-1yvuk4dpv1] {
    flex: none;
    color: var(--cp-color-content-muted);
    pointer-events: none;
}

/* Transparent and borderless: the shell above is the control, this is only where the text goes —
   the same division of labour as a Dx editor and its inner input. */
.address-lookup__input[b-1yvuk4dpv1] {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0;
    border: none;
    background: none;
    color: var(--cp-color-content);
    font-family: var(--cp-font-family-base);
    font-size: var(--cp-control-font-size);
    line-height: var(--cp-control-line-height);
}

.address-lookup__input:focus[b-1yvuk4dpv1] {
    outline: none;
}

.address-lookup__input[b-1yvuk4dpv1]::placeholder {
    color: var(--cp-control-placeholder);
}

.address-lookup__field:has(.address-lookup__input:disabled)[b-1yvuk4dpv1] {
    border-color: var(--cp-control-border-disabled);
    background: var(--cp-color-surface-muted);
}

.address-lookup__input:disabled[b-1yvuk4dpv1] {
    color: var(--cp-control-content-disabled);
    cursor: not-allowed;
}

.address-lookup__field:has(.address-lookup__input:disabled) .address-lookup__icon[b-1yvuk4dpv1] {
    color: var(--cp-control-content-disabled);
}

/* The quota message and its siblings. Below the box, in flow, because unlike the dropdown it stays
   until the customer does something about it and must not sit over the fields it points at. */
.address-lookup__message[b-1yvuk4dpv1] {
    display: flex;
    align-items: flex-start;
    gap: var(--cp-space-2);
    margin-top: var(--cp-space-2);
    color: var(--cp-color-content-muted);
}
/* /Components/Common/Address/AddressSuggestionList.razor.rz.scp.css */
/* The address lookup's dropdown. Every colour, radius, shadow and measurement comes through the
   --cp-* facade — no DevExpress variable, no Bootstrap variable, no hex (CLAUDE.md, ADR-0008), and
   spacing off the --cp-space-* rungs. */

/* Absolutely positioned against the lookup's own container, so opening the list does not push the
   editors down the card and back up again. z-index is above the form beneath it and nothing else —
   this is not a modal. */
.address-lookup__menu[b-f4qmhv1gsv] {
    position: absolute;
    top: calc(100% + var(--cp-space-1));
    left: 0;
    right: 0;
    z-index: 20;
    margin: 0;
    padding: var(--cp-space-1);
    list-style: none;
    max-height: 16rem;
    overflow-y: auto;
    border: 1px solid var(--cp-color-border);
    border-radius: var(--cp-control-radius);
    background: var(--cp-color-surface);
    box-shadow: var(--cp-shadow-2);
}

.address-lookup__option[b-f4qmhv1gsv] {
    display: flex;
    align-items: center;
    gap: var(--cp-space-2);
    padding: var(--cp-space-2) var(--cp-space-3);
    cursor: pointer;
    color: var(--cp-color-content);
    font-size: var(--cp-text-body2-size);
    line-height: var(--cp-text-body2-line);
    border-radius: var(--cp-radius-sm);
}

.address-lookup__option > i[b-f4qmhv1gsv] {
    color: var(--cp-color-content-muted);
}

/* Hover and keyboard highlight are the same state deliberately: the customer moving the mouse and
   the customer pressing Arrow Down are both pointing at a row, and two different-looking answers to
   that would read as two different things happening. */
.address-lookup__option:hover[b-f4qmhv1gsv],
.address-lookup__option--active[b-f4qmhv1gsv] {
    background: var(--cp-color-surface-muted-hover);
}

/* "Searching…" and "No matches" — the same row shape as an option, but not one: no pointer, no
   highlight, and muted so it does not read as something to pick. */
.address-lookup__note[b-f4qmhv1gsv] {
    display: flex;
    align-items: center;
    gap: var(--cp-space-2);
    padding: var(--cp-space-2) var(--cp-space-3);
    color: var(--cp-color-content-muted);
    font-size: var(--cp-text-body2-size);
    line-height: var(--cp-text-body2-line);
}
/* /Components/Common/Card/Card.razor.rz.scp.css */
.dx-card-wrapper[b-bj29jytwyu] {
	display: flex;
	flex-direction: column;
	gap: .25rem;
}

/* ── Header ── */
.dx-card-header[b-bj29jytwyu] {

}

.dx-card-header.inside-card[b-bj29jytwyu] {
	font-size: var(--cp-text-subtitle2-size);
	font-weight: 500;
	margin-bottom: .1rem;
	color: var(--cp-color-content-muted);
}

.dx-card-header-row[b-bj29jytwyu] {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: .5rem;
}

.dx-card-header-row-clickable[b-bj29jytwyu] {
	cursor: pointer;
	user-select: none;
}

/* Collapse chevron — a plain accessible button (kept dependency-free so the component stays
   bUnit-testable, unlike a DevExpress DxButton). */
.dx-card-toggle[b-bj29jytwyu] {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	border: none;
	background: transparent;
	color: var(--cp-color-content-muted);
	cursor: pointer;
	padding: .25rem;
	border-radius: var(--cp-radius-sm);
	line-height: 1;
	transition: background-color .2s ease, color .2s ease;
}

.dx-card-toggle:hover[b-bj29jytwyu] {
	background: var(--cp-color-surface-muted);
	color: var(--cp-color-content);
}

/* ── Card surface (defaults match the historic look == CardSize.Medium) ── */
.dx-card[b-bj29jytwyu] {
	border: 1px solid var(--dxbl-border-color);
	border-radius: var(--cp-radius);
	box-shadow: var(--cp-shadow-2);
	padding: 1rem;
	background: var(--cp-color-surface);

	width: fit-content;
	height: fit-content;
}

.dx-card-body[b-bj29jytwyu] {
	border-radius: inherit;
}

/* ── Size variants (Medium is the base above; sm/lg fall back to Bootstrap tokens if the
   DevExpress sizing step is absent) ── */
.dx-card.card-sm[b-bj29jytwyu] {
	border-radius: var(--cp-radius-sm);
	box-shadow: var(--cp-shadow-1);
	padding: .5rem;
}

.dx-card.card-lg[b-bj29jytwyu] {
	border-radius: var(--cp-radius-lg);
	box-shadow: var(--cp-shadow-3);
	padding: 1.5rem;
}

/* ── Visual toggles ── */
.dx-card.card-no-shadow[b-bj29jytwyu] { box-shadow: none !important; }
.dx-card.card-no-border[b-bj29jytwyu] { border: none !important; }
.dx-card.card-no-background[b-bj29jytwyu] { background: transparent !important; }

/* ── Collapse ──
   The max-height cap + overflow only exist on collapsible cards, so a plain card never clips
   content taller than the cap. */
.dx-card.collapsible[b-bj29jytwyu] {
	max-height: 1000px;
	overflow: hidden;
	transition: max-height .5s, border-color .5s, padding-top .25s, padding-bottom .25s;
}

.dx-card.collapsed[b-bj29jytwyu] {
	max-height: 0;
	border-color: transparent;
	padding-top: 0 !important;
	padding-bottom: 0 !important;
}

/* ── Accent: subtle tinted background (HubBlazor's "Gradient", adapted to Bootstrap subtle
   tokens — dark mode is handled by the token set) ── */
.dx-card.accent-gradient.card-accent-info[b-bj29jytwyu] { background: var(--cp-role-primary-bg); }
.dx-card.accent-gradient.card-accent-success[b-bj29jytwyu] { background: var(--cp-role-success-bg); }
.dx-card.accent-gradient.card-accent-warning[b-bj29jytwyu] { background: var(--cp-role-warning-bg); }
.dx-card.accent-gradient.card-accent-error[b-bj29jytwyu] { background: var(--cp-role-danger-bg); }
.dx-card.accent-gradient.card-accent-neutral[b-bj29jytwyu] { background: var(--cp-role-neutral-bg); }
.dx-card.accent-gradient.card-accent-black[b-bj29jytwyu] { background: var(--cp-role-neutral-bg); }

/* ── Accent: left border ── */
.dx-card.accent-border[b-bj29jytwyu] {
	border-left-width: 3px;
	border-left-style: solid;
}

.dx-card.accent-border.card-accent-info[b-bj29jytwyu] { border-left-color: var(--cp-color-info); }
.dx-card.accent-border.card-accent-success[b-bj29jytwyu] { border-left-color: var(--cp-color-success); }
.dx-card.accent-border.card-accent-warning[b-bj29jytwyu] { border-left-color: var(--cp-color-warning); }
.dx-card.accent-border.card-accent-error[b-bj29jytwyu] { border-left-color: var(--cp-color-danger); }
.dx-card.accent-border.card-accent-neutral[b-bj29jytwyu] { border-left-color: var(--cp-color-content-muted); }
.dx-card.accent-border.card-accent-black[b-bj29jytwyu] { border-left-color: var(--cp-color-content); }

/* ── Accent: dot before the (inside-card) title ── */
.dx-card.accent-dot > .dx-card-header[b-bj29jytwyu] {
	display: flex;
	align-items: center;
	gap: .4rem;
}

.dx-card.accent-dot > .dx-card-header[b-bj29jytwyu]::before {
	content: '';
	width: .5rem;
	height: .5rem;
	border-radius: 50%;
	flex-shrink: 0;
}

.dx-card.accent-dot.card-accent-info > .dx-card-header[b-bj29jytwyu]::before { background: var(--cp-color-info); }
.dx-card.accent-dot.card-accent-success > .dx-card-header[b-bj29jytwyu]::before { background: var(--cp-color-success); }
.dx-card.accent-dot.card-accent-warning > .dx-card-header[b-bj29jytwyu]::before { background: var(--cp-color-warning); }
.dx-card.accent-dot.card-accent-error > .dx-card-header[b-bj29jytwyu]::before { background: var(--cp-color-danger); }
.dx-card.accent-dot.card-accent-neutral > .dx-card-header[b-bj29jytwyu]::before { background: var(--cp-color-content-muted); }
.dx-card.accent-dot.card-accent-black > .dx-card-header[b-bj29jytwyu]::before { background: var(--cp-color-content); }
/* /Components/Common/Divider/Divider.razor.rz.scp.css */
/* Spacing is intentionally not set on .divider — callers add margin utilities (e.g. my-3) or rely
   on the parent flex gap, so the atom stays flush and never doubles up with a flex-row gap. */

/* ── Horizontal (plain) ─────────────────────────────────────────────────── */
.divider--horizontal[b-3haenigdu7] {
    width: 100%;
    height: 1px;
    background: var(--cp-color-border);
}

/* ── Vertical ───────────────────────────────────────────────────────────── */
/* align-self:stretch fills the height of a flex row; min-height is the fallback when the divider
   sits outside a stretch context. */
.divider--vertical[b-3haenigdu7] {
    align-self: stretch;
    width: 1px;
    min-height: 1rem;
    background: var(--cp-color-border);
}

/* ── Horizontal with a centred label ────────────────────────────────────── */
.divider--labelled[b-3haenigdu7] {
    display: flex;
    align-items: center;
    gap: .75rem;
    width: 100%;
}

.divider--labelled .divider-line[b-3haenigdu7] {
    flex: 1 1 auto;
    height: 1px;
    background: var(--cp-color-border);
}

.divider-label[b-3haenigdu7] {
    font-size: var(--cp-text-caption-size);
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
    color: var(--cp-color-content-muted);
}
/* /Components/Common/EmptyState.razor.rz.scp.css */
.empty-state-icon[b-506x3eh50m] {
    font-size: 2rem;
    color: var(--cp-color-content-muted);
}
/* /Components/Common/Feedback/FeedbackPanel.razor.rz.scp.css */
.feedback-panel[b-l8p0xfa3sy] {
	display: flex;
	flex-direction: column;
	gap: 12px;

	width: fit-content;
	max-width: 640px;
}

.feedback-alert[b-l8p0xfa3sy] {
	display: flex;
	justify-content: space-between;
	gap: 16px;

	border-radius: var(--cp-radius);
	box-shadow: var(--cp-shadow-3);
	padding: var(--cp-space-5);

/*	border: 1px solid transparent;*/
}

/* Title typography now comes from CpText (Subtitle1 + .fw-semibold); spacing via the .mb-1 utility,
   because scoped rules do not reach a child component's rendered element. */

.feedback-alert__message[b-l8p0xfa3sy] {
	opacity: 0.95;
}

.feedback-alert--error[b-l8p0xfa3sy] {
	background-color: color-mix(in srgb, var(--cp-color-surface-danger) 12%, transparent);
	border-color: color-mix(in srgb, var(--cp-color-surface-danger) 20%, transparent);
}

.feedback-alert--warning[b-l8p0xfa3sy] {
	background-color: color-mix(in srgb, var(--cp-color-surface-warning) 12%, transparent);
	border-color: color-mix(in srgb, var(--cp-color-surface-warning) 20%, transparent);
}

.feedback-alert--info[b-l8p0xfa3sy] {
	background-color: color-mix(in srgb, var(--cp-color-surface-info) 12%, transparent);
	border-color: color-mix(in srgb, var(--cp-color-surface-info) 20%, transparent);
}
/* /Components/Common/Feedback/JobProcessLoader.razor.rz.scp.css */
/* Time-sliced visibility engine (same idea as VectorPartDrawLoader) for the 8-item order lifecycle:
   Logo, Document, Magnifier, Nesting, In-Production part, Box, Truck, Parcel. Each item self-draws on
   its own 4s cycle; a slot is visible for its 1/8 (12.5%) of the 32s master cycle, aligned so it opens
   at (position-1)/8 of the cycle — hence the delays make slots appear in SOURCE order. CSS only.
   (Each step icon defines its own keyframes — Blazor scopes @keyframes per file, so they can't be
   shared across components.) */

.jpl[b-scu5jbyrco] {
    --jpl-cycle: 32s; /* 8 items x each item's 4s draw cycle */

    width: 100%;
    max-width: 220px;
    margin-inline: auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.7rem;
}

.jpl-stage[b-scu5jbyrco] {
    position: relative;
    width: 100%;
    aspect-ratio: 1;
}

.jpl-slot[b-scu5jbyrco] {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    opacity: 0;
    animation: jplSlot-b-scu5jbyrco var(--jpl-cycle) linear infinite;
}

.jpl-slot--1[b-scu5jbyrco] { animation-delay: 0s; }
.jpl-slot--2[b-scu5jbyrco] { animation-delay: calc(var(--jpl-cycle) * -0.875); }
.jpl-slot--3[b-scu5jbyrco] { animation-delay: calc(var(--jpl-cycle) * -0.75); }
.jpl-slot--4[b-scu5jbyrco] { animation-delay: calc(var(--jpl-cycle) * -0.625); }
.jpl-slot--5[b-scu5jbyrco] { animation-delay: calc(var(--jpl-cycle) * -0.5); }
.jpl-slot--6[b-scu5jbyrco] { animation-delay: calc(var(--jpl-cycle) * -0.375); }
.jpl-slot--7[b-scu5jbyrco] { animation-delay: calc(var(--jpl-cycle) * -0.25); }
.jpl-slot--8[b-scu5jbyrco] { animation-delay: calc(var(--jpl-cycle) * -0.125); }

/* Each item visible for its eighth (12.5%) of the cycle; 11% -> 12.5% is a short crossfade handoff. */
@keyframes jplSlot-b-scu5jbyrco {
    0%, 11%     { opacity: 1; }
    12.5%, 100% { opacity: 0; }
}

.jpl__label[b-scu5jbyrco] {
    font-size: 0.85rem;
    line-height: 1.3;
    text-align: center;
    color: var(--cp-color-content-secondary, #6c757d);
}

.jpl__sr-only[b-scu5jbyrco] {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

@media (prefers-reduced-motion: reduce) {
    .jpl-slot[b-scu5jbyrco] { animation: none; opacity: 0; }
    .jpl-slot--1[b-scu5jbyrco] { opacity: 1; }
}
/* /Components/Common/Feedback/LoadingOverlay.razor.rz.scp.css */
.loading-overlay[b-w6o4tycfx1] {
    position: fixed;
    inset: 0;
    background-color: rgba(0, 0, 0, 0.25);
    z-index: 1090;
    display: flex;
    align-items: center;
    justify-content: center;

    /* Duration comes from the markup, which takes it from LoadingOverlay.Fade — the component waits
       exactly this long before pulling the element, so the two cannot drift apart.

       Arriving is an animation and leaving is a transition, and that asymmetry is forced: a
       transition needs a previous value to move from, and an element that has just been inserted
       has none. Keyframes run on insertion without that. The two are the same length and mirror
       each other, so it reads as one gesture played forwards and back. */
    animation: loadingOverlayIn-b-w6o4tycfx1 var(--loading-overlay-fade, 150ms) linear;
    transition: opacity var(--loading-overlay-fade, 150ms) linear;
}

@keyframes loadingOverlayIn-b-w6o4tycfx1 {
    from { opacity: 0; }
}

/* Leaving, not left: the class is rendered while the element is still in the DOM, and the component
   removes it once the fade has run. A close this short does not register as a wait, which is the
   whole point — the alternative was holding the panel for a full turn of the logo's animation.

   MinVisible is longer than the fade, so the entry keyframes have always finished by the time this
   lands; were that ever reversed, the animation would still own opacity and this would not run. */
.loading-overlay--leaving[b-w6o4tycfx1] {
    opacity: 0;
    pointer-events: none;
}

.loading-overlay--leaving .loading-overlay__panel[b-w6o4tycfx1] {
    transform: scale(0.97);
}

.loading-overlay__panel[b-w6o4tycfx1] {
    background-color: var(--cp-color-surface);
    color: var(--cp-color-content);
    border-radius: 0.5rem;
    padding: 1.25rem 5rem;
    box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1rem;
    min-width: 14rem;

    /* Eases out on the way in and in on the way out — the conventional pairing, so the panel settles
       as it arrives and gathers pace as it goes. */
    animation: loadingOverlayPanelIn-b-w6o4tycfx1 var(--loading-overlay-fade, 150ms) ease-out;
    transition: transform var(--loading-overlay-fade, 150ms) ease-in;
}

@keyframes loadingOverlayPanelIn-b-w6o4tycfx1 {
    from { transform: scale(0.97); }
}

/* The panel still arrives and still goes, it just does not move or fade doing it. The component's
   timers are unchanged, and since opacity is already where it needs to be there is nothing left to
   see during the wait. */
@media (prefers-reduced-motion: reduce) {
    .loading-overlay[b-w6o4tycfx1],
    .loading-overlay__panel[b-w6o4tycfx1] {
        animation: none;
        transition: none;
    }

    .loading-overlay--leaving .loading-overlay__panel[b-w6o4tycfx1] {
        transform: none;
    }
}

/* The animation sits above the message. ::deep reaches the child VectorDrawLoader's root, which lives
   in its own CSS-isolation scope. */
.loading-overlay__panel[b-w6o4tycfx1]  .vd-loader {
    max-width: 12rem;
}

/* Typography now comes from CpText (Body2); alignment via the .text-center utility on the element,
   because scoped rules do not reach a child component's rendered element. */
/* /Components/Common/Feedback/VectorDrawBox.razor.rz.scp.css */
/* Packing icon. Local keyframes (Blazor scopes @keyframes per file). */
.vd-box[b-e7pt4ss5a6] {
    position: relative;
    width: 100%;
    height: auto;
    overflow: visible;
}

.vd-box__edge[b-e7pt4ss5a6] {
    fill: none;
    stroke: var(--cp-color-primary, #0d6efd);
    stroke-width: 2.6;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
    animation-duration: 4s;
    animation-timing-function: cubic-bezier(0.62, 0, 0.2, 1);
    animation-iteration-count: infinite;
}

.vd-box__edge--1[b-e7pt4ss5a6] { animation-name: vdBoxStep1-b-e7pt4ss5a6; }
.vd-box__edge--2[b-e7pt4ss5a6] { animation-name: vdBoxStep2-b-e7pt4ss5a6; }
.vd-box__edge--3[b-e7pt4ss5a6] { animation-name: vdBoxStep3-b-e7pt4ss5a6; }
.vd-box__edge--4[b-e7pt4ss5a6] { animation-name: vdBoxStep4-b-e7pt4ss5a6; }

@keyframes vdBoxStep1-b-e7pt4ss5a6 {
    0%   { stroke-dashoffset: 100; opacity: 0; }
    3%   { opacity: 1; }
    13%  { stroke-dashoffset: 0; }
    56%  { stroke-dashoffset: 0; }
    75%  { opacity: 1; }
    76%  { stroke-dashoffset: -100; opacity: 0; }
    100% { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdBoxStep2-b-e7pt4ss5a6 {
    0%, 8% { stroke-dashoffset: 100; opacity: 0; }
    11%    { opacity: 1; }
    21%    { stroke-dashoffset: 0; }
    64%    { stroke-dashoffset: 0; }
    83%    { opacity: 1; }
    84%    { stroke-dashoffset: -100; opacity: 0; }
    100%   { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdBoxStep3-b-e7pt4ss5a6 {
    0%, 16% { stroke-dashoffset: 100; opacity: 0; }
    19%     { opacity: 1; }
    29%     { stroke-dashoffset: 0; }
    72%     { stroke-dashoffset: 0; }
    91%     { opacity: 1; }
    92%     { stroke-dashoffset: -100; opacity: 0; }
    100%    { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdBoxStep4-b-e7pt4ss5a6 {
    0%, 24% { stroke-dashoffset: 100; opacity: 0; }
    27%     { opacity: 1; }
    37%     { stroke-dashoffset: 0; }
    80%     { stroke-dashoffset: 0; }
    99%     { opacity: 1; }
    100%    { stroke-dashoffset: -100; opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .vd-box__edge[b-e7pt4ss5a6] { animation: none !important; stroke-dashoffset: 0; opacity: 1; }
}
/* /Components/Common/Feedback/VectorDrawBracket.razor.rz.scp.css */
/* Bracket loader: L-shaped angle + two mounting holes. Same cascade as the other part vectors — every
   edge shares ONE 4s cycle, staggered 8%, each drawing ~13% then winding up (offset 0 -> -100). Opacity
   0 at the zero-length ends stops the round cap flashing a dot. */

.vd-bracket[b-k2lpzpunwd] {
    --vd-bracket-duration: 4s;

    position: relative;
    width: 100%;
    height: auto;
    overflow: visible;
}

.vd-bracket__edge[b-k2lpzpunwd] {
    fill: none;
    stroke: var(--cp-color-primary, #0d6efd);
    stroke-width: 2.3;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
    animation-duration: var(--vd-bracket-duration);
    animation-timing-function: cubic-bezier(0.62, 0, 0.2, 1);
    animation-iteration-count: infinite;
}

.vd-bracket__edge--1[b-k2lpzpunwd] { animation-name: vdBracketOutline-b-k2lpzpunwd; }
.vd-bracket__edge--2[b-k2lpzpunwd] { animation-name: vdBracketHole1-b-k2lpzpunwd; }
.vd-bracket__edge--3[b-k2lpzpunwd] { animation-name: vdBracketHole2-b-k2lpzpunwd; }

@keyframes vdBracketOutline-b-k2lpzpunwd {
    0%   { stroke-dashoffset: 100; opacity: 0; }
    3%   { opacity: 1; }
    13%  { stroke-dashoffset: 0; }
    64%  { stroke-dashoffset: 0; }
    83%  { opacity: 1; }
    84%  { stroke-dashoffset: -100; opacity: 0; }
    100% { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdBracketHole1-b-k2lpzpunwd {
    0%   { stroke-dashoffset: 100; opacity: 0; }
    8%   { stroke-dashoffset: 100; opacity: 0; }
    11%  { opacity: 1; }
    21%  { stroke-dashoffset: 0; }
    72%  { stroke-dashoffset: 0; }
    91%  { opacity: 1; }
    92%  { stroke-dashoffset: -100; opacity: 0; }
    100% { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdBracketHole2-b-k2lpzpunwd {
    0%   { stroke-dashoffset: 100; opacity: 0; }
    16%  { stroke-dashoffset: 100; opacity: 0; }
    19%  { opacity: 1; }
    29%  { stroke-dashoffset: 0; }
    80%  { stroke-dashoffset: 0; }
    99%  { opacity: 1; }
    100% { stroke-dashoffset: -100; opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .vd-bracket__edge[b-k2lpzpunwd] { animation: none !important; stroke-dashoffset: 0; opacity: 1; }
}
/* /Components/Common/Feedback/VectorDrawDocument.razor.rz.scp.css */
/* Order Confirmed icon. Keyframes are local (Blazor CSS isolation scopes @keyframes per file, so they
   can't be shared across components). */
.vd-doc[b-71fdauvjjc] {
    position: relative;
    width: 100%;
    height: auto;
    overflow: visible;
}

.vd-doc__edge[b-71fdauvjjc] {
    fill: none;
    stroke: var(--cp-color-primary, #0d6efd);
    stroke-width: 2.4;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
    animation-duration: 4s;
    animation-timing-function: cubic-bezier(0.62, 0, 0.2, 1);
    animation-iteration-count: infinite;
}

.vd-doc__edge--1[b-71fdauvjjc] { animation-name: vdDocStep1-b-71fdauvjjc; }
.vd-doc__edge--2[b-71fdauvjjc] { animation-name: vdDocStep2-b-71fdauvjjc; }
.vd-doc__edge--3[b-71fdauvjjc] { animation-name: vdDocStep3-b-71fdauvjjc; }

@keyframes vdDocStep1-b-71fdauvjjc {
    0%   { stroke-dashoffset: 100; opacity: 0; }
    3%   { opacity: 1; }
    13%  { stroke-dashoffset: 0; }
    56%  { stroke-dashoffset: 0; }
    75%  { opacity: 1; }
    76%  { stroke-dashoffset: -100; opacity: 0; }
    100% { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdDocStep2-b-71fdauvjjc {
    0%, 8% { stroke-dashoffset: 100; opacity: 0; }
    11%    { opacity: 1; }
    21%    { stroke-dashoffset: 0; }
    64%    { stroke-dashoffset: 0; }
    83%    { opacity: 1; }
    84%    { stroke-dashoffset: -100; opacity: 0; }
    100%   { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdDocStep3-b-71fdauvjjc {
    0%, 16% { stroke-dashoffset: 100; opacity: 0; }
    19%     { opacity: 1; }
    29%     { stroke-dashoffset: 0; }
    72%     { stroke-dashoffset: 0; }
    91%     { opacity: 1; }
    92%     { stroke-dashoffset: -100; opacity: 0; }
    100%    { stroke-dashoffset: -100; opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .vd-doc__edge[b-71fdauvjjc] { animation: none !important; stroke-dashoffset: 0; opacity: 1; }
}
/* /Components/Common/Feedback/VectorDrawFlange.razor.rz.scp.css */
/* Flange loader: rim + bore + six-hole bolt circle, drawn in the app's primary colour.

   Every edge shares ONE animation-duration (the whole cycle) so they restart in lock-step. Each
   section runs for the SAME active span (84% of the cycle: draw -> hold -> wind up) but staggered by
   only 8%; each stroke draws quickly (~10% of the cycle) so consecutive strokes barely overlap while
   being drawn, then hold — around mid-cycle the whole flange is assembled. The
   stagger is baked into three keyframes (not animation-delay, which would desync the restart). To
   overlap more/less, change the 8% stagger (rim 0, bore 8, bolts 16) and keep bolts ending at 100%:
       rim   0% -> 84%   then waits (hidden) until the cycle ends
       bore  8% -> 92%
       bolts 16% -> 100%  finishes winding up exactly at the cycle end -> everything restarts together
   Each edge is opacity 0 while its stroke has zero visible length, so the round cap never shows as a
   stray dot; round caps also close the small circles cleanly at full draw. */

.vd-flange[b-yd6qrfe0ll] {
    /* one knob steers the whole cascade */
    --vd-flange-duration: 4s;

    position: relative;
    width: 100%;
    height: auto;
    overflow: visible;
}

.vd-flange__edge[b-yd6qrfe0ll] {
    fill: none;
    stroke: var(--cp-color-primary, #0d6efd);
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
    animation-duration: var(--vd-flange-duration);
    animation-timing-function: cubic-bezier(0.62, 0, 0.2, 1);
    animation-iteration-count: infinite;
}

.vd-flange__edge--1[b-yd6qrfe0ll] { animation-name: vdFlangeRim-b-yd6qrfe0ll; }
.vd-flange__edge--2[b-yd6qrfe0ll] { animation-name: vdFlangeBore-b-yd6qrfe0ll; }
.vd-flange__edge--3[b-yd6qrfe0ll] { animation-name: vdFlangeBolts-b-yd6qrfe0ll; }

/* rim: draws first, winds up early, then waits (hidden) for the rest to finish */
@keyframes vdFlangeRim-b-yd6qrfe0ll {
    0%   { stroke-dashoffset: 100; opacity: 0; }
    3%   { opacity: 1; }
    13%  { stroke-dashoffset: 0; }
    64%  { stroke-dashoffset: 0; }
    83%  { opacity: 1; }
    84%  { stroke-dashoffset: -100; opacity: 0; }
    100% { stroke-dashoffset: -100; opacity: 0; }
}

/* bore: same span, shifted +8% */
@keyframes vdFlangeBore-b-yd6qrfe0ll {
    0%   { stroke-dashoffset: 100; opacity: 0; }
    8%   { stroke-dashoffset: 100; opacity: 0; }
    11%  { opacity: 1; }
    21%  { stroke-dashoffset: 0; }
    72%  { stroke-dashoffset: 0; }
    91%  { opacity: 1; }
    92%  { stroke-dashoffset: -100; opacity: 0; }
    100% { stroke-dashoffset: -100; opacity: 0; }
}

/* bolts: same span, shifted +16% — wind up completes exactly at the cycle end */
@keyframes vdFlangeBolts-b-yd6qrfe0ll {
    0%   { stroke-dashoffset: 100; opacity: 0; }
    16%  { stroke-dashoffset: 100; opacity: 0; }
    19%  { opacity: 1; }
    29%  { stroke-dashoffset: 0; }
    80%  { stroke-dashoffset: 0; }
    99%  { opacity: 1; }
    100% { stroke-dashoffset: -100; opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .vd-flange__edge[b-yd6qrfe0ll] { animation: none !important; stroke-dashoffset: 0; opacity: 1; }
}
/* /Components/Common/Feedback/VectorDrawHex.razor.rz.scp.css */
/* Hex plate loader: hexagon outline + central bore. Same cascade as the other part vectors — ONE 4s
   cycle, staggered, draw -> hold -> wind up (offset 0 -> -100). Opacity 0 at the zero-length ends stops
   the round cap flashing a dot. */

.vd-hex[b-jtato6y4wq] {
    --vd-hex-duration: 4s;

    position: relative;
    width: 100%;
    height: auto;
    overflow: visible;
}

.vd-hex__edge[b-jtato6y4wq] {
    fill: none;
    stroke: var(--cp-color-primary, #0d6efd);
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
    animation-duration: var(--vd-hex-duration);
    animation-timing-function: cubic-bezier(0.62, 0, 0.2, 1);
    animation-iteration-count: infinite;
}

.vd-hex__edge--1[b-jtato6y4wq] { animation-name: vdHexOutline-b-jtato6y4wq; }
.vd-hex__edge--2[b-jtato6y4wq] { animation-name: vdHexBore-b-jtato6y4wq; }

@keyframes vdHexOutline-b-jtato6y4wq {
    0%   { stroke-dashoffset: 100; opacity: 0; }
    3%   { opacity: 1; }
    15%  { stroke-dashoffset: 0; }
    66%  { stroke-dashoffset: 0; }
    87%  { opacity: 1; }
    88%  { stroke-dashoffset: -100; opacity: 0; }
    100% { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdHexBore-b-jtato6y4wq {
    0%   { stroke-dashoffset: 100; opacity: 0; }
    12%  { stroke-dashoffset: 100; opacity: 0; }
    15%  { opacity: 1; }
    27%  { stroke-dashoffset: 0; }
    80%  { stroke-dashoffset: 0; }
    99%  { opacity: 1; }
    100% { stroke-dashoffset: -100; opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .vd-hex__edge[b-jtato6y4wq] { animation: none !important; stroke-dashoffset: 0; opacity: 1; }
}
/* /Components/Common/Feedback/VectorDrawLogo.razor.rz.scp.css */
/* Self-drawing Lasered mark — real logo geometry, vectorised 1:1 from lclogo.png (one continuous
   contour per colour). Both colours share ONE keyframe and start together, so red and grey draw in
   sync. One knob steers tempo.

   Two timelines, picked by .vd-logo--filled:

   OUTLINE ONLY — the original 4s cycle: the outline draws (stroke-dashoffset 100 -> 0), holds, then
   wipes forward (0 -> -100). The 4s is load-bearing beyond this file: JobProcessLoader and
   VectorPartDrawLoader build their master cycle out of it (32s = 8 x 4s, 20s = 5 x 4s) and slice
   slot visibility against that, so changing it desynchronises both loaders.

   FILLED — 3.00s, and not a list of phases but two nested spans. The line lives for 2.65s; the fill
   lives for 1.75s, which is 0.66 of that, and the two are concentric — same midpoint, so the fill is
   inset by an equal 0.45s at each end. Everything else falls out of that:

       line   |------------------------------------------|         0 -> 2.65s
       fill            |------------------------------|             0.45 -> 2.20s

   Each span is also symmetric in itself: the line draws in 1.10s and leaves in 1.10s, the fill
   paints in 0.80s and peels away in 0.80s. Nothing hides faster than it appeared, which was the
   whole complaint against every earlier cut of this.

   What the geometry then gives for free: the brush sets off with the line 41% drawn, the fill lands
   0.15s after the line closes, the whole mark stands for 0.15s, the fill starts leaving 0.15s before
   the line does, and the fill is gone 0.45s before the line is. The line is complete and still for
   0.45s in total, but the fill is busy for all but 0.15s of that, so there is no stall to see. A
   0.35s empty beat closes the turn.

   Only three of these numbers are chosen — the cycle, the 0.15s on the finished mark, and the 0.66
   ratio. The rest are derived, and the tuning bench that derives them is the place to change any of
   it; hand-editing one percentage here breaks the concentricity or the symmetry, which is how every
   earlier cut of this went wrong.

   The brush is one wide stroke inside a mask, drawn with the same trick as the outline: running its
   dashoffset down to 0 paints, carrying on to -100 wipes, the start lifting as the sweep continues
   in the direction it came from. Both loop ends are the hidden state, so the wrap is seamless and
   nothing rewinds. */

.vd-logo[b-q7r0lm610w] {
    --vd-logo-red: #DE1F26;
    --vd-logo-grey: #9aa0a6;
    --vd-logo-duration: 4s;
    --vd-logo-stroke: 8; /* line thickness, in the 0..400 viewBox units */

    width: 100%;
    max-width: 130px;
    margin-inline: auto;
    display: grid;
    place-items: center;
}

/* The brush pass gets its own cycle — see the header comment. Nothing outside this file depends on
   the number: LoadingOverlay closes on its own work finishing and cuts the animation wherever it is,
   rather than waiting for the turn to end. */
.vd-logo--filled[b-q7r0lm610w] {
    --vd-logo-duration: 3s;
}

.vd-logo__art[b-q7r0lm610w] {
    width: 100%;
    height: auto;
    overflow: visible;
}

/* The outline, on top. Geometry comes from <defs> through <use>; stroke properties are inherited,
   so styling the <use> styles the contour it references. */
.vd-logo__shape[b-q7r0lm610w] {
    fill: none;
    stroke-width: var(--vd-logo-stroke);
    stroke-linejoin: round;
    stroke-linecap: round;
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
    animation: vdLogoDraw-b-q7r0lm610w var(--vd-logo-duration) ease-in-out infinite;
}

.vd-logo--filled .vd-logo__shape[b-q7r0lm610w] {
    animation-name: vdLogoDrawFilled-b-q7r0lm610w;
    animation-timing-function: linear; /* deliberate — see the note above the filled keyframes */
}

.vd-logo__shape--red[b-q7r0lm610w] { stroke: var(--vd-logo-red); }
.vd-logo__shape--grey[b-q7r0lm610w] { stroke: var(--vd-logo-grey); }

/* The fill, underneath, revealed through the brush mask. */
.vd-logo__solid[b-q7r0lm610w] { stroke: none; }
.vd-logo__solid--red[b-q7r0lm610w] { fill: var(--vd-logo-red); }
.vd-logo__solid--grey[b-q7r0lm610w] { fill: var(--vd-logo-grey); }

/* Lives inside <mask>, where white means "show" and black means "hide" — this is a mask value, not
   a colour, so it is not a design token and must not be swapped for one. Stroke width comfortably
   exceeds the mark's 566-unit diagonal, so the sweep covers it edge to edge. */
.vd-logo__brush[b-q7r0lm610w] {
    fill: none;
    stroke: #fff;
    stroke-width: 650;
    stroke-linecap: round;
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
    opacity: .35;
    animation: vdLogoBrush-b-q7r0lm610w var(--vd-logo-duration) linear infinite;
}

/* Outline only: draw (offset 100 -> 0), hold, then wipe forward (offset 0 -> -100) — the start lifts
   and the wipe sweeps to the end, the same direction it was drawn, not a rewind. Both loop ends are
   the hidden state (offset +100 at 0%, -100 at 100%), so the wrap is seamless. */
@keyframes vdLogoDraw-b-q7r0lm610w {
    0% { stroke-dashoffset: 100; }
    38% { stroke-dashoffset: 0; }
    54% { stroke-dashoffset: 0; }
    100% { stroke-dashoffset: -100; }
}

/* Both filled keyframes run LINEAR, and that is a decision rather than an omission. An ease-in-out
   is slow at both ends of every segment it governs: on the way out it barely moves for the first
   fraction, so the mark reads as standing there fully painted long after it should have started
   leaving, and then rips through the middle, so the disappearance reads as a glitch. The perceived
   "holds too long, then vanishes too fast" came from the curve, not from the durations — which is
   why the fix is here and not in the numbers above. Anything reintroducing a timing function per
   keyframe brings both complaints back.

   The line's span: 0 to 88.2% of the cycle, which is 2.65s. Drawing and leaving take 36.6% each
   (1.10s apiece — equal by design, that is the symmetry), with 15% standing still between them. */
@keyframes vdLogoDrawFilled-b-q7r0lm610w {
    0% {
        stroke-dashoffset: 100;
    }

    36.6% {
        stroke-dashoffset: 0;
    }

    51.6% {
        stroke-dashoffset: 0;
    }

    88.2%, 100% {
        stroke-dashoffset: -100;
    }
}

/* The fill's span: 15% to 73.2%, which is 1.75s, 0.66 of the line's 2.65s, and sitting dead centre
   inside it — both spans share the 44.1% midpoint. Painting and peeling away take 26.6% each (0.80s
   apiece), with 5% standing still between them.

   These four numbers are not independent of the four above: move one and the span stops being 0.66
   of the line's, or stops being concentric with it, and the whole arrangement is gone. Retune on the
   bench, which shows both spans and their ratio, rather than nudging a percentage here. */
@keyframes vdLogoBrush-b-q7r0lm610w {
    0%, 15% {
        stroke-dashoffset: 100;
    }

    41.6% {
        stroke-dashoffset: 0;
    }

    46.6% {
        stroke-dashoffset: 0;
    }

    73.2%, 100% {
        stroke-dashoffset: -100;
    }
}

/* At rest the mark is whole: outline closed, brush parked at the end of its paint stroke. Stopping
   the brush animation also stops the filter being recomputed frame after frame. */
@media (prefers-reduced-motion: reduce) {
    .vd-logo__shape[b-q7r0lm610w] { animation: none; stroke-dashoffset: 0; }
    .vd-logo__brush[b-q7r0lm610w] { animation: none; stroke-dashoffset: 0; }
}
/* /Components/Common/Feedback/VectorDrawMagnifier.razor.rz.scp.css */
/* Preparing for Production icon. Local keyframes (Blazor scopes @keyframes per file). */
.vd-mag[b-6jpkhs4euu] {
    position: relative;
    width: 100%;
    height: auto;
    overflow: visible;
}

.vd-mag__edge[b-6jpkhs4euu] {
    fill: none;
    stroke: var(--cp-color-primary, #0d6efd);
    stroke-width: 2.4;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
    animation-duration: 4s;
    animation-timing-function: cubic-bezier(0.62, 0, 0.2, 1);
    animation-iteration-count: infinite;
}

.vd-mag__edge--1[b-6jpkhs4euu] { animation-name: vdMagStep1-b-6jpkhs4euu; }
.vd-mag__edge--2[b-6jpkhs4euu] { animation-name: vdMagStep2-b-6jpkhs4euu; }
.vd-mag__edge--3[b-6jpkhs4euu] { animation-name: vdMagStep3-b-6jpkhs4euu; }

@keyframes vdMagStep1-b-6jpkhs4euu {
    0%   { stroke-dashoffset: 100; opacity: 0; }
    3%   { opacity: 1; }
    13%  { stroke-dashoffset: 0; }
    56%  { stroke-dashoffset: 0; }
    75%  { opacity: 1; }
    76%  { stroke-dashoffset: -100; opacity: 0; }
    100% { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdMagStep2-b-6jpkhs4euu {
    0%, 8% { stroke-dashoffset: 100; opacity: 0; }
    11%    { opacity: 1; }
    21%    { stroke-dashoffset: 0; }
    64%    { stroke-dashoffset: 0; }
    83%    { opacity: 1; }
    84%    { stroke-dashoffset: -100; opacity: 0; }
    100%   { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdMagStep3-b-6jpkhs4euu {
    0%, 16% { stroke-dashoffset: 100; opacity: 0; }
    19%     { opacity: 1; }
    29%     { stroke-dashoffset: 0; }
    72%     { stroke-dashoffset: 0; }
    91%     { opacity: 1; }
    92%     { stroke-dashoffset: -100; opacity: 0; }
    100%    { stroke-dashoffset: -100; opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .vd-mag__edge[b-6jpkhs4euu] { animation: none !important; stroke-dashoffset: 0; opacity: 1; }
}
/* /Components/Common/Feedback/VectorDrawNesting.razor.rz.scp.css */
/* Scheduled for Production (nesting) icon. Local keyframes (Blazor scopes @keyframes per file). */
.vd-nest[b-yz7efcspm2] {
    position: relative;
    width: 100%;
    height: auto;
    overflow: visible;
}

.vd-nest__edge[b-yz7efcspm2] {
    fill: none;
    stroke: var(--cp-color-primary, #0d6efd);
    stroke-width: 2.4;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
    animation-duration: 4s;
    animation-timing-function: cubic-bezier(0.62, 0, 0.2, 1);
    animation-iteration-count: infinite;
}

.vd-nest__edge--1[b-yz7efcspm2] { animation-name: vdNestStep1-b-yz7efcspm2; }
.vd-nest__edge--2[b-yz7efcspm2] { animation-name: vdNestStep2-b-yz7efcspm2; }
.vd-nest__edge--3[b-yz7efcspm2] { animation-name: vdNestStep3-b-yz7efcspm2; }
.vd-nest__edge--4[b-yz7efcspm2] { animation-name: vdNestStep4-b-yz7efcspm2; }

@keyframes vdNestStep1-b-yz7efcspm2 {
    0%   { stroke-dashoffset: 100; opacity: 0; }
    3%   { opacity: 1; }
    13%  { stroke-dashoffset: 0; }
    56%  { stroke-dashoffset: 0; }
    75%  { opacity: 1; }
    76%  { stroke-dashoffset: -100; opacity: 0; }
    100% { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdNestStep2-b-yz7efcspm2 {
    0%, 8% { stroke-dashoffset: 100; opacity: 0; }
    11%    { opacity: 1; }
    21%    { stroke-dashoffset: 0; }
    64%    { stroke-dashoffset: 0; }
    83%    { opacity: 1; }
    84%    { stroke-dashoffset: -100; opacity: 0; }
    100%   { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdNestStep3-b-yz7efcspm2 {
    0%, 16% { stroke-dashoffset: 100; opacity: 0; }
    19%     { opacity: 1; }
    29%     { stroke-dashoffset: 0; }
    72%     { stroke-dashoffset: 0; }
    91%     { opacity: 1; }
    92%     { stroke-dashoffset: -100; opacity: 0; }
    100%    { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdNestStep4-b-yz7efcspm2 {
    0%, 24% { stroke-dashoffset: 100; opacity: 0; }
    27%     { opacity: 1; }
    37%     { stroke-dashoffset: 0; }
    80%     { stroke-dashoffset: 0; }
    99%     { opacity: 1; }
    100%    { stroke-dashoffset: -100; opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .vd-nest__edge[b-yz7efcspm2] { animation: none !important; stroke-dashoffset: 0; opacity: 1; }
}
/* /Components/Common/Feedback/VectorDrawParcel.razor.rz.scp.css */
/* Delivered icon. Local keyframes (Blazor scopes @keyframes per file). */
.vd-parcel[b-ig8cwurip0] {
    position: relative;
    width: 100%;
    height: auto;
    overflow: visible;
}

.vd-parcel__edge[b-ig8cwurip0] {
    fill: none;
    stroke: var(--cp-color-primary, #0d6efd);
    stroke-width: 2.6;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
    animation-duration: 4s;
    animation-timing-function: cubic-bezier(0.62, 0, 0.2, 1);
    animation-iteration-count: infinite;
}

.vd-parcel__edge--1[b-ig8cwurip0] { animation-name: vdParcelStep1-b-ig8cwurip0; }
.vd-parcel__edge--2[b-ig8cwurip0] { animation-name: vdParcelStep2-b-ig8cwurip0; }
.vd-parcel__edge--3[b-ig8cwurip0] { animation-name: vdParcelStep3-b-ig8cwurip0; }
.vd-parcel__edge--4[b-ig8cwurip0] { animation-name: vdParcelStep4-b-ig8cwurip0; }

@keyframes vdParcelStep1-b-ig8cwurip0 {
    0%   { stroke-dashoffset: 100; opacity: 0; }
    3%   { opacity: 1; }
    13%  { stroke-dashoffset: 0; }
    56%  { stroke-dashoffset: 0; }
    75%  { opacity: 1; }
    76%  { stroke-dashoffset: -100; opacity: 0; }
    100% { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdParcelStep2-b-ig8cwurip0 {
    0%, 8% { stroke-dashoffset: 100; opacity: 0; }
    11%    { opacity: 1; }
    21%    { stroke-dashoffset: 0; }
    64%    { stroke-dashoffset: 0; }
    83%    { opacity: 1; }
    84%    { stroke-dashoffset: -100; opacity: 0; }
    100%   { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdParcelStep3-b-ig8cwurip0 {
    0%, 16% { stroke-dashoffset: 100; opacity: 0; }
    19%     { opacity: 1; }
    29%     { stroke-dashoffset: 0; }
    72%     { stroke-dashoffset: 0; }
    91%     { opacity: 1; }
    92%     { stroke-dashoffset: -100; opacity: 0; }
    100%    { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdParcelStep4-b-ig8cwurip0 {
    0%, 24% { stroke-dashoffset: 100; opacity: 0; }
    27%     { opacity: 1; }
    37%     { stroke-dashoffset: 0; }
    80%     { stroke-dashoffset: 0; }
    99%     { opacity: 1; }
    100%    { stroke-dashoffset: -100; opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .vd-parcel__edge[b-ig8cwurip0] { animation: none !important; stroke-dashoffset: 0; opacity: 1; }
}
/* /Components/Common/Feedback/VectorDrawPart.razor.rz.scp.css */
/* Part loader: rectangular plate + a row of three bores (large -> medium -> small, drilled right to
   left), drawn in the app's primary colour.

   Cascade timing mirrors VectorDrawFlange: every edge shares ONE animation-duration (the whole cycle)
   so they restart in lock-step. Each section runs for the SAME active span (76% of the cycle:
   draw -> hold -> wind up) but staggered by only 8%; each stroke draws in ~13% of the cycle and winds
   up (wipes, offset 0 -> -100) over ~20% — the SAME rates the flange uses — so the wind reads as a
   visible wipe, not a pop-out. The stagger is baked into the keyframes (not animation-delay,
   which would desync the restart). To overlap more/less, change the 8% stagger and keep the last
   section ending at 100%:
       plate  0%  -> 76%   then waits (hidden) until the cycle ends
       bore   8%  -> 84%   (r17, right-hand)
       mid    16% -> 92%   (r11)
       small  24% -> 100%  (r6) winds up exactly at the cycle end -> everything restarts together
   The three bores drill in size order (largest first), so the part reads as a progressive drilling
   sequence. Each edge is opacity 0 while its stroke has zero visible length, so the round cap never
   shows as a stray dot; round caps also close the shapes cleanly at full draw.

   Stroke widths are scaled up (2.7 / 1.35 vs the flange's 2 / 1) to match the flange's *rendered*
   line weight: this drawing's viewBox is wider (140 vs 104), so at the same on-screen width every
   unit — and therefore every stroke — is scaled down more. 140/104 ~= 1.35 restores parity. */

.vd-part[b-dsdj84ithz] {
    /* one knob steers the whole cascade */
    --vd-part-duration: 4s;

    position: relative;
    width: 100%;
    height: auto;
    overflow: visible;
}

.vd-part__edge[b-dsdj84ithz] {
    fill: none;
    stroke: var(--cp-color-primary, #0d6efd);
    stroke-width: 2.7;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
    animation-duration: var(--vd-part-duration);
    animation-timing-function: cubic-bezier(0.62, 0, 0.2, 1);
    animation-iteration-count: infinite;
}

.vd-part__edge--1[b-dsdj84ithz] { animation-name: vdPartPlate-b-dsdj84ithz; }
.vd-part__edge--2[b-dsdj84ithz] { animation-name: vdPartBore-b-dsdj84ithz; }
.vd-part__edge--3[b-dsdj84ithz] { animation-name: vdPartHoleMid-b-dsdj84ithz; }
.vd-part__edge--4[b-dsdj84ithz] { animation-name: vdPartHoleSmall-b-dsdj84ithz; }

/* plate: draws first (13%), winds up early, then waits (hidden) for the rest to finish */
@keyframes vdPartPlate-b-dsdj84ithz {
    0%   { stroke-dashoffset: 100; opacity: 0; }
    3%   { opacity: 1; }
    13%  { stroke-dashoffset: 0; }
    56%  { stroke-dashoffset: 0; }
    75%  { opacity: 1; }
    76%  { stroke-dashoffset: -100; opacity: 0; }
    100% { stroke-dashoffset: -100; opacity: 0; }
}

/* bore (r17, right-hand): same 13% draw, shifted +8% */
@keyframes vdPartBore-b-dsdj84ithz {
    0%   { stroke-dashoffset: 100; opacity: 0; }
    8%   { stroke-dashoffset: 100; opacity: 0; }
    11%  { opacity: 1; }
    21%  { stroke-dashoffset: 0; }
    64%  { stroke-dashoffset: 0; }
    83%  { opacity: 1; }
    84%  { stroke-dashoffset: -100; opacity: 0; }
    100% { stroke-dashoffset: -100; opacity: 0; }
}

/* mid hole (r11): same 13% draw, shifted +16% */
@keyframes vdPartHoleMid-b-dsdj84ithz {
    0%   { stroke-dashoffset: 100; opacity: 0; }
    16%  { stroke-dashoffset: 100; opacity: 0; }
    19%  { opacity: 1; }
    29%  { stroke-dashoffset: 0; }
    72%  { stroke-dashoffset: 0; }
    91%  { opacity: 1; }
    92%  { stroke-dashoffset: -100; opacity: 0; }
    100% { stroke-dashoffset: -100; opacity: 0; }
}

/* small hole (r6): same 13% draw, shifted +24% — winds up exactly at the cycle end */
@keyframes vdPartHoleSmall-b-dsdj84ithz {
    0%   { stroke-dashoffset: 100; opacity: 0; }
    24%  { stroke-dashoffset: 100; opacity: 0; }
    27%  { opacity: 1; }
    37%  { stroke-dashoffset: 0; }
    80%  { stroke-dashoffset: 0; }
    99%  { opacity: 1; }
    100% { stroke-dashoffset: -100; opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .vd-part__edge[b-dsdj84ithz] { animation: none !important; stroke-dashoffset: 0; opacity: 1; }
}
/* /Components/Common/Feedback/VectorDrawTruck.razor.rz.scp.css */
/* Dispatched icon. Local keyframes (Blazor scopes @keyframes per file).
   8 edges cascade-draw (6% stagger), hold, then wipe together near the end. */
.vd-truck[b-qidbnv7l2b] {
    position: relative;
    width: 100%;
    height: auto;
    overflow: visible;
}

.vd-truck__edge[b-qidbnv7l2b] {
    fill: none;
    stroke: var(--cp-color-primary, #0d6efd);
    stroke-width: 2.6;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
    animation-duration: 4s;
    animation-timing-function: cubic-bezier(0.62, 0, 0.2, 1);
    animation-iteration-count: infinite;
}

.vd-truck__edge--1[b-qidbnv7l2b] { animation-name: vdTruckStep1-b-qidbnv7l2b; }
.vd-truck__edge--2[b-qidbnv7l2b] { animation-name: vdTruckStep2-b-qidbnv7l2b; }
.vd-truck__edge--3[b-qidbnv7l2b] { animation-name: vdTruckStep3-b-qidbnv7l2b; }
.vd-truck__edge--4[b-qidbnv7l2b] { animation-name: vdTruckStep4-b-qidbnv7l2b; }
.vd-truck__edge--5[b-qidbnv7l2b] { animation-name: vdTruckStep5-b-qidbnv7l2b; }
.vd-truck__edge--6[b-qidbnv7l2b] { animation-name: vdTruckStep6-b-qidbnv7l2b; }
.vd-truck__edge--7[b-qidbnv7l2b] { animation-name: vdTruckStep7-b-qidbnv7l2b; }
.vd-truck__edge--8[b-qidbnv7l2b] { animation-name: vdTruckStep8-b-qidbnv7l2b; }

@keyframes vdTruckStep1-b-qidbnv7l2b {
    0%   { stroke-dashoffset: 100; opacity: 0; }
    3%   { opacity: 1; }
    12%  { stroke-dashoffset: 0; }
    90%  { stroke-dashoffset: 0; }
    99%  { opacity: 1; }
    100% { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdTruckStep2-b-qidbnv7l2b {
    0%, 6% { stroke-dashoffset: 100; opacity: 0; }
    9%     { opacity: 1; }
    18%    { stroke-dashoffset: 0; }
    90%    { stroke-dashoffset: 0; }
    99%    { opacity: 1; }
    100%   { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdTruckStep3-b-qidbnv7l2b {
    0%, 12% { stroke-dashoffset: 100; opacity: 0; }
    15%     { opacity: 1; }
    24%     { stroke-dashoffset: 0; }
    90%     { stroke-dashoffset: 0; }
    99%     { opacity: 1; }
    100%    { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdTruckStep4-b-qidbnv7l2b {
    0%, 18% { stroke-dashoffset: 100; opacity: 0; }
    21%     { opacity: 1; }
    30%     { stroke-dashoffset: 0; }
    90%     { stroke-dashoffset: 0; }
    99%     { opacity: 1; }
    100%    { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdTruckStep5-b-qidbnv7l2b {
    0%, 24% { stroke-dashoffset: 100; opacity: 0; }
    27%     { opacity: 1; }
    36%     { stroke-dashoffset: 0; }
    90%     { stroke-dashoffset: 0; }
    99%     { opacity: 1; }
    100%    { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdTruckStep6-b-qidbnv7l2b {
    0%, 30% { stroke-dashoffset: 100; opacity: 0; }
    33%     { opacity: 1; }
    42%     { stroke-dashoffset: 0; }
    90%     { stroke-dashoffset: 0; }
    99%     { opacity: 1; }
    100%    { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdTruckStep7-b-qidbnv7l2b {
    0%, 36% { stroke-dashoffset: 100; opacity: 0; }
    39%     { opacity: 1; }
    48%     { stroke-dashoffset: 0; }
    90%     { stroke-dashoffset: 0; }
    99%     { opacity: 1; }
    100%    { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes vdTruckStep8-b-qidbnv7l2b {
    0%, 42% { stroke-dashoffset: 100; opacity: 0; }
    45%     { opacity: 1; }
    54%     { stroke-dashoffset: 0; }
    90%     { stroke-dashoffset: 0; }
    99%     { opacity: 1; }
    100%    { stroke-dashoffset: -100; opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .vd-truck__edge[b-qidbnv7l2b] { animation: none !important; stroke-dashoffset: 0; opacity: 1; }
}
/* /Components/Common/Feedback/VectorPartDrawLoader.razor.rz.scp.css */
/* Cycles the Lasered mark (in the app's primary colour) then technical part vectors (Part, Flange,
   Bracket, Hex) one after another. Each item self-draws on its own 4s cycle; this loader time-slices
   visibility so exactly one is shown at a time, its slot aligned to the item's cycle so it always
   appears drawing fresh. All items are hidden at each 4s handoff, so the switch is invisible. CSS only
   — no timer, no server traffic.

   N items: set --vpl-cycle = N x 4s and the visible window in @keyframes vplSlot to 100%/N (currently
   5 items -> 20%). Each slot's negative delay phase-shifts its window; see the delays below, ordered so
   slots appear in source order. */

.vpl[b-p5t4cc5p9a] {
    --vpl-cycle: 20s; /* 5 items x each item's 4s draw cycle */

    width: 100%;
    max-width: 220px;
    margin-inline: auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.7rem;
}

.vpl-stage[b-p5t4cc5p9a] {
    position: relative;
    width: 100%;
    aspect-ratio: 1;
}

.vpl-slot[b-p5t4cc5p9a] {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    opacity: 0;
    animation: vplSlot-b-p5t4cc5p9a var(--vpl-cycle) linear infinite;
}

/* A slot's negative delay opens its visibility window at (position-1)/N of the cycle, so these are
   ordered to make slots appear in SOURCE order: Logo, Flange, Part, Bracket, Hex. */
.vpl-slot--1[b-p5t4cc5p9a] { animation-delay: 0s; }
.vpl-slot--2[b-p5t4cc5p9a] { animation-delay: calc(var(--vpl-cycle) * -0.8); }
.vpl-slot--3[b-p5t4cc5p9a] { animation-delay: calc(var(--vpl-cycle) * -0.6); }
.vpl-slot--4[b-p5t4cc5p9a] { animation-delay: calc(var(--vpl-cycle) * -0.4); }
.vpl-slot--5[b-p5t4cc5p9a] { animation-delay: calc(var(--vpl-cycle) * -0.2); }

/* Each item is visible for its fifth of the master cycle (20%), hidden the rest. The 18% -> 20% edge
   is a short crossfade at the handoff, where both items are already near-hidden — smooth and forgiving. */
@keyframes vplSlot-b-p5t4cc5p9a {
    0%, 18%   { opacity: 1; }
    20%, 100% { opacity: 0; }
}

/* Typography now comes from CpText (Caption + Muted); alignment via the .text-center utility on the
   element, because scoped rules do not reach a child component's rendered element. */

.vpl__sr-only[b-p5t4cc5p9a] {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

@media (prefers-reduced-motion: reduce) {
    .vpl-slot[b-p5t4cc5p9a] { animation: none; opacity: 0; }
    .vpl-slot--1[b-p5t4cc5p9a] { opacity: 1; }
}
/* /Components/Common/Hint/CpHint.razor.rz.scp.css */
/* Hover-to-read hint. Colours/spacings go through the --cp-* token layer, so light and dark are
   handled for us (CLAUDE.md: no DevExpress or Bootstrap variables, no raw hex).

   Reveal is entirely in this file — the component ships no open/closed state, so nothing here may
   depend on a class Blazor toggles. Note the specificity: the base rule hides the panel with one
   class (0,1,0) and the two reveal rules carry three (0,3,0), so they win wherever they sit in the
   file. Do NOT add another rule below this block that sets visibility on .cp-hint-panel. */

/* Positioning context for the panel, and the box whose :hover reveals it. inline-flex so the
   trigger sits on the text baseline of whatever line it is dropped into rather than adding leading
   to it. */
.cp-hint[b-b9mz3glq6a] {
    position: relative;
    display: inline-flex;
    align-items: center;
}

.cp-hint-trigger[b-b9mz3glq6a] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: transparent;
    color: var(--cp-color-content-muted);
    cursor: help;
    padding: 0;
    line-height: 1;
    border-radius: var(--cp-radius-sm);
    transition: color .2s ease;
}

/* The icon takes the accent whenever the panel is up, so the trigger and the panel never disagree
   about what is happening. */
.cp-hint:hover .cp-hint-trigger[b-b9mz3glq6a],
.cp-hint:focus-within .cp-hint-trigger[b-b9mz3glq6a] {
    color: var(--cp-color-primary);
}

/* The panel. Absolutely positioned on purpose: reading a hint must not move the layout around it.
   width:max-content with a max-width lets a short hint stay short and a long one wrap.

   Hidden with visibility, not display: display is not animatable, so a display-toggled panel can
   only snap in. visibility keeps the two properties that matter — the panel is out of the
   accessibility tree and takes no pointer events while hidden — and it transitions, flipping to
   visible at the START of the reveal and to hidden at the END of the dismissal, which is exactly the
   ordering a fade needs. It costs nothing in layout either way, because the panel is out of flow. */
.cp-hint-panel[b-b9mz3glq6a] {
    visibility: hidden;
    opacity: 0;
    transform: translateY(-.25rem) scale(.98);
    position: absolute;
    top: calc(100% + .35rem);
    z-index: 20;
    width: max-content;
    max-width: min(18rem, 70vw);
    padding: .5rem .625rem;
    border: 1px solid var(--cp-color-border);
    border-radius: var(--cp-radius-sm);
    background: var(--cp-color-surface);
    box-shadow: var(--cp-shadow-2);
    color: var(--cp-color-content);
    /* Explicit, not inherited: a hint often hangs off a bold title or a caption, and the panel is
       body copy either way. */
    font-size: var(--cp-text-body2-size);
    font-weight: var(--cp-text-body2-weight);
    line-height: var(--cp-text-body2-line);
    text-align: left;
    white-space: normal;
    /* Dismissal: quicker than the reveal and with no easing flourish. A hint on its way out is not
       the thing the customer is looking at. */
    transition: opacity .1s ease-in, transform .1s ease-in, visibility .1s;
}

/* Bridges the gap between the trigger and the panel. Without it, moving the pointer down onto the
   panel crosses .35rem of neither element and the hint flickers shut on the way. Inherits the
   panel's visibility, so it is no trap while the panel is hidden. */
.cp-hint-panel[b-b9mz3glq6a]::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: -.45rem;
    height: .45rem;
}

/* Pointer over the trigger (or the panel), or the trigger focused — by Tab, or by a tap, which is
   what makes this work on a touch screen with no hover to speak of.

   The panel grows out of the trigger's corner rather than appearing over it: it starts a quarter-rem
   high and a hair small, and settles on a decelerating curve. The origin is set per alignment below,
   so an end-aligned panel unfolds from its right edge, where the icon actually is. */
.cp-hint:hover .cp-hint-panel[b-b9mz3glq6a],
.cp-hint:focus-within .cp-hint-panel[b-b9mz3glq6a] {
    visibility: visible;
    opacity: 1;
    transform: none;
    transition: opacity .16s cubic-bezier(.2, .8, .2, 1),
                transform .16s cubic-bezier(.2, .8, .2, 1),
                visibility .16s;
}

.cp-hint-panel-start[b-b9mz3glq6a] {
    left: 0;
    transform-origin: top left;
}

.cp-hint-panel-end[b-b9mz3glq6a] {
    right: 0;
    transform-origin: top right;
}

/* Respect a reduced-motion preference: the hint still appears and disappears, it just does not
   travel or fade. Nothing here is conveyed by the motion itself. */
@media (prefers-reduced-motion: reduce) {
    .cp-hint-panel[b-b9mz3glq6a],
    .cp-hint:hover .cp-hint-panel[b-b9mz3glq6a],
    .cp-hint:focus-within .cp-hint-panel[b-b9mz3glq6a] {
        transform: none;
        transition: none;
    }
}
/* /Components/Common/Navigation/SectionNav.razor.rz.scp.css */
/* Fixed left-edge "jump to section" rail. At rest: just the dashes, the active one in brand blue.
   Hover or focus an item and its title expands in; where the side gutter is wide enough the active
   section's title also stays shown. Colours use Bootstrap tokens so light/dark themes are handled
   automatically. The rail lives in the empty gutter beside the centred .wq-shell (--cp-content-max)
   and is hidden when the viewport is too narrow to have that gutter. */
.section-nav[b-888n4hz2u4] {
    position: fixed;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    z-index: 1000;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    margin: 0;
    padding: 0.75rem 0.875rem;
    /* The rail floats over the page — only the dashes/labels should catch the pointer, so clicks in
       the empty gutter still reach the content underneath. */
    pointer-events: none;
}

.sn-item[b-888n4hz2u4] {
    display: flex;
    align-items: center;
    gap: 0;
    text-decoration: none;
    color: inherit;
    pointer-events: auto;
    border-radius: var(--cp-radius);
    outline-offset: 3px;
}

.sn-dash[b-888n4hz2u4] {
    flex: 0 0 auto;
    width: 12px;
    height: 4px;
    border-radius: 3px;
    background-color: var(--cp-color-border);
    transition: width .18s ease, background-color .18s ease;
}

/* Collapsed out of layout by default (max-width:0 + no margin), so an inactive item's hit area is
   just its dash and the empty gutter stays clickable. Revealed on hover/focus, and — on wide
   viewports — for the active item (see the min-width rule below). */
.sn-label[b-888n4hz2u4] {
    max-width: 0;
    margin-left: 0;
    overflow: hidden;
    opacity: 0;
    font-size: var(--cp-text-body2-size);
    white-space: nowrap;
    color: var(--cp-color-content-muted);
    /* A halo in the page background keeps the title legible if it expands over content on hover. */
    text-shadow: 0 0 6px var(--cp-color-surface), 0 0 6px var(--cp-color-surface), 0 0 6px var(--cp-color-surface);
    transition: max-width .18s ease, margin-left .18s ease, opacity .18s ease, color .18s ease;
    pointer-events: none;
}

.sn-item:hover .sn-label[b-888n4hz2u4],
.sn-item:focus-visible .sn-label[b-888n4hz2u4] {
    max-width: 12rem;
    margin-left: 0.5rem;
    opacity: 1;
}

.sn-item:hover .sn-dash[b-888n4hz2u4],
.sn-item:focus-visible .sn-dash[b-888n4hz2u4] {
    width: 20px;
    background-color: var(--cp-color-content-muted);
}

/* Active section: the dash is always brand blue; the title's colour/weight are set here but it only
   expands on hover/focus, unless the viewport is wide enough (see the min-width rule below). */
.sn-item.active .sn-dash[b-888n4hz2u4] {
    width: 20px;
    background-color: var(--cp-color-primary);
}

.sn-item.active .sn-label[b-888n4hz2u4] {
    color: var(--cp-color-primary);
    font-weight: 500;
}

/* Wide enough for a real side gutter: keep the active section's title permanently visible. Below
   this the active section is shown by its blue dash alone and every title reveals on hover/focus, so
   the rail never paints a persistent label over the centred content. */
@media (min-width: 1440px) {
    .sn-item.active .sn-label[b-888n4hz2u4] {
        max-width: 12rem;
        margin-left: 0.5rem;
        opacity: 1;
    }
}

/* Below ~1280px there is no side gutter for the rail to sit in — hand navigation back to normal
   scrolling and the existing mobile CTA. */
@media (max-width: 1279.98px) {
    .section-nav[b-888n4hz2u4] {
        display: none;
    }
}

@media print {
    .section-nav[b-888n4hz2u4] {
        display: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .sn-dash[b-888n4hz2u4],
    .sn-label[b-888n4hz2u4] {
        transition: none;
    }
}
/* /Components/Common/Pill/PillWrapper.razor.rz.scp.css */
.pill[b-fdm5qe1pz2] {
    display: inline-flex;
    align-items: center;
    gap: .375rem;
    border-radius: 999px;
    border: 1px solid transparent;
    line-height: 1.15;
}

.pill > i[b-fdm5qe1pz2] {
    font-size: .9em;
}

/* ── Sizes (shared scale) ─────────────────────────────────────────────── */
.pill--xs[b-fdm5qe1pz2] {
    font-size: var(--cp-text-caption-size);
    padding: .15rem .5rem;
}

.pill--sm[b-fdm5qe1pz2] {
    font-size: var(--cp-text-body2-size);
    padding: .3rem .65rem;
}

.pill--md[b-fdm5qe1pz2] {
    font-size: var(--cp-text-body2-size);
    padding: .35rem .8rem;
}

.pill--lg[b-fdm5qe1pz2] {
    font-size: var(--cp-text-body1-size);
    padding: .45rem 1rem;
}

.pill--xl[b-fdm5qe1pz2] {
    font-size: var(--cp-text-h6-size);
    padding: .55rem 1.15rem;
}

/* ── Variants (--cp-role-* facade tokens) ─────────────────────────────── */
.pill--neutral[b-fdm5qe1pz2] {
    background: var(--cp-role-neutral-bg);
    color: var(--cp-role-neutral-fg);
    border-color: var(--cp-role-neutral-border);
}

.pill--primary[b-fdm5qe1pz2] {
    background: var(--cp-role-primary-bg);
    color: var(--cp-role-primary-fg);
    border-color: var(--cp-role-primary-border);
}

.pill--success[b-fdm5qe1pz2] {
    background: var(--cp-role-success-bg);
    color: var(--cp-role-success-fg);
    border-color: var(--cp-role-success-border);
}

.pill--warning[b-fdm5qe1pz2] {
    background: var(--cp-role-warning-bg);
    color: var(--cp-role-warning-fg);
    border-color: var(--cp-role-warning-border);
}

.pill--danger[b-fdm5qe1pz2] {
    background: var(--cp-role-danger-bg);
    color: var(--cp-role-danger-fg);
    border-color: var(--cp-role-danger-border);
}

.pill--info[b-fdm5qe1pz2] {
    background: var(--cp-role-info-bg);
    color: var(--cp-role-info-fg);
    border-color: var(--cp-role-info-border);
}

/* ── No-background / no-border modifiers ───────────────────────────────── */
/* Placed after the variants so they win on equal specificity. */
/* Drops the fill only — the variant's border and text/icon tone stay (outlined chip). */
.pill--no-bg[b-fdm5qe1pz2] {
    background: transparent;
}

/* Drops the border only — the variant's fill and text/icon tone stay. */
.pill--no-border[b-fdm5qe1pz2] {
    border-color: transparent;
}

.pill--no-wrap[b-fdm5qe1pz2] {
    white-space: nowrap;
    line-height: 1;
}
/* /Components/Common/StaggerItem.razor.rz.scp.css */
.stagger-item[b-f1wcivm0u4] {
	opacity: 0;
	transform: translateY(calc(var(--offset) * -1));
	animation: stagger-in-b-f1wcivm0u4 var(--duration) ease-out forwards;
	animation-delay: var(--delay);
	will-change: transform, opacity;
}

@keyframes stagger-in-b-f1wcivm0u4 {
	to {
		opacity: 1;
		transform: translateY(0);
	}
}

@media (prefers-reduced-motion: reduce) {
	.stagger-item[b-f1wcivm0u4] {
		animation: none;
		opacity: 1;
		transform: none;
	}
}
/* /Components/Common/Steps/Step.razor.rz.scp.css */
/* Step row — ported from the original "What happens next" timeline so both consumers share one
   look. Colours/spacings use Bootstrap tokens (never hardcoded) so light and dark themes are
   handled automatically. */

.step[b-usvz7dvzd2] {
    display: flex;
    gap: .85rem;
    /* Entrance cascade: each row fades in and rises 18px, staggered by its position (--step-i, set on
       the <li> from the 1-based Number) so the steps appear distinctly one after another. `both` keeps
       the row hidden through its stagger delay and settled afterwards. Plays once when the row enters
       the DOM; Blazor reuses elements across re-renders, so it does not replay on parameter changes. */
    animation: step-in-b-usvz7dvzd2 450ms ease-out both;
    animation-delay: calc(var(--step-i, 0) * 140ms);
}

@keyframes step-in-b-usvz7dvzd2 {
    from {
        opacity: 0;
        transform: translateY(18px);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

/* Respect a reduced-motion preference — settle straight to the final state, no fade or movement. The
   step content is fully present without the animation, so nothing is conveyed by motion alone. */
@media (prefers-reduced-motion: reduce) {
    .step[b-usvz7dvzd2] {
        animation: none;
    }
}

/* Marker column: numbered dot on top, connector line filling the rest of the row height so
   consecutive dots read as a single vertical track. */
.step-marker[b-usvz7dvzd2] {
    display: flex;
    flex-direction: column;
    align-items: center;
    flex-shrink: 0;
}

.step-dot[b-usvz7dvzd2] {
    width: 1.75rem;
    height: 1.75rem;
    border-radius: 50%;
    background: var(--cp-color-primary);
    color: var(--cp-color-on-primary);
    font-size: .8rem;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
}

.step-line[b-usvz7dvzd2] {
    flex: 1 1 auto;
    width: 2px;
    min-height: .5rem;
    margin: .35rem 0;
    background: var(--cp-color-border);
}

/* Last row has nothing below it — drop the trailing connector. */
.step:last-child .step-line[b-usvz7dvzd2] {
    display: none;
}

/* flex:1 lets the body (and a full-width CTA inside an interactive step) fill the row; the original
   timeline only held text, which filled the row on its own. */
.step-body[b-usvz7dvzd2] {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: .2rem;
    padding-bottom: 1.1rem;
}

.step:last-child .step-body[b-usvz7dvzd2] {
    padding-bottom: 0;
}

/* Highlighted step — the numbered dot swaps its solid fill for a dashed blue (primary) outline on a
   plain body background, so the step reads as pending/optional without changing the row layout.
   Tokens keep it correct in light and dark; border-box keeps the dot the same size as the solid ones. */
.step--highlighted .step-dot[b-usvz7dvzd2] {
    box-sizing: border-box;
    background: var(--cp-color-surface);
    color: var(--cp-color-primary);
    /* Subtle primary tint for the ring so the dashes don't overpower the number, which stays full primary. */
    border: 2px dashed var(--cp-role-primary-border);
}

/* Step title/description typography now comes from CpText (Subtitle1 + .fw-semibold / Caption + Muted)
   in Step.razor — scoped rules do not reach a child component's rendered element. */

/* ── Head row ──
   Title (plus the collapsed summary) on the left, actions and the collapse chevron pushed to the
   right-hand end so a "Change" button sits level with the title rather than trailing the value it
   edits. baseline, not center: a one-line button should align with the title's text, not with the
   middle of a title that has wrapped to two lines. */
.step-head[b-usvz7dvzd2] {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--cp-space-2);
}

.step-head-main[b-usvz7dvzd2] {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-1);
}

.step-head-actions[b-usvz7dvzd2] {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: var(--cp-space-1);
}

.step-head-clickable[b-usvz7dvzd2] {
    cursor: pointer;
    user-select: none;
}

/* Collapsed summary: the value, quieter than the title above it, truncated rather than wrapped so
   the head row stays one line however long a PO reference is. */
.step-summary[b-usvz7dvzd2] {
    font-size: var(--cp-text-body2-size);
    color: var(--cp-color-content-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Collapse chevron — matches Card's (.dx-card-toggle), kept as a plain button so the component
   stays bUnit-testable. */
.step-toggle[b-usvz7dvzd2] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: transparent;
    color: var(--cp-color-content-muted);
    cursor: pointer;
    padding: var(--cp-space-1);
    border-radius: var(--cp-radius-sm);
    line-height: 1;
    transition: background-color .2s ease, color .2s ease;
}

.step-toggle:hover[b-usvz7dvzd2] {
    background: var(--cp-color-surface-muted);
    color: var(--cp-color-content);
}

/* display:none, not a max-height transition: the content of a collapsible step is form controls, and
   a zero-height-but-rendered block keeps them in the tab order and in the accessibility tree —
   hidden controls the keyboard can still reach. The trade is no slide animation.

   Both classes in the selector on purpose: .step-content further down this file sets display:flex at
   the same specificity, and being the later rule it would win. */
.step-content.step-content-collapsed[b-usvz7dvzd2] {
    display: none;
}

/* Interactive-step controls get comfortable spacing and a little separation from the title. */
.step-content[b-usvz7dvzd2] {
    display: flex;
    flex-direction: column;
    gap: .5rem;
/*    margin-top: .4rem;*/
}
/* /Components/Common/Steps/Steps.razor.rz.scp.css */
/* Ordered-list reset for the step sequence. The row styling lives in Step.razor.css (each row is a
   Step component, so its scoped CSS applies to the row markup it authors). Colours/spacings use
   Bootstrap tokens so light and dark themes are handled automatically. */
.steps[b-no3m527uu1] {
    list-style: none;
    margin: 0;
    padding: 0;
}
/* /Components/Common/TitledValue.razor.rz.scp.css */
[b-dfb93ehr1w] .wrapper {
	display: flex;
	flex-direction: column;
	gap: 0;
	min-width: 0;
}

/* Colour now comes from CpText (TitleColor=Muted); this rule keeps only the layout tightening.
   font-weight stays on .value as a role invariant ("the value reads semibold") independent of the
   caller's chosen Typo. */
[b-dfb93ehr1w] .title {
	margin: 0;
	padding: 0;
}

[b-dfb93ehr1w] .value {
	margin: 0;
	padding: 0;
	font-weight: 600;
}

[b-dfb93ehr1w] .foot {
	margin-top: .35rem;
	line-height: 1.35;
}
/* /Components/Job/Account/JobListFilterBar.razor.rz.scp.css */
/* Filter bar: a wrapping row of fields that collapses to one column on a phone. Widths are
   flex-basis rather than fixed so the fields share the row rather than overflow it. */
.filter-bar[b-loph7dhzpr] {
    display: flex;
    flex-wrap: wrap;
    gap: var(--cp-space-3);
    align-items: flex-end;
}

.filter-field[b-loph7dhzpr] {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    flex: 1 1 12rem;
    min-width: 0;
}

.filter-field--reference[b-loph7dhzpr],
.filter-field--part[b-loph7dhzpr] {
    flex: 2 1 16rem;
}

.filter-field--date[b-loph7dhzpr] {
    flex: 1 1 10rem;
}

.filter-field label[b-loph7dhzpr] {
    font-size: var(--cp-text-caption-size);
    color: var(--cp-color-content-muted);
}

.filter-actions[b-loph7dhzpr] {
    display: flex;
    gap: var(--cp-space-2);
    flex: 0 0 auto;
}

@media (max-width: 575.98px) {
    .filter-field[b-loph7dhzpr],
    .filter-field--reference[b-loph7dhzpr],
    .filter-field--part[b-loph7dhzpr],
    .filter-field--date[b-loph7dhzpr] {
        flex: 1 1 100%;
    }

    .filter-actions[b-loph7dhzpr] {
        flex: 1 1 100%;
    }
}
/* /Components/Job/Account/JobListTable.razor.rz.scp.css */
/* The grid renders its own chrome; what is styled here is the first column's stack (link,
   description, meta) and the fact that a row is clickable. */

[b-1kwen3vtud] .job-row {
    cursor: pointer;
}

[b-1kwen3vtud] .job-no {
    font-weight: 600;
    text-decoration: none;
}

[b-1kwen3vtud] .job-no:hover,
[b-1kwen3vtud] .job-no:focus-visible {
    text-decoration: underline;
}

[b-1kwen3vtud] .job-description {
    color: var(--cp-color-content);
    margin-top: 0.125rem;
}

[b-1kwen3vtud] .job-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0 var(--cp-space-3);
    font-size: var(--cp-text-caption-size);
    color: var(--cp-color-content-muted);
}

/* The first two columns stack more than one line, so their cells must wrap even though
   TextWrapEnabled is off for the dense date and money columns. */
[b-1kwen3vtud] .job-list-grid .dxbl-grid-table td:first-child,
[b-1kwen3vtud] .job-list-grid .dxbl-grid-table td:nth-child(2) {
    white-space: normal;
}
/* /Components/Job/Account/JobOrderBreakdown.razor.rz.scp.css */
/* The breakdown's sections, with the same air between them the quote page leaves. */
.order-sections[b-iy3e7wzknx] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-8);
}

/* A headed section: its title, then its body. The same shape the quote page's Checkout and Parts
   Finishing sections have, so the two surfaces read as one document. */
.ob-section[b-iy3e7wzknx] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-2);
}

/* Two columns on desktop, in the quote page's proportions: the narrow card leads, the wide one
   follows. Sections with one card leave the second track empty rather than stretching that card
   across the page - a totals card or an address is no wider for having room. */
.ob-section-body[b-iy3e7wzknx] {
    display: grid;
    grid-template-columns: 1fr 1.5fr;
    gap: var(--cp-space-6);
    align-items: start;
}

@media (max-width: 880px) {
    .ob-section-body[b-iy3e7wzknx] {
        grid-template-columns: 1fr;
    }
}

/* Section-jump anchors: leave a little breathing room above a section when the left rail scrolls it
   into view (the scroll happens inside .app-main, not the window). */
.ob-anchor[b-iy3e7wzknx] {
    scroll-margin-top: var(--cp-space-5);
}

/* The rail moves focus here (via a JS-set tabindex="-1") after a jump; the section wrapper is not an
   interactive control, so it should not paint a focus ring. */
.ob-anchor:focus[b-iy3e7wzknx] {
    outline: none;
}
/* /Components/Job/Account/JobOrderDelivery.razor.rz.scp.css */
/* Auto-fitting stat grid: as many columns as the width allows, one on a phone. */
.summary-grid[b-vh3ftf818w] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-3);
}

.summary-address[b-vh3ftf818w] {
    grid-column: 1 / -1;
}

.summary-address address[b-vh3ftf818w] {
    font-style: normal;
}
/* /Components/Job/Account/JobOrderDispatches.razor.rz.scp.css */
.dispatch-list[b-lsyy0v5qze] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-3);
}

.dispatch[b-lsyy0v5qze] {
    display: flex;
    align-items: flex-start;
    gap: var(--cp-space-2);
}

.dispatch > i[b-lsyy0v5qze] {
    color: var(--cp-color-content-muted);
    margin-top: 0.15rem;
}

.dispatch-title[b-lsyy0v5qze] {
    display: flex;
    flex-wrap: wrap;
    gap: 0 var(--cp-space-2);
    font-weight: 600;
}

.dispatch-date[b-lsyy0v5qze] {
    font-weight: 400;
    color: var(--cp-color-content-muted);
}

.tracking-number[b-lsyy0v5qze] {
    margin-left: var(--cp-space-3);
    font-family: var(--cp-font-family-mono);
}
/* /Components/Job/Account/JobRepeatPopup.razor.rz.scp.css */
/* The quantity box.

   DxSpinEdit's own Width parameter does not survive this row: the editor's root is a flex item and
   stretches to whatever space is left, so each row's box came out a different width depending on how
   long the part code beside it was.

   !important is load-bearing here, the same way it is on the browse grids: the editor's own theme
   rules set the root and the inner input to full width, and they are emitted after the isolated
   bundle. Revisit after each DevExpress major bump. The inner input is reached through the
   documented InputCssClass parameter rather than by guessing at a DevExpress class name.

   content-box is the point of the box-sizing line: Bootstrap makes everything border-box, and the
   theme gives this input a padding-right of its own (padding-left it zeroes), so a border-box 6ch
   would be six characters minus that padding — visibly less than six characters. content-box makes
   the number below mean what it says.

   Six characters is the floor, not the ceiling — field-sizing lets the box grow for a customer
   ordering more digits than that. Browsers without field-sizing (Firefox, Safari today) keep the 6ch
   box and scroll the number: same floor, no growth.

   Alignment is deliberately left to the theme, which sets text-align: initial on this input. The
   app right-aligns figures only where they stack in a column and their magnitudes are meant to be
   compared — JobPriceBreakdown, JobTotalsLine. These quantities each sit in their own bordered box,
   so there is nothing to line up, and JobQuoteQuantityEditor's spin edit leaves the default alone
   too. Any gap between the number and the spin buttons is the theme's own padding, not this width. */
.repeat-body[b-qd2byhslya]  .repeat-qty {
    flex: 0 0 auto !important;
    width: fit-content !important;
    min-width: 0 !important;
}

.repeat-body[b-qd2byhslya]  .repeat-qty-input {
    box-sizing: content-box !important;
    width: 6ch !important;
    min-width: 6ch !important;
    field-sizing: content;
}
/* /Components/Job/Account/QuoteListTable.razor.rz.scp.css */
/* The grid renders its own chrome; what is styled here is the first column's stack (link,
   description, meta) and the fact that a row is clickable. */

[b-13aq1alk0r] .quote-row {
    cursor: pointer;
}

[b-13aq1alk0r] .quote-no {
    font-weight: 600;
    text-decoration: none;
}

[b-13aq1alk0r] .quote-no:hover,
[b-13aq1alk0r] .quote-no:focus-visible {
    text-decoration: underline;
}

[b-13aq1alk0r] .quote-description {
    color: var(--cp-color-content);
    margin-top: 0.125rem;
}

[b-13aq1alk0r] .quote-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0 var(--cp-space-3);
    font-size: var(--cp-text-caption-size);
    color: var(--cp-color-content-muted);
}

/* The first column stacks three lines, so its cells must wrap even though TextWrapEnabled is off
   for the dense date and money columns. */
[b-13aq1alk0r] .quote-list-grid .dxbl-grid-table td:first-child {
    white-space: normal;
}
/* /Components/Job/Dialogs/JobPartViewerPopup.razor.rz.scp.css */
/* Header: part name and its overall size on one line, the 2D/3D switch pinned right. DxPopup's own
   close button sits outside this content area, so the row only has to leave room for the switch. */
.pv-head[b-85bm94upnl] {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--cp-space-3);
    width: 100%;
    min-width: 0;
}

.pv-titles[b-85bm94upnl] {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--cp-space-1) var(--cp-space-3);
    min-width: 0;
}

/* Part names are long and machine-generated (Waterbeach.Q3034.P80.Cappings_2), so the name is sized
   as a subtitle rather than a headline and truncates instead of pushing the switch off the row. */
.pv-name[b-85bm94upnl] {
    font-size: var(--cp-text-subtitle1-size);
    font-weight: var(--cp-text-subtitle1-weight);
    line-height: var(--cp-text-subtitle1-line);
    color: var(--cp-color-content);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

.pv-sub[b-85bm94upnl] {
    font-size: var(--cp-text-body2-size);
    line-height: var(--cp-text-body2-line);
    color: var(--cp-color-content-muted);
    white-space: nowrap;
}

.pv-switch[b-85bm94upnl] {
    flex: 0 0 auto;
    align-self: center;
    white-space: nowrap;
}

/* The viewers measure this container on mount and paint it themselves (dark or light, their own
   toggle), so it only supplies the height — no background, no padding of ours. min-height:0 keeps
   it shrinkable inside DxPopup's body flex. */
.pv-host[b-85bm94upnl] {
    height: 100%;
    min-height: 0;
    position: relative;
}
/* /Components/Job/JobBrandHeader.razor.rz.scp.css */
.wq-header[b-n3ankmsxwk] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    flex-wrap: wrap;

    width: 100%;
    max-width: var(--cp-content-max);
    padding: 1rem;
    border-bottom: 1px solid var(--cp-color-border);
    background-color: var(--cp-color-surface);

    position: fixed;
    top: 0;
    left: 50%;
    translate: -50% 0;
    z-index: 1000;
}

.wq-header-brand-only[b-n3ankmsxwk] {
    border-bottom: none;
    padding-bottom: 0;
}

.wq-logo[b-n3ankmsxwk] {
    display: none;
    height: 32px;
    width: auto;
}

.wq-logo-min[b-n3ankmsxwk] {
    display: none;
    height: 32px;
    width: auto;
}

.wq-brand-sub[b-n3ankmsxwk] {
    font-size: .7rem;
    text-transform: uppercase;
    letter-spacing: .12em;
    color: var(--cp-color-content-muted);
    margin-top: .4rem;
}

@media screen and (max-width: 423px) {
    .wq-header[b-n3ankmsxwk] {
        justify-content: start;
    }
}


@media screen and (max-width: 624px) and (min-width: 424px) {
    .wq-logo-min[b-n3ankmsxwk] {
        display: block;
    }
}

@media screen and (min-width: 625px) {
    .wq-logo[b-n3ankmsxwk] {
        display: block;
    }
}
/* /Components/Job/JobFooter.razor.rz.scp.css */
.wq-footer[b-ug39uxrhvv] {
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: .5rem;
    margin-top: .5rem;
    padding-top: 1rem;
    border-top: 1px solid var(--cp-color-border);
}
/* /Components/Job/JobHero.razor.rz.scp.css */
.wq-hero[b-2hcb4x7mte] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-7);
}
/* /Components/Job/JobIdentityHero.razor.rz.scp.css */
.wq-hero-base-data[b-xqxnggm0bw] {
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: 1rem;
}


/* Deliberate off-scale exception (see JobIdentityHero.razor): the hero number is larger than any
   --cp-text-* Typo tier, so it is sized here rather than through CpText / ValueTypo. */
[b-xqxnggm0bw] .wq-hero-quote-number-value {
    font-size: 3.5rem;
    line-height: 1;
}
/* /Components/Job/JobStatRow.razor.rz.scp.css */
.wq-stats[b-fdzw2at4oq] {
    display: flex;
    flex-wrap: wrap;
    gap: var(--cp-space-4);
}

/* grow + shrink + basis — flex-grow alone leaves the cells at their content width. ::deep because the
   class lands on the Card component's own root element, outside this component's markup. */
[b-fdzw2at4oq] .wq-stats-item {
    flex: 1 1 10rem;
    min-width: fit-content;
}
/* /Components/Job/JobTolerances.razor.rz.scp.css */
.wq-tol-row[b-nmhljiyki2] {
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

.wq-tol-val[b-nmhljiyki2] {
    display: flex;
    flex-wrap: wrap;
    flex-direction: column;
    align-items: start;
    gap: .35rem;
    font-size: .85rem;
}
/* /Components/Job/LineItems/JobItem.razor.rz.scp.css */
/* The card surface (border, radius, shadow, background, dark mode) comes from the shared Card
   component; here we only lay out the content inside its padding-zeroed body. The media band bleeds to
   the card edges, and this content block carries the padding and grows (flex: 1) to fill the card so
   the price summary can pin to the foot and cards in a row stay equal height. */
.wq-card-content[b-r324pr22b5] {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: .6rem;
    padding: .75rem;
}

.wq-card-ops[b-r324pr22b5] {
    display: flex;
    flex-wrap: wrap;
    align-items: start;
    gap: .4rem;
    height: 100%;
}

.wq-card-foot[b-r324pr22b5] {
    background-color: var(--cp-role-primary-bg);
    padding: .75rem;
    border-radius: var(--cp-radius);
    margin: .75rem;
    margin-top: 0;
}

/* Part codes are long, unbroken dotted tokens that would otherwise overflow the compact card and get
   clipped without an ellipsis. Let them break anywhere, cap at two lines with an ellipsis, and expose
   the full name via the title tooltip (the media badge already identifies the item). ::deep is needed
   because the title is rendered by the child CpText component, outside this component's CSS scope. */
[b-r324pr22b5] .wq-card-title {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    overflow-wrap: anywhere;
}
/* /Components/Job/LineItems/JobItemList.razor.rz.scp.css */
/* Heading + count. Allowed to shrink (min-width:0) so its long caption wraps to a second line instead
   of claiming the whole row and squeezing the tools cluster into stacking. */
.wq-line-heading[b-kon0tohyb7] {
    flex: 1 1 14rem;
    min-width: 0;
}

/* Find-a-part box + view toggle: one row that stays one row. It takes the space the heading leaves and
   breaks below the heading as a unit when there is not enough of it. */
.wq-line-tools[b-kon0tohyb7] {
    flex: 1 1 20rem;
    min-width: 0;
    justify-content: flex-end;
}

/* The box absorbs the shrinking (the toggle beside it does not), so no hard flex-basis: a basis wide
   enough to be comfortable is also wide enough to force a wrap on a mid-width viewport. ::deep because
   the class lands on DxTextBox's own root element, which is not part of this component's markup. */
[b-kon0tohyb7] .wq-line-search {
    flex: 1 1 auto;
    min-width: 7rem;
    max-width: 22rem;
}

/* Line-item cards as a wrapping grid that grows downward — the page scrolls vertically, not a sideways
   carousel. auto-fill packs as many compact cards per row as fit, then wraps to new rows below. Tune
   the min track width to change how many sit per row / how small the cards get. */
.wq-line-grid[b-kon0tohyb7] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(264px, 1fr));
    gap: .75rem;
    padding: .25rem;

    max-height: 75vh;
    overflow: auto;
}
/* /Components/Job/LineItems/JobItemMedia.razor.rz.scp.css */
/* Positioned frame for the preview + overlays. Owns the background and the divider to the card body;
   its top corners are clipped by the card's border-radius (the card sets overflow: hidden). */
.wq-media[b-swvfn2ps3t] {
    position: relative;
    aspect-ratio: 16 / 10;
    flex-shrink: 0;
    background: var(--cp-color-surface-muted);
    border-bottom: 1px solid var(--cp-color-border);
    border-radius: inherit;
    overflow: hidden;
}

/* Item number, in a circle over the top-left. Non-interactive, so it never swallows a click meant
   for the preview beneath it. */
.wq-media-num[b-swvfn2ps3t] {
    position: absolute;
    top: .5rem;
    left: .5rem;
    z-index: 1;
    pointer-events: none;
    width: 1.75rem;
    height: 1.75rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--cp-color-surface);
    border: 1px solid var(--cp-color-border);
    color: var(--cp-color-content-muted);
    font-size: .8rem;
    font-weight: 600;
    font-feature-settings: "tnum";
}

/* "3D" action, over the bottom-left — a real button, so it keeps pointer events and focus. */
.wq-media-3d[b-swvfn2ps3t] {
    position: absolute;
    bottom: .5rem;
    left: .5rem;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    padding: .25rem .55rem;
    font-size: var(--cp-text-caption-size);
    font-weight: 500;
    line-height: 1;
    color: var(--cp-color-content);
    background: var(--cp-color-surface);
    border: 1px solid var(--cp-color-border);
    border-radius: 999px;
    cursor: pointer;
    transition: background-color .12s ease, border-color .12s ease;
}

.wq-media-3d:hover[b-swvfn2ps3t] {
    background: var(--cp-color-surface-muted);
    border-color: var(--cp-color-content-muted);
}

.wq-media-3d:focus-visible[b-swvfn2ps3t] {
    outline: 2px solid var(--cp-color-primary);
    outline-offset: 1px;
}
/* /Components/Job/LineItems/JobItemSpec.razor.rz.scp.css */
/* Spacing between the TitledValue pairs; each pair styles its own label/value/foot. */
.wq-spec[b-70nzocxzd7] {
    display: flex;
    flex-direction: column;
    gap: .7rem;
}
/* /Components/Job/LineItems/JobItemTable.razor.rz.scp.css */
/* Only inspectable rows are clickable (row click opens the 2D viewer), so only those — tagged
   "wq-line-row" by CustomizeElement — get the pointer cursor. The wrapping div carries the CSS
   isolation scope so ::deep can reach DevExpress's generated row elements. */
.wq-line-table[b-8e8q5th0nj]  .wq-line-row {
    cursor: pointer;
}

/* An expanded row's detail panel: a tinted band under its row, wrapping re-enabled (the grid turns it
   off for its own one-line cells), so the panel reads as belonging to the row above it rather than as
   another row of the same table. */
.wq-line-table[b-8e8q5th0nj]  .wq-line-detail {
    padding: var(--cp-space-2) var(--cp-space-3);
    background: var(--cp-color-surface-muted);
    white-space: normal;
}
/* /Components/Job/LineItems/JobLineTable.razor.rz.scp.css */
/* Unit price cell: the figure, then the price-break toggle tucked against it. Right-aligned like the
   other money columns; the toggle is a fixed-width slot so prices stay lined up down the column whether
   or not a row offers one. ::deep throughout — the cells are DxGrid's markup, not this component's. */
[b-a1toba8kan] .wq-unit {
    display: inline-flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--cp-space-1);
    width: 100%;
}

[b-a1toba8kan] .wq-unit__toggle {
    display: inline-flex;
    width: 1.75rem;
    justify-content: center;
    flex: 0 0 auto;
}

/* DevExpress's text button carries its own padding; a tight icon-only hit target keeps the row dense. */
[b-a1toba8kan] .wq-unit__btn {
    padding: 0 0.25rem;
    line-height: 1;
}
/* /Components/Job/LineItems/JobPartThumb.razor.rz.scp.css */
/* Fills the media frame provided by the parent (JobItemMedia). The frame owns the
   background and border; the button itself is transparent — only the image + expand chip show. */
.wq-thumb[b-st69qowtfb] {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
    margin: 0;
    padding: 0;
    font: inherit;
    color: inherit;
    background: transparent;
    border: 0;
    cursor: pointer;
    outline: 2px solid transparent;
    border-radius: inherit;
    outline-offset: -2px;
    transition: outline-color .12s ease;
}

.wq-thumb:hover[b-st69qowtfb],
.wq-thumb:focus-visible[b-st69qowtfb] {
    outline-color: var(--cp-color-primary);
}

/* Static preview (nothing to inspect) — no pointer affordance and no hover highlight. */
.wq-thumb-static[b-st69qowtfb] {
    cursor: default;
}

.wq-thumb-static:hover[b-st69qowtfb] {
    outline-color: transparent;
}

.wq-thumb-img[b-st69qowtfb] {
    width: 100%;
    height: 100%;
    object-fit: contain;
    padding: .75rem;
}

/* Expand affordance (open the 2D viewer / measure). A subtle round chip in the bottom-right. */
.wq-thumb-expand[b-st69qowtfb] {
    position: absolute;
    bottom: .375rem;
    right: .375rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.5rem;
    border-radius: 50%;
    background: var(--cp-color-surface);
    border: 1px solid var(--cp-color-border);
    color: var(--cp-color-content-muted);
    font-size: .7rem;
}
/* /Components/Job/Pricing/JobPriceBreakdown.razor.rz.scp.css */
/* The shared money-breakdown block: the net (ex VAT) figure is the 1.5rem headline, VAT + gross drop
   beneath it. This is the single home for the wq-price styling — the totals card, the confirm-order
   dialog and the per-line summary all render through JobPriceBreakdown, so the rules that used to
   be hand-duplicated across those components now live here only. */
.wq-price[b-xnqi6zuyhj] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-2);
    margin: 0;
}

.wq-price-row[b-xnqi6zuyhj] {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--cp-space-3);
}

.wq-price-row dt[b-xnqi6zuyhj] {
    margin: 0;
}

.wq-price-row dd[b-xnqi6zuyhj] {
    margin: 0;
    text-align: right;
}

.wq-price-net[b-xnqi6zuyhj] {
/*    color: var(--cp-color-primary);*/
}
/* /Components/Job/Pricing/JobTotals.razor.rz.scp.css */
.job-totals-wrapper[b-r0k3l4z4ml] {
    max-width: 524px;
}
/* /Components/Job/Pricing/JobTotalsSummary.razor.rz.scp.css */
/* The summed figures sit on a tinted panel so the eye separates them from the per-line recap above,
   which is the same treatment the line cards give their price foot. */
.job-totals-sum[b-dclr1zb4bf] {
    background-color: var(--cp-role-primary-bg);
    padding: var(--cp-space-3);
    border-radius: var(--cp-radius);
}
/* /Components/Job/Pricing/Totals/JobTotalsLine.razor.rz.scp.css */
/* VAT breakdown is secondary detail: hidden until the row is hovered/focused so the
   totals list reads as a clean price column at a glance. */
.totals-line-vat[b-lxq152xji4] {
    max-height: 0;
    opacity: 0;
    overflow: hidden;
    transform: translateY(-0.125rem);
    transition: max-height 500ms ease, opacity 500ms ease, transform 500ms ease;
    transition-delay: 0s;
}

.totals-line:hover .totals-line-vat[b-lxq152xji4],
.totals-line:focus-within .totals-line-vat[b-lxq152xji4] {
    max-height: 2rem;
    opacity: 1;
    transform: none;
    transition-delay: 500ms;
}

.totals-line-vat-text[b-lxq152xji4] {
    width: 100%;
    text-align: right;
}

/* Touch devices have no hover state — keep the breakdown permanently visible there. */
@media (hover: none) {
    .totals-line-vat[b-lxq152xji4] {
        max-height: 2rem;
        opacity: 1;
        transform: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .totals-line-vat[b-lxq152xji4] {
        transition: none;
    }
}
/* /Components/Job/Pricing/Totals/JobTotalsLineList.razor.rz.scp.css */
/* Capped rather than unbounded: a fifty-line job would otherwise push the total itself off the card.
   Thin scrollbar so the list keeps its width whether or not it overflows. */
.totals-lines[b-iieasavxi5] {
    max-height: 480px;
    overflow: auto;
    scrollbar-width: thin;
}
/* /Components/Job/Quote/Delivery/JobQuoteDeliveryPicker.razor.rz.scp.css */
/* Delivery picker: the two timing option cards keep their layout/style; the date itself is a stock
   DevExpress DxDateEdit (its own sizing/spacing — no bespoke calendar CSS). Root flows flat inside
   the checkout Step ("1 · Choose delivery"), which supplies the number/title/framing. All
   colours/fonts/spacings go through Bootstrap tokens (CLAUDE.md: never hardcoded). */

.qv-del[b-773vy9td0o] {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    /* Query container for the option row below. A media query would be wrong here: this card's width
       does not track the viewport's — the checkout section drops from a two-column grid to one at
       880px, which makes the card WIDER, not narrower — so the tiles would go side by side at
       exactly the sizes where they do not fit. */
    container-type: inline-size;
}

/* ── Option row ──
   Stacked by default, two across once there is room. Mobile-first on purpose: where container
   queries are unsupported the tiles simply stay stacked, which is the layout they had before. */
.qv-opts[b-773vy9td0o] {
    display: grid;
    grid-template-columns: 1fr;
    gap: .5rem;
    /* Each tile keeps its own height. The grid default (stretch) made the "soonest" tile grow to
       match whatever the date tile had expanded to — a tall empty box next to the calendar, which
       read as the unselected option having something in it. */
    align-items: start;
}

@container (min-width: 32rem) {
    .qv-opts[b-773vy9td0o] {
        grid-template-columns: 1fr 1fr;
    }

    /* Half the width, so the tile spends less of it on padding. */
    .qv-opts .qv-opt-choose[b-773vy9td0o] {
        padding: .75rem 1rem;
    }

    .qv-opts .qv-opt-date[b-773vy9td0o] {
        padding: 0 1rem .75rem;
    }

    .qv-opts .qv-opt-header[b-773vy9td0o] {
        gap: .75rem;
    }
}

/* ── Timing option cards ──────────────────────────────────────────────
   Borderless tinted panels, the same treatment the money blocks use (see
   JobQuoteTotalsSummary: no border, tinted surface, one radius) so the two halves of the checkout
   read as one family. Unselected is the quiet grey surface; selected takes the primary tint that
   marks the figure being agreed to.

   Specificity note: .qv-opt and .qv-opt-sel both carry one class, so the selected rule wins only by
   sitting later in the file. Both set background-color (not the background shorthand) so there is no
   longhand-vs-shorthand surprise if they are ever reordered. */
.qv-opt[b-773vy9td0o] {
    width: 100%;
    margin: 0;
    border: none;
    border-radius: var(--cp-radius);
    background-color: var(--cp-color-surface-muted);
    /* No padding and no overflow clip here: the padding belongs to the button inside (so the whole
       panel is the hit target), and clipping would cut off the date hint's popover panel. */
    padding: 0;

    transition: background-color .25s;
}

/* Hover darkens the panel — the step a DevExpress button takes, not a shadow or a border, so a tile
   and a DxButton behave the same way under the pointer. Skipped on the selected tile: it is already
   tinted, and it is the one holding the date editor, so hovering that would repaint the panel for no
   reason. Three classes, so this beats both .qv-opt and .qv-opt-sel wherever they sit. */
.qv-opt:not(.qv-opt-sel):hover[b-773vy9td0o] {
    background-color: var(--cp-color-surface-muted-hover);
}

.qv-opt-sel[b-773vy9td0o] {
    background-color: var(--cp-role-primary-bg);
}

/* The button that makes the tile pressable. Stripped back to the panel it sits in — DevExpress paints
   its own buttons, this one only has to be one. Inset outline so the focus ring follows the panel's
   corners instead of drawing outside them. */
.qv-opt-choose[b-773vy9td0o] {
    display: block;
    width: 100%;
    border: none;
    background: transparent;
    color: inherit;
    font: inherit;
    text-align: left;
    padding: .75rem 1.25rem;
    cursor: pointer;
    border-radius: inherit;
}

.qv-opt-choose:focus-visible[b-773vy9td0o] {
    outline: 2px solid var(--cp-color-primary);
    outline-offset: -2px;
}


.qv-opt-header[b-773vy9td0o] {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 1rem;
}

/* Date editor + its hint on one line. A grid, not a flex row: DevExpress editors size themselves to
   100% of their container, which in a flex row would push the hint out of the tile — in a
   minmax(0, 1fr) track that 100% resolves to the track instead, and no ::deep reach into the
   editor's own markup is needed. */
.qv-opt-date[b-773vy9td0o] {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: .5rem;
    /* The tile's padding lives on the button above, which already supplies the gap over this row. */
    padding: 0 1.25rem .75rem;
}

.qv-opt-ico[b-773vy9td0o] {
    flex: 0 0 auto;
    font-size: var(--cp-text-h5-size);
    color: var(--cp-color-content-muted);
    line-height: 1;
}

.qv-opt-sel .qv-opt-ico[b-773vy9td0o] {
    color: var(--cp-color-primary);
}
/* /Components/Job/Quote/Dialogs/JobQuoteConfirmOrderPopup.razor.rz.scp.css */
/* Binding-terms consent (credit orders): tick on the left, the statement wrapping beside it. */
.confirm-consent[b-iskfu7k5zg] {
    display: flex;
    align-items: flex-start;
    gap: var(--cp-space-3);
    padding: var(--cp-space-3) var(--cp-space-4);
    border-radius: var(--cp-radius);
    background: var(--cp-color-surface-muted);
}

/* Ticked: the box takes the info surface, so the state is visible beyond the check mark itself. */
.confirm-consent--ticked[b-iskfu7k5zg] {
    background: var(--cp-role-primary-bg);
}

.confirm-consent__text[b-iskfu7k5zg] {
    flex: 1 1 auto;
    min-width: 0;
    cursor: pointer;
}
/* /Components/Job/Quote/Dialogs/JobQuoteConfirmSummaryRow.razor.rz.scp.css */
/* The value indents to the width of the icon plus its gap, so every value in the column starts on
   the same left edge as its label's text instead of under the icon. Weight and size come from the
   CpText the caller puts in the slot — nothing here overrides the type scale. */
.confirm-summary-row-value[b-3j6j8v5mwp] {
    padding-left: var(--cp-space-6);
}
/* /Components/Job/Quote/Holding/JobQuoteConfirmingCard.razor.rz.scp.css */
/* Confirming interstitial (concierge split). The left panel is the brand "ring" status motif
   (echoing the Lasered logo mark); the right panel is "what happens next". Layout + the ring / dot
   motion live here; colour comes from Bootstrap tokens so light and dark themes are handled
   automatically. Blue / neutral only — the wait must never read red; the one red accent is the
   brand ring arc (the logo's own red), never a status signal. Card padding is removed via `p-0` on
   the surface (Bootstrap utility, crosses the isolation boundary) so the two panels meet at a divider. */

.wq-confirm-root[b-td0kexsoph] {
    min-height: 60vh;
    display: flex;
    align-items: center;
    justify-content: center;
}

.wq-confirm[b-td0kexsoph] {
    display: grid;
    grid-template-columns: 0.9fr 1.1fr;
    min-height: 340px;
}

/* ── Left: brand ring status motif ─────────────────────────────────────── */
.wq-confirm-status[b-td0kexsoph] {
    /* Lasered logo red — no global brand token exists; used only for the decorative ring arc. */
    --wq-confirm-brand: #DE1F26;

    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    gap: .35rem;
    padding: 2.25rem 1.75rem;
    background: radial-gradient(120% 120% at 50% -10%, var(--cp-role-primary-bg), var(--cp-color-surface) 58%);
    border-right: 1px solid var(--cp-color-border);
}

[data-bs-theme="dark"] .wq-confirm-status[b-td0kexsoph] {
    --wq-confirm-brand: #F0555A;
}

.wq-confirm-rings[b-td0kexsoph] {
    position: relative;
    width: 116px;
    height: 116px;
    margin-bottom: .85rem;
}

.wq-confirm-ring[b-td0kexsoph] {
    position: absolute;
    inset: 0;
    margin: auto;
    border-radius: 50%;
    border: 2px solid var(--cp-role-primary-border);
}

.wq-confirm-ring-1[b-td0kexsoph] {
    width: 116px;
    height: 116px;
    border-top-color: var(--cp-color-primary);
    animation: wq-confirm-spin-b-td0kexsoph 7s linear infinite;
}

.wq-confirm-ring-2[b-td0kexsoph] {
    width: 84px;
    height: 84px;
    opacity: .85;
    border-right-color: var(--wq-confirm-brand);
    animation: wq-confirm-spin-b-td0kexsoph 5s linear infinite reverse;
}

.wq-confirm-ring-3[b-td0kexsoph] {
    width: 52px;
    height: 52px;
    border-color: var(--cp-color-border);
}

.wq-confirm-core[b-td0kexsoph] {
    position: absolute;
    inset: 0;
    margin: auto;
    width: 52px;
    height: 52px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-size: 1.15rem;
    color: var(--cp-color-primary);
    background: var(--cp-role-primary-bg);
}

.wq-confirm-core .spinner-border[b-td0kexsoph] {
    width: 1.35rem;
    height: 1.35rem;
    border-width: .18em;
}

@keyframes wq-confirm-spin-b-td0kexsoph {
    to { transform: rotate(360deg); }
}

/* Timed-out: pause the motion and mute the accents — calm, never alarming. */
.wq-confirm-rings--wait .wq-confirm-ring[b-td0kexsoph] {
    animation: none;
}

.wq-confirm-rings--wait .wq-confirm-ring-1[b-td0kexsoph] {
    border-top-color: var(--cp-color-border);
}

.wq-confirm-rings--wait .wq-confirm-ring-2[b-td0kexsoph] {
    opacity: 1;
    border-right-color: var(--cp-color-border);
}

.wq-confirm-rings--wait .wq-confirm-core[b-td0kexsoph] {
    color: var(--cp-color-content-muted);
    background: var(--cp-color-surface-muted);
}

.wq-confirm-qno[b-td0kexsoph] {
    font-variant-numeric: tabular-nums;
}

.wq-confirm-fin[b-td0kexsoph] {
    color: var(--cp-color-primary);
}

.wq-confirm-fin--wait[b-td0kexsoph] {
    color: var(--cp-color-content-muted);
}

.wq-confirm-dots span[b-td0kexsoph] {
    animation: wq-confirm-blink-b-td0kexsoph 1.4s infinite;
}

.wq-confirm-dots span:nth-child(2)[b-td0kexsoph] { animation-delay: .2s; }
.wq-confirm-dots span:nth-child(3)[b-td0kexsoph] { animation-delay: .4s; }

@keyframes wq-confirm-blink-b-td0kexsoph {
    0%, 100% { opacity: .2; }
    50% { opacity: 1; }
}

.wq-confirm-auto[b-td0kexsoph] {
    color: var(--cp-color-content-muted);
    display: inline-flex;
    align-items: center;
    gap: .45rem;
}

/* Live "checking" dot — a soft ping, disabled under reduced-motion. */
.wq-confirm-live[b-td0kexsoph] {
    position: relative;
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--cp-color-primary);
}

.wq-confirm-live[b-td0kexsoph]::after {
    content: "";
    position: absolute;
    inset: -4px;
    border-radius: 50%;
    border: 2px solid var(--cp-color-primary);
    opacity: .5;
    animation: wq-confirm-ping-b-td0kexsoph 1.8s ease-out infinite;
}

@keyframes wq-confirm-ping-b-td0kexsoph {
    0% { transform: scale(.6); opacity: .55; }
    100% { transform: scale(1.7); opacity: 0; }
}

/* ── Right: what happens next ───────────────────────────────────────────── */
.wq-confirm-next[b-td0kexsoph] {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    padding: 2.25rem 1.75rem;
}

.wq-confirm-eyebrow[b-td0kexsoph] {
    margin: 0;
    font-size: .78rem;
    font-weight: 700;
    letter-spacing: .08em;
}

.wq-confirm-steps[b-td0kexsoph] {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.wq-confirm-steps li[b-td0kexsoph] {
    display: flex;
    align-items: flex-start;
    gap: .85rem;
}

.wq-confirm-ic[b-td0kexsoph] {
    flex: 0 0 auto;
    width: 38px;
    height: 38px;
    border-radius: 10px;
    display: grid;
    place-items: center;
    font-size: 1.05rem;
    color: var(--cp-color-primary);
    background: var(--cp-color-surface-muted);
    border: 1px solid var(--cp-color-border);
}

.wq-confirm-step[b-td0kexsoph] {
    display: flex;
    flex-direction: column;
    gap: .1rem;
}

.wq-confirm-step-t[b-td0kexsoph] {
    font-weight: 600;
}

.wq-confirm-step-d[b-td0kexsoph] {
    font-size: .8125rem;
    color: var(--cp-color-content-muted);
}

/* Foot pinned to the bottom of the taller (grid-stretched) column. */
.wq-confirm-foot[b-td0kexsoph] {
    margin-top: auto;
    padding-top: 1rem;
    border-top: 1px solid var(--cp-color-border);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
}

.wq-confirm-contact[b-td0kexsoph] {
    color: var(--cp-color-content-muted);
}

.wq-confirm-contact a[b-td0kexsoph] {
    color: var(--cp-color-primary);
    text-decoration: none;
}

.wq-confirm-contact a:hover[b-td0kexsoph] {
    text-decoration: underline;
}

/* ── Responsive: stack the split, move the divider under the status ─────── */
@media (max-width: 820px) {
    .wq-confirm[b-td0kexsoph] {
        grid-template-columns: 1fr;
    }

    .wq-confirm-status[b-td0kexsoph] {
        border-right: none;
        border-bottom: 1px solid var(--cp-color-border);
    }
}

@media (max-width: 480px) {
    .wq-confirm-status[b-td0kexsoph],
    .wq-confirm-next[b-td0kexsoph] {
        padding: 1.5rem 1.25rem;
    }
}

@media (prefers-reduced-motion: reduce) {
    .wq-confirm-live[b-td0kexsoph]::after,
    .wq-confirm-ring[b-td0kexsoph],
    .wq-confirm-dots span[b-td0kexsoph] {
        animation: none;
    }
}
/* /Components/Job/Quote/Holding/JobQuoteHoldingCard.razor.rz.scp.css */
.wq-hold-icon[b-0m8lham7dj] {
    width: 72px;
    height: 72px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-size: 2rem;
    margin-bottom: .5rem;
}

.wq-hold-draft[b-0m8lham7dj] {
    background: var(--cp-role-warning-bg);
    color: var(--cp-role-warning-fg);
    border: 1px solid var(--cp-role-warning-border);
}

.wq-hold-ok[b-0m8lham7dj] {
    background: var(--cp-role-success-bg);
    color: var(--cp-role-success-fg);
    border: 1px solid var(--cp-role-success-border);
}

.wq-hold-muted[b-0m8lham7dj] {
    background: var(--cp-color-surface-muted);
    color: var(--cp-color-content-muted);
    border: 1px solid var(--cp-color-border);
}

.wq-hold-foot[b-0m8lham7dj] {
    margin-top: 1.25rem;
    padding-top: 1rem;
    border-top: 1px solid var(--cp-color-border);
    width: 100%;
}
/* /Components/Job/Quote/JobQuoteFileUpload.razor.rz.scp.css */
[b-fruj7bnp4r] .web-qutoe-file-input {
    padding-top: 0;
}

    [b-fruj7bnp4r] .web-qutoe-file-input .dxbl-upload-file-list-view {
        padding-top: 0;
    }

    [b-fruj7bnp4r] .web-qutoe-file-input .dxbl-upload-container {
        gap: 1rem;
    }
/* /Components/Job/Quote/Layout/JobQuoteMobileCta.razor.rz.scp.css */
.wq-mobile-cta[b-z02gpp38bx] {
    display: none;
}

.wq-mobile-total[b-z02gpp38bx] {
    display: flex;
    flex-direction: column;
    line-height: 1.2;
}

/* ::deep so the scope lands on the container, not the <strong> — the element is now rendered
   inside the CpText child component and would otherwise not match the scoped selector. */
.wq-mobile-total[b-z02gpp38bx]  strong {
    font-feature-settings: "tnum";
}

/* Total's second line: "inc. VAT · 2h 14m 5s left". Tabular figures stop the countdown jittering
   the line's width as the seconds tick. */
.wq-mobile-sub[b-z02gpp38bx] {
    display: flex;
    align-items: baseline;
    gap: .3rem;
    white-space: nowrap;
    font-feature-settings: "tnum";
}

@media (max-width: 720px) {
    .wq-mobile-cta[b-z02gpp38bx] {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: .75rem;
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 90;
        padding: .75rem 1rem;
        background: var(--cp-color-surface);
        border-top: 1px solid var(--cp-color-border);
        box-shadow: 0 -8px 24px -12px rgba(0, 0, 0, .4);
        transition: transform .22s ease, opacity .22s ease;
    }

    /* Tucked: the checkout section is on screen, so its own accept button is the live CTA — the bar
       slides out rather than competing with it. Class toggled by wwwroot/js/sticky-cta.js. */
    .wq-mobile-cta.is-tucked[b-z02gpp38bx] {
        transform: translateY(100%);
        opacity: 0;
        pointer-events: none;
    }
}

/* Respect a reduced-motion preference: still get out of the way, just without the slide. */
@media (max-width: 720px) and (prefers-reduced-motion: reduce) {
    .wq-mobile-cta[b-z02gpp38bx] {
        transition: none;
    }
}
/* /Components/Job/Quote/Layout/JobQuoteNextSteps.razor.rz.scp.css */
/* Step-timeline styling moved to the shared Common/Steps component (Step.razor.css). What remains
   here is specific to the live-tracking callout below the steps. Colours use Bootstrap tokens so
   light and dark themes are handled automatically. */

.wq-tracking[b-60dyoofhn7] {
    display: flex;
    flex-direction: column;
    gap: .75rem;
    /*    background-color: var(--cp-role-primary-bg);*/
/*    border: 1px solid var(--cp-color-primary);
    padding: 1rem;
    border-radius: var(--cp-radius);*/
}

.wq-tracking-head[b-60dyoofhn7] {
    display: flex;
    align-items: center;
    gap: .55rem;
    flex-wrap: wrap;
}

.wq-tracking-head strong[b-60dyoofhn7] {
    font-size: .95rem;
    color: var(--cp-role-primary-fg);
}

/* Pulsing "live" indicator — the expanding ring signals the tracking page is active.
   Suppressed under prefers-reduced-motion below. */
.wq-live-dot[b-60dyoofhn7] {
    position: relative;
    width: .6rem;
    height: .6rem;
    border-radius: 50%;
    background: var(--cp-color-primary);
    flex-shrink: 0;
}

.wq-live-dot[b-60dyoofhn7]::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: var(--cp-color-primary);
    animation: wq-live-pulse-b-60dyoofhn7 1.8s ease-out infinite;
}

@keyframes wq-live-pulse-b-60dyoofhn7 {
    0% { transform: scale(1); opacity: .6; }
    70% { transform: scale(3); opacity: 0; }
    100% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .wq-live-dot[b-60dyoofhn7]::after { animation: none; }
}

/* Three benefit columns — replaces the former paragraph of prose. minmax(0,1fr) keeps the
   grid from overflowing the half-width section it shares with Tolerances. */
.wq-benefits[b-60dyoofhn7] {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: .75rem;
}

.wq-benefit[b-60dyoofhn7] {
    display: flex;
    flex-direction: column;
    gap: .25rem;
}

.wq-benefit i[b-60dyoofhn7] {
    font-size: 1.05rem;
    color: var(--cp-color-primary);
}
/* /Components/Job/Quote/LineItems/JobQuoteQuantityEditor.razor.rz.scp.css */
/* Keep the stepper compact so it sits inside the line card's price rows without stretching them. */
.qv-qty-edit[b-84lyffwqqj] {
    width: auto;
}

[b-84lyffwqqj] .qv-qty-input {
    width: auto;
/*    max-width: 64px;*/
}

/* Square-ish +/- buttons so the pair reads as one stepper rather than two separate actions. */
[b-84lyffwqqj] .qv-qty-step {
    min-width: 2.25rem;
    padding-left: var(--cp-space-2, .5rem);
    padding-right: var(--cp-space-2, .5rem);
}

/* The anchored stepper panel. DevExpress portals this subtree out to the popup root, so it is no
   longer a descendant of .qv-qty-edit — size it from the inside instead of from the dropdown, and let
   the dropdown shrink-wrap to it. Bounded rather than fixed: wide enough for the stepper plus its two
   buttons on one row, narrow enough that it still reads as a panel hanging off the pencil rather than
   a dialog. */
.qv-qty-pop__body[b-84lyffwqqj] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-2);
    min-width: 13rem;
    max-width: 18rem;
}
/* /Components/Job/Quote/Pricing/FinishUpgrade/JobQuoteFinishUpgrade.razor.rz.scp.css */
/* Before / after edge cross-sections. The markup is commented out in the card for now; the rules stay
   with it so the pair can be switched back on in one place. */
.wq-finish-figs[b-k0rji58av0] {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--cp-space-3);
}

.wq-finish-fig[b-k0rji58av0] {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--cp-space-1);
    padding: var(--cp-space-2);
    border: 1px solid var(--cp-color-border);
    border-radius: var(--cp-radius-sm);
    background: var(--cp-color-surface-muted);
    text-align: center;
}

    .wq-finish-fig svg[b-k0rji58av0] {
        display: block;
        width: 100%;
        max-width: 8rem;
        height: auto;
    }

.wq-finish-fig--before .wq-finish-fig__shape[b-k0rji58av0] {
    fill: var(--cp-color-content-muted);
}

.wq-finish-fig--after .wq-finish-fig__shape[b-k0rji58av0] {
    fill: var(--cp-color-primary);
}

@media (max-width: 480px) {
    .wq-finish-figs[b-k0rji58av0] {
        grid-template-columns: 1fr;
    }
}
/* /Components/Job/Quote/Pricing/FinishUpgrade/JobQuoteFinishUpgradeBenefits.razor.rz.scp.css */
/* Tick + line, three times. Tokens only, so light/dark follow the Fluent semantic set. */
.wq-finish-benefits[b-81yaxkf0gv] {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-1);
}

    .wq-finish-benefits li[b-81yaxkf0gv] {
        display: flex;
        align-items: flex-start;
        gap: var(--cp-space-2);
    }

    .wq-finish-benefits i[b-81yaxkf0gv] {
        flex: 0 0 auto;
        color: var(--cp-color-success);
        line-height: 1.35;
    }
/* /Components/Job/Quote/Pricing/FinishUpgrade/JobQuoteFinishUpgradeEtchConfirm.razor.rz.scp.css */
/* Tinted and bordered so the consequence reads as a stop, not as body copy. Text colour is set on the
   container and inherited by the CpText inside — CSS isolation does not reach another component's
   element, but colour crosses the boundary the ordinary way. */
.wq-finish-etch[b-95thm1ru26] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-2);
    padding: var(--cp-space-3);
    border: 1px solid var(--cp-role-warning-border);
    border-radius: var(--cp-radius-sm);
    background: var(--cp-role-warning-bg);
    color: var(--cp-color-content);
}

.wq-finish-etch__warn[b-95thm1ru26] {
    display: flex;
    align-items: flex-start;
    gap: var(--cp-space-2);
}

    .wq-finish-etch__warn i[b-95thm1ru26] {
        flex: 0 0 auto;
        color: var(--cp-role-warning-fg);
        line-height: 1.35;
    }
/* /Components/Job/Quote/Pricing/FinishUpgrade/JobQuoteFinishUpgradePrice.razor.rz.scp.css */
/* Captions hug the breakdown they frame. */
.wq-finish-price[b-d9n4cfau57] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-1);
}
/* /Components/Job/Quote/Pricing/JobQuotePowderHangConfirm.razor.rz.scp.css */
/* Tinted and bordered so the consequence reads as a stop, not as body copy. Matches the etch
   confirmation deliberately: the two are the same kind of question — an assertion about the customer's
   own parts that we cannot check for them — and giving them different weight would suggest one matters
   less. Text colour is set on the container and inherited by the CpText inside; CSS isolation does not
   reach another component's element, but colour crosses the boundary the ordinary way. */
.wq-powder-hang[b-mgp5wlnmwp] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-2);
    padding: var(--cp-space-3);
    border: 1px solid var(--cp-role-warning-border);
    border-radius: var(--cp-radius-sm);
    background: var(--cp-role-warning-bg);
    color: var(--cp-color-content);
}

.wq-powder-hang__warn[b-mgp5wlnmwp] {
    display: flex;
    align-items: flex-start;
    gap: var(--cp-space-2);
}

    .wq-powder-hang__warn i[b-mgp5wlnmwp] {
        flex: 0 0 auto;
        color: var(--cp-role-warning-fg);
        line-height: 1.35;
    }
/* /Components/Job/Quote/Pricing/JobQuotePowderPicker.razor.rz.scp.css */
/* The "remove powder coating" affordance.

   Separated from the rest of the card by a rule and pushed to the right, because it is the opposite
   decision from everything above it. A destructive action sitting next to the primary Apply button is
   the classic misclick: same visual weight, opposite consequence. A link button rather than a Danger
   button on purpose — it is reversible while the quote is live (re-add means picking a colour again,
   not a phone call), so the styling should not shout. */
.wq-powder-remove[b-fjo1p921ow] {
    display: flex;
    justify-content: flex-end;
    padding-top: var(--cp-space-2);
    border-top: 1px solid var(--cp-color-border);
}

    /* The link inherits DevExpress's own button colour, which reads as another primary action here.
       Muted to sit below the card's real CTA without becoming unreadable. */
    [b-fjo1p921ow] .wq-powder-remove .dxbl-btn-link {
        color: var(--cp-color-content-muted);
    }

    [b-fjo1p921ow] .wq-powder-remove .dxbl-btn-link:hover:not(:disabled) {
        color: var(--cp-role-danger-fg);
    }
/* /Components/Job/Quote/Pricing/JobQuotePowderSwatchGrid.razor.rz.scp.css */
/* Swatch grid.

   Scrolls inside a fixed height: the catalogue runs to dozens of RAL colours, and letting it grow made
   the Colour card several times taller than everything else in the checkout column. auto-fill keeps the
   tiles reflowing rather than squashing, since a colour is judged by eye. */
.wq-swatches[b-ewn6pe44dc] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
    gap: var(--cp-space-2);
    max-height: 19rem;
    overflow-y: auto;
    padding-right: var(--cp-space-1);
}

.wq-swatch[b-ewn6pe44dc] {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.25rem;
    padding: var(--cp-space-1);
    background: var(--cp-color-surface);
    border: 1px solid var(--cp-color-border);
    border-radius: var(--cp-radius-sm);
    cursor: pointer;
    text-align: left;
}

.wq-swatch:hover:not(:disabled)[b-ewn6pe44dc] {
    background: var(--cp-color-surface-muted);
}

.wq-swatch:disabled[b-ewn6pe44dc] {
    opacity: 0.6;
    cursor: default;
}

/* The picked tile. Border + ring + the tick on the chip — never colour alone, because the tile's own
   colour IS the swatch, so a colour-only cue is invisible on a good half of the catalogue. */
.wq-swatch--picked[b-ewn6pe44dc] {
    border-color: var(--cp-color-primary) !important;
    background-color: var(--cp-role-primary-bg);
}

/* Every chip carries a border, so white, cream and clear-coat read as a colour rather than as a hole in
   the card. This was the bug in the first cut: the border was there but referenced token names that do
   not exist, so it resolved to nothing. */
.wq-swatch__chip[b-ewn6pe44dc] {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 1.75rem;
    border: 1px solid var(--cp-color-border);
    border-radius: var(--cp-radius-sm);
    overflow: hidden;
}

/* No hex in the catalogue. Says so, rather than rendering a blank tile that reads as an invisible colour. */
.wq-swatch__chip--none[b-ewn6pe44dc] {
    background: var(--cp-color-surface-muted);
    border-style: dashed;
}

.wq-swatch__noprev[b-ewn6pe44dc] {
    font-size: 0.625rem;
    line-height: 1;
    color: var(--cp-color-content-muted);
}

.wq-swatch__name[b-ewn6pe44dc] {
    font-size: var(--cp-text-caption-size);
    line-height: 1.15;
    color: var(--cp-color-content);
}

.wq-swatch__price[b-ewn6pe44dc] {
    font-size: var(--cp-text-caption-size);
    color: var(--cp-color-content-muted);
}

/* "On your quote" — the standing state, distinct from the tick, which is what the customer just clicked. */
.wq-swatch__badge[b-ewn6pe44dc] {
    align-self: flex-start;
    padding: 0 0.25rem;
    font-size: 0.625rem;
    line-height: 1.4;
    color: var(--cp-color-on-primary);
    background: var(--cp-color-primary);
    border-radius: var(--cp-radius-sm);
}
/* /Components/Job/Quote/Pricing/JobQuotePriceBreakLadder.razor.rz.scp.css */
/* The price-break ladder as a table, inside an expanded grid row.

   Shrink-to-fit rather than full width: the detail cell spans every grid column, and five columns
   stretched across that would put the unit price a screen away from the quantity it belongs to. The
   figures are right-aligned with tabular digits so the pounds line up down the column — that vertical
   read is the whole point of a price-break table. */
.wq-ladder[b-g09pimxbdn] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-2);
    white-space: normal;      /* the host grid switches wrapping off for its own cells */
    max-width: 100%;
}

.wq-ladder__head[b-g09pimxbdn] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--cp-space-2);
}

[b-g09pimxbdn] .wq-ladder__hide {
    padding: 0 0.25rem;
}

.wq-ladder__table[b-g09pimxbdn] {
    width: fit-content;
    max-width: 100%;
    border-collapse: collapse;
}

.wq-ladder__table th[b-g09pimxbdn],
.wq-ladder__table td[b-g09pimxbdn] {
    padding: 0.35rem 0.75rem;
    text-align: left;
    vertical-align: middle;
    border-bottom: 1px solid var(--cp-color-border);
}

.wq-ladder__table th[b-g09pimxbdn] {
    font-weight: normal;
}

.wq-ladder__table tbody tr:last-child td[b-g09pimxbdn] {
    border-bottom: none;
}

.wq-ladder__pick[b-g09pimxbdn] {
    width: 2rem;
    padding-inline: 0.5rem;
}

.wq-ladder__pick input[b-g09pimxbdn] {
    margin: 0;
    cursor: inherit;
}

.wq-ladder__num[b-g09pimxbdn] {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.wq-ladder__qty[b-g09pimxbdn] {
    display: inline-flex;
    align-items: center;
    gap: var(--cp-space-1);
    white-space: nowrap;
}

.wq-ladder__dash[b-g09pimxbdn] {
    color: var(--cp-color-content-muted);
}

/* Every row is an option — including the current one, which is how a pick is backed out of. */
.wq-ladder__row[b-g09pimxbdn] {
    cursor: pointer;
}

.wq-ladder__row:hover td[b-g09pimxbdn] {
    background: var(--cp-color-surface-muted);
}

/* The pick: tinted, with a bar down the left edge so it reads even where the tint is faint. */
.wq-ladder__row.is-selected td[b-g09pimxbdn] {
    background: var(--cp-role-primary-bg);
}

.wq-ladder__row.is-selected td:first-child[b-g09pimxbdn] {
    box-shadow: inset 3px 0 0 var(--cp-color-primary);
}

.wq-ladder.is-busy .wq-ladder__row[b-g09pimxbdn] {
    cursor: default;
    opacity: 0.6;
}

.wq-ladder__foot[b-g09pimxbdn] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--cp-space-3);
    flex-wrap: wrap;
}

.wq-ladder__foot > :first-child[b-g09pimxbdn] {
    flex: 1 1 16rem;
    min-width: 0;
}
/* /Components/Job/Quote/Pricing/JobQuotePriceBreakOption.razor.rz.scp.css */
/* One row of the price-break comparison. Moved out of JobQuotePriceBreakPanel.razor.css with the
   markup: scoped CSS only reaches the component that declares it. */

/* One quantity: two rows of two columns. Label left, figure right — the money block's shape. */
.wq-break[b-471e8mmqtw] {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    width: 100%;
    padding: var(--cp-space-1) var(--cp-space-2);
    border: 1px solid transparent;
    border-radius: var(--cp-radius-sm);
    background: none;
    text-align: left;
}

.wq-break__line[b-471e8mmqtw] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--cp-space-5);
}

/* An alternative. A button, because clicking it changes the quote — the confirmation dialog on the
   way through is what makes that safe, not a separate "apply" step here. */
.wq-break--option[b-471e8mmqtw] {
    background: var(--cp-color-surface-muted);
    cursor: pointer;

    transition: background .25s;
}

.wq-break--option-selected[b-471e8mmqtw] {
    background: var(--cp-role-primary-bg) !important;
}

.wq-break--option:hover:not(:disabled)[b-471e8mmqtw] {
    background: var(--cp-role-primary-bg);
    background: var(--cp-color-surface-muted-hover);
}

.wq-break--option:disabled[b-471e8mmqtw] {
    opacity: 0.6;
    cursor: default;
}

/* The baseline: aria-disabled, not disabled, so it stays reachable by keyboard — but it applies
   nothing, so it must not look clickable. */
.wq-break--option-selected[b-471e8mmqtw] {
    cursor: default;
}
/* /Components/Job/Quote/Pricing/JobQuotePriceBreakPanel.razor.rz.scp.css */
/* The card view's price-break panel, inside the dropdown anchored to its toggle.

   DevExpress portals this subtree out to the popup root, so it is no longer a descendant of the line
   card — it carries its own width rather than inheriting the footer's, and the dropdown shrink-wraps
   to it. Bounded rather than fixed: wide enough for "1000 off … £12.40 each" to stay on one line,
   narrow enough that it still reads as a panel hanging off a button rather than a dialog. The rungs
   scroll past a screenful so a long ladder cannot grow taller than the viewport. */
.wq-breaks__panel[b-bbxs138jrm] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-1);
    min-width: 17rem;
    max-width: 24rem;
    max-height: min(60vh, 26rem);
    overflow-y: auto;
}

.wq-breaks__head[b-bbxs138jrm] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--cp-space-2);
}

[b-bbxs138jrm] .wq-breaks__hide {
    padding: 0 0.25rem;
}

.wq-breaks__list[b-bbxs138jrm] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-1);
}
/* /Components/Job/Quote/Pricing/JobQuotePriceBreaks.razor.rz.scp.css */
/* Quantity price-breaks, in the footer of a quote line's card. What is left here is the toggle's own
   strip; the panel it opens is portaled away by DevExpress and carries its own styling in
   JobQuotePriceBreakPanel.razor.css.

   Full width and left-aligned — the same label/value shape as the Qty / Unit price / Net rows the
   money block renders directly above it, so the toggle reads as a continuation of that panel rather
   than a widget dropped into it. */
.wq-breaks[b-kfm54nj3nh] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-1);
    width: 100%;
}

/* DevExpress's text button carries its own padding; strip it so the toggle sits flush with the rows
   above and below rather than indented by a button's worth of space. */
[b-kfm54nj3nh] .wq-breaks__toggle {
    align-self: flex-start;
    padding-inline: 0;
    text-align: left;
    white-space: normal;
}
/* /Components/Job/Quote/Pricing/MaterialOptions/JobQuoteMaterialOptionRow.razor.rz.scp.css */
/* One row of the material comparison — the price-break tile's shape (JobQuotePriceBreakOption), so
   the two panels read alike: two lines of label-left, figure-right. */
.wq-mat[b-1dxaz3o3c8] {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    width: 100%;
    padding: var(--cp-space-1) var(--cp-space-2);
    border: 1px solid transparent;
    border-radius: var(--cp-radius-sm);
    background: var(--cp-color-surface-muted);
    text-align: left;
    cursor: pointer;
    transition: background .25s;
}

.wq-mat__line[b-1dxaz3o3c8] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--cp-space-3);
}

.wq-mat:hover:not(:disabled):not(.wq-mat--current)[b-1dxaz3o3c8] {
    background: var(--cp-color-surface-muted-hover);
}

.wq-mat:disabled[b-1dxaz3o3c8] {
    opacity: 0.6;
    cursor: default;
}

/* The baseline: reachable by keyboard, applies nothing, so it must not look clickable. */
.wq-mat--current[b-1dxaz3o3c8] {
    background: var(--cp-role-primary-bg);
    cursor: default;
}

/* No firm figure. Greyed so it reads as "ask us" rather than as a price — but still a button, because
   choosing it is how the customer asks. */
.wq-mat--tbc[b-1dxaz3o3c8] {
    opacity: 0.75;
}
/* /Components/Job/Quote/Pricing/MaterialOptions/JobQuoteMaterialOptions.razor.rz.scp.css */
/* A quote line's material control. The card form sits in the line's footer under the price-break
   toggle, with the same full-width, left-aligned strip; the panel it opens is portaled away by
   DevExpress and styled in JobQuoteMaterialOptionsPanel.razor.css. */
.wq-mats[b-69f8zuf953] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-1);
    width: 100%;
}

/* The table form: an icon in a cell, not a strip. */
.wq-mats--compact[b-69f8zuf953] {
    display: inline-flex;
    width: auto;
}

[b-69f8zuf953] .wq-mats__toggle {
    align-self: flex-start;
    padding-inline: 0;
    text-align: left;
    white-space: normal;
}
/* /Components/Job/Quote/Pricing/MaterialOptions/JobQuoteMaterialOptionsPanel.razor.rz.scp.css */
/* A line's material picker, inside the dropdown anchored to its toggle. DevExpress portals this subtree
   to the popup root, so it carries its own width, as the price-break panel does. A little wider than
   that panel: a combination ("STAINLESS 304 · 3 mm") is longer than a quantity. The options scroll past
   a screenful so a long list cannot grow taller than the viewport. */
.wq-mats__panel[b-mp5v2c08nz] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-1);
    min-width: 19rem;
    max-width: 26rem;
    max-height: min(65vh, 30rem);
    overflow-y: auto;
}

.wq-mats__head[b-mp5v2c08nz] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--cp-space-2);
}

[b-mp5v2c08nz] .wq-mats__hide {
    padding: 0 0.25rem;
}

.wq-mats__group[b-mp5v2c08nz] {
    margin-top: var(--cp-space-1);
}

.wq-mats__list[b-mp5v2c08nz] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-1);
}
/* /Components/Job/Quote/TimelineCard/JobQuoteTimelineCard.razor.rz.scp.css */
.wq-delivery-row[b-upsew682ea] {
    display: grid;
    grid-template-columns: 1fr auto 1fr auto 1fr;
    gap: 1rem;
    align-items: center;
}

.wq-delivery-range[b-upsew682ea] {
    font-size: .95rem;
    font-weight: 500;
}

.wq-delivery-final .wq-delivery-date[b-upsew682ea] {
    color: var(--cp-color-primary);
}

.wq-delivery-arrow[b-upsew682ea] {
    color: var(--cp-color-content-muted);
    font-size: 1.1rem;
}

.wq-delivery-note[b-upsew682ea] {
}

@media (max-width: 720px) {
    .wq-delivery-row[b-upsew682ea] {
        grid-template-columns: 1fr;
        justify-items: start;
    }

    .wq-delivery-arrow[b-upsew682ea] {
        transform: rotate(90deg);
    }
}
/* /Components/Job/Quote/TimelineCard/TimelineElement.razor.rz.scp.css */
.wq-delivery-shell[b-5bvbarhunb] {
    display: flex;
    flex-direction: row;
    justify-content: center; 
    align-items: center;
    gap: 0;
}

.wq-delivery-step[b-5bvbarhunb] {
   
}

.wq-delivery-label[b-5bvbarhunb] {
    font-size: .7rem;
    text-transform: uppercase;
    letter-spacing: .1em;
}

.wq-delivery-date[b-5bvbarhunb] {
    font-size: 1.15rem;
    font-weight: 600;
    line-height: 1.2;
}
/* /Components/Job/Quote/Timers/JobQuoteTimerCard.razor.rz.scp.css */
[b-m3popdx3qh] .wq-timer-card {
    min-width: 196px;
}

/* Tone — icon colour + track tint / fill. Bootstrap subtle tokens adapt to dark mode automatically. */
.wq-timer-icon.wq-timer--warning[b-m3popdx3qh] {
    color: var(--cp-color-warning);
}

.wq-timer-icon.wq-timer--accent[b-m3popdx3qh] {
    color: var(--cp-color-primary);
}

.wq-timer-track.wq-timer--warning[b-m3popdx3qh] {
    background: var(--cp-role-warning-bg);
}

.wq-timer-track.wq-timer--accent[b-m3popdx3qh] {
    background: var(--cp-role-primary-bg);
}

.wq-timer-track.wq-timer--warning .wq-timer-fill[b-m3popdx3qh] {
    background: var(--cp-color-warning);
}

.wq-timer-track.wq-timer--accent .wq-timer-fill[b-m3popdx3qh] {
    background: var(--cp-color-primary);
}
/* /Components/Job/Status/JobOrderStatusCard.razor.rz.scp.css */
/* Order summary rail — the order id, where it has got to, what varies about it, and what to do about
   it, stacked in one narrow column beside the status timeline. Success (green) tones: an order is a
   settled, positive state, distinct from the blue "confirming" wait of JobQuoteConfirmingCard.
   Colour comes from the --cp-* facade so light/dark are handled from one place. The card's own
   padding is removed so the tinted status block reaches its edges; every section pads itself. */

/* The element every ::deep rule below hangs off — scoped CSS only tags elements this component
   renders itself, and the card is a child component. See the note in the .razor. */
.wq-ordered-root[b-c7zs8svshr] {
    display: block;
    height: 100%;
}

.wq-ordered[b-c7zs8svshr] {
    display: flex;
    flex-direction: column;
}

/* ── The order, at a glance ─────────────────────────────────────────────── */
.wq-ordered-status[b-c7zs8svshr] {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    gap: var(--cp-space-1);
    padding: var(--cp-space-7) var(--cp-space-6) var(--cp-space-6);
    background: radial-gradient(120% 120% at 50% -10%, var(--cp-role-success-bg), var(--cp-color-surface) 62%);
    border-bottom: 1px solid var(--cp-color-border);
    border-radius: var(--cp-radius) var(--cp-radius) 0 0;
}

.wq-ordered-qno[b-c7zs8svshr] {
    font-variant-numeric: tabular-nums;
}

/* ── What varies between one order and the next ─────────────────────────── */
.wq-ordered-facts[b-c7zs8svshr] {
    display: flex;
    flex-direction: column;
    padding: var(--cp-space-1) var(--cp-space-6);
}

/* Label and value on one line while they fit, wrapping onto two rather than squeezing a long
   reference to an ellipsis — the rail is narrow, and a truncated PO number is no reference at all. */
.wq-ordered-fact[b-c7zs8svshr] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--cp-space-1) var(--cp-space-3);
    padding: var(--cp-space-2) 0;
    border-top: 1px solid var(--cp-color-border);
}

.wq-ordered-fact:first-child[b-c7zs8svshr] {
    border-top: none;
}

/* ── What to do about it ────────────────────────────────────────────────── */
.wq-ordered-foot[b-c7zs8svshr] {
    padding: var(--cp-space-4) var(--cp-space-6) var(--cp-space-6);
    border-top: 1px solid var(--cp-color-border);
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--cp-space-3);
}

/* Stacked and full width: side by side in a rail this narrow, either label is one browser zoom step
   away from being unreadable. */
.wq-ordered-actions[b-c7zs8svshr] {
    display: flex;
    flex-direction: column;
    align-self: stretch;
    gap: var(--cp-space-2);
}

.wq-ordered-contact[b-c7zs8svshr] {
    color: var(--cp-color-content-muted);
}

.wq-ordered-contact a[b-c7zs8svshr] {
    color: var(--cp-color-primary);
    text-decoration: none;
}

.wq-ordered-contact a:hover[b-c7zs8svshr] {
    text-decoration: underline;
}

/* Stacked under the timeline on a phone, where the rail is simply the block above it. The drawing
   keeps its size; everything else tightens. */
@media (max-width: 480px) {
    .wq-ordered-status[b-c7zs8svshr] {
        padding: var(--cp-space-6) var(--cp-space-5) var(--cp-space-5);
    }

    .wq-ordered-facts[b-c7zs8svshr] {
        padding: var(--cp-space-1) var(--cp-space-5);
    }

    .wq-ordered-foot[b-c7zs8svshr] {
        padding: var(--cp-space-4) var(--cp-space-5) var(--cp-space-5);
    }
}
/* /Components/Job/Status/JobOrderStatusView.razor.rz.scp.css */
/* Summary rail beside the timeline. Two columns rather than one card, because the timeline is as
   tall as the order is old and the summary would otherwise be pushed off the top of it.

   Both columns take what their content needs and wrap onto a second row when there is no room for
   both, rather than squeezing. Nothing is pinned: the rail is short enough that it is on screen when
   the customer arrives, which is when they read it.

   Centred rather than left-aligned, on both doors alike: neither column grows to fill the row, so on
   a wide screen a left-aligned pair leaves the whole right-hand half empty beside them. */
.order-status-view[b-0rkpmat5b6] {
    display: flex;
    flex-direction: row;
    gap: var(--cp-space-6);
    flex-wrap: wrap;
    justify-content: center;
}

/* Both columns are the view's own elements rather than the cards themselves, because what is inside
   them is a component and this stylesheet could not reach it.

   The rail takes what its content needs and may shrink below it (min-width: 0), so a long status line
   wraps inside the card rather than widening the row. The timeline keeps a floor instead: below it a
   step's date and its label collide. */
.order-status-rail[b-0rkpmat5b6] {
    min-width: 0;
}

.order-status-timeline[b-0rkpmat5b6] {
    min-width: 264px;
}

/* Below lg the two stack, and the rail leads: on a phone "where is my order" is answered by the
   status line before anybody scrolls to the steps. */
@media (max-width: 991.98px) {
    .order-status-view[b-0rkpmat5b6] {
        flex-direction: column;
        gap: var(--cp-space-5);
    }
}
/* /Components/Job/Track/CollectionCompletePanel.razor.rz.scp.css */

[b-vfvgnsy8hu] .collection-complete-icon {
    flex: 0 0 auto;
    display: flex;
    font-size: 24px;
    line-height: 1;
    color: var(--cp-color-surface-success);
    border-radius: 24px;
    padding: 12px;
    background-color: var(--cp-color-surface-success-subtle);
}

[b-vfvgnsy8hu] .collection-complete-body {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

[b-vfvgnsy8hu] .collection-complete-title {
    font-size: var(--cp-text-body1-size);
    font-weight: 700;
}

[b-vfvgnsy8hu] .collection-complete-text {
    margin: 0;
    font-size: var(--cp-text-caption-size);
}

@media (max-width: 520px) {
    .collection-complete[b-vfvgnsy8hu] {
        align-items: flex-start;
    }
}
/* /Components/Job/Track/CollectionReadyPanel.razor.rz.scp.css */
/*::deep .collection-panel {
    padding: 0;
}

::deep .collection-info {
    flex: 1 1 auto;
    min-width: 0;
    z-index: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 12px;
    padding: 16px;
}

::deep .collection-address-body {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

::deep .collection-address-label {
    font-size: 12px;
    color: var(--cp-color-content-muted);
    margin-bottom: 2px;
}

.collection-address-name {
    font-size: 15px;
    font-weight: 600;
    line-height: 1.35;
    color: var(--cp-color-content);
}

.collection-address-lines {
    margin: 0;
    font-style: normal;
    font-size: 13px;
    line-height: 1.5;
    color: var(--cp-color-content-muted);
}

.collection-maps-button {
    align-self: flex-start;
}

::deep .collection-map {
    flex: 0 0 46%;
    align-self: stretch;
    -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 46%);
    mask-image: linear-gradient(to right, transparent 0%, #000 46%);
    background-image: url('images/collection-map.svg');
    background-size: cover;
}*/

/* Stack on narrow screens: map drops below the address and fades in from the top instead. */
/*@media (max-width: 520px) {
    .collection-ready-panel {
        flex-direction: column;
    }

    .collection-map {
        flex: 0 0 auto;
        width: 100%;
        min-height: 150px;
        -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 42%);
        mask-image: linear-gradient(to bottom, transparent 0%, #000 42%);
    }
}*/










.collection-ready-wrapper[b-6krs3r47ms] {
    display: flex;
    align-items: stretch;
    overflow: hidden;
}

.collection-info[b-6krs3r47ms] {
    flex: 1 1 auto;
    min-width: 0;
    z-index: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 12px;
    padding: 16px;
}

.collection-address-body[b-6krs3r47ms] {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.collection-address-label[b-6krs3r47ms] {
    font-size: 12px;
    color: var(--cp-color-content-muted);
    margin-bottom: 2px;
}

.collection-address-name[b-6krs3r47ms] {
    font-size: 15px;
    font-weight: 600;
    line-height: 1.35;
    color: var(--cp-color-content);
}

.collection-address-lines[b-6krs3r47ms] {
    margin: 0;
    font-style: normal;
    font-size: 13px;
    line-height: 1.5;
    color: var(--cp-color-content-muted);
}

[b-6krs3r47ms] .collection-maps-button {
    align-self: flex-start;
}

.collection-map[b-6krs3r47ms] {
    position: relative;
    flex: 0 0 60%;
    align-self: stretch;
    overflow: hidden;
    -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 46%);
    mask-image: linear-gradient(to right, transparent 0%, #000 46%);
}

/* Absolutely positioned so the image does NOT contribute intrinsic height: the panel height is
   driven by the address column on the left, and the image just fills whatever height that yields
   (falls back to the default facility graphic). */
.collection-map-image[b-6krs3r47ms] {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: 10px 30%;
}

@media (max-width: 520px) {
    .collection-ready-panel[b-6krs3r47ms] {
        flex-direction: column;
    }

    .collection-map[b-6krs3r47ms] {
        flex: 0 0 auto;
        width: 100%;
        min-height: 150px;
        -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 42%);
        mask-image: linear-gradient(to bottom, transparent 0%, #000 42%);
    }
}
/* /Components/Job/Track/DeliveryTrackingPanel.razor.rz.scp.css */
.tracking-status-panel[b-hzcnpx1x05] {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-top: 2px;
}

.tracking-status-grid[b-hzcnpx1x05] {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.tracking-status-title[b-hzcnpx1x05] {
    font-size: 12px;
    font-weight: 600;
    color: var(--cp-color-content-muted);
}

.tracking-status-row[b-hzcnpx1x05] {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 6px 8px;
    border: 1px solid var(--cp-color-border);
    border-radius: 10px;
    background: var(--cp-color-surface);
}

.tracking-number[b-hzcnpx1x05] {
    font-family: Consolas, "Courier New", monospace;
    font-size: 12px;
    background: var(--cp-color-surface-muted);
    border: 1px solid var(--cp-color-border);
    border-radius: 8px;
    padding: 2px 8px;
}

.tracking-number-link[b-hzcnpx1x05] {
    text-decoration: none;
    color: var(--cp-color-primary);
}

.tracking-number-link:hover[b-hzcnpx1x05] {
    text-decoration: underline;
}

.tracking-status-pill[b-hzcnpx1x05] {
    font-size: 12px;
    font-weight: 600;
    border-radius: 999px;
    padding: 2px 10px;
}

.tracking-status-delivered[b-hzcnpx1x05] {
    background: var(--cp-role-success-bg);
    color: var(--cp-role-success-fg);
}

.tracking-status-progress[b-hzcnpx1x05] {
    background: var(--cp-role-primary-bg);
    color: var(--cp-role-primary-fg);
}

.tracking-status-error[b-hzcnpx1x05] {
    background: var(--cp-role-danger-bg);
    color: var(--cp-role-danger-fg);
}

.tracking-status-neutral[b-hzcnpx1x05] {
    background: var(--cp-color-surface-muted);
    color: var(--cp-color-content-muted);
}

.tracking-status-collection[b-hzcnpx1x05] {
    background: var(--cp-role-warning-bg);
    color: var(--cp-role-warning-fg);
}

.collection-location-link[b-hzcnpx1x05] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: 999px;
    border: 1px solid var(--cp-role-warning-border);
    background: var(--cp-role-warning-bg);
    color: var(--cp-role-warning-fg);
    text-decoration: none;
}

.collection-location-link:hover[b-hzcnpx1x05] {
    background: var(--cp-role-warning-border);
}
/* /Components/Job/Track/JobTrackCurrentMarker.razor.rz.scp.css */

.wrapper[b-yhdwam82ol] {
	width: 16px;
	aspect-ratio: 1 / 1;

	--animation-duration: 1.75s;
	--color: var(--cp-color-primary);

	position: relative;
}

.out-circle[b-yhdwam82ol] {
	width: 100%;
	aspect-ratio: 1 / 1;
	background-color: transparent;
	border-radius: 50%;
	border: 2px solid var(--color);

	position: absolute;
	top: 50%;
	left: 50%;
	translate: -50% -50%;

	animation: pulse-b-yhdwam82ol var(--animation-duration) .35s infinite ease-in-out;
}

.inside-dot[b-yhdwam82ol] {
	width: 40%;
	aspect-ratio: 1 / 1;
	background-color: var(--color);
	border-radius: 50%;

	position: absolute;
	top: 50%;
	left: 50%;
	translate: -50% -50%;

	animation: pulse-bigger-b-yhdwam82ol var(--animation-duration) infinite ease-in-out;
}

@keyframes pulse-b-yhdwam82ol {
	0% {
		scale: 1;
	}
	50% {
		scale: 1.2;
	}
	100% {
		scale: 1;
	}
}


@keyframes pulse-bigger-b-yhdwam82ol {
	0% {
		scale: 1;
	}

	50% {
		scale: 1.3;
	}

	100% {
		scale: 1;
	}
}

/* Respect users who prefer reduced motion — drop the infinite pulse. */
@media (prefers-reduced-motion: reduce) {
	.out-circle[b-yhdwam82ol],
	.inside-dot[b-yhdwam82ol] {
		animation: none;
	}
}
/* /Components/Job/Track/JobTrackFooter.razor.rz.scp.css */
.order-status-footer[b-bfvdllm7b7] {
    width: 100%;
    margin-top: 28px;
}

.footer-grid[b-bfvdllm7b7] {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 22px 36px;
}

@media (max-width: 768px) {
    .footer-grid[b-bfvdllm7b7] {
        grid-template-columns: 1fr;
        gap: 18px;
    }
}

.footer-text[b-bfvdllm7b7] {
    font-size: 14px;
    line-height: 1.6;
    color: var(--cp-color-content-muted);
    margin: 0;
}

.footer-link[b-bfvdllm7b7] {
    color: var(--cp-color-primary);
    text-decoration: none;
    font-weight: 500;
}

.footer-link:hover[b-bfvdllm7b7] {
    text-decoration: underline;
    color: var(--cp-color-primary);
}

.footer-link-icon[b-bfvdllm7b7] {
    font-size: 11px;
    margin-left: 3px;
    color: var(--cp-color-content-muted);
}

.footer-contact[b-bfvdllm7b7] {
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-size: 14px;
    color: var(--cp-color-content-muted);
}

.footer-contact-row[b-bfvdllm7b7] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    line-height: 1.5;
}

.footer-contact-label[b-bfvdllm7b7] {
    font-weight: 600;
    color: var(--cp-color-content);
}

.footer-contact-sep[b-bfvdllm7b7] {
    color: var(--cp-color-border);
}
/* /Components/Job/Track/JobTrackFooterCertifications.razor.rz.scp.css */
.footer-certifications[b-r5myminm6d] {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 20px;
    padding-top: 18px;
    border-top: 1px solid var(--cp-color-border);
}

.footer-cert-shield[b-r5myminm6d] {
    font-size: 15px;
    color: var(--cp-color-primary);
    margin-right: 2px;
}

.footer-cert-badge[b-r5myminm6d] {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--cp-color-content-muted);
    background: var(--cp-color-surface-muted);
    border: 1px solid var(--cp-color-border);
    border-radius: 999px;
    padding: 3px 10px;
}

.footer-cert-caption[b-r5myminm6d] {
    font-size: 12px;
    color: var(--cp-color-content-muted);
    margin-left: 4px;
}
/* /Components/Job/Track/JobTrackFooterSection.razor.rz.scp.css */
.footer-section[b-t2e0spyhmr] {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.footer-section-header[b-t2e0spyhmr] {
    display: flex;
    align-items: center;
    gap: 8px;
}

.footer-section-icon[b-t2e0spyhmr] {
    font-size: 15px;
    color: var(--cp-color-primary);
    flex-shrink: 0;
}

.footer-title[b-t2e0spyhmr] {
    font-size: 14px;
    font-weight: 600;
    color: var(--cp-color-content);
    margin: 0;
    line-height: 1.3;
}
/* /Components/Job/Track/JobTrackStatusDisplay.razor.rz.scp.css */
.wrapper[b-281f7ds8d8] {
	display: grid;
	grid-template:
		"marker header"
		". content";
	justify-content: start;
	column-gap: 1rem;
	row-gap: .5rem;
}

.marker[b-281f7ds8d8] {
	grid-area: marker;
	display: flex;
	align-items: center;
	justify-content: center;
}

.header[b-281f7ds8d8] {
	grid-area: header;
}

.content[b-281f7ds8d8] {
	grid-area: content;
}

.status-name[b-281f7ds8d8] {
	margin-bottom: -4px;
}

.status-description[b-281f7ds8d8] {
	margin: 0;
	font-weight: 700;
}

.change-date[b-281f7ds8d8] {
	background-color: var(--cp-color-surface-info-subtle);
	padding: 0px 12px;
	border-radius: 12px;

	font-weight: 500;

	width: fit-content;
	margin-top: 2px;
}

.grayed[b-281f7ds8d8] {
	color: var(--cp-color-content-muted);
}
/* /Components/Job/Track/JobTrackStatusIcon.razor.rz.scp.css */

.status-icon[b-apr7brpqie] {
	font-size: 16px;
}

.current[b-apr7brpqie] {
	color: var(--cp-color-surface-info);
}

.finished[b-apr7brpqie] {
	color: var(--cp-color-surface-success);
}
/* /Components/Job/Track/JobTrackStatusPanel.razor.rz.scp.css */
/* Loading shape for the timeline: a marker and a line per step, in the same rhythm the real steps
   render in, so the column does not jump when they arrive. */
.skeleton[b-8s6of58ai9] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-5);
}

.skeleton-row[b-8s6of58ai9] {
    display: flex;
    align-items: center;
    gap: var(--cp-space-4);
}

.skeleton-dot[b-8s6of58ai9],
.skeleton-bar[b-8s6of58ai9] {
    background: var(--cp-color-surface-info-subtle);
    animation: skeleton-pulse-b-8s6of58ai9 1.4s ease-in-out infinite;
}

.skeleton-dot[b-8s6of58ai9] {
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
    border-radius: 50%;
}

.skeleton-bar[b-8s6of58ai9] {
    height: 12px;
    border-radius: 6px;
}

@keyframes skeleton-pulse-b-8s6of58ai9 {
    0%, 100% { opacity: 1; }
    50% { opacity: .45; }
}

@media (prefers-reduced-motion: reduce) {
    .skeleton-dot[b-8s6of58ai9],
    .skeleton-bar[b-8s6of58ai9] {
        animation: none;
    }
}
/* /Components/NavBar/NavigationBar.razor.rz.scp.css */
.logo[b-ln8eb1ds5y] {
	height: 26px;
}

/* inline-flex so the anchor is exactly the logo's box — an inline <a> adds descender space below
   the image and the hit area then sits low of what the eye reads as the logo. */
.logo-link[b-ln8eb1ds5y] {
	display: inline-flex;
	align-items: center;
	width: 100%;
	height: 100%;
}

.navigation-bar[b-ln8eb1ds5y] {
	display: flex;
	flex-direction: row;
	gap: 1.5rem;
	width: 100%;
	padding: 0 2rem;
/*	max-width: var(--cp-content-max);*/
	justify-content: space-between;
	align-items: center;
}

/* The portal links are rendered by DxMenu, so DevExpress owns their markup — reach it with
   ::deep. Only the active-section accent is ours; padding, hover and dark mode come from the
   DevExpress theme. Items collapsed into the hamburger render in an overlay outside this
   component's scope, so they carry no accent — the dropdown is short enough not to need one. */
[b-ln8eb1ds5y] .app-nav .dxbl-menu-item-list {
	margin-left: var(--cp-space-2);
}

[b-ln8eb1ds5y] .app-nav-item-active {
	box-shadow: inset 0 -2px 0 var(--cp-color-primary);
}

[b-ln8eb1ds5y] .app-nav-item-active .dxbl-menu-item-text {
	color: var(--cp-color-primary);
	font-weight: 600;
}

@media screen and (max-width: 824px) {
	.navigation-bar[b-ln8eb1ds5y] {
		padding: 0;
	}
}
/* /Components/Parts/Account/PartCard.razor.rz.scp.css */
/* The library card's layout, copied from the quote page's job-item card so the two line up
   pixel-for-pixel. Blazor's scoped CSS cannot be shared by reference, so the rules below are copies
   under part-* names — the same reason PartListFilterBar.razor.css is a copy of the job list's bar.

   The literal rems (.75rem, .7rem, .6rem, .4rem) are copied verbatim from JobItem / JobItemMedia /
   JobItemSpec / JobPartThumb rather than rounded onto the --cp-space-* scale. Rounding them would
   defeat the entire point of this file: the two cards have to measure the same. Where a rule is new
   rather than copied, it uses the scale.

   Every colour and radius is a --cp-* token. A name that is not in cp-tokens.css resolves to nothing
   and renders unstyled without failing a single test, so each one below was checked against the
   facade by hand. */

/* ── Media band ───────────────────────────────────────────────────────────────
   Copy of JobItemMedia's .wq-media, minus .wq-media-num and .wq-media-3d: the library has no item
   number (it is paged and sortable, so a positional number changes meaning with the sort) and no 3D
   model. Owns the background and the divider to the card body; its top corners are clipped by the
   card's border-radius, which the card sets via overflow-hidden. */
.part-media[b-z9gk9b6opm] {
    position: relative;
    aspect-ratio: 16 / 10;
    flex-shrink: 0;
    background: var(--cp-color-surface-muted);
    border-bottom: 1px solid var(--cp-color-border);
    border-radius: inherit;
    overflow: hidden;
}

/* Copy of JobPartThumb's .wq-thumb in its static (CanInspect="false") shape — the frame owns the
   background and border, so the thumb itself only positions the image. The button reset, the cursor,
   the hover outline and the expand chip are all interactive-mode rules and are left behind: here the
   whole card is the link. */
.part-thumb[b-z9gk9b6opm] {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
    border-radius: inherit;
}

.part-thumb-img[b-z9gk9b6opm] {
    width: 100%;
    height: 100%;
    object-fit: contain;
    padding: .75rem;
}

/* No drawing — the icon centred in the frame, sized off the type scale rather than a literal, since
   the glyph is text as far as the browser is concerned. */
.part-thumb-empty[b-z9gk9b6opm] {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--cp-color-content-muted);
    font-size: var(--cp-text-h4-size);
}

/* ── Drawing skeleton ─────────────────────────────────────────────────────────
   Covers the media frame while the drawing is in flight, and is removed the moment the image
   reports loaded or failed. It occupies exactly the frame, so nothing moves when the drawing lands —
   which is most of the value of having it at all.

   It re-paints the frame's own background rather than sitting transparent over it, because
   .part-thumb-img is object-fit: contain with padding: a drawing never covers the frame, so a tint
   behind the image would still be showing around the edges after loading. The visible part is the
   plate below, inset to where the drawing will appear.

   pointer-events: none is load-bearing: the stretched title link's hit area covers the card, and a
   decorative overlay must not take a click out of it. */
.part-thumb-skeleton[b-z9gk9b6opm] {
    position: absolute;
    inset: 0;
    pointer-events: none;
    overflow: hidden;
    background: var(--cp-color-surface-muted);
}

/* The plate, inset by the image's own padding so it sits exactly where the drawing will. Border on a
   muted surface is a visible step in both light and dark mode — both tokens are mode-aware. */
.part-thumb-skeleton[b-z9gk9b6opm]::before {
    content: "";
    position: absolute;
    inset: .75rem;
    border-radius: var(--cp-radius-sm);
    background: var(--cp-color-border);
}

/* A sweep of the plain surface colour across the plate. Motion is the affordance, so it is the part
   that goes when motion is unwelcome — the plate stays, static, and still reads as "not yet". */
.part-thumb-skeleton[b-z9gk9b6opm]::after {
    content: "";
    position: absolute;
    inset: 0;
    opacity: .5;
    background: linear-gradient(90deg, transparent, var(--cp-color-surface), transparent);
    animation: part-thumb-shimmer-b-z9gk9b6opm 1.4s linear infinite;
}

@keyframes part-thumb-shimmer-b-z9gk9b6opm {
    from { transform: translateX(-100%); }
    to { transform: translateX(100%); }
}

@media (prefers-reduced-motion: reduce) {
    .part-thumb-skeleton[b-z9gk9b6opm]::after {
        display: none;
    }
}

/* ── Content block ────────────────────────────────────────────────────────────
   Copy of JobItem's .wq-card-content. Carries the padding and grows (flex: 1) to fill the card, so
   the foot pins to the bottom and cards in a row stay equal height. */
.part-card-content[b-z9gk9b6opm] {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: .6rem;
    padding: .75rem;
}

/* Copy of JobItem's ::deep .wq-card-title. Part codes are long, unbroken dotted tokens that would
   otherwise overflow the compact card and get clipped without an ellipsis. Let them break anywhere,
   cap at two lines with an ellipsis, and expose the full name via the title tooltip. ::deep is
   needed because the title is rendered by the child CpText component, outside this component's CSS
   scope. */
[b-z9gk9b6opm] .part-card-title {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    overflow-wrap: anywhere;
}

/* The title link inherits the card's text colour rather than arriving as a blue underlined link: the
   whole card is the target, and colouring the title would make it look like the only part that is.
   The underline on hover / focus is what says it is a link, and the focus ring stays the browser's. */
.part-card-link[b-z9gk9b6opm] {
    color: inherit;
    text-decoration: none;
}

.part-card-link:hover[b-z9gk9b6opm],
.part-card-link:focus-visible[b-z9gk9b6opm] {
    text-decoration: underline;
}

/* ::deep because the description is the CpText element itself. Its one line is reserved whether or
   not the part has a description, so a card without one keeps the spec pairs level with the card
   beside it. Derived from the type scale rather than guessed — one body2 line is exactly its size
   times its line-height. */
[b-z9gk9b6opm] .part-card-desc {
    min-height: calc(var(--cp-text-body2-size) * var(--cp-text-body2-line));
}

/* Copy of JobItemSpec's .wq-spec — spacing between the TitledValue pairs; each pair styles its own
   label / value. */
.part-spec[b-z9gk9b6opm] {
    display: flex;
    flex-direction: column;
    gap: .7rem;
}

/* Copy of JobItem's .wq-card-ops, the row the quote card gives its operations. */
.part-card-pills[b-z9gk9b6opm] {
    display: flex;
    flex-wrap: wrap;
    align-items: start;
    gap: .4rem;
    height: 100%;
}

/* ── Tinted foot ──────────────────────────────────────────────────────────────
   Copy of JobItem's .wq-card-foot. */
.part-card-foot[b-z9gk9b6opm] {
    background-color: var(--cp-role-primary-bg);
    padding: .75rem;
    border-radius: var(--cp-radius);
    margin: .75rem;
    margin-top: 0;
}

/* Copy of JobPriceBreakdown's .wq-price / .wq-price-row — label left, value right, baseline-aligned.
   Copied rather than rendered through JobPriceBreakdown itself, whose rows are VAT and basket
   quantity: a library part has neither. */
.part-card-price[b-z9gk9b6opm] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-2);
    margin: 0;
}

.part-card-price-row[b-z9gk9b6opm] {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--cp-space-3);
}

.part-card-price-row dt[b-z9gk9b6opm] {
    margin: 0;
}

.part-card-price-row dd[b-z9gk9b6opm] {
    margin: 0;
    text-align: right;
}
/* /Components/Parts/Account/PartCardGrid.razor.rz.scp.css */
/* Copy of JobItemList's .wq-line-grid, so a library card and a quote line card are the same width
   on the same viewport. Cards as a wrapping grid that grows downward: auto-fill packs as many
   compact cards per row as fit, then wraps to new rows below. The 264px min track and the literal
   gaps are copied verbatim rather than rounded onto the --cp-space-* scale — matching the quote
   grid's rhythm is the whole point of copying it.

   Its max-height / overflow are deliberately not copied. The quote page scrolls its line items
   inside a fixed panel; the library is a paged page with a pager and a reach note below the grid,
   and a second scroll region here would hide them. */
.part-card-grid[b-r2osuwb8ly] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(264px, 1fr));
    gap: .75rem;
    padding: .25rem;
}
/* /Components/Parts/Account/PartDetailsCard.razor.rz.scp.css */
/* Label left, value right, a rule between rows — the fact rows of JobOrderStatusCard. Baselines rather
   than centres, so a value that wraps to two lines keeps its first line level with the label.
   Wrapping onto two lines rather than truncating: a clipped description or drawing number is no use
   to someone matching it against their own paperwork. */
.part-facts[b-fpdxkawowg] {
    display: flex;
    flex-direction: column;
    margin: 0;
}

.part-fact[b-fpdxkawowg] {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--cp-space-1) var(--cp-space-3);
    padding: var(--cp-space-2) 0;
    border-top: 1px solid var(--cp-color-border);
}

.part-fact:first-child[b-fpdxkawowg] {
    border-top: none;
}
/* /Components/Parts/Account/PartJobSteps.razor.rz.scp.css */
/* Step staggers each row's entrance by its position, 140ms apiece. That was sized for the four- and
   five-row sequences on the quote page; an order history can run to dozens, and at 140ms a row the
   thirtieth would arrive four seconds after the page did. From the sixth row on, every row waits as
   long as the fifth: the first screenful still cascades in, and nothing below it keeps the customer
   waiting for a list that has already loaded.

   ::deep because the rows are Step's markup, reached through this component's own wrapper. The
   selector outranks Step's own rule (three parts to its two), so the order the bundled stylesheets
   land in does not decide it. */
.part-job-steps[b-uuwvo9s7wi]  .step:nth-child(n + 6) {
    animation-delay: 560ms;
}

/* The order number reads as the job list's does (JobListTable's .job-no): the link colour, no
   underline until it is pointed at or focused. Seven underlined titles stacked in a column read as
   a block of hyperlinks rather than as a history. */
.part-job-link[b-uuwvo9s7wi] {
    text-decoration: none;
}

.part-job-link:hover[b-uuwvo9s7wi],
.part-job-link:focus-visible[b-uuwvo9s7wi] {
    text-decoration: underline;
}
/* /Components/Parts/Account/PartListFilterBar.razor.rz.scp.css */
/* Filter bar: a wrapping row of fields that collapses to one column on a phone. Widths are
   flex-basis rather than fixed so the fields share the row rather than overflow it.

   The same layout as the job list's bar, copied rather than shared: scoped component CSS cannot be
   referenced from another component, and the two bars have different fields to size. */
.filter-bar[b-14snz0j79i] {
    display: flex;
    flex-wrap: wrap;
    gap: var(--cp-space-3);
    align-items: flex-end;
}

.filter-field[b-14snz0j79i] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-1);
    flex: 1 1 12rem;
    min-width: 0;
}

/* The one free-text field the customer actually types a part into gets the room. */
.filter-field--search[b-14snz0j79i] {
    flex: 2 1 16rem;
}

/* A material code is a handful of characters and a thickness is one or two digits — neither needs
   the default basis, and giving it to them pushes the actions onto a second row. */
.filter-field--material[b-14snz0j79i],
.filter-field--thickness[b-14snz0j79i] {
    flex: 1 1 8rem;
}

/* Wide enough for the longest option ("Recently ordered") without the combo truncating it. */
.filter-field--sort[b-14snz0j79i] {
    flex: 1 1 11rem;
}

.filter-field label[b-14snz0j79i] {
    font-size: var(--cp-text-caption-size);
    color: var(--cp-color-content-muted);
}

.filter-actions[b-14snz0j79i] {
    display: flex;
    gap: var(--cp-space-2);
    flex: 0 0 auto;
}

@media (max-width: 575.98px) {
    .filter-field[b-14snz0j79i],
    .filter-field--search[b-14snz0j79i],
    .filter-field--material[b-14snz0j79i],
    .filter-field--thickness[b-14snz0j79i],
    .filter-field--sort[b-14snz0j79i] {
        flex: 1 1 100%;
    }

    .filter-actions[b-14snz0j79i] {
        flex: 1 1 100%;
    }
}
/* /Components/Parts/Account/PartPriceChart.razor.rz.scp.css */
/* The price line. One series in the brand colour, recessive grid and labels in the border and muted
   tokens, so the line is the only thing on the card with any weight. Every colour is a --cp-* token,
   which is also what keeps it readable in dark mode: the dots' ring is the surface colour, so it
   reads as a gap in the line rather than as a white halo on a dark card. */

/* The tooltip is placed against this box, in the same proportions as the drawing inside it. */
.part-price[b-bo10cdafs6] {
    position: relative;
}

.part-price-svg[b-bo10cdafs6] {
    display: block;
    width: 100%;
    height: auto;
    overflow: visible;
}

.part-price-grid[b-bo10cdafs6] {
    stroke: var(--cp-color-border);
    stroke-width: 1;
}

.part-price-axis[b-bo10cdafs6] {
    fill: var(--cp-color-content-muted);
    font-size: var(--cp-text-caption-size);
    font-variant-numeric: tabular-nums;
}

.part-price-line[b-bo10cdafs6] {
    fill: none;
    stroke: var(--cp-color-primary);
    stroke-width: 2;
    stroke-linejoin: round;
    stroke-linecap: round;
}

.part-price-dot[b-bo10cdafs6] {
    fill: var(--cp-color-primary);
    stroke: var(--cp-color-surface);
    stroke-width: 2;
}

/* Invisible, and larger than the dot: the target the pointer actually has to find. */
.part-price-hit[b-bo10cdafs6] {
    fill: transparent;
}

.part-price-point[b-bo10cdafs6] {
    cursor: default;
    outline: none;
}

/* The browser's focus ring does not follow an SVG group's shape, so focus is drawn on the dot
   itself — in the content colour, which is the one guaranteed to stand off the primary fill. */
.part-price-point:focus-visible .part-price-dot[b-bo10cdafs6] {
    stroke: var(--cp-color-content);
}

.part-price-cross[b-bo10cdafs6] {
    stroke: var(--cp-color-border);
    stroke-width: 1;
    stroke-dasharray: 3 3;
}

.part-price-last[b-bo10cdafs6] {
    fill: var(--cp-color-content);
    font-size: var(--cp-text-caption-size);
    font-weight: 600;
}

/* Above and to the right of the point it describes, or to the left of it on the right-hand side of
   the line, so it never runs off the card. pointer-events: none keeps it from stealing the hover
   from the point underneath it. */
.part-price-tip[b-bo10cdafs6] {
    position: absolute;
    z-index: 1;
    transform: translate(var(--cp-space-3), calc(-100% - var(--cp-space-3)));
    padding: var(--cp-space-2) var(--cp-space-3);
    background: var(--cp-color-surface);
    border: 1px solid var(--cp-color-border);
    border-radius: var(--cp-radius-sm);
    box-shadow: var(--cp-shadow-3);
    white-space: nowrap;
    pointer-events: none;
}

.part-price-tip--left[b-bo10cdafs6] {
    transform: translate(calc(-100% - var(--cp-space-3)), calc(-100% - var(--cp-space-3)));
}
/* /Components/Parts/Account/PartSummaryCard.razor.rz.scp.css */
/* PartCard's tinted foot, row for row: label left, value right, baseline-aligned, the headline price
   over a rule. The tint itself is the Card's info gradient accent — the same --cp-role-primary-bg the
   library card's foot is painted with. */
.part-summary[b-xgeck1ju0l] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-2);
    margin: 0;
}

.part-summary-row[b-xgeck1ju0l] {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--cp-space-3);
}

.part-summary-row--headline[b-xgeck1ju0l] {
    padding-bottom: var(--cp-space-2);
    border-bottom: 1px solid var(--cp-color-border);
}
/* /Components/Parts/Account/PartViewerPanel.razor.rz.scp.css */
/* The panel fills the height its parent gives it — the page pins it and sizes it to the window — and
   every layer between here and the viewer passes that height down: the Card's wrapper, its surface,
   its body, the stage. min-height: 0 on each is what lets a flex child be shorter than its content;
   without it the viewer's own toolbar would push the frame taller than the column it sits in.

   The Card's layers are the Card's markup, so they are reached with ::deep through .part-viewer, the
   element this component renders itself. Prefixing the selector with it also makes each rule outrank
   Card's own width/height: fit-content, whatever order the bundled stylesheets land in. */
.part-viewer[b-ukwvh62z60] {
    height: 100%;
    display: flex;
    flex-direction: column;
    min-height: 0;
}

/* Full screen: the browser sizes the panel to the screen but paints nothing behind it, and the Card's
   title sits outside the card surface — on the default black backdrop it would be unreadable. */
.part-viewer:fullscreen[b-ukwvh62z60] {
    padding: var(--cp-space-4);
    background: var(--cp-color-surface);
}

.part-viewer[b-ukwvh62z60]  .part-viewer-card {
    flex: 1 1 auto;
    min-height: 0;
}

.part-viewer[b-ukwvh62z60]  .part-viewer-surface {
    flex: 1 1 auto;
    min-height: 0;
    height: auto;
    display: flex;
    flex-direction: column;
}

.part-viewer[b-ukwvh62z60]  .part-viewer-body {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/* The blank's size on the left, full screen on the right — the popup's header row, inside the card. */
.part-viewer-head[b-ukwvh62z60] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--cp-space-3);
    padding: var(--cp-space-2) var(--cp-space-3) var(--cp-space-2) var(--cp-space-4);
    border-bottom: 1px solid var(--cp-color-border);
}

/* The viewer measures its host on mount and paints it itself, so the host is pinned to the stage's
   edges rather than sized by a class — the viewer replaces the host's class list when it mounts. The
   child selector reaches it by position for the same reason. */
.part-viewer-stage[b-ukwvh62z60] {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
}

.part-viewer-stage > div[b-ukwvh62z60] {
    position: absolute;
    inset: 0;
}

.part-viewer-empty[b-ukwvh62z60] {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--cp-color-surface-muted);
}
/* /Components/PortalLoginDisplay.razor.rz.scp.css */
.portal-user[b-i5n657hmpu] {
    display: flex;
    justify-content: flex-end;
}

/* Avatar chip trigger */
.portal-user-trigger[b-i5n657hmpu] {
    display: inline-flex;
    align-items: center;
    gap: .55rem;
    padding: .3rem .6rem .3rem .35rem;
    background: var(--cp-color-surface, #fff);
    border: 1px solid var(--cp-color-border, #e3e6ea);
    border-radius: 999px;
    cursor: pointer;
    color: var(--cp-color-content, #1f2329);
    line-height: 1.1;
    transition: background-color .12s ease, border-color .12s ease, box-shadow .12s ease;
}

.portal-user-trigger:hover[b-i5n657hmpu] {
    background: var(--cp-color-surface-muted, #f4f6f8);
    border-color: var(--cp-color-border, #d0d5da);
    box-shadow: 0 1px 3px rgba(0, 0, 0, .06);
}

.portal-user-trigger:focus-visible[b-i5n657hmpu] {
    outline: 2px solid var(--cp-color-primary);
    outline-offset: 2px;
}

.portal-user-trigger-name[b-i5n657hmpu] {
    font-size: .82rem;
    font-weight: 600;
    max-width: 12rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.portal-user-caret[b-i5n657hmpu] {
    color: var(--cp-color-content-muted, #9aa1a9);
    font-size: .7rem;
    line-height: 1;
    transition: transform .15s ease;
    margin-left: .1rem;
}

.portal-user-caret.is-open[b-i5n657hmpu] {
    transform: rotate(180deg);
}

/* Circular initials badge (shared by chip + dropdown header) */
.portal-user-avatar[b-i5n657hmpu] {
    flex: 0 0 auto;
    width: 1.5rem;
    height: 1.5rem;
    border-radius: 50%;
    background: var(--cp-color-primary);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: .72rem;
    font-weight: 600;
    letter-spacing: .02em;
}

.portal-user-avatar-lg[b-i5n657hmpu] {
    width: 2.5rem;
    height: 2.5rem;
    font-size: .9rem;
}

/* Dropdown content — rendered in the DevExpress overlay, but the scoped attribute is baked
   into these elements at compile time so these rules still match. Design-system tokens (--cp-*)
   inherit from :root into the overlay, so light/dark both adapt. */
.portal-user-card[b-i5n657hmpu] {
    display: flex;
    flex-direction: column;
    min-width: 12rem;
}

.portal-user-card-id[b-i5n657hmpu] {
    display: flex;
    align-items: center;
    gap: .65rem;
    padding: .1rem .1rem .65rem;
}

.portal-user-card-meta[b-i5n657hmpu] {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.portal-user-card-name[b-i5n657hmpu] {
    font-weight: 600;
    font-size: .9rem;
    color: var(--cp-color-content, #1f2329);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.portal-user-card-email[b-i5n657hmpu] {
    font-size: .78rem;
    color: var(--cp-color-content-muted, #6b7280);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Separator above the sign-out action (the border-top used to live on the link itself). */
.portal-user-card-actions[b-i5n657hmpu] {
    margin-top: .1rem;
    padding-top: .55rem;
    border-top: 1px solid var(--cp-color-border, #edeff2);
}

/* DxButton renders its own <button> as a child component, so it does not carry this
   component's scoped-CSS attribute — reach it with ::deep. Make it a full-width, left-aligned
   menu-style action that turns danger on hover. */
[b-i5n657hmpu] .portal-user-card-signout {
    width: 100%;
    justify-content: flex-start;
}

[b-i5n657hmpu] .portal-user-card-signout:hover {
    color: var(--cp-color-danger);
    border-color: var(--cp-color-danger);
}

@media screen and (max-width: 824px) {
    .portal-user-trigger-name[b-i5n657hmpu] {
        display: none;
    }
}
/* /Layout/MainLayout.razor.rz.scp.css */
.content[b-yowetl7prf] {
	width: 100vw;
	height: 100vh;
}

/* The scroll area, window-wide on purpose: the scrollbar belongs at the edge of the window, which is
   where a reader's hand and eye look for it, not inset at the edge of the text. Nothing about the
   page's width is decided here.

   scrollbar-gutter reserves the bar's width on BOTH sides, so the column inside stays centred on the
   window rather than on the window-minus-scrollbar. Without it the page header — which sits outside
   this element and knows nothing about its scrollbar — is centred half a scrollbar to the right of
   the content it heads, and the two never line up. Browsers with overlay scrollbars reserve nothing
   and are unaffected. */
.app-main[b-yowetl7prf] {
	overflow: auto;
	width: 100%;
	scrollbar-gutter: stable both-edges;
}

/* The app's content column. Width and side gutter come from --cp-content-* in cp-tokens.css, so
   this is the only place either is decided for a MainLayout page — pages set no width of their own
   and every screen lines up with the header above it and with the web quote. */
.app-content[b-yowetl7prf] {
	max-width: var(--cp-content-max);
	margin: 0 auto;
	padding: 0 var(--cp-content-gutter) 1rem;
}

@media screen and (max-width: 634px) {

	.app-content[b-yowetl7prf] {
		padding: 0 1rem 1rem 1rem;
	}
}
/* /Layout/PaymentLayout.razor.rz.scp.css */
.payment-layout[b-ilw1phe80q] {
    min-height: 100vh;
    background: #131416;
}
/* /Pages/AccountPartDetail.razor.rz.scp.css */
/* The drawing and the facts, side by side: the viewer column a little over three-fifths of the width,
   as the drawing is the thing the page is about, and the cards beside it.

   align-items: start is load-bearing. A grid stretches its items to the row's height by default, and
   a column as tall as its row has nowhere to stick — the viewer would scroll away with the cards. */
.part-detail-layout[b-mkm0i8gnjp] {
    display: grid;
    grid-template-columns: minmax(0, 8fr) minmax(0, 5fr);
    gap: var(--cp-space-6);
    align-items: start;
}

/* Pinned at the top of the scroll area while the cards scroll past, and as tall as what is visible
   of the window below that edge — measured by viewport-fit.js, which the page starts once the part
   is on screen. top: 0 needs no offset: MainLayout's scroll area already begins below the page
   header (see AccountParts.razor.css).

   The fallback is only for the moment before the measurement lands, or if it never does: roughly
   the chrome above the scroll area, so the first frame is close rather than a full window tall.
   The space-4 subtracted in both cases is .app-content's bottom padding, so the frame's lower edge
   sits where the page's content does. The floor keeps the viewer usable on a short window, at the
   cost of its foot being reached by scrolling. */
.part-detail-viewer-col[b-mkm0i8gnjp] {
    position: sticky;
    top: 0;
    height: calc(var(--cp-viewport-fit-height, 100dvh - 12rem) - var(--cp-space-4));
    min-height: 24rem;
}

.part-detail-info-col[b-mkm0i8gnjp] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-6);
    min-width: 0;
}

/* Below lg the columns stack and nothing is pinned: a phone has no room for a drawing that never
   scrolls away, and the cards are what it reads first after the drawing. */
@media (max-width: 991.98px) {
    .part-detail-layout[b-mkm0i8gnjp] {
        grid-template-columns: minmax(0, 1fr);
        gap: var(--cp-space-5);
    }

    .part-detail-viewer-col[b-mkm0i8gnjp] {
        position: static;
        height: min(70vh, 32rem);
        min-height: 0;
    }
}
/* /Pages/AccountParts.razor.rz.scp.css */
/* The parts library's two pinned blocks: the filter bar at the top of the scroll area, the pager
   and reach note at the bottom, with only the cards moving between them.

   position: sticky on two ordinary page children, NOT an inner scroll pane around the grid. The
   page keeps the app's single window scroll, so the scrollbar stays where it is on every other
   screen, the keyboard's Page Down still pages the list, and the browser's own scroll restoration
   still works. The page is also given no viewport height of its own: a sticky child needs a tall
   page to have anything to stick against, and capping the page height is what would force the
   nested scroll region this deliberately avoids.

   WHY THE OFFSETS ARE PLAIN ZEROES. MainLayout puts the scrollport on .app-main and leaves
   AppPageHeader outside it, so the first pixel of the scroll area is already the first pixel below
   the page header: top: 0 pins directly under it and bottom: 0 pins at the window edge. No header
   height is measured here and nothing breaks when the header's own height changes. Anyone tempted
   to add an offset should check where the scrollport is first.

   WHY THEY LINE UP WITH THE CARDS. Both blocks are children of .app-content, the same padding box
   the card grid sits in, so they span exactly the cards' width and the side gutter beside them is
   padding no card can reach either. .app-main reserves its scrollbar width on both edges outside
   that box, which moves the blocks and the cards by the same amount. */

/* Shared by both blocks.

   The opaque background is the page's own: Bootstrap paints the body with the background token that
   cp-tokens.css defines as var(--cp-color-surface), so re-declaring that token here is the page
   colour rather than a colour chosen to look like it. Without it the cards scroll visibly through
   a pinned block.

   z-index is load-bearing, not decoration. Every card's media band is positioned, so it paints in
   the positioned layer; with both left at auto the cards would beat the header block on DOM order
   alone and scroll straight over the filter fields. 30 clears everything this page's content can
   reach (no card sets a z-index, and the highest any scrolling content sets anywhere in the app is
   20) while staying far below the global chrome that must always win: the quote page's mobile CTA
   at 90, ErrorPopup at 1080/1081, LoadingOverlay at 1090, and the DevExpress popup layer that
   ConfirmDialog renders into. */
.part-list-head[b-trbsgnkakr],
.part-list-foot--pinned[b-trbsgnkakr] {
    position: sticky;
    z-index: 30;
    background: var(--cp-color-surface);
}

/* The vertical padding is part of the cover, not just breathing room: it is the strip a card passes
   behind on its way out of view, so the card is hidden before it reaches the controls instead of
   peeking out beside them. It replaces the mb-3 / mt-3 / mt-2 margins these blocks used to carry —
   a margin sits outside the box and covers nothing. */
.part-list-head[b-trbsgnkakr] {
    top: 0;
    padding: var(--cp-space-3) 0 var(--cp-space-4);
    border-bottom: 1px solid var(--cp-color-border);
}

.part-list-foot[b-trbsgnkakr] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-2);
    padding-top: var(--cp-space-4);
}

.part-list-foot--pinned[b-trbsgnkakr] {
    bottom: 0;
    padding-bottom: var(--cp-space-3);
    border-top: 1px solid var(--cp-color-border);
}

/* Phones. Below this width PartListFilterBar drops its fields into a single column — four stacked
   editors plus the action row — which is a large fraction of a short phone viewport. Pinned at the
   top AND a pinned foot would leave a slot barely a card tall for the cards themselves, so the
   filter bar scrolls away here and only the pager stays. The pager is one compact row and is the
   control worth keeping within reach; the filter is set once and then left alone.

   The breakpoint is the filter bar's own collapse point, because the bar becoming tall is the
   reason for unpinning it. It is not .app-content's 634px gutter change, which costs no height.

   The divider stays: static, it reads as the line between the filter and the list. */
@media (max-width: 575.98px) {

    .part-list-head[b-trbsgnkakr] {
        position: static;
    }
}
/* /Pages/Error.razor.rz.scp.css */
/* Error page. Mirrors the quote holding-card icon disc so a failure reads as part of the same
   product, using the danger role tokens rather than the muted ones. */
.err-icon[b-lg3lbjm3zi] {
    width: 72px;
    height: 72px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-size: 2rem;
    margin-bottom: .5rem;
    background: var(--cp-role-danger-bg);
    color: var(--cp-role-danger-fg);
    border: 1px solid var(--cp-role-danger-border);
}

.err-ref[b-lg3lbjm3zi] {
    font-family: var(--cp-font-family-mono);
    color: var(--cp-color-content-muted);
}

.err-foot[b-lg3lbjm3zi] {
    margin-top: 1.25rem;
    padding-top: 1rem;
    border-top: 1px solid var(--cp-color-border);
    width: 100%;
}

/* The support actions are anchors wearing DxButton's classes (see Error.razor for why they cannot be
   DxButtons). DevExpress leaves --dxbl-btn-text-decoration unset for non-link styles, which is fine
   on the <button> it expects but would leave the browser's underline on an <a>. Stated rather than
   relied on. */
.err-action[b-lg3lbjm3zi] {
    text-decoration: none;
}
/* /Pages/Index.razor.rz.scp.css */
[b-41jqrxvi9l] .welcome-gridlayout {
    margin: auto;
    width: auto;
    height: auto;
}

[b-41jqrxvi9l] .welcome-gridlayout .dxbl-gridlayout-root {
    align-content: center;
    justify-content: center;
}

[b-41jqrxvi9l] .title {
    text-align: center;
}

[b-41jqrxvi9l] .welcome-cards {
    display: flex;
    flex-wrap: wrap;
    gap: 1.5rem;
    justify-content: center;
}

[b-41jqrxvi9l] .welcome-card {
    width: 26.25rem;
    height: 15rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    box-shadow: 0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.1);
    transition: box-shadow 0.2s;
    border-radius: 1rem;
    color: var(--cp-color-primary, var(--cp-color-primary));
    gap: 1.5rem;
    text-decoration: none;
    position: relative;
}

[b-41jqrxvi9l] .welcome-card:hover {
    box-shadow: 0px 20px 25px -5px rgba(0, 0, 0, 0.1), 0px 8px 10px -6px rgba(0, 0, 0, 0.1);
}

[b-41jqrxvi9l] .welcome-card .welcome-card-img {
    width: 6.5rem;
    height: 6.5rem;
}

[b-41jqrxvi9l] .welcome-card .welcome-card-text {
    font-size: 1.75rem;
    font-weight: 600;
    letter-spacing: 0em;
    text-align: center;
    text-decoration: unset;
}

[b-41jqrxvi9l] .welcome-card .welcome-card-back {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: var(--cp-color-content, var(--cp-color-content));
    opacity: 0.05;
    border-radius: 1rem;
    z-index: -2;
}
/* /Pages/JobQuote.razor.rz.scp.css */
/* Web quote — page-level layout only. Section styling lives in each component's scoped
   .razor.css under Components/Job/; colours and spacings come from the --cp-* tokens in
   cp-tokens.css, which are mode-aware, so light and dark are handled from one place. */

/* The page is on MainLayout, so the layout owns what this used to: .app-main is the scroll container
   (html,body are overflow:hidden app-wide), .app-content inside it carries the content width and side
   gutter from cp-tokens.css, and the nav bar stands above both in flow — there is no fixed header of
   the page's own left to reserve room for. What is left here is the quote's own vertical rhythm. */
.wq-shell[b-39ikeiidzr] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-8);
    padding-bottom: var(--cp-space-11);
}

.wq-checkout[b-39ikeiidzr] {
    display: flex;
    flex-direction: column;
    gap: var(--cp-space-2);
}

.wq-checkout-body[b-39ikeiidzr] {
    display: grid;
    grid-template-columns: 1fr 1.25fr;
    gap: var(--cp-space-6);
    align-items: start;
}

.wq-parts-finish-body[b-39ikeiidzr] {
    display: grid;
    grid-template-columns: 1fr 1.5fr;
    gap: var(--cp-space-6);
    align-items: start;
}

    /* Either card can be absent — no powder work on the job, or no finish upgrade offered. Left in the
       two-column track, the survivor renders at 40% width with a hole beside it. */
    /* ::deep because the survivor is another component's root element, which carries no scope of ours. */
    .wq-parts-finish-body[b-39ikeiidzr] >  :only-child {
        grid-column: 1 / -1;
    }

.wq-sec-details[b-39ikeiidzr] {
    display: grid;
    grid-template-columns: 1fr 1.5fr;
    gap: var(--cp-space-6);
    align-items: start;
}

/* Tablet: timeline spans the full width, the two timers share the row below. */
@media (max-width: 992px) {
    .wq-timeline-row[b-39ikeiidzr] {
        grid-template-columns: 1fr 1fr;
    }

        .wq-timeline-row > :first-child[b-39ikeiidzr] {
            grid-column: 1 / -1;
        }
}

/* Narrow: everything stacks. */
@media (max-width: 560px) {
    .wq-timeline-row[b-39ikeiidzr] {
        grid-template-columns: 1fr;
    }

    .wq-timeline-row > :first-child[b-39ikeiidzr] {
        grid-column: auto;
    }
}

@media (max-width: 880px) {
    .wq-checkout-body[b-39ikeiidzr] {
        grid-template-columns: 1fr;
    }

    .wq-parts-finish-body[b-39ikeiidzr] {
        grid-template-columns: 1fr;
    }

    .wq-sec-details[b-39ikeiidzr] {
        grid-template-columns: 1fr;
    }
}

@media (max-width: 720px) {
    .wq-shell[b-39ikeiidzr] {
        padding-bottom: var(--cp-space-12);
    }
}

/* Section-jump anchors: leave a little breathing room above a section when the left rail
   scrolls it into view (the scroll happens inside .app-main, not the window). */
.wq-anchor[b-39ikeiidzr] {
    scroll-margin-top: var(--cp-space-5);
}

/* The rail moves focus here (via a JS-set tabindex="-1") after a jump; the section wrapper is not an
   interactive control, so it should not paint a focus ring. */
.wq-anchor:focus[b-39ikeiidzr] {
    outline: none;
}
/* /Pages/JobTrack.razor.rz.scp.css */
/* No width or gutter here — MainLayout .app-content owns both for every page (--cp-content-* in
   cp-tokens.css). The two columns are JobOrderStatusView's own business; this page only stacks that
   view above the footer. */
.order-status-content[b-94pnkg0w7b] {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    width: 100%;
}
