/* Adman Steel Sheds — Application Theme
   Neutral warm-grey surfaces with the Adman navy used sparingly as the single accent.
   Montserrat is loaded once from _Layout.cshtml — do not re-@import it here. */

:root {
    /* ── surfaces ─────────────────────────────── */
    --surface:        #FFFFFF;   /* cards, sidebar, topbar */
    --surface-page:   #FAFAF9;   /* page background */
    --surface-muted:  #F5F5F4;   /* table headers, hover rows, inactive fills */

    /* ── borders ──────────────────────────────── */
    --border:         #E7E5E4;   /* hairlines — the default */
    --border-strong:  #D6D3D1;   /* inputs, outline buttons */

    /* ── text ─────────────────────────────────── */
    --text:           #1C1A2E;   /* body + headings */
    --text-muted:     #78716C;   /* labels, captions, table headers */
    --text-subtle:    #A8A29E;   /* placeholders, footer, empty states */

    /* The sidebar's resting nav text. Its own token rather than --text-muted:
       the nav is read at a glance all day and wants the brand's near-black,
       while --text-muted is right for the captions and table headers it also
       drives elsewhere. Dark mode keeps the muted grey it already had. */
    --sidebar-link:   #2C3238;

    /* ── accent: the Adman navy, lifted ───────── */
    --navy:           #2A2470;
    --navy-hover:     #352E8A;
    --navy-tint:      #F2F1F8;   /* active nav fill, selected states */
    --navy-ring:      rgba(42,36,112,0.12);

    /* ── semantic ─────────────────────────────── */
    --success:        #2F6355;
    --danger:         #9B3B33;

    /* ── status palette (the quote/order states) ──
       -bg/-fg are the badge tint pair; -solid is the dashboard bar fill.

       Cancelled is deliberately the quiet cousin of Rejected rather than a second red: the two
       sit next to each other in the reports breakdown, and two reds there would be a chart the
       reader has to consult a legend to read. Desaturated towards stone, it still says "stopped"
       at a glance while Rejected keeps the alarm. It is not the Incomplete grey either — that one
       is neutral, and a cancelled order is not neutral. */
    --st-incomplete-bg: #F5F5F4; --st-incomplete-fg: #57534E; --st-incomplete-solid: #A8A29E;
    --st-pending-bg:    #FDF6EC; --st-pending-fg:    #8A6516; --st-pending-solid:    #C9A227;
    --st-accepted-bg:   #EEF1FA; --st-accepted-fg:   #33417A; --st-accepted-solid:   #5566A8;
    --st-rejected-bg:   #FBEDEC; --st-rejected-fg:   #9B3B33; --st-rejected-solid:   #B4564C;
    --st-onhold-bg:     #F3F0FA; --st-onhold-fg:     #5B4E8C; --st-onhold-solid:     #8074B5;
    --st-scheduled-bg:  #EBF3F0; --st-scheduled-fg:  #2F6355; --st-scheduled-solid:  #4E8C7B;
    --st-delivered-bg:  #EDF3EC; --st-delivered-fg:  #3F6B39; --st-delivered-solid:  #6B9160;
    --st-cancelled-bg:  #F1EDEC; --st-cancelled-fg:  #6B5651; --st-cancelled-solid:  #927B75;

    --alert-danger-border:  #F0DCDA;
    --alert-success-border: #D8E7E2;
    --alert-info-border:    #DDE3F2;
    --alert-warning-border: #F2E6D2;

    /* Second series on the dashboard activity chart (Quotes). */
    --chart-secondary: #A9A4CE;

    /* ── shape + depth ────────────────────────── */
    --radius-sm:      6px;
    --radius:         8px;
    --radius-lg:      10px;
    --shadow-sm:      0 1px 2px rgba(28,26,46,0.04);
    --shadow-md:      0 2px 8px rgba(28,26,46,0.06);

    /* ── back-compat aliases — do not add new usages ── */
    --adman-navy:     var(--navy);
    --adman-charcoal: var(--text);
    --adman-warm:     var(--border);
    --adman-cream:    var(--surface-page);
    --adman-accent:   var(--navy);   /* the gold #e8a020 is retired */
    --adman-danger:   var(--danger);
    --adman-success:  var(--success);
    /* These two were referenced by Users/UserProducts views but never defined —
       they previously resolved to nothing. Now they resolve. */
    --adman-muted:    var(--text-muted);
    --adman-light:    var(--surface-muted);

    --sidebar-width: 260px;
    --header-height: 64px;
    /* One height for inputs, selects and full-size buttons, so anything sitting on the
       same row lines up regardless of its font size or line-height. */
    --control-height: 40px;
    --control-height-sm: 30px;
}

/* ── DARK THEME ────────────────────────────────────
   Applied by setting data-theme="dark" on <html>. The inline script in
   _Layout.cshtml sets this before first paint so there is no flash.
   Only tokens are redefined here — every component reads through them, so
   nothing below this block needs a dark-specific rule. Keep it that way:
   if a component needs a dark override, it is usually a missing token. */
:root[data-theme="dark"] {
    /* Surfaces are deliberately pure neutral grey — R=G=B, no colour cast.
       Do not tint these towards the accent; a hue here reads as a wash over
       the whole page rather than as a colour choice. */
    --surface:        #1A1A1A;
    --surface-page:   #121212;
    --surface-muted:  #242424;

    --border:         #2E2E2E;
    --border-strong:  #414141;

    --text:           #EDEDED;
    --text-muted:     #A3A3A3;
    --text-subtle:    #737373;
    --sidebar-link:   #A3A3A3;   /* as --text-muted — dark mode is unchanged */

    /* The brand navy is near-invisible on a dark ground, so the accent has to
       lift a long way. Lifting #2A2470 alone reads violet, so this also shifts
       towards blue to stay recognisably the same colour as the light theme. */
    --navy:           #4F73DD;
    --navy-hover:     #6488E8;
    --navy-tint:      #1E2534;
    --navy-ring:      rgba(79,115,221,0.35);

    --success:        #5FA88F;
    --danger:         #E0796E;

    --shadow-sm:      0 1px 2px rgba(0,0,0,0.40);
    --shadow-md:      0 4px 16px rgba(0,0,0,0.45);

    /* Neutral states stay grey; the rest keep just enough hue to stay semantic. */
    --st-incomplete-bg: #262626; --st-incomplete-fg: #B0B0B0; --st-incomplete-solid: #6E6E6E;
    --st-pending-bg:    #33291A; --st-pending-fg:    #E3BB66; --st-pending-solid:    #C9A227;
    --st-accepted-bg:   #1E2740; --st-accepted-fg:   #8FA9EA; --st-accepted-solid:   #5B7AC8;
    --st-rejected-bg:   #372121; --st-rejected-fg:   #EA9E94; --st-rejected-solid:   #C05F54;
    --st-onhold-bg:     #262B3A; --st-onhold-fg:     #A3B0D4; --st-onhold-solid:     #7D8CB4;
    --st-scheduled-bg:  #1D3129; --st-scheduled-fg:  #7FC8AB; --st-scheduled-solid:  #55997F;
    --st-delivered-bg:  #253120; --st-delivered-fg:  #9FC98B; --st-delivered-solid:  #739C66;
    --st-cancelled-bg:  #2E2725; --st-cancelled-fg:  #C5B0AA; --st-cancelled-solid:  #96807A;

    --alert-danger-border:  #4A2C2C;
    --alert-success-border: #26443A;
    --alert-info-border:    #2B3555;
    --alert-warning-border: #46381F;

    /* Lighter and much less saturated than --navy, so the two chart series
       separate on lightness as well as hue. */
    --chart-secondary: #8B9BB5;
}

/* Dark-only exceptions — things that are not just a colour token.
   The mark is navy, which all but disappears on --surface, so on dark it is
   swapped for the white artwork rather than tinted or given a chip. */
:root[data-theme="dark"] .sidebar-brand-logo-img,
:root[data-theme="dark"] .auth-logo-img,
:root[data-theme="dark"] .landing-logo {
    content: url("/images/adman-logo-white.png");
}

/* Primary buttons keep white text in both themes; the lifted navy is dark
   enough behind it to stay readable. */
:root[data-theme="dark"] .btn-adman,
:root[data-theme="dark"] .btn-adman-accent {
    color: #fff;
}

/* Bootstrap paints form controls white via its own variables. */
:root[data-theme="dark"] {
    --bs-body-bg: var(--surface);
    --bs-body-color: var(--text);
    --bs-border-color: var(--border);
    /* Bootstrap's .table draws its text from --bs-emphasis-color, which stays #000
       unless it is redefined — that is what put black text on dark tables. Its -rgb
       twin feeds the striped/hover tints, so both have to move together. */
    --bs-emphasis-color: var(--text);
    --bs-emphasis-color-rgb: 237, 237, 237;
    /* .form-text — the hint under a field — draws from this, and Bootstrap only moves it
       under its own [data-bs-theme], which this app never sets: it switches on data-theme.
       Left alone it stays rgba(33,37,41,.75), near-black on a near-black page. */
    --bs-secondary-color: var(--text-muted);
    color-scheme: dark;
}

:root[data-theme="dark"] .form-control,
:root[data-theme="dark"] .form-select,
:root[data-theme="dark"] .input-group-text {
    background-color: var(--surface-muted);   /* not `background` — see .form-select */
    border-color: var(--border-strong);
    color: var(--text);
}

:root[data-theme="dark"] .input-group-text.bg-white { background: var(--surface-muted) !important; }

/* Bootstrap draws the select caret as a dark SVG, which disappears on a dark field —
   without it a <select> is indistinguishable from a text input. */
:root[data-theme="dark"] .form-select {
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23a3a3a3' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}

/* The native date/time picker glyph is black-on-black otherwise. */
:root[data-theme="dark"] input[type="date"]::-webkit-calendar-picker-indicator {
    filter: invert(1) opacity(0.6);
}

*, *::before, *::after { box-sizing: border-box; }

html, body {
    height: 100%;
    margin: 0;
    font-family: 'Montserrat', 'Segoe UI', sans-serif;
    font-size: 14px;
    background: var(--surface-page);
    color: var(--text);
    -webkit-font-smoothing: antialiased;
}

a { color: var(--navy); }

/* ── SIDEBAR SHELL ─────────────────────────────── */
.app-shell {
    display: flex;
    min-height: 100vh;
}

/* ── SIDEBAR ───────────────────────────────────── */
.sidebar {
    width: var(--sidebar-width);
    background: var(--surface);
    border-right: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    flex-shrink: 0;
    position: fixed;
    top: 0; left: 0; bottom: 0;
    z-index: 1000;
    overflow-y: auto;
    transition: transform 0.25s ease;
}

.sidebar-brand {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 20px 24px 16px;
    border-bottom: 1px solid var(--border);
    text-decoration: none;
}

/* kept for fallback — replaced by .sidebar-brand-logo-img */
.sidebar-brand-logo {
    width: 40px;
    height: 40px;
    background: var(--navy-tint);
    border-radius: var(--radius);
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 18px;
    color: var(--navy);
    letter-spacing: -1px;
    flex-shrink: 0;
}

/* The mark is a wide banner (800×325), so it gets a width and takes its own
   height — a square box would letterbox it with dead space above and below. */
.sidebar-brand-logo-img {
    width: 96px;
    height: auto;
    flex-shrink: 0;
}

/* Signed-out landing splash — same banner, larger. Lives here rather than inline
   so the dark-theme swap above can reach it. */
.landing-logo {
    width: 200px;
    height: auto;
    margin-bottom: 20px;
}

.sidebar-brand-text {
    line-height: 1.15;
}

.sidebar-brand-name {
    font-weight: 900;
    font-size: 20px;
    color: var(--text);
    letter-spacing: -0.5px;
    display: block;
}

.sidebar-section {
    padding: 20px 0 8px;
}

.sidebar-section-label {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    color: var(--text-subtle);
    padding: 0 24px 8px;
}

.sidebar-nav {
    list-style: none;
    margin: 0;
    padding: 0;
}

.sidebar-nav .nav-item { margin: 1px 12px; }

.sidebar-nav .nav-link {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 9px 12px;
    border-radius: var(--radius);
    color: var(--sidebar-link);
    font-size: 13.5px;
    font-weight: 500;
    text-decoration: none;
    transition: background 0.15s, color 0.15s;
}

.sidebar-nav .nav-link:hover {
    background: var(--surface-muted);
    color: var(--text);
}

.sidebar-nav .nav-link.active {
    background: var(--navy-tint);
    color: var(--navy);
    font-weight: 600;
}

.sidebar-nav .nav-link .nav-icon {
    width: 18px;
    text-align: center;
    flex-shrink: 0;
    font-size: 15px;
}

.sidebar-nav .nav-link-coming-soon {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 9px 12px;
    border-radius: var(--radius);
    color: var(--text-subtle);
    font-size: 13.5px;
    font-weight: 500;
    cursor: default;
    user-select: none;
}

.nav-soon-badge {
    margin-left: auto;
    font-size: 9px;
    font-weight: 600;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: var(--text-subtle);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 1px 5px;
}

.sidebar-footer {
    margin-top: auto;
    padding: 16px 12px;
    border-top: 1px solid var(--border);
}

/* Account menu — collapsed by default, opened by the account button below it. */
.sidebar-account-panel {
    display: none;
    margin-bottom: 8px;
    padding: 6px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
}

.sidebar-footer.open .sidebar-account-panel { display: block; }

.sidebar-footer.open .sidebar-user-caret { transform: rotate(180deg); }

.sidebar-account-item {
    all: unset;
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 9px 12px;
    border-radius: var(--radius);
    color: var(--text-muted);
    font-family: 'Montserrat', sans-serif;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    box-sizing: border-box;
    transition: background 0.15s, color 0.15s;
}

.sidebar-account-item:hover { background: var(--surface-muted); color: var(--text); }

.sidebar-account-signout:hover { color: var(--danger); }

.sidebar-signout-btn {
    all: unset;           /* wipe every Bootstrap/browser default */
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    width: 100%;
    padding: 9px 14px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--text-muted);
    font-family: 'Montserrat', sans-serif;
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.2px;
    cursor: pointer;
    box-sizing: border-box;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
}

.sidebar-signout-btn:hover {
    background: var(--surface-muted);
    border-color: var(--danger);
    color: var(--danger);
}

.sidebar-user {
    all: unset;           /* it is a <button> — wipe browser/Bootstrap defaults */
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 10px 12px;
    border-radius: var(--radius);
    text-decoration: none;
    color: var(--text-muted);
    font-family: 'Montserrat', sans-serif;
    font-size: 12.5px;
    cursor: pointer;
    box-sizing: border-box;
    transition: background 0.15s;
}

.sidebar-user-caret {
    margin-left: auto;
    font-size: 12px;
    color: var(--text-subtle);
    transition: transform 0.15s;
}

.sidebar-user:hover { background: var(--surface-muted); color: var(--text); }

.sidebar-user-name {
    font-size: 12.5px;
    font-weight: 600;
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.sidebar-user-meta {
    font-size: 11px;
    color: var(--text-subtle);
}

/* Shared avatar — sidebar footer and topbar */
.avatar {
    width: 32px;
    height: 32px;
    background: var(--navy-tint);
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 13px;
    color: var(--navy);
    flex-shrink: 0;
}

.sidebar-avatar {
    width: 32px;
    height: 32px;
    background: var(--navy-tint);
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 13px;
    color: var(--navy);
    flex-shrink: 0;
}

/* ── MAIN CONTENT ──────────────────────────────── */
.main-content {
    margin-left: var(--sidebar-width);
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 100vh;

    /* A flex item's min-width is auto, which means it refuses to shrink below the
       widest thing inside it. So a grid with more columns than a phone is wide
       stopped being a table with a scrollbar and became a page 900px wider than
       the screen, taking the topbar and every card with it — the card's own
       overflow-x:auto could not contain it, because the card itself was being
       sized by the table. Nothing that already fits is affected. */
    min-width: 0;
}

/* ── TOP BAR ───────────────────────────────────── */
/* There is no page top bar — pages carry their own heading. This strip exists only
   to hold the sidebar toggle, so it is hidden above the mobile breakpoint. */
.topbar-bare {
    display: none;
    padding: 8px 16px;
    background: transparent;
}

/* ── PAGE BODY ─────────────────────────────────── */
.page-body {
    flex: 1;
    padding: 28px 32px;
}

/* ── CARDS ─────────────────────────────────────── */
.card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    overflow: hidden;
}

.card-header {
    padding: 16px 20px 14px;
    border-bottom: 1px solid var(--border);
    background: var(--surface);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

.card-title {
    font-size: 14px;
    font-weight: 600;
    color: var(--text);
    letter-spacing: 0;
    margin: 0;
}

.card-body { padding: 20px; }

/* ── STAT CARDS ────────────────────────────────── */
.stat-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: 20px 22px;
    display: flex;
    align-items: center;
    gap: 16px;
    box-shadow: var(--shadow-sm);
    transition: border-color 0.15s;
}

.stat-card:hover {
    border-color: var(--border-strong);
}

.stat-icon {
    width: 44px;
    height: 44px;
    border-radius: var(--radius);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 18px;
    flex-shrink: 0;
}

.stat-icon.navy   { background: var(--navy-tint);           color: var(--navy); }
.stat-icon.accent { background: var(--navy-tint);           color: var(--navy); }
.stat-icon.success{ background: rgba(47,99,85,0.10);        color: var(--success); }
.stat-icon.danger { background: rgba(155,59,51,0.10);       color: var(--danger); }
.stat-icon.muted  { background: var(--surface-muted);       color: var(--text-muted); }

.stat-value {
    font-size: 26px;
    font-weight: 700;
    color: var(--text);
    line-height: 1;
    letter-spacing: -0.6px;
}

.stat-label {
    font-size: 12px;
    font-weight: 500;
    color: var(--text-muted);
    letter-spacing: 0.2px;
    margin-top: 4px;
}

/* ── DASHBOARD KPI CARDS ───────────────────────── */
.kpi-label {
    font-size: 11.5px;
    font-weight: 500;
    letter-spacing: 0.2px;
    color: var(--text-muted);
    margin-bottom: 6px;
}

.kpi-value {
    font-size: 30px;
    font-weight: 700;
    color: var(--text);
    line-height: 1;
    letter-spacing: -0.8px;
}

.kpi-sub {
    font-size: 12.5px;
    color: var(--text-muted);
    margin-top: 6px;
}

/* ── REP DASHBOARD: THE AT-A-GLANCE TILES ──────────
   Styled after the sales office TV board (tv-board.css): a navy headline card
   first, then white cards with a heavy number. Sized for a dashboard rather than
   a TV, and built on the office tokens — not the TV board's pinned light values —
   so dark mode follows the rest of the app. Four to a row on a desktop, two on a
   phone, the headline card the width of two either way. */
.rg { margin-bottom: 20px; }

.rg-heading {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 4px 12px;
    margin-bottom: 8px;
}

.rg-heading-title {
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.rg-heading-note { font-size: 12.5px; color: var(--text-subtle); }

.rg-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px;
}

.rg-tile {
    position: relative;
    display: flex;
    flex-direction: column;
    min-width: 0;
    padding: 14px 16px 14px 20px;
    overflow: hidden;
    background: var(--surface);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    text-decoration: none;
    transition: border-color 0.15s, box-shadow 0.15s;
}

/* The navy edge down the left of every card. */
.rg-tile::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 5px;
    background: var(--navy);
}

.rg-tile:hover,
.rg-tile:focus-visible {
    color: var(--text);
    border-color: var(--border-strong);
    box-shadow: var(--shadow-md);
}

.rg-title {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.rg-figure {
    margin-top: 6px;
    font-size: 34px;
    font-weight: 800;
    line-height: 1.05;
    letter-spacing: -0.8px;
    font-variant-numeric: tabular-nums;
    color: var(--navy);
}

.rg-label {
    margin-top: 6px;
    font-size: 12.5px;
    line-height: 1.35;
    color: var(--text-muted);
}

/* Something to do: the number turns to the danger colour, the card keeps its shape. */
.rg-attention .rg-figure,
.rg-critical { color: var(--danger); }

.rg-critical { font-weight: 600; }

/* The headline card: the first, navy, the width of two. */
.rg-head {
    grid-column: 1 / -1;
    background: var(--navy);
    color: #fff;
    border-color: var(--navy);
}

.rg-head::before { background: rgba(255, 255, 255, 0.35); }

.rg-head:hover,
.rg-head:focus-visible {
    color: #fff;
    border-color: var(--navy-hover);
}

.rg-head .rg-title { color: rgba(255, 255, 255, 0.75); }
.rg-head .rg-figure { color: #fff; font-size: 44px; }
.rg-head .rg-label { color: rgba(255, 255, 255, 0.85); }

.rg-note { margin: 8px 0 0; font-size: 12px; color: var(--text-muted); }

@media (min-width: 992px) {
    .rg-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
    .rg-head { grid-column: span 2; }
    .rg-figure { font-size: 40px; }
    .rg-head .rg-figure { font-size: 52px; }
}


/* Small numeric count pill used in tables */
.count-pill {
    display: inline-block;
    min-width: 24px;
    padding: 1px 9px;
    border-radius: var(--radius-sm);
    background: var(--surface-muted);
    color: var(--text-muted);
    font-size: 12px;
    font-weight: 600;
}

.count-pill-navy {
    background: var(--navy-tint);
    color: var(--navy);
}

/* A count that is carrying something overdue. On the sales rep dashboard's tabs this replaces the
   navy rather than sitting beside it — two pills per tab was half of what made the strip too wide,
   and one pill that changes colour says the same thing in the space of a number. The tab keeps a
   title attribute saying how many, because a colour on its own is not readable to everyone. */
.count-pill-danger {
    background: color-mix(in srgb, var(--danger) 14%, transparent);
    color: var(--danger);
}

/* The same pill on a strip that is already tight. */
.count-pill-sm {
    min-width: 18px;
    padding: 0 6px;
    font-size: 11px;
}

/* Status breakdown bars */
.bar-track {
    height: 5px;
    border-radius: 3px;
    background: var(--surface-muted);
    overflow: hidden;
}

.bar-fill {
    height: 100%;
    border-radius: 3px;
    transition: width 0.3s;
}

/* ── BUTTONS ───────────────────────────────────── */
.btn-adman {
    background: var(--navy);
    color: #fff;
    border: 1px solid var(--navy);
    border-radius: var(--radius);
    min-height: var(--control-height);
    padding: 8px 16px;
    font-size: 13px;
    font-weight: 600;
    font-family: 'Montserrat', sans-serif;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    /* Centred, so an icon-only button sits in the middle of its box rather than flush left.
       Ten views used to carry a hand-written inline justify-content to patch this. */
    justify-content: center;
    gap: 6px;
    text-decoration: none;
    transition: background 0.15s, border-color 0.15s;
    letter-spacing: 0.1px;
    /* min-height with a wrapping label makes a button taller than the ones beside it —
       "Schedule for Production" next to "Accept" was the visible case. */
    white-space: nowrap;
    /* Stated rather than inherited: <a> and <button> happen to agree on `normal` today,
       and nothing was making them. */
    line-height: 1.2;
    vertical-align: middle;
}

/* A table's action column. The cell stays a table-cell — making it a flex container would
   take it out of the table's column sizing — so alignment is fixed on the items instead.
   The delete buttons are wrapped in POST forms, and an inline form of default display puts
   the button on a different baseline from the plain anchors beside it. */
.row-actions { white-space: nowrap; }

.row-actions form {
    display: inline-flex;
    vertical-align: middle;
    margin: 0;
}

.row-actions > a,
.row-actions > button,
.row-actions form > button { vertical-align: middle; }

.row-actions > a + a,
.row-actions > a + form,
.row-actions form + form,
.row-actions form + a { margin-left: 4px; }

/* Icon-only buttons: square, so a row of them is even and the glyph is centred.
   Pair with a title — the icon is the only label the user gets. */
.btn-icon {
    width: var(--control-height);
    padding-left: 0;
    padding-right: 0;
}

.btn-sm.btn-icon { width: var(--control-height-sm); }

/* The large form buttons on the sign-in and account pages, which each used to restate
   their own padding, font size and corner radius inline — four padding values and a
   border-radius that existed nowhere else between them. */
.btn-adman-lg {
    padding: 12px 28px;
    font-size: 14px;
    min-height: 46px;
}

.btn-adman:hover {
    background: var(--navy-hover);
    border-color: var(--navy-hover);
    color: #fff;
}

/* Deprecated — the gold variant. Kept so existing markup stays sane; it is
   now identical to .btn-adman. Prefer .btn-adman for new work. */
.btn-adman-accent {
    background: var(--navy);
    color: #fff;
    border-color: var(--navy);
}

.btn-adman-accent:hover {
    background: var(--navy-hover);
    border-color: var(--navy-hover);
    color: #fff;
}

.btn-adman-outline {
    background: var(--surface);
    border: 1px solid var(--border-strong);
    color: var(--text);
}

.btn-adman-outline:hover {
    border-color: var(--border-strong);
    color: var(--text);
    background: var(--surface-muted);
}

/* Destructive actions — quiet by default, red only on hover, so a delete never
   competes with the primary action next to it. */
.btn-adman-danger {
    background: var(--surface);
    border: 1px solid var(--border-strong);
    color: var(--danger);
}

.btn-adman-danger:hover {
    background: var(--danger);
    border-color: var(--danger);
    color: #fff;
}

.btn-adman:focus-visible,
.btn-adman-outline:focus-visible,
.btn-adman-danger:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px var(--navy-ring);
}

.btn-sm { padding: 5px 11px; font-size: 12px; min-height: var(--control-height-sm); }

/* Held, not forbidden — the add button on a repeater whose last row is still on "choose". Kept
   legible rather than greyed to nothing, because the title on it is what explains the wait. */
.btn-adman-outline:disabled,
.btn-adman-outline.is-waiting {
    background: var(--surface-muted);
    color: var(--text-subtle);
    border-color: var(--border-strong);
    cursor: not-allowed;
}

.btn-adman-outline:disabled:hover,
.btn-adman-outline.is-waiting:hover {
    background: var(--surface-muted);
    color: var(--text-subtle);
}

/* ── BADGE / STATUS ────────────────────────────── */
.badge-status {
    display: inline-flex;
    align-items: center;
    /* Without this an inline-flex pill sits on its own text baseline, so a row of badges
       next to running text drifts up and down. Middle pins them all to one line. */
    vertical-align: middle;
    gap: 6px;
    padding: 3px 9px;
    border-radius: var(--radius-sm);
    font-size: 11.5px;
    line-height: 1.4;
    font-weight: 600;
    letter-spacing: 0.1px;
    white-space: nowrap;
}

.badge-status::before {
    content: '';
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    flex-shrink: 0;
}

/* Canonical status palette — the single source of truth for the
   quote/order states. Orders have eight, quotes use the seven they share. Referenced by _OrderStatusBadge, _QuoteStatusBadge,
   the dashboard breakdown bars and the order action buttons.
   Values come from the --st-* tokens so the dark theme swaps them in one place. */
.status-incomplete { background: var(--st-incomplete-bg); color: var(--st-incomplete-fg); }
.status-pending    { background: var(--st-pending-bg);    color: var(--st-pending-fg); }
.status-accepted   { background: var(--st-accepted-bg);   color: var(--st-accepted-fg); }
.status-rejected   { background: var(--st-rejected-bg);   color: var(--st-rejected-fg); }
.status-onhold     { background: var(--st-onhold-bg);     color: var(--st-onhold-fg); }
.status-scheduled  { background: var(--st-scheduled-bg);  color: var(--st-scheduled-fg); }
.status-delivered  { background: var(--st-delivered-bg);  color: var(--st-delivered-fg); }
.status-cancelled  { background: var(--st-cancelled-bg);  color: var(--st-cancelled-fg); }

/* Solid variant — for the dashboard breakdown bars, where the status colour
   is the fill rather than a tint behind text. */
.status-fill-incomplete { background: var(--st-incomplete-solid); }
.status-fill-pending    { background: var(--st-pending-solid); }
.status-fill-accepted   { background: var(--st-accepted-solid); }
.status-fill-rejected   { background: var(--st-rejected-solid); }
.status-fill-onhold     { background: var(--st-onhold-solid); }
.status-fill-scheduled  { background: var(--st-scheduled-solid); }
.status-fill-delivered  { background: var(--st-delivered-solid); }
.status-fill-cancelled  { background: var(--st-cancelled-solid); }

/* Provenance and lock badges. Deliberately NOT drawn from the status palette above:
   how an order was raised, and whether its spec is frozen, are both orthogonal to
   where it stands in the workflow, and borrowing a status colour would imply a
   relationship that isn't there. --navy-tint keeps the source badge quiet, since it
   sits beside a status badge that should stay the louder of the two. The lock badge
   takes the On Hold tokens because "frozen" is the nearest thing the palette already
   says, and an operator reading amber as "stop" is reading it correctly. */
.badge-source { background: var(--navy-tint);     color: var(--navy); }

/* The one line on a partner's document that the customer bought from Adman — see
   OrderLine.IsDirectSale. Takes the same quiet navy tint as the source badge above and for the
   same reason: how a line was sold says nothing about where the order stands in the workflow, and
   borrowing a status colour would imply a relationship that is not there. It sits inside a table
   row beside the item's own name, where a loud badge on every direct line would fight the row. */
.badge-direct { background: var(--navy-tint);     color: var(--navy); }

/* A row that is on the order only so it bills the way the partner's own purchase order does —
   see OrderLine.IsBillingOnly. B&Q buy a 6x13'6 Multistore as a Multigrow unit plus a Multigrow
   extra, and this marks the second of the two codes. Same reasoning as the direct badge above:
   it says how a line is invoiced, not where the order stands, so it borrows no status colour.
   Muted rather than tinted, because unlike a direct line it is not something anybody acts on —
   it is there so the office can see what the shop is being charged for. */
.badge-billing { background: var(--surface-muted); color: var(--text-muted); }
.badge-locked { background: var(--st-onhold-bg);  color: var(--st-onhold-fg); }

/* Order confirmation badges, and orthogonal to the workflow palette for the same reason the two
   above are: whether the customer has answered their confirmation says nothing about where the
   order stands in production. Both of these mean "somebody has to ring this customer", so both
   take the Rejected tokens — the palette's one genuinely alarming pair — rather than the amber the
   lock badge uses. Amber here would read as "waiting", and neither of these is waiting for us.
   There is deliberately no badge for the confirmed or the not-yet-answered case: a badge on every
   order in the list is a badge nobody reads, and these two exist to be noticed. */
.badge-bounced { background: var(--st-rejected-bg); color: var(--st-rejected-fg); }
.badge-flagged { background: var(--st-rejected-bg); color: var(--st-rejected-fg); }

/* Solid, not tinted: this one is an instruction, and the list should show it from across the room. */
.badge-sage-hand { background: var(--danger); color: #fff; }

/* The card on an order that Sage has to be corrected for by hand: an ordinary card with a red edge,
   under the customer and order cards. Loud enough to be seen, not a page-top alarm (Sean). */
.sage-hand { border-left: 4px solid var(--danger); }
.sage-hand-head .card-title { color: var(--danger); }
.sage-hand-body p { margin: 0 0 6px; }
.sage-hand-body ul { margin: 0 0 12px; padding-left: 20px; }

/* Payment state palette — _OrderPaymentBadge, the order's own page and the dashboard.

   Orthogonal to the workflow palette above, like the source and lock badges, because where an
   order stands on money and where it stands in production are different questions: a delivered
   order can be unpaid and a fully paid one can be sitting Incomplete. But unlike those two this
   IS a state with its own direction of travel, so the tokens are borrowed to say the same things
   they say above rather than kept deliberately quiet.

   Awaiting is muted, not alarming: an order nobody has asked for money on yet is the ordinary
   first state of every order, and colouring it red would put a warning on half the grid. Pending
   takes the amber "waiting" tokens, which is exactly what it is — somebody has asked and it has
   not arrived. Part paid takes the in-progress blue, fully paid the settled green, and on account
   the quiet navy tint the informational badges use, because no money is expected against it and
   nothing about it is for the order desk to act on. */
.badge-pay-awaiting { background: var(--surface-muted);     color: var(--text-muted); }
.badge-pay-pending  { background: var(--st-onhold-bg);      color: var(--st-onhold-fg); }
.badge-pay-partdeposit { background: var(--st-incomplete-bg); color: var(--st-incomplete-fg); }
.badge-pay-part     { background: var(--st-scheduled-bg);   color: var(--st-scheduled-fg); }
.badge-pay-full     { background: var(--st-accepted-bg);    color: var(--st-accepted-fg); }
.badge-pay-account  { background: var(--navy-tint);         color: var(--navy); }

/* A saved layout drawing holding a placement the shed will not take — see
   _PlacementWarningBadge and Domain\Orders\BackGableRackPolicy. Orthogonal to the workflow
   palette for the same reason the badges above are. It takes the On Hold tokens rather than the
   Rejected ones the two confirmation badges use: nobody has to ring anybody, the drawing has to be
   opened and corrected before the shed is built — which is the same "stop and check this" the lock
   badge's amber already means on this grid. */
.badge-placement { background: var(--st-onhold-bg); color: var(--st-onhold-fg); }

/* Legacy badge names — retained for any markup not yet migrated. */
.badge-pending  { background: var(--st-pending-bg);   color: var(--st-pending-fg); }
.badge-confirmed{ background: var(--st-accepted-bg);  color: var(--st-accepted-fg); }
.badge-progress { background: var(--st-scheduled-bg); color: var(--st-scheduled-fg); }
.badge-complete { background: var(--st-delivered-bg); color: var(--st-delivered-fg); }
.badge-cancelled{ background: var(--st-rejected-bg);  color: var(--st-rejected-fg); }

/* ── BOOTSTRAP OVERRIDES ───────────────────────── */
/* A handful of views still use Bootstrap's own button and badge classes.
   Pull them into the palette so they don't reintroduce Bootstrap blue/red/grey. */
.btn-outline-secondary {
    --bs-btn-color: var(--text-muted);
    --bs-btn-border-color: var(--border-strong);
    --bs-btn-hover-color: var(--text);
    --bs-btn-hover-bg: var(--surface-muted);
    --bs-btn-hover-border-color: var(--border-strong);
}

.btn-outline-danger {
    --bs-btn-color: var(--danger);
    --bs-btn-border-color: var(--border-strong);
    --bs-btn-hover-color: var(--danger);
    --bs-btn-hover-bg: #FBEDEC;
    --bs-btn-hover-border-color: var(--danger);
}

.badge.bg-secondary {
    background: var(--surface-muted) !important;
    color: var(--text-muted);
    font-weight: 600;
}

/* Bootstrap resolves .text-muted to a near-black at 75% opacity, which is
   invisible on a dark background. Point it at the token in both themes. */
.text-muted {
    --bs-secondary-color: var(--text-muted);
    color: var(--text-muted) !important;
}

/* ── TABLE ─────────────────────────────────────── */
.adman-table {
    width: 100%;
    border-collapse: collapse;
}

/* The admin grids are wider than a tablet held in portrait — the Product Details item table sets
   about 1250px of column widths on its own — and the app is used on a tablet on the factory floor.
   Without this the table simply overflows its card, with the right-hand columns unreachable and
   nothing on screen suggesting they are there. Scrolling the table rather than the page keeps the
   sidebar and headings put. Applied here rather than by wrapping each table in .table-responsive
   so a new grid gets it without having to remember. */
.card-body:has(> .adman-table) {
    overflow-x: auto;
}

.adman-table th {
    padding: 10px 14px;
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: 0.3px;
    color: var(--text-muted);
    border-bottom: 1px solid var(--border);
    white-space: nowrap;
    background: var(--surface-muted);
    text-align: left;
}

.adman-table td {
    padding: 13px 14px;
    font-size: 13.5px;
    border-bottom: 1px solid var(--border);
    color: var(--text);
    vertical-align: middle;
}

.adman-table tr:last-child td { border-bottom: none; }

.adman-table tbody tr:hover td { background: var(--surface-muted); }

/* Rows wired up by site.js — the whole row opens the record. An empty data-href
   means the row has no target, so it must not look clickable. */
.adman-table tbody tr[data-href]:not([data-href=""]) { cursor: pointer; }

.adman-table tbody tr[data-href]:focus-visible {
    outline: 2px solid var(--navy);
    outline-offset: -2px;
}

/* ── RETAIL PARTNER ROWS ───────────────────────────
   The dashboard's partner tabs (Woodies, B&Q, Chadwicks) fill a whole row in the partner's
   colour once an order is booked in, and red once it is cancelled — carried over from the
   spreadsheets these lists replaced, so a row reads the same to anyone who worked from them.

   The fill is a custom property set by the state class on the <tr>, and one rule paints the
   cells from it, so the hover and last-child rules above are not repeated per partner. An
   outstanding row carries none of these classes at all — it is an ordinary table row.

   Text colour is pinned rather than left to --text: these are fixed light fills, so in dark
   mode the theme's near-white text would be unreadable on them. */
.adman-table tbody tr.partner-row td {
    background: var(--partner-row-fill, transparent);
    color: var(--partner-row-text, var(--text));
}

/* Hover must not wash the fill out, so it deepens the row instead of replacing it. */
.adman-table tbody tr.partner-row:hover td {
    background: var(--partner-row-fill, var(--surface-muted));
    box-shadow: inset 0 0 0 999px rgb(0 0 0 / 0.07);
}

.partner-row-done-woodies   { --partner-row-fill: #92D050; --partner-row-text: #14210a; }
.partner-row-done-bq        { --partner-row-fill: #FFC000; --partner-row-text: #241a00; }
.partner-row-done-chadwicks { --partner-row-fill: #00B0F0; --partner-row-text: #04202b; }
.partner-row-cancelled      { --partner-row-fill: #FF0000; --partner-row-text: #ffffff; }

/* A coloured row's own controls have to stay legible against it, and the same way round in both
   themes: left alone, a dark-theme button keeps its dark surface and a light-theme outline button
   goes white text on white. Pinned light, like the row text is pinned dark. */
.adman-table tbody tr.partner-row td .badge-status,
.adman-table tbody tr.partner-row td .form-control,
.adman-table tbody tr.partner-row td .btn-adman {
    background: rgb(255 255 255 / 0.92);
    color: #201b57;
    border-color: rgb(32 27 87 / 0.25);
}

/* The dot on each tab, and the same swatch in the legend under the strip. */
.partner-tab-dot {
    display: inline-block;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    margin-right: 6px;
    vertical-align: 1px;
    background: var(--partner-row-fill, var(--text-muted));
}

.partner-dot-woodies   { --partner-row-fill: #92D050; }
.partner-dot-bq        { --partner-row-fill: #FFC000; }
.partner-dot-chadwicks { --partner-row-fill: #00B0F0; }
.partner-dot-cancelled { --partner-row-fill: #FF0000; }

.partner-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    font-size: 12px;
    color: var(--text-muted);
}

/* The note box sits in a table cell, so it has to stay on one line and not stretch the column.
   The retail partner tabs and the All Orders list both have one, and it is the same note in
   both — see Order.InternalNote. */
.internal-note-input {
    font-size: 12.5px;
    padding: 4px 8px;
    min-width: 150px;
}

/* Fixed column widths so columns line up across multiple separate
   <table> elements (e.g. one table per category on the item picker). */
.adman-table-fixed { table-layout: fixed; }
.adman-table-fixed td { word-break: break-word; }
.adman-table-fixed th { white-space: normal; line-height: 1.3; }

/* ── FORMS ─────────────────────────────────────── */
.form-label {
    font-size: 12.5px;
    font-weight: 600;
    color: var(--text);
    margin-bottom: 5px;
    letter-spacing: 0.1px;
}

.form-control, .form-select {
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    padding: 9px 13px;
    font-size: 13.5px;
    font-family: 'Montserrat', sans-serif;
    color: var(--text);
    /* background-color, not the shorthand: the shorthand also resets background-image,
       -repeat, -position and -size, which is what a <select> draws its caret with. */
    background-color: var(--surface);
    min-height: var(--control-height);
    transition: border-color 0.15s, box-shadow 0.15s;
}

/* The caret is declared here in full rather than left to Bootstrap, so its colour
   follows the theme and its geometry survives any background-colour change. */
.form-select {
    appearance: none;
    padding-right: 34px;
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%2378716c' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
    background-repeat: no-repeat;
    background-position: right 12px center;
    background-size: 12px 9px;
}

.form-control:focus, .form-select:focus {
    border-color: var(--navy);
    box-shadow: 0 0 0 3px var(--navy-ring);
    outline: none;
}

.form-control::placeholder { color: var(--text-subtle); }

/* The file input's Browse button is painted by Bootstrap from its own light-mode
   variables, which leaves it near-invisible on the dark theme. Drive it from our
   tokens instead so it reads in both. */
.form-control::file-selector-button {
    margin: -9px -13px;
    margin-inline-end: 13px;
    padding: 9px 13px;
    border: 0;
    border-inline-end: 1px solid var(--border-strong);
    border-radius: 0;
    background: var(--surface-muted);
    color: var(--text);
    font-family: 'Montserrat', sans-serif;
    font-size: 13.5px;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
}

/* Matches Bootstrap's own hover selector — anything less specific loses to it and
   the button flips back to its light-mode fill. */
.form-control:hover:not(:disabled):not([readonly])::file-selector-button {
    background: var(--border);
    color: var(--text);
}

.form-check-input:checked {
    background-color: var(--navy);
    border-color: var(--navy);
}

.form-check-input:focus {
    border-color: var(--navy);
    box-shadow: 0 0 0 3px var(--navy-ring);
}

/* ── HIDDEN, ACTUALLY HIDDEN ────────────────────── */
/* Bootstrap ships both `[hidden]{display:none!important}` in its reboot and
   `.d-flex{display:flex!important}` in its utilities. Two !important declarations of EQUAL
   specificity — one attribute selector, one class — so source order decides, and the utilities come
   last. The result is that a `.d-flex` element with the hidden attribute set stays on screen.

   It is a quiet fault, because everything about the element says it is hidden: the attribute is
   there, `el.hidden` reads true, and only the computed style gives it away. It put a `Discount
   -€0.00` row on the payment step's Balance card.

   This file also carries `.pdf-glance[hidden]`, `.adman-confirm[hidden]`, `.picker-chip[hidden]`
   and half a dozen more, but those are NOT earlier attempts at this — they are plain
   `display: none`, which `[hidden]`'s own `!important` already beat, so every one of them is dead
   code. Worth knowing before reaching for the same shape again: only an `!important` display
   utility creates this clash, which is why the rule below names the flex utilities and nothing
   else.

   Deliberately only the flex utilities. `d-block`, `d-grid` and the rest lose to `[hidden]` on
   source order in the other direction and need nothing. */
.d-flex[hidden],
.d-inline-flex[hidden] { display: none !important; }

/* ── REQUIRED FIELDS & VALIDATION ──────────────── */

/* The asterisk beside a label that must be answered. Same mark as .picker-required. */
.required-mark { color: var(--danger); margin-left: 2px; font-weight: 700; }

/* ASP.NET puts .input-validation-error on a control whose value was rejected. Paint the box
   itself, not only the message under it: on a form the length of the Customer step, a red
   border is what makes "there are three things still to do" visible without reading.
   --st-rejected-bg is the existing red tint from the status palette, so this stays on
   the brand and follows the dark theme without a second colour to keep in step. */
.form-control.input-validation-error,
.form-select.input-validation-error {
    border-color: var(--danger);
    background-color: var(--st-rejected-bg);
}

.form-control.input-validation-error:focus,
.form-select.input-validation-error:focus {
    border-color: var(--danger);
    box-shadow: 0 0 0 3px rgba(155, 59, 51, 0.15);
}

/* ASP.NET swaps .field-validation-valid for this class on the message span once there is
   something to say, so the existing `class="text-danger"` spans need no change. */
.field-validation-error {
    display: block;
    margin-top: 3px;
    font-size: 12px;
    color: var(--danger);
}

/* ── EMAIL VERIFY TICK ─────────────────────────── */
/* The button on the right of the Customer step's email box, and the colour it puts on the box.

   Deliberately its OWN classes rather than ASP.NET's .input-validation-error above: that one means
   "the server rejected what you typed" and this one means "we went and asked whether the mailbox
   exists". Both can be true at once, and sharing a class would have them fighting over the border.

   Green is only ever set from a deep check that came back Deliverable — see
   EmailVerificationRules.IsProven. Amber is the honest middle: a catch-all domain, a spent credit
   balance, a timeout. There is no amber border, only an amber tick and a note, because "we could
   not tell" does not warrant marking up the field itself. */
/* The verdict of the email check, carried by the whole control rather than by a paragraph under
   it.

   Drawn as an `outline`, and on the .input-group where there is one, for three reasons:

     - The primary box shares an .input-group with the tick. Colouring the input's own border left
       the tick outside the mark, wearing a second border of its own, with a notch between them
       where the two rounded corners met in the middle of one control.
     - An outline is painted outside the box and takes no part in layout, so nothing shifts by the
       2px and no ring gets sheared off by the group's clipping.
     - Border colour on a .form-control is set by a dozen rules in this file and by Bootstrap's
       validation states, several of which apply only while the box is not focused. Outline is not
       in that fight, which is what makes the mark survive a blur. */
.input-group:has(> .email-checked-ok),
.input-group:has(> .email-checked-bad),
.input-group:has(> .email-checked-unknown),
.form-control.email-checked-ok,
.form-control.email-checked-bad,
.form-control.email-checked-unknown,
.form-stack .input-group:has(> .input-validation-error),
.form-stack .form-control.input-validation-error {
    border-radius: var(--radius-sm);
    outline-offset: 0;
}

/* The group wins where there is one: the inner input must not draw a second mark inside it. */
.input-group:has(> .email-checked-ok) > .form-control,
.input-group:has(> .email-checked-bad) > .form-control,
.input-group:has(> .email-checked-unknown) > .form-control,
.form-stack .input-group:has(> .input-validation-error) > .form-control {
    outline: 0;
}

/* A malformed address — jQuery validation's own verdict, not the server check's.

   Same mark as a failed check, for the same reason: it is the one control in an .input-group, so
   the app-wide 1px border-colour rule for .input-validation-error left the tick outside the mark
   and notched between the two. Scoped to .form-stack so every other form in the app keeps the
   border treatment it has today.

   The red tint that rule also applies is kept — it is doing no harm behind an outline, and it is
   what makes a refused field visible at a glance while scrolling a long step. */
.form-stack .input-group:has(> .input-validation-error),
.form-stack .form-control.input-validation-error {
    outline: 2px solid var(--danger);
    box-shadow: 0 0 0 3px rgba(155, 59, 51, 0.12);
}

.form-stack .input-group:has(> .input-validation-error:focus),
.form-stack .form-control.input-validation-error:focus {
    box-shadow: 0 0 0 3px rgba(155, 59, 51, 0.26);
}

/* The inner border stops competing with the outline around the group, but the tint stays. */
.form-stack .input-group:has(> .input-validation-error) > .input-validation-error {
    border-color: var(--border-strong);
}

.form-stack .input-group:has(> .input-validation-error) .email-verify {
    border-color: var(--border-strong);
}

.input-group:has(> .email-checked-ok),
.form-control.email-checked-ok {
    outline: 2px solid var(--success);
    box-shadow: 0 0 0 3px rgba(47, 99, 85, 0.12);
}

.input-group:has(> .email-checked-bad),
.form-control.email-checked-bad {
    outline: 2px solid var(--danger);
    box-shadow: 0 0 0 3px rgba(155, 59, 51, 0.12);
}

/* Asked, and nobody could tell. The same ring as the phone check's equivalent and for the same
   reason — its own colour rather than green or red says what the note says: an answer, but not a
   verdict. --st-onhold-fg is #5B4E8C; the ring is that colour at low opacity.

   It has to be a mark on the BOX. This state had only the question-mark tick to itself for one
   commit, on the grounds that it should stay quiet, and quiet turned out to mean invisible: a
   secondary address wearing one was still read as an unchecked box. An unchecked box is the one
   thing it must never be mistaken for. */
.input-group:has(> .email-checked-unknown),
.form-control.email-checked-unknown {
    outline: 2px solid var(--st-onhold-fg);
    box-shadow: 0 0 0 3px rgba(91, 78, 140, 0.12);
}

/* Focus deepens the ring rather than replacing the mark, so the verdict never disappears just
   because the rep clicked into the box to correct it. */
.input-group:has(> .email-checked-ok:focus),
.form-control.email-checked-ok:focus {
    box-shadow: 0 0 0 3px rgba(47, 99, 85, 0.26);
}

.input-group:has(> .email-checked-bad:focus),
.form-control.email-checked-bad:focus {
    box-shadow: 0 0 0 3px rgba(155, 59, 51, 0.26);
}

.input-group:has(> .email-checked-unknown:focus),
.form-control.email-checked-unknown:focus {
    box-shadow: 0 0 0 3px rgba(91, 78, 140, 0.26);
}

/* The tick keeps its own colour as an icon, but not a border of its own — the outline around the
   group is the mark now, and two nested marks on one control was the glitchy part. */
.input-group:has(> .email-checked-ok) .email-verify,
.input-group:has(> .email-checked-bad) .email-verify,
.input-group:has(> .email-checked-unknown) .email-verify {
    border-color: var(--border-strong);
}

/* The explanation behind the colour. Hidden until the rep asks for it by hovering or focusing the
   box — the colour is the verdict, this is only the reason.

   An overlay, so appearing and disappearing costs the page no height. In the flow it pushed
   everything below it down by two lines and pulled it back up again on every pass of the mouse,
   which on a form this tall moved the fields the rep was reaching for.

   No top/left of its own beyond the horizontal stretch: an absolutely positioned box with `top:
   auto` sits at its static position, which is exactly where it belongs — directly under the
   control it explains. The containing block is the column, set relative below.

   Opaque, bordered and shadowed because it now covers real content rather than sitting in a gap
   of its own. */
.email-domain-warning {
    display: none;
    position: absolute;
    /* Anchored to its own .input-group, which customer-step.js now makes its parent. Bootstrap
       already gives an .input-group position: relative, so this is the control's own box: top:
       100% is its bottom edge and left/right: 0 is its exact width.

       This replaced insets of calc(var(--bs-gutter-x) * .5), which existed only to undo the
       padding of the Bootstrap column the note used to be measured against. Anchoring to the
       control makes the fudge unnecessary rather than better. */
    top: 100%;
    left: 0;
    right: 0;
    z-index: 30;
    /* The pointer passes straight through it, which does two jobs. It cannot hold itself open by
       being hovered — the reason it felt persistent — and it cannot swallow a click meant for
       whatever it is floating over, which on a stack of extra rows is the next address box. A
       transient hint that eats clicks is worse than no hint. */
    pointer-events: none;
    margin-top: .3rem;
    padding: 7px 10px;
    border-radius: var(--radius-sm);
    background: var(--surface);
    border: 1px solid var(--border-strong);
    box-shadow: var(--shadow-md);
    font-size: 12.5px;
    line-height: 1.45;
    color: var(--text-muted);
}

/* A safety net, not the ordinary route.
   Ordinarily the note's parent is its own .input-group, which Bootstrap already makes relative —
   so this changes nothing there. It matters only on the fallback path in customer-step.js, where
   a box has no .input-group at all and the note goes after the input itself; without a positioned
   parent the overlay would escape to the nearest one and land somewhere unrelated. */
div:has(> .email-domain-warning) {
    position: relative;
}

/* Undo what Bootstrap does to any child of an .input-group.
   Its rule is
     .input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip)
                 :not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback)
   which squares the left corners and pulls the child a border-width to the left, so the segments
   of a control join into one shape. Correct for the boxes and buttons in the row; wrong for this,
   which is not a segment of the control but a panel hanging under it — it came out with two
   rounded corners and two square ones, sitting 1px left of the field.

   !important rather than out-specifying it: that selector carries seven class-level components by
   design, so anything that beat it on specificity would be a contrivance nobody could read. The
   two declarations here are exactly the two it forces, and nothing else about the note is
   Bootstrap's to decide.

   Making the note the FIRST child would have dodged the selector instead, and is the trap to
   avoid: :not(:first-child) would then match the real input and square ITS left corners. */
.input-group > .email-domain-warning {
    margin-left: 0 !important;
    border-radius: var(--radius-sm) !important;
}

.email-domain-warning.is-bad     { color: var(--danger); }
.email-domain-warning.is-ok      { color: var(--success); }
.email-domain-warning.is-unknown { color: var(--st-onhold-fg); }

/* Shown only while the pointer is on the EMAIL BOX itself, and not a moment longer.

   Three things were keeping it up when it should have gone:

     .input-group:hover — the whole group, so resting on the tick or the × held the note open even
     though neither of them is what it is about. It is :has(> input:hover) now: the box, nothing
     else in the row.

     .has-message:hover — the note kept ITSELF open once the pointer reached it, which is what
     made it feel persistent. Gone, and pointer-events: none below means it cannot come back by
     any route.

     :focus-within — same problem as the first, one step worse: focus stays somewhere until it is
     moved, so tabbing to the tick pinned the note open. Narrowed to the box's own focus, which is
     kept deliberately: a keyboard user has no hover, and dropping it would leave them no way to
     read the message at all. */
.input-group:has(> input.form-control:hover) .email-domain-warning.has-message,
.input-group:has(> input.form-control:focus) .email-domain-warning.has-message,
input:hover + .email-domain-warning.has-message,
input:focus + .email-domain-warning.has-message {
    display: block;
}

/* One message, not two. jQuery validation's own span sits after the note in this column, and for
   a malformed address both had something to say about the same characters — "The Email field is
   not a valid e-mail address." above, and the floating note's plainer version of it.

   Conditional on .has-message rather than hidden outright: with nothing in the note, the span is
   the only thing that can explain a refused post, and a server-side refusal arrives before the
   rep has touched the box for the note to have been filled. */
/* Matched through the group rather than as a sibling of the note, since the note moved inside it.
   Holds for both shapes: on the primary field the validation span follows the .input-group, and on
   an extra-contact row it follows the row — which IS the .input-group. Get this wrong and jQuery's
   "not a valid e-mail address" returns as a second message beside the floating one. */
.input-group:has(.email-domain-warning.has-message) + .field-validation-error {
    display: none;
}

.email-verify.is-ok      { color: var(--success); border-color: var(--success); }
.email-verify.is-bad     { color: var(--danger);  border-color: var(--danger); }
.email-verify.is-unknown { color: var(--st-onhold-fg); }

/* Spun while the request is in flight. The button is disabled at the same time, so this is the only
   thing telling the rep that pressing it did something — a check can take a second or two. */
.email-verify.is-checking i {
    display: inline-block;
    animation: email-verify-spin 0.9s linear infinite;
}

@keyframes email-verify-spin {
    to { transform: rotate(360deg); }
}

/* ── The customer VAT number check ─────────────────────────────────────────────
   The pricing step's counterpart to the email check above, and styled as its sibling on purpose:
   the two ask the same shape of question and a rep should not have to learn a second vocabulary of
   colours.

   Generic .field-check-* names rather than .vat-*, because the VAT box is not the only user: the
   standalone checkers under Company resources wear the same treatment, and so would any future
   "go and ask somebody whether this is real" field.

   THE SAME LOOK AS THE EMAIL CHECK, WRITTEN OUT A SECOND TIME. The email version of these rules
   lives on the customer-step-sections branch, which is 31 commits of unrelated Customer-step work
   and is not merged — so depending on its class names would mean this could not ship without it.
   The values here are its values. When that branch lands there will be two rule sets saying the
   same thing, and converging them on one class is a tidy-up for whoever merges second.

   Drawn as an `outline`, and on the .input-group where there is one, for three reasons:

     - The box shares an .input-group with the check button. Colouring the input's own border left
       the button outside the mark, wearing a second border of its own, with a notch between them
       where the two rounded corners met in the middle of one control.
     - An outline is painted outside the box and takes no part in layout, so nothing shifts by the
       2px and no ring gets sheared off by the group's clipping.
     - Border colour on a .form-control is set by a dozen rules in this file and by Bootstrap's
       validation states, several of which apply only while the box is not focused. Outline is not
       in that fight, which is what makes the mark survive a blur.

   Green is only ever set from a service answering Valid. Red is a definite Invalid — a member
   state's "not registered", or a string that could not be a number at all. Amber is the honest
   middle: the service could not be reached, so the value is neither confirmed nor refused.

   Amber IS outlined, in --st-onhold-fg, the colour its own note is written in. It was left
   unpainted at first, on the reasoning that "we could not tell" does not warrant marking up the
   field — but unpainted is exactly what an unchecked box looks like, so a rep could not tell a
   number nobody had asked about from one the service had failed to answer for without hovering.
   Its own colour rather than green or red says the same thing the note does: an answer, but not a
   verdict. */
.input-group:has(> .field-check-ok),
.input-group:has(> .field-check-bad),
.input-group:has(> .field-check-unknown),
.form-control.field-check-ok,
.form-control.field-check-bad,
.form-control.field-check-unknown {
    border-radius: var(--radius-sm);
    outline-offset: 0;
}

/* The group wins where there is one: the inner input must not draw a second mark inside it. */
.input-group:has(> .field-check-ok) > .form-control,
.input-group:has(> .field-check-bad) > .form-control,
.input-group:has(> .field-check-unknown) > .form-control {
    outline: 0;
}

.input-group:has(> .field-check-ok),
.form-control.field-check-ok {
    outline: 2px solid var(--success);
    box-shadow: 0 0 0 3px rgba(47, 99, 85, 0.12);
}

.input-group:has(> .field-check-bad),
.form-control.field-check-bad {
    outline: 2px solid var(--danger);
    box-shadow: 0 0 0 3px rgba(155, 59, 51, 0.12);
}

/* --st-onhold-fg is #5B4E8C; the ring is the same colour at low opacity. */
.input-group:has(> .field-check-unknown),
.form-control.field-check-unknown {
    outline: 2px solid var(--st-onhold-fg);
    box-shadow: 0 0 0 3px rgba(91, 78, 140, 0.12);
}

/* Focus deepens the ring rather than replacing the mark, so the verdict never disappears just
   because the rep clicked into the box to correct it. */
.input-group:has(> .field-check-ok:focus),
.form-control.field-check-ok:focus {
    box-shadow: 0 0 0 3px rgba(47, 99, 85, 0.26);
}

.input-group:has(> .field-check-bad:focus),
.form-control.field-check-bad:focus {
    box-shadow: 0 0 0 3px rgba(155, 59, 51, 0.26);
}

.input-group:has(> .field-check-unknown:focus),
.form-control.field-check-unknown:focus {
    box-shadow: 0 0 0 3px rgba(91, 78, 140, 0.26);
}

/* The button keeps its own colour as an icon, but not a border of its own — the outline around the
   group is the mark now, and two nested marks on one control was the glitchy part. */
.input-group:has(> .field-check-ok) .field-check,
.input-group:has(> .field-check-bad) .field-check,
.input-group:has(> .field-check-unknown) .field-check {
    border-color: var(--border-strong);
}

.field-check.is-ok      { color: var(--success); }
.field-check.is-bad     { color: var(--danger); }
.field-check.is-unknown { color: var(--st-onhold-fg); }

/* Spun while the request is in flight. The button is disabled at the same time, so this is the only
   thing telling the rep that pressing it did something — VIES relays to a national register and can
   take a second or two. Shares the email check's keyframes rather than declaring its own. */
.field-check.is-checking i {
    display: inline-block;
    animation: email-verify-spin 0.9s linear infinite;
}

/* The sentence under the box. Coloured from the verdict, and it carries the trading name VIES
   returned — the only part of the answer a person can actually check against the company they are
   talking to.

   ALWAYS SHOWN, unlike the email check's note, which is an overlay revealed on hover. That name is
   the whole reason for asking: it is what catches a transposed digit that happens to be somebody
   else's valid registration, which no check of the number's shape ever could. A rep will not hover
   a field to find out who they are zero-rating a sale to. */
.field-check-note            { font-size: 12px; display: block; margin-top: 4px; }
.field-check-note.is-ok      { color: var(--success); }
.field-check-note.is-bad     { color: var(--danger); }
.field-check-note.is-unknown { color: var(--st-onhold-fg); }

/* ── The phone check's reason, on hover ────────────────────────────────────────
   The colour on the box is the verdict; this is only the reason behind it, so it stays out of the
   way until the rep asks for it by hovering or focusing the box.

   NOT a sentence under the field, which is what .field-check-note above is. On the Customer step
   the phone box has extra-number rows stacked directly beneath it, and a note that appears and
   disappears in the flow pushes every one of them down two lines and pulls them back on each pass
   of the mouse — moving the fields the rep is reaching for. The standalone checker under Company
   resources has nothing below its box and keeps the plain sentence.

   This is the same treatment .email-domain-warning wears on the customer-step-sections branch,
   which is where the email tick already works this way. That branch is 31 commits of unrelated
   Customer-step work and is not merged, so depending on its class would mean this could not ship
   without it — the values here are its values. When it lands there will be two rule sets saying
   the same thing, and converging them on one class is a tidy-up for whoever merges second. */
.phone-check-note {
    display: none;
    position: absolute;
    /* Anchored to its own .input-group, which phone-check.js makes its parent. Bootstrap already
       gives an .input-group position: relative, so top: 100% is the control's bottom edge and
       left/right: 0 is its exact width. */
    top: 100%;
    left: 0;
    right: 0;
    z-index: 30;
    /* The pointer passes straight through, so the note cannot hold itself open by being hovered,
       and cannot swallow a click meant for the extra-number row it is floating over. */
    pointer-events: none;
    margin-top: .3rem;
    padding: 7px 10px;
    border-radius: var(--radius-sm);
    background: var(--surface);
    border: 1px solid var(--border-strong);
    box-shadow: var(--shadow-md);
    font-size: 12.5px;
    line-height: 1.45;
    color: var(--text-muted);
}

/* A safety net, not the ordinary route: only for the fallback in phone-check.js where a box has no
   .input-group at all. Without a positioned parent the overlay would escape to the nearest one and
   land somewhere unrelated. */
div:has(> .phone-check-note) {
    position: relative;
}

/* Undo what Bootstrap does to any child of an .input-group. Its
   `.input-group>:not(:first-child):not(.dropdown-menu)…` rule squares the left corners and pulls
   the child a border-width left, so the segments of a control join into one shape. Right for the
   boxes and buttons in the row; wrong for this, which is a panel hanging under the control rather
   than a segment of it.

   !important rather than out-specifying it: that selector carries seven class-level components by
   design. Making the note the FIRST child would dodge it instead, and is the trap to avoid —
   :not(:first-child) would then match the real input and square ITS left corners. */
.input-group > .phone-check-note {
    margin-left: 0 !important;
    border-radius: var(--radius-sm) !important;
}

.phone-check-note.is-bad     { color: var(--danger); }
.phone-check-note.is-ok      { color: var(--success); }
.phone-check-note.is-unknown { color: var(--st-onhold-fg); }

/* Shown only while the pointer is on the PHONE BOX itself, and not a moment longer — not the whole
   group, or resting on the tick, the dialling code or the × would hold it open when none of them
   is what it is about. Focus is kept alongside hover deliberately: a keyboard user has no hover,
   and dropping it would leave them no way to read the message at all. */
.input-group:has(> input.form-control:hover) .phone-check-note.has-message,
.input-group:has(> input.form-control:focus) .phone-check-note.has-message,
input:hover + .phone-check-note.has-message,
input:focus + .phone-check-note.has-message {
    display: block;
}


/* Respect a reader who has asked for less movement: the disabled button still says "working". */
@media (prefers-reduced-motion: reduce) {
    .email-verify.is-checking i,
    .field-check.is-checking i { animation: none; }
}

/* ── AUTH PAGES ────────────────────────────────── */
.auth-page {
    min-height: 100vh;
    background: var(--surface-page);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 32px 16px;
}

.auth-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    width: 100%;
    max-width: 420px;
    box-shadow: var(--shadow-md);
    overflow: hidden;
}

.auth-header {
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    padding: 28px 36px 24px;
    text-align: center;
}

/* Legacy fallback — replaced by .auth-logo-img */
.auth-logo {
    width: 56px;
    height: 56px;
    background: var(--navy-tint);
    border-radius: var(--radius-lg);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 22px;
    color: var(--navy);
    margin-bottom: 14px;
}

/* The wide banner stands on its own — no chip, no border. */
.auth-logo-img {
    width: 180px;
    height: auto;
    display: block;
    margin: 0 auto 14px;
}

.auth-brand-name {
    display: block;
    font-weight: 700;
    font-size: 15px;
    color: var(--text);
    letter-spacing: 0.6px;
    text-transform: uppercase;
}

.auth-brand-sub {
    display: block;
    font-size: 24px;
    font-weight: 900;
    color: var(--text);
    letter-spacing: -0.5px;
    margin-top: 2px;
}

.auth-body { padding: 32px 36px 36px; }

.auth-title {
    font-size: 19px;
    font-weight: 600;
    color: var(--text);
    margin-bottom: 4px;
    letter-spacing: -0.2px;
}

.auth-subtitle {
    font-size: 13px;
    color: var(--text-muted);
    margin-bottom: 24px;
}

.auth-divider {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 20px 0;
    color: var(--text-subtle);
    font-size: 12px;
}

.auth-divider::before, .auth-divider::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--border);
}

/* ── ALERTS ────────────────────────────────────── */
.alert {
    border-radius: var(--radius);
    padding: 12px 16px;
    font-size: 13px;
    border: 1px solid transparent;
}

.alert-danger  { background: var(--st-rejected-bg);  border-color: var(--alert-danger-border);  color: var(--st-rejected-fg); }
.alert-success { background: var(--st-scheduled-bg); border-color: var(--alert-success-border); color: var(--st-scheduled-fg); }
.alert-info    { background: var(--st-accepted-bg);  border-color: var(--alert-info-border);    color: var(--st-accepted-fg); }
.alert-warning { background: var(--st-pending-bg);   border-color: var(--alert-warning-border); color: var(--st-pending-fg); }

/* ── TOASTS ────────────────────────────────────── */
/* Driven by wwwroot/js/toast.js. The stack is fixed top-right and ignores
   pointer events so it can never block the page behind it; each toast turns
   them back on for its own hover-to-hold and close button. */
.adman-toast-stack {
    position: fixed;
    top: 16px;
    right: 16px;
    z-index: 1080;              /* above Bootstrap modals' backdrop (1050) */
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-width: min(380px, calc(100vw - 32px));
    pointer-events: none;
}

.adman-toast {
    pointer-events: auto;
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 12px 14px;
    font-size: 13px;
    line-height: 1.45;
    background: var(--surface);
    border: 2px solid var(--border);   /* variant classes below colour the whole outline */
    border-radius: var(--radius);
    box-shadow: var(--shadow-md);
    color: var(--text);
    /* Enter and exit both run through this: slide in from the right edge with a
       slight settle, fade and shrink back out. */
    opacity: 0;
    transform: translateX(24px) scale(0.97);
    transition: opacity 0.26s ease, transform 0.26s cubic-bezier(0.22, 1, 0.36, 1);
}

.adman-toast.is-shown {
    opacity: 1;
    transform: translateX(0) scale(1);
}

.adman-toast > .bi         { font-size: 15px; line-height: 1.3; }
.adman-toast-text          { flex: 1 1 auto; }
.adman-toast-success       { border-color: var(--st-scheduled-solid); }
.adman-toast-success > .bi { color: var(--success); }
/* Red outline for both a failure and a deliberate cancellation — only the icon differs. */
.adman-toast-error,
.adman-toast-cancelled         { border-color: var(--st-rejected-solid); }
.adman-toast-error > .bi,
.adman-toast-cancelled > .bi   { color: var(--danger); }

.adman-toast-close {
    flex: 0 0 auto;
    padding: 0;
    border: 0;
    background: none;
    line-height: 1;
    font-size: 15px;
    color: var(--text-subtle);
    transition: color 0.15s;
}

.adman-toast-close:hover { color: var(--text); }

/* The optional button on a toast — "Undo", for one. */
.adman-toast-action {
    flex: 0 0 auto;
    padding: 0 2px;
    border: 0;
    background: none;
    font-weight: 700;
    font-size: 13px;
    color: var(--navy);
    text-decoration: underline;
    text-underline-offset: 2px;
    cursor: pointer;
}

.adman-toast-action:hover { color: var(--text); }

@media (prefers-reduced-motion: reduce) {
    .adman-toast { transition: opacity 0.01s linear; transform: none; }
    .adman-toast.is-shown { transform: none; }
}

/* ── UTILITIES ─────────────────────────────────── */
.text-navy   { color: var(--navy) !important; }
.text-accent { color: var(--navy) !important; }
.text-muted-soft { color: var(--text-muted) !important; }
.fw-black    { font-weight: 700 !important; }

.section-header {
    margin-bottom: 20px;
}

.section-header h1, .section-header h2 {
    font-size: 21px;
    font-weight: 700;
    color: var(--text);
    letter-spacing: -0.4px;
    margin: 0 0 4px;
}

.section-header p {
    font-size: 13.5px;
    color: var(--text-muted);
    margin: 0;
}

/* .page-header / .page-title are used by a few views (Users, UserProducts).
   They were never styled, so h1 fell back to the browser default. */
.page-header { margin-bottom: 20px; }

.page-title {
    font-size: 21px;
    font-weight: 700;
    color: var(--text);
    letter-spacing: -0.4px;
    margin: 0 0 4px;
}

.page-header p {
    font-size: 13.5px;
    color: var(--text-muted);
    margin: 0;
}

/* ── SETTINGS ──────────────────────────────────── */
.setting-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
    padding: 18px 20px;
    border-bottom: 1px solid var(--border);
}

.setting-row:last-child { border-bottom: none; }

.setting-label {
    font-size: 13.5px;
    font-weight: 600;
    color: var(--text);
    margin-bottom: 2px;
}

.setting-help {
    font-size: 12.5px;
    color: var(--text-muted);
    margin: 0;
}

/* Segmented Light/Dark control */
.theme-switch {
    display: inline-flex;
    padding: 3px;
    gap: 3px;
    background: var(--surface-muted);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    flex-shrink: 0;
}

.theme-switch button {
    all: unset;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 7px 15px;
    border-radius: var(--radius-sm);
    font-family: 'Montserrat', sans-serif;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-muted);
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
}

.theme-switch button:hover { color: var(--text); }

.theme-switch button[aria-pressed="true"] {
    background: var(--surface);
    color: var(--navy);
    box-shadow: var(--shadow-sm);
}

.theme-switch button:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px var(--navy-ring);
}

/* ── HAMBURGER (mobile) ────────────────────────── */
.sidebar-toggle {
    display: none;
    background: none;
    border: none;
    cursor: pointer;
    padding: 4px;
    color: var(--text-muted);
    font-size: 20px;
    margin-right: 8px;
}

/* ── RESPONSIVE ────────────────────────────────── */
@media (max-width: 991px) {
    .sidebar {
        transform: translateX(-100%);
        /* The sidebar is now the same colour as the page, so it needs a
           shadow to read as an overlay when it slides in. */
        box-shadow: 0 0 40px rgba(28,26,46,0.16);
    }

    .sidebar.open {
        transform: translateX(0);
    }

    .main-content {
        margin-left: 0;
    }

    .sidebar-toggle {
        display: block;
    }

    .topbar-bare { display: flex; }

    .sidebar-overlay {
        display: none;
        position: fixed;
        inset: 0;
        background: rgba(0,0,0,0.35);
        z-index: 999;
    }

    .sidebar-overlay.show { display: block; }

    .page-body { padding: 20px 16px; }
}

@media (min-width: 768px) {
    html { font-size: 14px; }
}

/* ── PRODUCT TILE PICKER (Quote/Order step 1) ─── */
.product-tile-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 20px;
}

@media (max-width: 700px) {
    .product-tile-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 460px) {
    .product-tile-grid {
        grid-template-columns: 1fr;
    }
}

.product-tile {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: 18px;
    cursor: pointer;
    background: var(--surface);
    transition: border-color .15s ease, box-shadow .15s ease;
}

.product-tile:hover {
    border-color: var(--border-strong);
}

.product-tile.selected {
    border-color: var(--navy);
    box-shadow: 0 0 0 3px var(--navy-ring);
}

/* The range on a document that has gone out: shown rather than offered, so it must not read as
   something to press. The grid holds one tile in this state - see _ProductStepPinned.cshtml. */
.product-tile-fixed {
    cursor: default;
}

.product-tile-fixed:hover {
    border-color: var(--navy);
}

.product-tile-input {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0,0,0,0);
    white-space: nowrap;
    border: 0;
}

.product-tile img,
.product-tile-placeholder {
    width: 100%;
    aspect-ratio: 1 / 1;
    border-radius: var(--radius);
    object-fit: cover;
    margin-bottom: 8px;
    background: var(--surface-muted);
}

.product-tile-placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-subtle);
    font-size: 48px;
}

.product-tile .product-tile-check {
    position: absolute;
    top: 8px;
    right: 8px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: var(--navy);
    color: #fff;
    display: none;
    align-items: center;
    justify-content: center;
    font-size: 13px;
}

.product-tile.selected .product-tile-check {
    display: flex;
}

.product-tile-name {
    font-weight: 600;
    color: var(--text);
    font-size: 15px;
    line-height: 1.3;
}

/* ── DOCUMENTS (admin Documents page) ───────────────
   One collapsible section per document. The summary row carries the whole state of a
   document — its filename or that it is missing — so the sections can stay shut. */
.doc-section-head {
    display: flex;
    align-items: center;
    gap: 12px;
    cursor: pointer;
    list-style: none;        /* a flex summary drops the native marker anyway — the chevron
                                below replaces it, so the two cannot both appear */
}

.doc-section-head::-webkit-details-marker { display: none; }

.doc-section-chevron {
    flex-shrink: 0;
    font-size: 12px;
    color: var(--text-subtle);
    transition: transform 0.15s;
}

details[open] > .doc-section-head .doc-section-chevron { transform: rotate(90deg); }

/* Pushes the state and the info button to the right of the name, and lets the name be the
   part that gives way when the row runs out of room. */
.doc-section-head .card-title {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.doc-section-meta {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}

.doc-section-state {
    max-width: 240px;
    overflow: hidden;
    text-overflow: ellipsis;
}

.doc-file {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 2px;
}

/* File names are long and must not widen the row. */
.doc-file-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.doc-replace {
    margin-top: 16px;
    padding-top: 16px;
    border-top: 1px solid var(--border);
}

/* On a phone the filename tag is what goes: the name and the info button matter more. */
@media (max-width: 575px) {
    .doc-section-meta .doc-section-state { display: none; }
}

/* ── TAG ───────────────────────────────────────────
   Small neutral chip for list metadata (roles, product access). Reads through the
   theme tokens, unlike Bootstrap's .badge, which hard-codes its own colours. */
.tag {
    display: inline-block;
    padding: 2px 9px;
    border-radius: var(--radius-sm);
    background: var(--surface-muted);
    color: var(--text-muted);
    font-size: 11.5px;
    font-weight: 600;
    white-space: nowrap;
}

.tag-navy    { background: var(--navy-tint); color: var(--navy); }
.tag-success { background: var(--st-delivered-bg); color: var(--st-delivered-fg); }
.tag-warning { background: var(--st-pending-bg); color: var(--st-pending-fg); }
.tag-info    { background: var(--st-onhold-bg);  color: var(--st-onhold-fg); }
/* "Overdue" on a work item. Only drawn on the sales rep dashboard, where the sections group by
   what a thing is rather than by how urgent it is — so without this a row's urgency is invisible.
   The office dashboard's sections are urgencies, and it does not opt in. */
.tag-danger  { background: color-mix(in srgb, var(--danger) 13%, transparent); color: var(--danger); }

/* ── SETTINGS TABS ─────────────────────────────── */
.settings-tabs {
    display: flex;
    gap: 4px;
    border-bottom: 1px solid var(--border);
    margin-bottom: 20px;
}

.settings-tab {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 10px 16px;
    margin-bottom: -1px;
    border-bottom: 2px solid transparent;
    color: var(--text-muted);
    font-size: 13.5px;
    font-weight: 600;
    text-decoration: none;
    transition: color 0.15s, border-color 0.15s, background 0.15s;
}

.settings-tab:hover {
    color: var(--text);
    background: var(--surface-muted);
}

/* The underline is the only marker of the current tab, so it is drawn on top of the
   strip's own hairline (hence the -1px margin above) and kept solid navy. */
.settings-tab.active,
.settings-tab.active:hover {
    color: var(--navy);
    border-bottom-color: var(--navy);
    background: transparent;
}

/* Six tabs is more than any other strip in the app carries, and the rules above have no responsive
   behaviour at all — so on a phone the last tabs simply run off the side. Wrapping rather than
   scrolling: a horizontally scrolling strip hides tabs with no affordance that there is anything
   past the edge, and "Problems" would be the one to disappear.
   A modifier, not a change to .settings-tabs, which four other screens rely on. */
.settings-tabs-wrap { flex-wrap: wrap; }

/* A quieter strip, for a dashboard rather than a settings page. The tabs here carry a count as
   well as a name, which made them read as a row of buttons rather than as tabs — so less padding,
   slightly smaller text, and no icons. Sean's words were "the buttons are a bit too big looking".
   A modifier, so the settings, resources and retail strips are untouched. */
.settings-tabs-compact .settings-tab {
    gap: 6px;
    padding: 7px 11px;
    font-size: 13px;
}

/* A tab with nothing under it. Still shown and still clickable — a rep learns where things live,
   and the strip does not change shape from one day to the next. Never dimmed while it is the tab
   being read, or the page looks disabled. */
.settings-tab-empty { opacity: 0.55; }
.settings-tab-empty.active { opacity: 1; }

/* ── SETTINGS SUB-TABS ─────────────────────────────
   The second level under a settings tab. Pills rather than a second underlined row, so the
   strip above stays the one that reads as the page's tabs. The negative top margin pulls them
   up under that strip's hairline — they belong to the tab, not to the content below. */
.settings-subtabs {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: -8px 0 20px;
}

.settings-subtab {
    padding: 6px 14px;
    border-radius: 999px;
    color: var(--text-muted);
    font-size: 13px;
    font-weight: 600;
    text-decoration: none;
    transition: color 0.15s, background 0.15s;
}

.settings-subtab:hover {
    color: var(--text);
    background: var(--surface-muted);
}

.settings-subtab.active,
.settings-subtab.active:hover {
    color: var(--navy);
    background: var(--navy-tint);
}

/* ── PDF GLANCE MODAL ──────────────────────────────
   Shows a PDF in place rather than downloading it — see wwwroot/js/pdf-glance.js. */
.pdf-glance {
    position: fixed;
    inset: 0;
    z-index: 1100;          /* above the sidebar (1000) */
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
}

.pdf-glance[hidden] { display: none; }

/* Stops the page behind scrolling while the modal is up. */
body.pdf-glance-open { overflow: hidden; }

.pdf-glance-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(12, 12, 16, 0.62);
    backdrop-filter: blur(2px);
}

.pdf-glance-panel {
    position: relative;
    display: flex;
    flex-direction: column;
    width: min(1100px, 100%);
    height: min(90vh, 100%);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: 0 18px 50px rgba(0, 0, 0, 0.35);
    overflow: hidden;
}

.pdf-glance-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 16px;
    border-bottom: 1px solid var(--border);
    flex-wrap: wrap;
}

.pdf-glance-title {
    display: flex;
    align-items: center;
    gap: 8px;
    font-weight: 600;
    font-size: 14px;
    color: var(--text);
    min-width: 0;
}

.pdf-glance-title span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.pdf-glance-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}

.pdf-glance-body {
    flex: 1;
    min-height: 0;
    background: var(--surface-muted);
}

.pdf-glance-body iframe {
    width: 100%;
    height: 100%;
    border: 0;
    display: block;
}

@media (max-width: 575px) {
    .pdf-glance { padding: 0; }
    .pdf-glance-panel { height: 100%; border-radius: 0; border: 0; }
}

/* ── INFORMATION DIALOG ─────────────────────────────
   A native <dialog> holding guidance written in the view — see wwwroot/js/info-dialog.js.
   Sized and coloured to match the confirm dialog below, but its body is markup, so it can
   carry lists and filename examples that admanConfirm cannot. The element itself is the
   panel and carries no padding, so a click anywhere around it is a backdrop click. */
.adman-dialog {
    width: min(560px, calc(100vw - 32px));
    max-height: min(80vh, calc(100vh - 48px));
    padding: 0;
    background: var(--surface);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: 0 18px 50px rgba(0, 0, 0, 0.35);
    overflow: hidden;
}

.adman-dialog::backdrop {
    background: rgba(12, 12, 16, 0.62);
    backdrop-filter: blur(2px);
}

.adman-dialog-panel {
    display: flex;
    flex-direction: column;
    max-height: inherit;
}

.adman-dialog-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    padding: 16px 18px 12px;
}

.adman-dialog-title {
    display: flex;
    align-items: center;
    gap: 9px;
    margin: 0;
    font-size: 15px;
    font-weight: 600;
    color: var(--text);
    min-width: 0;
}

.adman-dialog-title .bi {
    font-size: 17px;
    color: var(--navy);
    flex-shrink: 0;
}

.adman-dialog-close {
    background: none;
    border: 0;
    padding: 2px 4px;
    line-height: 1;
    font-size: 14px;
    color: var(--text-subtle);
    cursor: pointer;
    flex-shrink: 0;
}

.adman-dialog-close:hover { color: var(--text); }

.adman-dialog-close:focus-visible {
    outline: none;
    border-radius: var(--radius-sm);
    box-shadow: 0 0 0 3px var(--navy-ring);
}

/* Scrolls on its own so the head and foot stay put on a long piece of guidance. */
.adman-dialog-body {
    padding: 0 18px 4px;
    overflow-y: auto;
    font-size: 13.5px;
    line-height: 1.55;
    color: var(--text-muted);
}

.adman-dialog-body p { margin-bottom: 10px; }
.adman-dialog-body p:last-child { margin-bottom: 0; }
.adman-dialog-body ul { margin-bottom: 10px; padding-left: 20px; }
.adman-dialog-body code { font-size: 12.5px; }

.adman-dialog-note {
    margin-top: 12px;
    padding: 10px 12px;
    background: var(--surface-muted);
    border-left: 3px solid var(--navy);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    font-size: 12.5px;
}

.adman-dialog-foot {
    display: flex;
    justify-content: flex-end;
    padding: 18px;
}

/* Wider, for a dialog that previews an email. */
.adman-dialog-wide { width: min(720px, calc(100vw - 32px)); max-height: min(90vh, calc(100vh - 32px)); }

/* The callout's "email the customer the date" check — Views/Callouts/Edit.cshtml. The date is the
   thing being promised, so it is the biggest thing in the dialog. */
.tell-date {
    padding: 12px 14px;
    margin-bottom: 12px;
    background: var(--surface-muted);
    border-left: 3px solid var(--navy);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.tell-date-label { font-size: 12px; color: var(--text-muted); }
.tell-date-value { font-size: 18px; font-weight: 700; color: var(--text); }
.tell-date-note  { font-size: 12.5px; color: var(--danger); margin-top: 2px; }

.tell-meta {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 4px 12px;
    margin-bottom: 12px;
    font-size: 13px;
}
.tell-meta dt { font-weight: 600; color: var(--text-muted); }
.tell-meta dd { margin: 0; color: var(--text); overflow-wrap: anywhere; }

/* White whatever the theme: it is the email as the customer's mail client will show it. */
.tell-preview {
    display: block;
    width: 100%;
    height: 380px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: #fff;
}

/* ── CONFIRM DIALOG ─────────────────────────────────
   Replaces the native confirm() box — see wwwroot/js/confirm.js. Markup is built by that
   script, so there is no partial to render; these rules are the whole of its appearance. */
.adman-confirm {
    position: fixed;
    inset: 0;
    z-index: 1090;          /* above the sidebar (1000), below PDF Glance (1100);
                               toasts (1080) still read over it */
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
}

.adman-confirm[hidden] { display: none; }

body.adman-confirm-open { overflow: hidden; }

.adman-confirm-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(12, 12, 16, 0.62);
    backdrop-filter: blur(2px);
}

.adman-confirm-panel {
    position: relative;
    display: flex;
    flex-direction: column;
    width: min(460px, 100%);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: 0 18px 50px rgba(0, 0, 0, 0.35);
    overflow: hidden;
}

.adman-confirm-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    padding: 16px 18px 12px;
}

.adman-confirm-title {
    display: flex;
    align-items: center;
    gap: 9px;
    margin: 0;
    font-size: 15px;
    font-weight: 600;
    color: var(--text);
    min-width: 0;
}

.adman-confirm-title .bi {
    font-size: 17px;
    color: var(--navy);
    flex-shrink: 0;
}

.is-danger .adman-confirm-title .bi { color: var(--danger); }

.adman-confirm-close {
    background: none;
    border: 0;
    padding: 2px 4px;
    line-height: 1;
    font-size: 14px;
    color: var(--text-subtle);
    cursor: pointer;
    flex-shrink: 0;
}

.adman-confirm-close:hover { color: var(--text); }

.adman-confirm-close:focus-visible {
    outline: none;
    border-radius: var(--radius-sm);
    box-shadow: 0 0 0 3px var(--navy-ring);
}

.adman-confirm-body { padding: 0 18px 4px; }

.adman-confirm-message {
    margin: 0;
    font-size: 13.5px;
    line-height: 1.55;
    color: var(--text-muted);
}

/* The thing being acted on — quote number, customer email, filename. Given its own
   strip so it is the part of the dialog the eye lands on. */
.adman-confirm-emphasis {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-top: 14px;
    padding: 10px 12px;
    background: var(--surface-muted);
    border-left: 3px solid var(--navy);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

.is-danger .adman-confirm-emphasis { border-left-color: var(--danger); }

/* The optional prompt field — see the prompt options in confirm.js. Margin only: the input and its
   label are the ordinary form controls, so they follow the theme without a second set of rules. */
.adman-confirm-prompt { margin-top: 14px; }
.adman-confirm-prompt .form-control { max-width: 320px; }

.adman-confirm-emphasis-text {
    font-size: 13.5px;
    font-weight: 600;
    color: var(--text);
    word-break: break-word;
}

/* Line one of the strip. The strip itself stacks, so what belongs beside the number — the
   customer's name against an order number — needs a row of its own. It wraps rather than
   squeezing, because a long name on a narrow dialog should drop to the next line whole. */
.adman-confirm-emphasis-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 8px;
}

/* Set down from the number it sits beside: the number is what is being deleted, the name is
   what tells you it is the right one. */
.adman-confirm-emphasis-detail {
    font-size: 13.5px;
    color: var(--text-muted);
    word-break: break-word;
}

/* The separator is drawn rather than written, so it goes with the element on a dialog that has
   no detail to show — and never ends up selected or read out on its own. */
.adman-confirm-emphasis-detail::before {
    content: "·";
    margin-right: 8px;
    color: var(--text-subtle);
}

.adman-confirm-note {
    font-size: 12px;
    color: var(--text-muted);
}

/* The headed lists a script caller may pass — see lists in confirm.js. Capped in height, because
   a quote can carry a dozen items and the buttons must stay on screen. */
.adman-confirm-lists {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 12px;
    max-height: 45vh;
    overflow-y: auto;
}

.adman-confirm-list-heading {
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}

.adman-confirm-list ul {
    margin: 4px 0 0;
    padding-left: 18px;
    font-size: 13px;
    line-height: 1.5;
    color: var(--text);
}

.adman-confirm-list.is-danger .adman-confirm-list-heading,
.adman-confirm-list.is-danger ul { color: var(--danger); }

.is-danger .adman-confirm-note {
    color: var(--danger);
    font-weight: 600;
}

.adman-confirm-foot {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding: 18px;
}

/* .btn-adman-danger is deliberately quiet on a page, where a delete sits beside other
   buttons. Inside the dialog the choice has already been made, so the confirm button is
   solid — it should read as the consequence it is. */
.adman-confirm-foot .btn-adman-danger {
    background: var(--danger);
    border-color: var(--danger);
    color: #fff;
}

.adman-confirm-foot .btn-adman-danger:hover { filter: brightness(0.92); }

/* Dark's --danger is a light salmon so it reads on a dark ground; white text on it
   does not. The label goes dark instead, as it would on any pale fill. */
:root[data-theme="dark"] .adman-confirm-foot .btn-adman-danger { color: #1A1A1A; }

/* The solid form on a page, for the one place asked for it: Deactivate account on Settings →
   Users → Edit (Sean, 24 Sep 2026). It sits on its own in the account panel rather than in a row of
   other buttons, which is the case .btn-adman-danger is kept quiet for, and it opens a confirmation
   before anything happens. Same fill, hover and dark-mode label as the dialog's confirm button. */
.btn-adman-danger.btn-adman-danger-solid {
    background: var(--danger);
    border-color: var(--danger);
    color: #fff;
}

.btn-adman-danger.btn-adman-danger-solid:hover { filter: brightness(0.92); }

:root[data-theme="dark"] .btn-adman-danger.btn-adman-danger-solid { color: #1A1A1A; }

/* Held while a data-confirm-challenge is unanswered. Greyed rather than left red, and set
   after the danger rules above so it wins on source order: a full-strength Delete button that
   does nothing when clicked reads as the app being broken, not as the dialog waiting on you.
   Scoped to the dialog's own footer, so no danger button anywhere else changes. */
.adman-confirm-foot .btn-adman:disabled,
.adman-confirm-foot .btn-adman:disabled:hover,
:root[data-theme="dark"] .adman-confirm-foot .btn-adman:disabled {
    background: var(--surface-muted);
    border-color: var(--border-strong);
    color: var(--text-subtle);
    filter: none;
    cursor: not-allowed;
}

@media (max-width: 575px) {
    .adman-confirm { padding: 16px; }
    .adman-confirm-foot { flex-direction: column-reverse; }
    .adman-confirm-foot .btn-adman { justify-content: center; }
}

/* ── ITEMS STEP PICKER ──────────────────────────────
   The quote/order items step. Everything chosen once — size, colours, doors — sits in a
   dropdown grid so the whole specification fits on one screen; accessories and windows are
   added a row at a time with a quantity. */
.picker-grid {
    display: grid;
    /* 300px rather than 260px: an accessory row can carry four columns - name, quantity, the
       Direct tick and the remove button - and at 260px the name is left with about 40px, which
       is an ellipsis and nothing else. */
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 16px 20px;
}

.picker-field { min-width: 0; }
.picker-field-wide { grid-column: 1 / -1; }

.picker-field .form-label { margin-bottom: 6px; }

/* The asterisk on anything that must be answered. The item picker's category headings and the
   wizard forms' labels use the same mark — see .required-mark's rule in FORMS. */
.picker-required { color: var(--danger); margin-left: 2px; }

/* "· 2 gutters included" after a field label — what the shed comes with as standard, said where the
   rep is choosing extras. Subtle on purpose: it explains the quantity the field opens at, and the
   running total below is what states the money. */
.picker-label-note { color: var(--text-subtle); font-size: 12px; font-weight: 400; }

.picker-empty {
    font-size: 13px;
    color: var(--text-subtle);
    padding: 9px 0;
}

/* ── Per-category search ───────────────────────────────────────
   Sits directly above the control it narrows, on the long lists only — the sizes and the
   accessories. See _ItemPickerSearch.cshtml. */
.picker-search {
    position: relative;
    margin-bottom: 6px;
}
.picker-search input {
    padding-left: 32px;
}
.picker-search i {
    position: absolute;
    left: 11px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 12px;
    color: var(--text-subtle);
    pointer-events: none;
}
.picker-no-match {
    font-size: 13px;
    color: var(--text-subtle);
    padding: 6px 0;
}

/* ── Combobox ──────────────────────────────────────────────────
   Every dropdown on the items step, and the filter bars' selects. A button shaped exactly like the
   .form-select it replaces, so the grid stays even, opening a panel with the search inside it once
   the list is long enough to want one. Built over the native select by item-picker.js and
   combobox.js — see the section of the same name in either. */
.picker-combo { position: relative; }

/* The select still posts, so it cannot be display:none'd away — clipped instead. */
/* ── No flash of the native dropdown ──────────────────────────
   combobox.js replaces these selects with .picker-combo-btn, and it runs from
   the bottom of the page: on a grid carrying a hundred rows the browser has
   long since painted the filter bar by the time it does. So every dropdown was
   drawn once as a plain <select> and then repainted as the branded control a
   moment later — the light grey flash on every page load and every sort.

   Nothing here is about how the control ends up looking. It is the same
   background, the same muted label and the same caret colour the button uses,
   put on the select that is about to be replaced, so that the first paint and
   the second are the same picture. Both rules are the combobox's own selector
   (see SELECTOR in combobox.js); a select that opts out with data-no-combobox
   is never replaced and must keep looking like the input it stays.

   The dark theme is where it showed, because that is where the two disagree:
   an input there is --surface-muted and the combobox button is --surface. */
:root[data-theme="dark"] form[data-live-list] select.form-select:not([data-no-combobox]),
:root[data-theme="dark"] select.form-select[data-combobox] {
    background-color: var(--surface);
    /* --text-subtle, matching .picker-combo-caret, rather than the --a3a3a3 the
       native caret uses everywhere else. */
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23737373' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}

/* "All salespeople" is a placeholder once the button draws it — see
   .picker-combo-placeholder — so the native select has to say so too, or the
   label flashes from full white to grey along with the fill. Only the
   empty-string case: a field whose "nothing chosen" is a number says so with
   data-combobox-empty, and those are not on a filter bar. */
form[data-live-list] select.form-select:not([data-no-combobox]):has(option[value=""]:checked),
select.form-select[data-combobox]:has(option[value=""]:checked) {
    color: var(--text-subtle);
}

.picker-combo-native {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    border: 0;
}

.picker-combo-btn {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    min-height: var(--control-height);
    padding: 8px 12px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--text);
    font-family: 'Montserrat', sans-serif;
    font-size: 13.5px;
    text-align: left;
    cursor: pointer;
    transition: border-color 0.15s, box-shadow 0.15s;
}

.picker-combo-btn:hover:not(:disabled) { border-color: var(--navy); }

/* Held shut until a shed size is chosen — see syncLocks in item-picker.js. Shaped like the
   disabled .form-select it stands in for, so a locked door field reads the same either way. */
.picker-combo-btn:disabled {
    background: var(--surface-muted);
    color: var(--text-subtle);
    cursor: not-allowed;
}

.picker-combo-btn:disabled .picker-combo-caret { opacity: 0.5; }

.picker-combo-btn:focus-visible,
.picker-combo-btn[aria-expanded="true"] {
    border-color: var(--navy);
    box-shadow: 0 0 0 3px var(--navy-ring);
    outline: none;
}

.picker-combo-btn.is-invalid {
    border-color: var(--danger);
    box-shadow: 0 0 0 3px rgba(155,59,51,0.14);
}

.picker-combo-value {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: baseline;
    gap: 8px;
}

.picker-combo-primary { font-weight: 600; white-space: nowrap; }
.picker-combo-placeholder { color: var(--text-subtle); }

/* The metric size beside the imperial one — the same shed said the other way, so it is set back
   rather than given equal weight. */
.picker-combo-sub {
    font-size: 12px;
    color: var(--text-subtle);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.picker-combo-caret {
    flex-shrink: 0;
    font-size: 11px;
    color: var(--text-subtle);
    transition: transform 0.15s;
}

.picker-combo-btn[aria-expanded="true"] .picker-combo-caret { transform: rotate(180deg); }

/* .card clips to its rounded corners, which cut the open panel off at the bottom of the
   Specification card. Released only while a panel is open, so the corners are unaffected the rest
   of the time.

   .adapt-card is here for the same reason and was added when it was: the Sage-code combobox on
   the admin-review panel sits inside one, and the panel was being cut off 146px short — you could
   see the search box and the first option and nothing else. Any new card that hides its overflow
   and can hold a combobox belongs on this list. */
.card:has(.picker-combo-panel:not([hidden])),
.card:has(.picker-combo-panel:not([hidden])) .card-body,
.adapt-card:has(.picker-combo-panel:not([hidden])),
.adapt-card:has(.picker-combo-panel:not([hidden])) .adapt-card-body { overflow: visible; }

/* And the same for a scrolling table around one. A combobox in a table cell — the Stores page's
   "Invoiced to" column — sits inside a horizontal scroller, which clips the open panel top and
   bottom as well as left and right. Released only while a panel is open, so a table too wide for
   its card still scrolls the rest of the time. */
.table-responsive:has(.picker-combo-panel:not([hidden])),
.card-body:has(> .adman-table):has(.picker-combo-panel:not([hidden])) { overflow: visible; }

.picker-combo-panel {
    position: absolute;
    z-index: 20;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    min-width: 240px;
    padding: 8px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    background: var(--surface);
    box-shadow: 0 10px 28px rgba(0,0,0,0.14);
}

/* The search stays put while the list under it scrolls — it is what the scrolling is for. */
.picker-combo-panel .picker-search { margin-bottom: 8px; }

.picker-combo-list {
    list-style: none;
    margin: 0;
    padding: 0;
    max-height: 300px;
    overflow-y: auto;
}

.picker-combo-group {
    padding: 8px 10px 4px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.4px;
    text-transform: uppercase;
    color: var(--text-subtle);
}

.picker-combo-group[hidden],
.picker-combo-option[hidden] { display: none; }

.picker-combo-option {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 7px 10px;
    border-radius: var(--radius-sm);
    font-size: 13.5px;
    cursor: pointer;
}

.picker-combo-option:hover,
.picker-combo-option.is-active { background: var(--navy-tint); }

.picker-combo-option[aria-selected="true"] {
    background: var(--navy-tint);
    color: var(--navy);
}

/* A door colour that rules the chosen side out disables that side rather than hiding it, so the
   rep can see the pairing exists and is not sold. chooseRow refuses it. */
.picker-combo-option.is-disabled {
    color: var(--text-subtle);
    cursor: not-allowed;
    opacity: 0.55;
}

.picker-combo-option.is-disabled:hover { background: transparent; }

.picker-combo-label { flex: 1; min-width: 0; font-weight: 600; }
.picker-combo-clear .picker-combo-label { font-weight: 400; color: var(--text-subtle); }

.picker-combo-tick {
    flex-shrink: 0;
    font-size: 13px;
    color: var(--navy);
    visibility: hidden;
}

.picker-combo-option[aria-selected="true"] .picker-combo-tick { visibility: visible; }

.picker-combo-panel .picker-no-match { padding: 8px 10px 4px; }

@media (max-width: 600px) {
    .picker-combo-panel { min-width: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .picker-combo-btn,
    .picker-combo-caret { transition: none; }
}

/* ── Locked until a size is chosen ─────────────────────────────
   The door fields, held shut while there is no shed to hang a door on. Dimmed rather than
   hidden, so the rep can see what is waiting rather than watching fields appear from nowhere.
   See ItemPickerBuilder.ApplyLocks. */
.picker-locked .form-select,
.picker-locked .picker-chips {
    opacity: 0.55;
    cursor: not-allowed;
}

.picker-lock-note {
    display: flex;
    align-items: center;
    gap: 5px;
    margin-top: 4px;
    font-size: 12px;
    color: var(--text-subtle);
}

/* Said under the shed size when Continue is pressed with nothing chosen. The same red as the
   required asterisk it sits below. */
.picker-invalid {
    margin-top: 4px;
    font-size: 12.5px;
    color: var(--danger);
}

/* A tick box riding on a field label — the floor beside the shed size. */
.picker-field .form-label:has(.picker-toggle) {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

.picker-toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin: 0;
    font-weight: 400;
    color: var(--text-muted);
    cursor: pointer;
}

.picker-toggle input { margin: 0; }
.picker-field .form-label:has(.picker-toggle input:checked) .picker-toggle { color: var(--navy); }

/* Tick-box chips — used where a category keeps its choose-from-a-list behaviour. */
.picker-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.picker-chip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 7px 12px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--surface);
    font-size: 13px;
    cursor: pointer;
    margin: 0;
}

/* display on .picker-chip would otherwise beat the hidden attribute the search sets. */
.picker-chip[hidden] { display: none; }

.picker-chip:hover { border-color: var(--navy); }
.picker-chip:has(input:checked) {
    border-color: var(--navy);
    background: var(--navy-tint);
    color: var(--navy);
}

/* The same chip as a link rather than a checkbox — the dashboard's queue filters are real pages
   with their own URLs, so there is no input inside them for :has to find. Kept beside the rule it
   mirrors rather than written as a second style, so the two cannot drift apart. */
.picker-chip.selected {
    border-color: var(--navy);
    background: var(--navy-tint);
    color: var(--navy);
}
a.picker-chip { text-decoration: none; color: inherit; }

/* The window colour on a repeater row. Sized to the words rather than left to stretch: it sits
   between the quantity and the Direct tick, and a full-width select there reads as a second item
   field. Hidden by item-picker.js on a row with nothing to ask — an Adman-coded window, or one
   bought direct — and the [hidden] rule is needed because the display below would otherwise beat
   the attribute, exactly as it does on .picker-chip. */
.picker-window-colour {
    flex: 0 0 auto;
    width: auto;
    min-width: 140px;
}

.picker-window-colour[hidden] { display: none; }

/* A door and the two questions that belong to it, kept on one line so the answers read as part of
   the door rather than as fields of their own. The door list stacks instead of wrapping: a chip
   that grows a colour and a side when ticked would otherwise reflow the ones beside it. */
.picker-doors { flex-direction: column; align-items: flex-start; }

.picker-door {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}

.picker-door-options { display: flex; gap: 6px; }
.picker-door-options[hidden] { display: none; }

/* How many of this door. Sized to three digits and no more: it sits inline beside the tick box and
   its colour, and a full-width number field would push the row onto two lines. Ordered after the
   colour so the row reads as the door, then what it looks like, then how many. */
.picker-door-qty { display: flex; }
.picker-door-qty[hidden] { display: none; }

.picker-door-qty .picker-qty {
    width: 72px;
    font-size: 13px;
    padding: 4px 6px 4px 10px;
    border-color: var(--border-strong);
}

.picker-door-options .form-select {
    width: auto;
    font-size: 13px;
    padding: 4px 26px 4px 10px;
    border-color: var(--border-strong);
}

/* The colour and side sit inline beside their door, so they are sized to their content rather than
   to the field — a full-width control per door would push the tick box off its own line. */
.picker-combo-sm { display: inline-block; }

.picker-combo-sm .picker-combo-btn {
    width: auto;
    min-height: 0;
    padding: 4px 10px;
    font-size: 13px;
    border-radius: var(--radius-sm);
}

.picker-combo-sm .picker-combo-panel { min-width: 180px; }

/* data-combobox-wide: a panel whose rows are longer than the control that opens it. The Stores
   page's "Invoiced to" column is a small select in a table cell, and a Sage row is an account
   number, a dash and a company name — at the cell's width every row wrapped onto three lines.
   Only the open panel is widened; the closed button still sizes to the column.

   Written to beat .picker-combo-sm above on specificity as well as order, since both classes land
   on the same wrapper. */
.picker-combo-wide .picker-combo-panel,
.picker-combo-sm.picker-combo-wide .picker-combo-panel {
    min-width: 400px;
    max-width: 90vw;
}

/* Where a remote list says what it is doing — too few letters typed, searching, nothing found,
   or could not ask. Sits under the search box, above the rows. */
.picker-combo-status {
    padding: 2px 10px 6px;
    font-size: 12px;
    color: var(--text-subtle);
}

@media (max-width: 600px) {
    .picker-combo-wide .picker-combo-panel,
    .picker-combo-sm.picker-combo-wide .picker-combo-panel { min-width: 0; }
}

/* Widths the wrapper has to take over.

   Once combobox.js has run, the .picker-combo it wraps the select in is what the row lays
   out - a max-width left on the select would size something no longer in the layout. Both of
   these sit in a flex row beside a button, so they need a basis to grow from as well. The
   select is named alongside so the widths still hold with no script, where it is what is
   left on the page. */
#basePlanKey,
.picker-combo:has(> #basePlanKey) { flex: 1 1 22rem; max-width: 32rem; }

select[name="salesRepUser"],
.picker-combo:has(> select[name="salesRepUser"]) { flex: 0 1 260px; max-width: 260px; }

.picker-repeat { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.picker-repeat-rows { display: flex; flex-direction: column; gap: 8px; width: 100%; }

.picker-repeat-row {
    display: grid;
    /* minmax(0, 1fr) rather than 1fr: a 1fr track will not shrink below its content's minimum,
       and the content here is an accessory name. On the narrowest .picker-grid track that pushed
       the last column - the remove button - off the end of the row, where grid auto-placement
       restarted it on a line of its own underneath. */
    grid-template-columns: minmax(0, 1fr) 104px auto;
    gap: 8px;
    align-items: center;
}

/* The combobox wrapper is what sits in the first column once the select is drawn over. Without
   min-width:0 a long accessory name stretches the column and pushes the quantity off the row.
   The select is named alongside so the row still holds before combobox.js has run, and on a page
   with no script, where the select is what is left in the column. */
.picker-repeat-row .picker-combo,
.picker-repeat-row > .form-select { min-width: 0; }

/* A column that has given way truncates rather than spilling over the tick and out of the card.
   Without these the name runs straight across the quantity box and is cut off by the card's own
   overflow, which reads as a broken row rather than as a name too long for the space. */
.picker-repeat-row .picker-combo-btn { overflow: hidden; }
.picker-repeat-row .picker-combo-primary { overflow: hidden; text-overflow: ellipsis; }

/* ── The additional door on a Multistore ─────────────────────────────────────
   Shaped like a repeater row and not one: a Multistore has one other gable, so the button
   uncovers a single field rather than adding to a list. Two columns rather than the accessory
   row's three, because one door needs no quantity - reusing .picker-repeat-row would leave a dead
   104px gap between the dropdown and the remove button. See _ItemPickerField. */
.picker-extra { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }

/* Stated, not inherited. There is no blanket [hidden] rule in this sheet - every component that
   hides itself says so, the way .picker-chip and .picker-door-qty do - and a display of flex or
   grid set on a class beats the user agent's own [hidden] on an attribute. Without these the
   panel stays on screen with the button, and the row stays on screen while it is locked.

   The button needs it for the same reason and a worse one: .btn-adman is display:inline-flex, so
   an [hidden] button is not hidden at all. Scoped here rather than fixed on .btn-adman, which is
   every button in the app - the repeater's own add button has the same fault and is capped at 99
   rows, so nobody has met it. */
.picker-extra[hidden],
.picker-extra-row[hidden],
.picker-extra [data-extra-door-add][hidden] { display: none; }

.picker-extra-row {
    display: grid;
    /* minmax(0, 1fr) for the reason .picker-repeat-row gives: a bare 1fr will not shrink below
       its content, and the content is a door name. */
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 8px;
    align-items: center;
    width: 100%;
}

/* -- Doors on a partner's document ------------------------------------------
   Laid out like a window row, because they carry the same Direct tick: the door, then the tick,
   then the remove button on the additional door. The main door has no remove button, so a spacer
   the button's width keeps its tick in line with the one below it. */
.picker-door-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto 32px;
    gap: 8px;
    align-items: center;
    width: 100%;
}
.picker-door-row[hidden] { display: none; }
.picker-door-row .picker-combo,
.picker-door-row > .form-select { min-width: 0; }
.picker-door-row [data-extra-door-remove] { width: 32px; justify-content: center; padding-inline: 0; }
.picker-door-row-fixed { grid-template-columns: minmax(0, 1fr) auto; }

/* The tick-box doors: one row per door, full width, with the colour, quantity and tick in shared
   columns the way the window rows line up. Each door is a subgrid of the list, so a colour on one
   door widens that column for all of them rather than pushing only its own row out of line. */
.picker-doors-direct {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto 104px auto;
    gap: 8px;
}
.picker-doors-direct .picker-door {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: subgrid;
    align-items: center;
}
/* Every child on the one line: placed out of source order, auto-placement would otherwise start
   a new line for the colour, which comes after the quantity in the markup. */
.picker-doors-direct .picker-door > *     { grid-row: 1; }
/* The height of the quantity box beside it, as a window's dropdown is. */
.picker-doors-direct .picker-chip         { grid-column: 1; min-width: 0; display: flex; align-self: stretch; }
.picker-doors-direct .picker-door-options { grid-column: 2; }
.picker-doors-direct .picker-door-qty     { grid-column: 3; }
.picker-doors-direct .picker-door-direct  { grid-column: 4; }
/* A door with no colour to ask takes the colour column too, and an unticked door the whole row. */
.picker-doors-direct .picker-door:not(:has(.picker-door-options:not([hidden]))) .picker-chip { grid-column: 1 / 3; }
.picker-doors-direct .picker-door:not(:has(.picker-chip-input:checked)) .picker-chip { grid-column: 1 / -1; }
/* Undoes the compact box the tick-box doors use elsewhere, so it matches a window row's. */
.picker-doors-direct .picker-door-qty .picker-qty { width: 100%; font-size: 13.5px; padding: 9px 13px; }
/* The colour fills its column, so every door's dropdown is the same width. */
.picker-doors-direct .picker-door-options .picker-combo,
.picker-doors-direct .picker-door-options .form-select { width: 100%; }
.picker-door-direct[hidden] { display: none; }

.picker-extra-row .picker-combo,
.picker-extra-row > .form-select { min-width: 0; }

.picker-extra-row .picker-combo-btn { overflow: hidden; }
.picker-extra-row .picker-combo-primary { overflow: hidden; text-overflow: ellipsis; }

.picker-qty { text-align: center; }

/* Quantity and area share the middle column: only one is ever shown, so they cannot collide, and
   the hidden one is display:none and takes no space. The column is wide enough for "36.8 m²"
   rather than just a quantity, which is why it grew from 82px. */
.picker-repeat-row .picker-qty,
.picker-repeat-row .adman-measure { grid-column: 2; }

/* ── "Direct" on an accessory row ────────────────────────────────────────────
   One more column, and only on the documents that offer the tick: an order or a purchase order
   sold through a retail partner, where a single accessory may still be bought from Adman. See
   OrderLine.IsDirectSale. The row keeps its three columns everywhere else rather than carrying an
   empty one, because a gap between the quantity and the remove button reads as a missing control.
   auto rather than a fixed width — the word is short and the box is what sets the height. */
.picker-repeat-row-direct { grid-template-columns: minmax(0, 1fr) 104px auto auto; }

/* A window row asks one more thing than an accessory row does — what colour it is — so it needs a
   column for it. Without one the grid has more children than tracks and auto-placement starts a
   new line, which put the remove button on a row of its own underneath. The same fault the
   minmax(0, 1fr) note above records, arrived at from the other end.

   Stated per shape rather than with grid-auto-columns, because the quantity track is a fixed 104px
   and the colour wants to size to its own words: one rule per combination is four lines and says
   exactly what each row is made of.

   The colour column is auto, not a fixed width — .picker-window-colour sets its own minimum below,
   and a fixed track here would fight it. */
.picker-repeat-row-colour        { grid-template-columns: minmax(0, 1fr) 104px auto auto; }
.picker-repeat-row-colour.picker-repeat-row-direct {
    grid-template-columns: minmax(0, 1fr) 104px auto auto auto;
}

.picker-direct {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    /* Quiet, and deliberately so: this is the exception on the row, not the question being asked.
       The badge on the priced tables is what makes a direct line stand out once it is saved. */
    font-size: 12px;
    font-weight: 600;
    color: var(--text-muted);
    white-space: nowrap;
    cursor: pointer;
    user-select: none;
}

.picker-direct input { margin: 0; cursor: pointer; }

/* Ticked, the label takes the navy the rest of the step uses for an answered field, so a rep can
   see at a glance which rows on a long accessory list are the direct ones. :has is what lets the
   label follow its own tick box with no script; a browser without it simply keeps the quiet
   colour, which is legible either way. */
.picker-direct:has(input:checked) { color: var(--navy); }

/* ── A number with its unit inside it ────────────────────────────────────────
   The shell carries the border, the radius and the focus ring; the input inside it is
   borderless. So the figure and its unit read as one control rather than as a box with a word
   floating next to it, and "36.8 m²" is a single field a rep can tab into.

   Built for the Multiroom's flooring, which is sold by the square metre, and named for the shape
   rather than for the floor so the next measured field reuses it instead of inventing a third
   way to draw an input.

   Every value comes from the same tokens .form-control uses — the same border, radius, height,
   type and focus ring — so a branded field and this one cannot drift apart, and the dark theme
   follows without a second rule. */
.adman-measure {
    display: inline-flex;
    align-items: center;
    gap: 1px;
    min-width: 0;
    padding-right: 11px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    background-color: var(--surface);
    min-height: var(--control-height);
    transition: border-color 0.15s, box-shadow 0.15s;
}

/* display:inline-flex above would otherwise beat the attribute. */
.adman-measure[hidden] { display: none; }

/* The ring lights the whole control rather than the bare input inside it, and uses the very
   border and shadow .form-control:focus uses. */
.adman-measure:focus-within {
    border-color: var(--navy);
    box-shadow: 0 0 0 3px var(--navy-ring);
}

.adman-measure > input {
    flex: 1 1 auto;
    min-width: 0;
    width: 100%;
    border: 0;
    background: transparent;
    padding: 9px 2px 9px 12px;
    font-family: 'Montserrat', sans-serif;
    font-size: 13.5px;
    color: var(--text);
    text-align: right;
    /* The native spinners are dropped on purpose: they take a third of the width of a field this
       narrow, they step in whole numbers where this steps in tenths, and a rep types 36.8 rather
       than clicking up to it. */
    -moz-appearance: textfield;
}

/* Any number box that is typed rather than stepped, for the reasons given just above: the
   spinners eat a third of a narrow field, they step in whole numbers where the field steps in
   fractions, and nobody clicks up to 452.18. Opt in with the class rather than stripping them
   from every input[type=number] in the app — a sort order or a quantity is genuinely nicer with
   them, and those are the majority. */
input.no-spinners {
    -moz-appearance: textfield;
    appearance: textfield;
}

.adman-measure > input::-webkit-outer-spin-button,
.adman-measure > input::-webkit-inner-spin-button,
input.no-spinners::-webkit-outer-spin-button,
input.no-spinners::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

/* The shell already shows focus, so a second ring inside it would draw a box within a box. */
.adman-measure > input:focus { outline: none; box-shadow: none; }

.adman-measure > input::placeholder { color: var(--text-subtle); }

/* Reads as a label rather than as something to type in, and cannot be selected by a drag across
   the field. */
.adman-measure-unit {
    color: var(--muted);
    font-size: 11.5px;
    font-weight: 600;
    white-space: nowrap;
    user-select: none;
    pointer-events: none;
}

/* A field the rep reads rather than answers — the Ministore door, set by the shed size. Shaped
   like the dropdown beside it so the grid stays even, but plainly not a control. */
.picker-fixed {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--surface-muted);
    color: var(--text-muted);
    font-size: 14px;
}

.picker-fixed i { color: var(--navy); }

.picker-fixed-note {
    font-size: 12px;
    color: var(--text-subtle);
    margin-top: 4px;
}

/* Something the rep should know but does not have to fix — what this shed has no room for,
   chiefly. Deliberately not danger red: buying an extra the shed cannot hold is allowed, and the
   drawing lists it at the side instead of fitting it. Same amber as the drawing page's own hint
   (shed-diagram.css .sd-manual) so one voice says both. */
.picker-note {
    display: flex;
    gap: 8px;
    align-items: flex-start;
    padding: 9px 12px;
    border-radius: 8px;
    /* Through the status palette's pending pair rather than the hex this held. Those tokens are
       this very cream and brown in the light theme, so nothing moves there — but they are redefined
       for the dark one, where a fixed cream panel was the one note in this file that did not follow
       the theme. shed-diagram.css .sd-manual was changed with it, or the one voice the comment above
       promises would have split in dark mode alone. */
    background: var(--st-pending-bg);
    color: var(--st-pending-fg);
    font-size: 13px;
    line-height: 1.5;
}

.picker-note i { margin-top: 2px; }

/* Door type + side need two dropdowns' worth of room. */
.picker-field-doors { grid-column: span 2; }

@media (max-width: 900px) {
    .picker-field-doors { grid-column: auto; }
}

/* ── CHANGE HISTORY (quote / order trail) ──────── */
/* A timeline down the left: one node per save, its changes stacked beneath. */
.doc-history {
    list-style: none;
    margin: 0;
    padding: 0;
}

.doc-history-item {
    display: flex;
    gap: 12px;
    padding-bottom: 12px;
    position: relative;
}

/* The connecting line, drawn behind the avatars and stopped at the last one. */
.doc-history-item::before {
    content: "";
    position: absolute;
    left: 15px;
    top: 34px;
    bottom: 0;
    width: 1px;
    background: var(--border);
}

.doc-history-item:last-child { padding-bottom: 0; }
.doc-history-item:last-child::before { display: none; }

.doc-history-avatar {
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--surface-muted);
    color: var(--text-muted);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: 0.3px;
    z-index: 1;
}

/* The save that raised the document — the one node worth tinting. */
.doc-history-avatar.is-creation {
    background: var(--navy-tint);
    color: var(--navy);
}

.doc-history-body { min-width: 0; flex: 1; }

.doc-history-head {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 6px;
    font-size: 13.5px;
    line-height: 1.5;
}

.doc-history-user {
    font-size: 11.5px;
    color: var(--text-subtle);
}

.doc-history-time {
    font-size: 12px;
    color: var(--text-muted);
    margin-left: auto;
    white-space: nowrap;
}

.doc-history-changes {
    list-style: none;
    margin: 4px 0 0;
    padding: 0;
    font-size: 13px;
}

/* icon · label · values — the values keep their own column so a long one wraps
   inside it rather than back under the icon. */
.doc-history-changes > li {
    display: grid;
    grid-template-columns: 12px auto minmax(0, 1fr);
    align-items: baseline;
    gap: 6px;
    padding: 2px 0;
    color: var(--text-muted);
}

.doc-history-changes > li > .bi {
    font-size: 11px;
    color: var(--text-subtle);
}

.doc-history-values {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 6px;
    min-width: 0;
}

/* Notes runs to 2,000 characters. Two lines is enough to see what changed; the
   rest is on the title attribute. */
.doc-history-from,
.doc-history-to {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.doc-history-field {
    color: var(--text);
    font-weight: 500;
}

.doc-history-detail { color: var(--text-muted); }

/* from → to: the old value reads as superseded, the new one as current. */
.doc-history-from {
    color: var(--text-subtle);
    text-decoration: line-through;
    text-decoration-thickness: 1px;
}

.doc-history-to {
    color: var(--text);
    font-weight: 500;
}

.doc-history-arrow { font-size: 10px; color: var(--text-subtle); }

@media (max-width: 576px) {
    /* The timestamp has nowhere to sit on the right at this width. */
    .doc-history-time { margin-left: 0; }
}

/* ── Change history: chip, then the change ──
   Only inside the card. The dashboard's Updates panel reuses .doc-history-changes with a field
   name and a value and should keep that: there a change is one line in a feed of other people's
   work, not a row somebody is reading down.

   Two columns, not four. Four put the part of the document, the field and the value an inch
   apart from each other — "Production | Finished | Yes" — which lined up beautifully and read
   like nothing at all. The chip says which part; everything that belongs to the change itself
   stays together as a phrase. */
.doc-history-card .doc-history-changes > li {
    /* Wide enough for the longest chip — "Payment & delivery" — since a chip clipped to
       "Payment & delive" is worse than the gutter it saves. */
    grid-template-columns: 150px minmax(0, 1fr);
    align-items: baseline;
    padding: 3px 0;
}

/* Which part of the document. The same word the filter dropdown uses for it, so the card and
   the filter agree. Neutral on purpose — the amber post-confirmation flag is the only colour
   on this card that means anything, and a chip per section in eleven colours would bury it. */
.doc-history-chip {
    display: inline-flex;
    align-items: center;
    /* A grid item fills its track unless told otherwise, which drew these as 124px boxes rather
       than as pills around their own words. */
    justify-self: start;
    gap: 5px;
    max-width: 100%;
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--surface-muted);
    border: 1px solid var(--border);
    color: var(--text-muted);
    font-size: 11px;
    font-weight: 600;
    line-height: 1.45;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.doc-history-chip > .bi { font-size: 10px; flex-shrink: 0; }

/* The rows under the first of a run keep the column without repeating the word. */
.doc-history-chip.is-continued {
    background: none;
    border-color: transparent;
    padding: 0;
}

/* The change itself. A measure rather than the width of the card: access notes and internal
   notes run to hundreds of characters, and set across a 1500px card that is a line the eye
   loses its place on halfway along. */
.doc-history-phrase {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 6px;
    min-width: 0;
    max-width: 68ch;
}

/* What happened to a line or an attachment, leading the phrase. */
.doc-history-verb {
    color: var(--text);
    font-weight: 600;
}

/* A station or a callout number — the subject of the sentence the chip could not name. */
.doc-history-qualifier {
    color: var(--text);
    font-weight: 600;
}

/* A yes/no answer. The word as well as the mark: a tick alone beside "Height restriction" is
   ambiguous about which way round it is, and these end up in front of customers. */
.doc-history-answer {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-weight: 600;
}

.doc-history-answer > .bi { font-size: 13px; }
.doc-history-answer.is-yes { color: var(--success); }
.doc-history-answer.is-no  { color: var(--text-muted); }

.doc-history-cleared {
    color: var(--text-subtle);
    font-style: italic;
}

@media (max-width: 720px) {
    /* No room for a fixed chip column. The chip takes the line above its run instead. */
    .doc-history-card .doc-history-changes > li { grid-template-columns: minmax(0, 1fr); }
    .doc-history-card .doc-history-chip { margin-bottom: 2px; }
    .doc-history-card .doc-history-chip.is-continued { display: none; }
}

/* ── Change history: the card around the trail ──
   Header counts, the filter toolbar, day headings and the confirmation line.
   Markup: Views/Shared/_DocumentHistory.cshtml. Behaviour: js/doc-history.js. */

.doc-history-count {
    font-size: 12px;
    color: var(--text-muted);
    white-space: nowrap;
}

/* The post-confirmation flag. Amber throughout — the same pair the Pending status badge
   uses — because this is the one thing on the card that wants to be found from across the
   page, and it is a caution rather than a fault. */
.doc-history-flag,
.section-changed-flag {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 9px;
    border-radius: 999px;
    background: var(--st-pending-bg);
    color: var(--st-pending-fg);
    border: 1px solid var(--alert-warning-border);
    font-size: 11.5px;
    font-weight: 600;
    line-height: 1.4;
    white-space: nowrap;
    text-decoration: none;
}

.doc-history-flag > .bi,
.section-changed-flag > .bi { font-size: 10px; }

/* Both are clickable: the card flag jumps to the trail and marks its own rows, the count in
   the history header marks every post-confirmation row. */
button.doc-history-flag,
.section-changed-flag { cursor: pointer; }

button.doc-history-flag:hover,
button.doc-history-flag:focus-visible,
.section-changed-flag:hover,
.section-changed-flag:focus-visible {
    background: var(--st-pending-fg);
    color: var(--st-pending-bg);
    border-color: var(--st-pending-fg);
}

/* ── The filter toolbar ──
   Its own strip between the header and the body: it is a control surface, not content, and
   sitting it inside the body would put it level with the first revision. */
.doc-history-toolbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    padding: 10px 20px;
    border-bottom: 1px solid var(--border);
    background: var(--surface-muted);
}

.doc-history-search {
    position: relative;
    flex: 1 1 240px;
    min-width: 0;
}

.doc-history-search > .bi {
    position: absolute;
    left: 11px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 12px;
    color: var(--text-subtle);
    pointer-events: none;
}

.doc-history-search > .form-control {
    height: var(--control-height-sm);
    padding-left: 30px;
    font-size: 13px;
}

.doc-history-select {
    height: var(--control-height-sm);
    width: auto;
    flex: 0 1 auto;
    padding-top: 0;
    padding-bottom: 0;
    font-size: 13px;
}

/* Both filters are branded comboboxes. Once combobox.js has run it is the .picker-combo wrapper
   that sits in this flex row, so the sizing has to name it as well — the select it wrapped is
   clipped out of the layout. Named alongside above, so the row still holds with no script. */
.doc-history-toolbar .picker-combo { flex: 0 1 auto; min-width: 0; }

.doc-history-toolbar .picker-combo-btn {
    min-height: var(--control-height-sm);
    background: var(--surface);
}

/* The "only since the customer was told" switch. Reads as the flag it filters to when on. */
.doc-history-toggle,
.doc-history-clear {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: var(--control-height-sm);
    padding: 0 12px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border-strong);
    background: var(--surface);
    color: var(--text-muted);
    font-size: 12.5px;
    font-weight: 600;
    white-space: nowrap;
}

.doc-history-toggle > .bi,
.doc-history-clear > .bi { font-size: 10px; }

.doc-history-toggle:hover,
.doc-history-clear:hover { border-color: var(--text-subtle); color: var(--text); }

.doc-history-toggle[aria-pressed="true"] {
    background: var(--st-pending-bg);
    border-color: var(--alert-warning-border);
    color: var(--st-pending-fg);
}

.doc-history-summary {
    font-size: 12px;
    color: var(--text-muted);
    margin-left: auto;
    white-space: nowrap;
}

/* ── Day headings ──
   In the same list as the revisions, indented to the body column so the avatars stay on one
   axis down the page. */
.doc-history-day {
    position: relative;
    padding: 0 0 8px 44px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.6px;
    text-transform: uppercase;
    color: var(--text-subtle);
}

.doc-history-day:not(:first-child) { padding-top: 6px; }

/* Day headings and the confirmation line sit in the middle of the timeline, so they carry
   their own length of spine — without it the line breaks at every heading. */
.doc-history-day::before,
.doc-history-divider::before {
    content: "";
    position: absolute;
    left: 15px;
    top: 0;
    bottom: 0;
    width: 1px;
    background: var(--border);
}

/* ── The confirmation line ──
   Where the customer got their copy. Everything above it in the list happened afterwards. */
.doc-history-divider {
    position: relative;
    padding: 2px 0 14px 44px;
    margin-top: 2px;
}

.doc-history-divider > span {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 4px 12px;
    border-radius: 999px;
    border: 1px dashed var(--alert-warning-border);
    background: var(--st-pending-bg);
    color: var(--st-pending-fg);
    font-size: 12px;
    font-weight: 600;
    line-height: 1.5;
}

.doc-history-divider > span > .bi { font-size: 12px; }

/* A save made after the customer was told.

   The timeline itself carries it: the thread joining the avatars runs amber from the top of the
   card down to the confirmation line, and those avatars are amber too. Nothing beside the rows,
   nothing behind them — a rule down the left margin read as a separate thing hung next to the
   trail, and a filled panel per save made a card nobody scrolls to the bottom of. The line was
   already there and already means "this happened, then this"; colouring it says which stretch
   of that happened behind the customer's back.

   The day headings and the confirmation line draw their own segments of the thread — see
   _DocumentHistory.cshtml, which marks the ones above the divider. */
.doc-history-item.is-after-confirmation::before,
.doc-history-day.is-after-confirmation::before,
.doc-history-divider::before {
    /* left stays at 15px with the width at 2: the avatar is 32px wide, so its centre is 16 and
       the thicker line has to grow into it rather than off it. */
    width: 2px;
    background: var(--st-pending-solid);
}

.doc-history-item.is-after-confirmation .doc-history-avatar {
    background: var(--st-pending-fg);
    color: var(--st-pending-bg);
}

/* ── Highlighted rows ──
   What a flag on one of the cards above points at. The rows stay where they happened, in
   among the rest — being able to see what came before and after a change is most of why
   anyone opens this card — so they are marked rather than filtered to. */
.doc-history-changes > li.is-highlighted {
    background: var(--st-pending-bg);
    box-shadow: inset 2px 0 0 var(--st-pending-solid);
    border-radius: var(--radius-sm);
    padding-left: 8px;
    margin-left: -8px;
}

.doc-history-changes > li.is-highlighted .doc-history-field { color: var(--st-pending-fg); }

.doc-history-item.has-highlight .doc-history-avatar {
    box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--st-pending-solid);
}

@media (max-width: 576px) {
    .doc-history-toolbar { padding: 10px 14px; }
    .doc-history-select { flex: 1 1 100%; width: 100%; }
    .doc-history-summary { margin-left: 0; }
}

/* ── Extra contacts ───────────────────────────────────────────────────────────
   The "+ Add another" email / phone boxes on the Customer step.
   Markup: Views/Shared/_ExtraContactFields.cshtml (boxes) and
   _ExtraContactAddLink.cshtml (the link, up in the label row).
   Behaviour: js/extra-contacts.js
   ------------------------------------------------------------------------- */

/* A field label carrying the add-link on its right. Same shape as .picker-field's
   label row, which does this for the Floor tick. */
.contact-label {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    width: 100%;
}

/* Grey, not link-blue: a quiet secondary action, the same weight as the
   "(optional)" hints beside the other labels. */
.extra-contact-add {
    color: var(--text-muted);
    text-decoration: none;
    font-weight: 500;
    white-space: nowrap;
}

.extra-contact-add:hover {
    color: var(--text-muted);
    text-decoration: underline;
}

/* Hidden past the cap, and hidden is the intent — not merely transparent. */
.extra-contact-add[hidden] { display: none; }

.extra-contacts .extra-contact-row { max-width: 100%; }

/* The rows carry their own spacing. Each one used to bring a Bootstrap `mt-1`, which meant the
   gap was decided in the markup of every row and was 4px whatever the row held. */
.extra-contacts-rows {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 8px;
}

.extra-contacts-rows .extra-contact-row { margin-top: 0; }

/* @Html.ValidationMessage emits a span after every row whether or not there is an error, and an
   empty one is a .field-validation-valid. In normal flow that costs nothing — an empty block
   generates no line box — but these spans are children of the flex column above, so each one is a
   flex ITEM and eats a whole 8px gap. Three rows came out with five gaps instead of two, which is
   the uneven spacing between the numbers.

   Hiding the empty one only: unobtrusive validation swaps the class for .field-validation-error
   when there is something to say, and that keeps its own visible rule. */
.extra-contacts-rows .field-validation-valid { display: none; }

/* Both trailing buttons keep .btn-icon's square 40px, because that is the row's real height.
   .input-group-sm suggests otherwise, but .form-control in this codebase carries a fixed
   height: var(--control-height), so the small variant only changes the font — the rows are 40px
   like the primary field. Sizing these to --control-height-sm made them 30 wide in a 40 tall row,
   which was worse than what it replaced. Only the shrink guard is wanted here. */
.extra-contact-row .extra-contact-remove,
.extra-contact-row .email-verify {
    flex: 0 0 auto;
    padding: 0;
}

/* The check button on an extra email row is the primary field's, only smaller. */
.extra-contact-row .email-verify .bi { font-size: 14px; }

/* The optional "whose number is this" box. A third of the row and no more: the number is the
   field that matters and this is a note beside it, so it must not grow at the number's expense.
   It says what it is in its placeholder rather than costing a label above every row. */
/* No font-size of its own. It carried 12.5px while the number box beside it was on Bootstrap's
   12.25px and the primary field on 13.5px — three sizes, two of them in the same row. Both boxes
   take .form-control's 13.5px now; only the colour says this one is the quieter of the two. */
.extra-contact-row .extra-contact-label {
    flex: 0 1 34%;
    min-width: 0;
    color: var(--text-muted);
}

.extra-contact-row .extra-contact-label::placeholder {
    color: var(--text-subtle);
    font-style: italic;
}

/* The × sits quiet until reached for: it removes something the user typed. */
.extra-contact-remove {
    color: var(--text-muted);
    border-color: var(--border);
    line-height: 1;
}

.extra-contact-remove:hover {
    color: #fff;
    background-color: var(--adman-danger);
    border-color: var(--adman-danger);
}

/* ── Region dropdown (IE / NI) ────────────────────────────────────────────────
   The IE/NI dialling code beside a phone number, and Eircode-or-Postcode beside
   the address's postcode. Sits as the leading segment of a Bootstrap input-group.
   Markup: Views/Shared/_RegionSelect.cshtml
   ------------------------------------------------------------------------- */
/* Bootstrap's own `.input-group > .form-select` sets `flex: 1 1 auto; width: 1%`, which
   splits the field down the middle and leaves the number squeezed into the other half.
   Beating it needs a selector at least as specific as that one — a bare `.region-select`
   loses and the rule silently does nothing. Hence the `.input-group >` prefix here, and
   the fixed widths: a two-option chooser should take only what its longest label needs. */
.input-group > .form-select.region-select {
    flex: 0 0 auto;
    width: 6.5rem;
    padding-left: 8px;
    padding-right: 24px;
    background-position: right 6px center;
    color: var(--text-muted);
    font-size: 13px;
    font-weight: 600;
}

/* Branded, the wrapper is the segment: the select inside it is clipped, so the widths and the
   joined corners have to be named here or the row splits down the middle again. Both sets are
   kept, since the select is still what a page with no script shows. */
.input-group > .picker-combo:has(> .region-select) {
    flex: 0 0 auto;
    width: 6.5rem;
}

/* The Eircode/Postcode chooser needs more room than the dialling code it shares these rules with.
   6.5rem was chosen for "+353"; this one says "Eircode" or "Postcode", and the word came out
   clipped mid-letter.

   Sized for "Postcode", the longer of the two, so switching sides does not resize the box and
   shove the Eircode field along with it.

   Matched on having no name attribute, which is what actually separates them: _RegionSelect emits
   `name` only when FieldName is set, and the postcode chooser is the one that posts nothing of
   its own — it drives the Country box instead. Deliberately NOT matched on data-country-target,
   which looks like the obvious discriminator and is not: Razor renders a null data-* attribute as
   an empty one rather than omitting it, so the dialling code carries data-country-target="" and
   would match presence just as well. */
.input-group > .picker-combo:has(> .region-select:not([name])),
.input-group > .form-select.region-select:not([name]) {
    width: 8.75rem;
}

.input-group > .picker-combo:has(> .region-select) > .picker-combo-btn {
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
    padding-left: 8px;
    padding-right: 6px;
    gap: 4px;
    color: var(--text-muted);
    font-size: 13px;
    font-weight: 600;
}

/* Nothing renders .input-group-sm any more — the extra-contact rows were its only user and they
   dropped it, because .form-control's min-height kept them 40px tall regardless and all the class
   still did was shrink the text below the primary field's. Its two sibling rules went with it;
   this one stays only because a .picker-combo-btn inheriting min-height from --control-height
   inside a small group is a trap worth leaving a note about if anybody reintroduces the class. */
.input-group-sm > .picker-combo:has(> .region-select) > .picker-combo-btn {
    min-height: 0;
    height: 100%;
    font-size: 12px;
}

/* The panel is wider than the 6.5rem button it hangs from, so it is not clipped to it. */
.input-group > .picker-combo:has(> .region-select) .picker-combo-panel { min-width: 190px; }

/* The flag on a region dropdown.

   Drawn here rather than written into the label, because the obvious way - putting the flag
   emoji in the text - does not work where it matters. Segoe UI Emoji has never carried flag
   glyphs, so on Windows the emoji falls back to the two letters of the country code and the
   office would read "IE" and "GB" instead of seeing a flag. An image is the same everywhere.

   The key comes off the <option> as data-icon and combobox.js copies it onto the row it builds
   and onto the button, so both the closed control and the open list carry it. */
[data-icon="ie"]::before,
[data-icon="gb"]::before {
    content: "";
    display: inline-block;
    width: 18px;
    height: 12px;
    /* Not negotiable, and this is why the flag came out half width in an extra-contact row.
       Inside .picker-combo-value the flag is a flex item, and being an empty pseudo-element its
       min-content width is 0 — so it is the only thing in that button that CAN shrink. Its
       sibling .picker-combo-primary is white-space: nowrap and the caret already carries
       flex-shrink: 0, so every pixel the button was short landed here. The narrower, smaller-font
       .input-group-sm variant was short by a few pixels, and the flag paid all of it. */
    flex: 0 0 auto;
    margin-right: 6px;
    vertical-align: -1px;
    background-size: cover;
    background-position: center;
    border: 1px solid rgba(0, 0, 0, 0.15);
    border-radius: 2px;
}

[data-icon="ie"]::before { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 20'%3E%3Crect width='30' height='20' fill='%23fff'/%3E%3Crect width='10' height='20' fill='%23169b62'/%3E%3Crect x='20' width='10' height='20' fill='%23ff883e'/%3E%3C/svg%3E"); }
[data-icon="gb"]::before { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 30'%3E%3CclipPath id='s'%3E%3Cpath d='M0,0 v30 h60 v-30 z'/%3E%3C/clipPath%3E%3CclipPath id='t'%3E%3Cpath d='M30,15 h30 v15 z v15 h-30 z h-30 v-15 z v-15 h30 z'/%3E%3C/clipPath%3E%3Cg clip-path='url(%23s)'%3E%3Cpath d='M0,0 v30 h60 v-30 z' fill='%23012169'/%3E%3Cpath d='M0,0 L60,30 M60,0 L0,30' stroke='%23fff' stroke-width='6'/%3E%3Cpath d='M0,0 L60,30 M60,0 L0,30' clip-path='url(%23t)' stroke='%23C8102E' stroke-width='4'/%3E%3Cpath d='M30,0 v30 M0,15 h60' stroke='%23fff' stroke-width='10'/%3E%3Cpath d='M30,0 v30 M0,15 h60' stroke='%23C8102E' stroke-width='6'/%3E%3C/g%3E%3C/svg%3E"); }

/* On the button the flag leads the value, so the span carrying it has to sit on the line. */
.picker-combo-value[data-icon] { display: inline-flex; align-items: center; }

/* ── Issue rows ───────────────────────────────────────────────
   A problem the fitting team reported, on the aftersales dashboard
   and on the issues list — one set of rules, since it is one
   partial. Deliberately not built on .work-item: that row is a
   derived to-do with snooze and dismiss controls, and this is a
   stored record with its age leading it. Colours come from the
   tokens above so the dark theme follows with no second set. */

.issue-row {
    display: flex;
    align-items: flex-start;
    gap: 14px;
    flex-wrap: wrap;
    padding: 14px 20px;
    border-bottom: 1px solid var(--border);
}

.issue-row:last-child { border-bottom: none; }
.issue-row:hover { background: var(--surface-muted); }

.issue-main { min-width: 0; flex: 1 1 320px; }

.issue-head {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-bottom: 3px;
}

.issue-number {
    font-weight: 700;
    font-size: 14px;
    color: var(--navy);
    text-decoration: none;
}

.issue-number:hover { text-decoration: underline; }

/* Two lines of the fitter's own words. The detail page carries the rest — a row that grows to
   fit a paragraph pushes the next report off the screen. */
.issue-words {
    font-size: 13px;
    color: var(--text);
    margin-bottom: 5px;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.issue-meta { font-size: 12px; }
.issue-meta a { color: var(--navy); font-weight: 600; text-decoration: none; }
.issue-meta a:hover { text-decoration: underline; }

.issue-open { flex-shrink: 0; }

/* How long it has been waiting, leading the row. This was the fitter's severity until that rating
   was removed; age took its place because it is a fact rather than a guess, and because it goes
   red on its own while nobody is looking, which is exactly when a report needs to start shouting.
   The number carries the meaning — the colour only sorts the list at a glance. */
.issue-age {
    flex: none;
    min-width: 62px;
    text-align: center;
    padding: 3px 8px;
    border-radius: var(--radius-sm);
    font-size: 13px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

/* Small and quiet, so "12d" reads as a number of days rather than as two figures. */
.issue-age-unit { font-size: 10px; font-weight: 600; opacity: 0.7; margin-left: 1px; }

.issue-age-fresh { background: var(--surface-muted);   color: var(--text-muted); }
.issue-age-warn  { background: var(--st-pending-bg);   color: var(--st-pending-fg); }
.issue-age-stale { background: var(--st-rejected-bg);  color: var(--st-rejected-fg); }
/* Closed: how long it took, stated without alarm. */
.issue-age-done  { background: var(--st-approved-bg);  color: var(--st-approved-fg); }

/* An open report nobody owns. Stated, not shouted — the Unassigned filter is where it gets found. */
.issue-unowned { font-style: italic; }

/* One recently fitted shed. Reads as clean unless something was reported against it, which is
   the whole reason the panel lists the quiet ones too. */
.fitted-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 11px 20px;
    border-bottom: 1px solid var(--border);
}

.fitted-row:last-child { border-bottom: none; }
.fitted-row-flagged { border-left: 3px solid var(--danger); }
/* width: 0 as well as min-width: 0. A truncated line inside still counts its full one-line width
   towards how wide the page wants to be, and on a phone a long callout cause pushed the whole
   dashboard sideways. With no width of its own the column only takes what the row has left. */
.fitted-main { flex: 1 1 auto; width: 0; min-width: 0; }
.fitted-when { font-size: 12px; color: var(--text-muted); }

/* The aftersales dashboard's live-callouts-by-type row. One line of figures, not a row of boxes. */
.callout-figures {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 22px;
    padding: 8px 14px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}
.callout-figure { display: flex; align-items: baseline; gap: 7px; }
.callout-figure-n { font-size: 20px; font-weight: 700; color: var(--text); line-height: 1; }
.callout-figure-l { font-size: 12px; color: var(--text-muted); }
.callout-figure-total { margin-left: auto; padding-left: 22px; border-left: 1px solid var(--border); }
@media (max-width: 575.98px) { .callout-figure-total { margin-left: 0; padding-left: 0; border-left: none; } }

/* ── Video players ────────────────────────────────────────────
   One stage and one player, shared by every page that plays a
   clip: the site-access player, the callout docket's watch page,
   and the fitter's clips on an issue.

   Dark behind the picture because phone video is nearly always
   portrait, so the player is letterboxed whatever we do and black
   sits better behind it than the page cream. Capped at 70vh so a
   tall clip cannot push the rest of the page off the screen.

   Here rather than in each view's <style> block, which is where
   these started: three copies had already drifted apart on the
   colour and the padding, and the second page to reuse a class
   name without its rules is what caused the issue gallery to
   render at 3000 pixels wide. */

.media-stage {
    background: #14131F;
    border-radius: var(--radius);
    padding: 10px;
}

.media-player {
    display: block;
    width: 100%;
    max-height: 70vh;
    margin: 0 auto;
    background: #000;
    border-radius: var(--radius-sm);
}

/* A staff access photo on the same stage: keep its shape instead of stretching to width. */
img.media-player { object-fit: contain; }

/* The file name under a player, on the dark stage. */
.media-filename {
    margin: 10px 2px 0;
    font-size: 12px;
    color: rgba(255, 255, 255, 0.55);
    word-break: break-all;
    text-align: center;
}

/* ── Aftersales progress line ─────────────────────────────────
   Where a job has got to, as a vertical run of steps. Rendered by
   _ProgressLine.cshtml on the issue page, the callout page and the
   order's cards; the customer's own status page is a standalone
   document and carries its own copy of these rules, because it
   loads no stylesheet at all — the same arrangement the dockets
   have. Keep the two in step.

   The colour is the last thing that carries meaning here: the
   labels say where the job is on their own, which is what makes
   the line survive being printed in black and white. */

.progress-line {
    list-style: none;
    margin: 0;
    padding: 0;
}

.progress-step {
    position: relative;
    display: flex;
    gap: 12px;
    padding: 0 0 18px;
}

/* The rail joining the markers, drawn behind them and stopped
   before the last one so the line does not dangle off the end. */
.progress-step:not(:last-child)::before {
    content: "";
    position: absolute;
    left: 10px;
    top: 22px;
    bottom: 0;
    width: 2px;
    background: var(--border);
}

.progress-step:last-child { padding-bottom: 0; }

.progress-marker {
    flex: none;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: 2px solid var(--border);
    background: var(--surface);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 12px;
    line-height: 1;
    z-index: 1;
}

.progress-body   { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.progress-label  { font-size: 14px; font-weight: 600; }
.progress-detail { font-size: 12.5px; color: var(--text-muted); }

/* Behind us: filled, with a tick. The tick is a text character
   rather than an icon font, because the customer's own status
   page loads no stylesheet but its own and an icon class there
   would render as an empty green circle. */
.progress-step.is-done .progress-marker {
    background: var(--success);
    border-color: var(--success);
    color: #FFFFFF;
    font-size: 13px;
    font-weight: 700;
}

.progress-marker.tick::after { content: "\2713"; }

/* Where it is now: ringed rather than filled, so it reads as
   open. Given weight in the label too, not only in the ring. */
.progress-step.is-current .progress-marker {
    border-color: var(--navy);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--navy) 18%, transparent);
}

.progress-step.is-current .progress-label { color: var(--navy); font-weight: 700; }

/* Not yet: present but plainly not reached, rather than hidden —
   a customer wants to see what is still to come. */
.progress-step.is-todo .progress-label,
.progress-step.is-todo .progress-detail { color: var(--text-muted); }

/* ── Attached photo and clip galleries ────────────────────────
   Shared by the callout edit page and the issue detail page. Here
   rather than in a <style> block on one of them, which is where
   these started: the second page to use the class names got no
   rules at all, so phone photos rendered at their natural 3000-odd
   pixels and took the whole page into horizontal scroll with them.
   A gallery is not page furniture — it belongs where both pages
   can see it. */

.callout-media {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--surface);
}

/* Contain, not cover: a cropped photo of a fault can crop out the fault.
   max-width alongside the fixed height so a portrait phone photo cannot
   push its column wider than the card. */
.callout-media img {
    width: 100%;
    max-width: 100%;
    height: 190px;
    object-fit: contain;
    background: var(--surface-muted);
    display: block;
}

.callout-media-body { padding: 10px 12px; }

/* Smaller tiles under a box on the callout form, where they sit among the text rather than in a
   gallery of their own. Scoped, because the issue page's gallery uses .callout-media too. */
.callout-gallery .callout-media img { height: 150px; }
.callout-gallery .callout-media-body { padding: 8px; }

/* One clip on a callout: play, its name, a caption and the two buttons, on one line where there is
   room and wrapping under each other on a phone. */
.callout-clip {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    padding: 6px 8px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
}
.callout-clip-name { flex: 1 1 160px; min-width: 0; font-size: 13px; }
.callout-clip input { flex: 1 1 180px; width: auto; }

/* The thumbnail is too small to judge a dented panel by, so it opens the
   full picture. Block, so the whole tile is the target. */
.callout-media-link { display: block; text-decoration: none; }
.callout-media-link:hover img { opacity: 0.92; }

/* Progress and status text for the brokered uploads. Several views carry
   their own copy of this and are left alone; this is the canonical one for
   anything new. */
.access-video-status { font-size: 13px; color: var(--text-muted); min-height: 1.2em; }
.access-video-status.is-ok    { color: var(--success); font-weight: 600; }
.access-video-status.is-error { color: var(--danger); font-weight: 600; }

/* ── Dashboard work queue ─────────────────────────────────────
   The to-do rows on "/". Colours come from the tokens above so the
   dark theme follows without a second set of rules here. */

.work-item {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    padding: 14px 20px;
    border-bottom: 1px solid var(--border);
}

.work-item:last-child { border-bottom: none; }
.work-item:hover { background: var(--surface-muted); }

.work-item-main { min-width: 0; flex: 1 1 320px; }

.work-item-title {
    font-weight: 600;
    font-size: 14px;
    color: var(--text);
    margin-bottom: 2px;
}

.work-item-detail {
    font-size: 13px;
    color: var(--text-muted);
    margin-bottom: 6px;
}

.work-item-meta {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.work-item-ref {
    font-size: 12px;
    color: var(--text-subtle);
}

/* The "Only mine" switch on the sales office dashboard. A link drawn as a switch between two
   named settings: "Everyone" on the left, "Only mine" on the right. The one in force is lit. */
.mine-switch {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-subtle);
    text-decoration: none;
    user-select: none;
}
.mine-switch:hover .mine-switch-track { filter: brightness(.95); }
.mine-switch .mine-switch-label-off,
.mine-switch.is-on .mine-switch-label-on { color: var(--navy); }
.mine-switch.is-on .mine-switch-label-off { color: var(--text-subtle); }
.mine-switch-track {
    position: relative;
    width: 36px;
    height: 20px;
    border-radius: 999px;
    background: var(--border-strong);
    transition: background-color .15s ease;
    flex-shrink: 0;
}
.mine-switch-knob {
    position: absolute;
    top: 2px;
    left: 2px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 2px rgba(0, 0, 0, .25);
    transition: transform .15s ease;
}
.mine-switch.is-on .mine-switch-track { background: var(--navy); }
.mine-switch.is-on .mine-switch-knob  { transform: translateX(16px); }
.mine-switch:focus-visible { outline: none; }
.mine-switch:focus-visible .mine-switch-track { box-shadow: 0 0 0 3px var(--navy-tint); }

/* Whose quote or order a row is, on the sales office dashboard. Quieter than a tag: it is who to
   ask, not something wrong. */
.work-item-owner {
    font-size: 12px;
    color: var(--text-muted);
    white-space: nowrap;
}

.work-item-actions {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
}

/* Each button is wrapped in its own POST form. Without this the *form* is the flex item and
   the button inside it stops taking part in the row's alignment and gap; display:contents
   lets the button itself be the flex item. The snooze form is excluded — it is a dropdown
   and needs a positioning context of its own. */
.work-item-actions form:not(.dropdown) { display: contents; }

/* ── A section heading on the rep dashboard ─────────────────────
   Stacked, not spread. The card header elsewhere puts its title left and its note hard right,
   which works for a note about the whole card; here the note describes the heading and belongs
   under it rather than a screen-width away. */
.work-section-head {
    display: block;
    padding-top: 12px;
    padding-bottom: 12px;
}

.work-section-title {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.work-section-blurb {
    margin-top: 3px;
    font-size: 12px;
    color: var(--text-muted);
}

/* ── A work item laid out in columns ────────────────────────────
   Opt-in, on the sales rep dashboard only. The default row is a flex pair — everything on the
   left, buttons on the right — which on a wide screen leaves a lake of empty space in the middle
   and stacks three sentences down the left edge.

   Three columns instead: what it is, what it is waiting on, what to do about it. They line up
   down the list, so a rep can read one column rather than re-parsing every row. The middle
   column takes the slack, which is what closes the gap. */
.work-item-cols {
    display: grid;
    grid-template-columns: minmax(210px, 20%) 1fr auto;
    align-items: start;
    gap: 20px;
    padding: 15px 20px;
}

.work-item-cols .work-item-main { flex: none; min-width: 0; }

/* The customer, not a sentence about the customer. Bigger than the reference under it so the
   eye lands on the name first going down the list. */
.work-item-cols .work-item-title {
    font-size: 14.5px;
    line-height: 1.25;
    margin-bottom: 4px;
}

.work-item-cols .work-item-meta { gap: 6px; row-gap: 4px; }

/* The document number, sharing the heading with the customer's name. Quieter than the name and
   tabular, so the numbers line up down the list and can be compared at a glance — they differ
   only in their last digits. */
.work-item-docno {
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.work-item-sep { color: var(--text-subtle); margin: 0 2px; }

/* The middle column. Top-aligned: with a heading, a needs list and a row of facts under it, this
   column is usually taller than the identity block beside it, and centring it then floats the
   heading below the customer's name it belongs to. */
.work-item-needs { min-width: 0; align-self: start; }

.work-item-needs-label {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-subtle);
    margin-bottom: 5px;
}

/* Countable. The whole reason this is a list and not the sentence it used to be: "a county, an
   Eircode and the site access details" is three things a reader has to take apart, and three
   bullets is three things.

   Flowed rather than set in CSS columns: with two short items, columns puts one hard left and the
   other hard right with a chasm between them, which reads as two unrelated things rather than a
   list of two. Wrapping keeps them adjacent and still packs a long list onto few lines. */
.work-item-needs-list {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-wrap: wrap;
    column-gap: 22px;
    row-gap: 2px;
    font-size: 13px;
    color: var(--text);
}

.work-item-needs-list li {
    position: relative;
    padding-left: 15px;
}

/* The summaries are written to be read in a sentence — "still needs the shed layout diagram and
   a county" — so as bullets they all start lowercase. Lifted here rather than in the data,
   because the sentence form is still used and is still correct; and first-letter rather than
   capitalize, which would put a capital on every word. */
.work-item-needs-list li::first-letter { text-transform: uppercase; }

.work-item-needs-list li::before {
    content: "";
    position: absolute;
    left: 3px;
    top: 7px;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--text-subtle);
}

.work-item-cols .work-item-detail { margin-bottom: 0; }

/* ── The order's own facts ──────────────────────────────────────
   When it is going out, which shed, where to, what it is worth. Quieter than the needs list above
   them: a need is something to act on, a fact is there to be recognised in passing. */
.work-item-facts {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    column-gap: 14px;
    row-gap: 3px;
    margin-top: 7px;
    font-size: 12.5px;
    color: var(--text-muted);
}

.work-item-fact {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    white-space: nowrap;
}

/* Deliberately not .tag-danger: that class is the Overdue tag on the identity block, and the one
   urgency assertion in the verify suite counts it. A missing date is a gap to fill, not an alarm. */
.work-item-fact-date.is-missing { color: var(--st-pending-fg); font-weight: 600; }

.work-item-fact-value { font-weight: 600; color: var(--text); }

/* ── The updates feed, laid out as work rows ────────────────────
   Same three columns as a task, so a change reads the same way on the same page. The list loses
   its own padding and dividers because .work-item already draws them. */
.doc-history-cols {
    list-style: none;
    margin: 0;
    padding: 0;
}

/* .card-body has padding of its own; the rows want the full width, like a task list does. */
.card-body > .doc-history-cols { margin: -14px -20px; }

.doc-history-cols .doc-history-item { padding: 14px 20px; }

/* The change list is the middle column here rather than an indented block under a paragraph. */
.doc-history-cols .doc-history-changes { margin: 0; }

.doc-history-cols .work-item-ref { font-size: 12px; }

/* Stack below the tablet breakpoint: three columns on a phone gives each about a hundred
   pixels, and the middle one would wrap every bullet to two lines. */
@media (max-width: 900px) {
    .work-item-cols {
        grid-template-columns: 1fr;
        gap: 10px;
    }
    .work-item-cols .work-item-actions { justify-content: flex-start; }
    .work-item-needs-list { columns: 1; }
}

.work-snooze-menu {
    font-size: 13px;
    min-width: 210px;
    padding: 6px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    box-shadow: 0 8px 24px rgba(0,0,0,0.10);
}

.work-snooze-menu .dropdown-item {
    border-radius: var(--radius-sm);
    padding: 7px 10px;
    font-size: 13px;
    color: var(--text);
    background: none;
    border: 0;
    width: 100%;
    text-align: left;
    cursor: pointer;
}

.work-snooze-menu .dropdown-item:hover,
.work-snooze-menu .dropdown-item:focus {
    background: var(--navy-tint);
    color: var(--text);
}

.work-snooze-head {
    display: block;
    padding: 4px 10px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.3px;
    text-transform: uppercase;
    color: var(--text-subtle);
}

.work-snooze-date { padding: 2px 10px 6px; }

/* ── Split buttons and their menus ────────────────────────────
   A primary action with a caret beside it opening related, rarer actions — the quote's
   Convert to Order with Convert to Pro Forma Invoice behind the caret.

   Written here because Bootstrap's own split button cannot help: every one of its
   border-collapsing rules is scoped to `.btn-group > .btn`, and these buttons carry
   `.btn-adman`, so not one of them matches. Views\Reports\_ReportFilter.cshtml hit the same
   wall and worked around it with inline border-radius styles on each segment; this is that
   workaround stated once, as CSS, so the next split button does not need a fourth copy. */

.btn-split { display: inline-flex; }

/* The toggle sits inside the POST form, because the menu item it opens is that form's
   submit button. So the form is a flex item rather than display:contents — it has to stay
   the positioned ancestor the menu is measured against. */
.btn-split > form { display: flex; }

.btn-split-main   { border-radius: var(--radius) 0 0 var(--radius); }
.btn-split-toggle { border-radius: 0 var(--radius) var(--radius) 0; }

/* Pulls the toggle onto the main button's border so the pair reads as one control rather
   than two buttons that happen to touch — without it the seam is a 2px double border. */
.btn-split-toggle { margin-left: -1px; }

/* Both halves are the same navy, so the collapsed seam above is invisible and the caret
   looks welded on. A translucent white divider is the exception to this file's tokens-only
   rule, and deliberately so: it is not a colour, it is a lightening of whatever the button
   already is, and it therefore needs no dark-mode counterpart — navy is #2A2470 in light
   and #4F73DD in dark, and white reads on both. */
.btn-split-toggle { border-left-color: rgba(255, 255, 255, 0.28); }

/* The focus ring is a 3px box-shadow, which the neighbour would otherwise paint over on
   the shared edge. Lifting whichever half is being used puts its own ring on top. */
.btn-split-main:hover,   .btn-split-toggle:hover,
.btn-split-main:focus-visible, .btn-split-toggle:focus-visible { position: relative; z-index: 1; }

/* Greyed with the primary it belongs to, and scoped to the pair rather than to .btn-adman at
   large: a caret that still opened while the button beside it was refusing would be a way round
   the Place Order completeness gate, so the two halves must always be in the same state. */
.btn-split > .btn-adman:disabled,
.btn-split > .btn-adman:disabled:hover {
    background: var(--surface-muted);
    color: var(--text-subtle);
    border-color: var(--border-strong);
    cursor: not-allowed;
}

/* The menu itself. Every value is a token, so dark mode comes free — which is the whole
   point: this started life as a bare `.dropdown-menu` reading through Bootstrap's own
   variables, and two of those are not bridged in site.css's dark block
   (--bs-dropdown-link-hover-bg and --bs-dropdown-border-color), so the panel arrived with a
   light border and a light hover tint sitting on a dark page.

   Duplicates .work-snooze-menu above, which predates it and says the same thing in the same
   values. Fold the two together when the dashboard's snooze menu is next touched — not
   today, to keep this change to the screen it is about. */
.adman-menu {
    min-width: 250px;
    padding: 6px;
    font-size: 13px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-md);
}

.adman-menu .dropdown-item {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 8px 10px;
    font-size: 13px;
    text-align: left;
    color: var(--text);
    background: none;
    border: 0;
    border-radius: var(--radius-sm);
    cursor: pointer;
}

/* :active is listed alongside hover, not left to Bootstrap — its default is a hardcoded
   #0d6efd with white text, which flashes bright blue on mousedown against this palette. */
.adman-menu .dropdown-item:hover,
.adman-menu .dropdown-item:focus,
.adman-menu .dropdown-item:active {
    background: var(--navy-tint);
    color: var(--text);
}

/* The icon column reads as chrome beside the label, not as part of it. */
/* Wider, for a menu whose options each carry a line of explanation under the label — but capped,
   or a one-line explanation simply makes the panel as wide as itself and the menu sprawls out from
   under the button it belongs to. Between the two the explanation wraps, which is what it is for. */
.adman-menu-wide {
    min-width: 320px;
    max-width: 360px;
}

/* An option that explains itself: the label on one line, what it actually does on the next. That
   explanation belongs here and not only in the confirmation dialog, because this menu is where
   the choice is made — by the time the dialog is up, it has been. */
.adman-menu .dropdown-item-detail {
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    /* The explanation wraps; .dropdown-item's inherited nowrap would push the menu off screen. */
    white-space: normal;
}

.adman-menu .dropdown-item-detail strong { font-weight: 600; }

.adman-menu .dropdown-item-detail span {
    font-size: 12px;
    line-height: 1.35;
    color: var(--text-subtle);
}

/* Held at the muted tone on hover: the label lifts to --text and the explanation stays put, so
   the eye is not asked to read two lines of equal weight. */
.adman-menu .dropdown-item-detail:hover span,
.adman-menu .dropdown-item-detail:focus span { color: var(--text-subtle); }

.adman-menu .dropdown-item i { color: var(--text-muted); }
.adman-menu .dropdown-item:hover i,
.adman-menu .dropdown-item:focus i { color: var(--text); }

/* An entry that applies but cannot be used yet (the order PDF before a delivery date). Left on the
   menu, greyed, with its reason underneath: an entry that has vanished reads as a fault. */
.adman-menu .dropdown-item:disabled,
.adman-menu .dropdown-item.disabled {
    opacity: .6;
    cursor: not-allowed;
    background: none;
    pointer-events: auto;
}

/* The Orders list's documents button: the PDF icon plus a small chevron, so it reads as a menu
   rather than the single-click PDF button it replaced. Same height as the row's other buttons. */
.doc-menu-toggle {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding-left: 8px;
    padding-right: 7px;
}
.doc-menu-toggle .doc-menu-caret { font-size: 10px; line-height: 1; }

/* A 3px edge per bucket — enough to tell them apart down the page
   without three differently-coloured cards shouting at each other. */
.work-bucket-overdue { border-left: 3px solid var(--danger); }
.work-bucket-today   { border-left: 3px solid var(--navy); }
.work-bucket-soon    { border-left: 3px solid var(--border-strong); }

/* ═══════════════════════════════════════════════════════════════
   Reports — charts, filter chips and the county league table
   ═══════════════════════════════════════════════════════════════ */

/* ── Chart bars ───────────────────────────────────────────────
   The bars are focusable so a chart can be read with the keyboard.
   Everything here is a token, so dark mode needs no second block. */
.chart-bar { cursor: default; outline: none; }

a.chart-bar { cursor: pointer; }

.chart-bar rect { transition: opacity 0.12s ease; }

.chart-bar:hover rect,
.chart-bar:focus-visible rect { opacity: 0.78; }

/* A ring rather than a fill change: the bar's own colour is the data,
   and dimming it to show focus would misreport the value. */
.chart-bar:focus-visible {
    outline: 2px solid var(--navy);
    outline-offset: 2px;
    border-radius: 2px;
}

.chart-legend {
    display: flex;
    gap: 14px;
    flex-wrap: wrap;
    margin-bottom: 10px;
}

.chart-legend-item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 11.5px;
    color: var(--text-muted);
}

.chart-swatch {
    width: 10px;
    height: 10px;
    border-radius: 2px;
    display: inline-block;
}

.chart-swatch-primary   { background: var(--navy); }
.chart-swatch-secondary { background: var(--chart-secondary); }

/* ── Chart tooltip ────────────────────────────────────────────
   Fixed and parented to <body>, so the card's overflow-x cannot clip it.
   Positioned by report-charts.js; everything visual is here. */
.chart-tooltip {
    position: fixed;
    z-index: 1080;              /* over the sticky topbar, under a modal */
    left: 0;
    top: 0;
    max-width: 260px;
    padding: 7px 10px;
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-md);
    font-size: 12px;
    line-height: 1.45;
    color: var(--text);
    pointer-events: none;       /* never eat the click meant for the bar */
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.12s ease;
}

.chart-tooltip.is-shown { opacity: 1; visibility: visible; }

.chart-tooltip-label {
    display: block;
    font-weight: 600;
    margin-bottom: 2px;
}

.chart-tooltip-detail { display: block; color: var(--text-muted); }

@media (prefers-reduced-motion: reduce) {
    .chart-tooltip { transition: none; }
    .chart-bar rect { transition: none; }
}

/* ── Active filter chips ──────────────────────────────────────
   Each chip's x is a plain link back to the same page with one route
   value dropped, so the strip works with scripting off. */
.filter-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    align-items: center;
    margin-bottom: 12px;
}

.filter-chips-label {
    font-size: 11.5px;
    color: var(--text-subtle);
    margin-right: 2px;
}

.filter-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 8px 3px 10px;
    background: var(--navy-tint);
    color: var(--navy);
    border: 1px solid transparent;
    border-radius: 999px;
    font-size: 11.5px;
    font-weight: 500;
    text-decoration: none;
}

.filter-chip:hover,
.filter-chip:focus-visible {
    border-color: var(--navy);
    color: var(--navy);
    text-decoration: none;
}

.filter-chip i { font-size: 11px; opacity: 0.7; }
.filter-chip:hover i { opacity: 1; }

.filter-chip-clear {
    background: transparent;
    color: var(--text-muted);
    border-color: var(--border-strong);
}

.filter-chip-clear:hover,
.filter-chip-clear:focus-visible {
    border-color: var(--text-muted);
    color: var(--text);
}

/* Groups the filter bar into "when" and "what" so ten controls do not
   wrap into a ragged block. */
.filter-group-label {
    display: block;
    width: 100%;
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.4px;
    text-transform: uppercase;
    color: var(--text-subtle);
    margin-bottom: 2px;
}

/* ── List grids: the folded filters and the column chooser ────
   Both are <details>, so they open with no script running and need no
   outside-click handling of their own. Shared by Orders, Quotes and the
   retail partner tabs, which is why nothing here is named for one of
   them. */

.list-more {
    border-top: 1px solid var(--border);
    margin-top: 6px;
    padding-top: 8px;
}

/* The same second row of filters with nothing folding it away, which is what the
   Orders and Quotes grids show now. The toggle cost a click and a line of the card
   to hide controls that take up the same room either way, so it earned neither.
   The rule above the row stays: the narrowing filters still read as a group under
   the search box rather than as more of the same bar. */
.list-more-open {
    border-top: 1px solid var(--border);
    margin-top: 6px;
    padding-top: 10px;
}

/* The default triangle is replaced by our own chevron below, so that the
   marker sits with the icon and the count rather than hard against the
   left edge of the row. */
.list-more-toggle,
.list-popover-toggle {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    cursor: pointer;
    list-style: none;
    user-select: none;
}

.list-more-toggle::-webkit-details-marker,
.list-popover-toggle::-webkit-details-marker { display: none; }

.list-more-toggle {
    font-size: 12.5px;
    font-weight: 600;
    color: var(--text-muted);
    padding: 2px 0;
}

.list-more-toggle:hover { color: var(--navy); }

/* A Bootstrap icon in the markup rather than a glyph in content:, which is what
   every other caret on the site is (see .picker-combo-caret) and which does not
   depend on the font having the character. */
.list-more-caret {
    font-size: 10px;
    opacity: 0.7;
    transition: transform 0.12s ease;
}

.list-more[open] > .list-more-toggle .list-more-caret { transform: rotate(180deg); }

.list-more-panel { margin-top: 10px; }

/* .card clips to its rounded corners, which is right for a card wrapping a table
   and wrong for one holding a dropdown: the column panel was cut off halfway down
   its own list. Opted into per card rather than loosened on .card, which every
   grid, tile and summary on the site uses. */
.list-filter-card { overflow: visible; }

/* The floating panel shared by the column chooser and the status picker. Named
   for what it is rather than for the first of the two that used it: .list-columns
   and .list-status are each one's own identity, and are what anything targeting a
   particular panel should say. Both sit in the filter bar beside Clear, so the
   button matches the controls it stands next to rather than the text links above. */
.list-popover { position: relative; }

.list-popover-toggle {
    min-height: var(--control-height);
    padding: 5px 12px;
    font-size: 12.5px;
    white-space: nowrap;
}

/* Floated over the rows rather than pushing them down: a panel that
   reflows the whole grid every time somebody checks which columns are on
   is a panel nobody opens twice. Right-aligned because the button sits at
   the right-hand end of the bar and a left-aligned panel would hang off
   the page on a narrow screen. */
.list-popover-panel {
    position: absolute;
    z-index: 30;
    right: 0;
    margin-top: 6px;
    min-width: 200px;
    padding: 8px;
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    box-shadow: var(--shadow-md);
}

.list-popover-panel {
    max-height: 60vh;
    overflow-y: auto;
}

.list-popover-option {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 5px 7px;
    border-radius: var(--radius-sm);
    font-size: 13px;
    color: var(--text);
    cursor: pointer;
}

.list-popover-option:hover { background: var(--surface-muted); }

.list-popover-option input { cursor: pointer; }

/* The status picker is the column chooser's panel with different boxes in it, so it
   takes those styles wholesale and adds only what it does not share. */

/* The one rule in the quotes panel, under the statuses and above "include the ones
   that became orders" — a different kind of question that would read as another
   status sitting flush against them. */
.list-status-rule {
    margin: 6px 4px;
    border: 0;
    border-top: 1px solid var(--border);
}

/* A filter that is narrowing something should look different from one that is not.
   The chip strip above the rows says which statuses, so the button only has to say
   that there are some. */
.list-status:has(input:checked) > .list-popover-toggle {
    border-color: var(--navy);
    background: var(--navy-tint);
    color: var(--navy);
}

/* The picker sits under a label in the filter row, where the controls beside it are
   full width. Left to the button's own text it stepped in and out as the summary
   changed from "Any status" to "3 chosen". */
.list-status { display: block; }
.list-status > .list-popover-toggle { width: 100%; justify-content: flex-start; }
.list-status > .list-popover-toggle span:first-of-type { flex: 1; text-align: left; }

/* The buttons at the right-hand end of a filter bar — the column chooser, Clear,
   and the noscript Filter button. They are sized by their own text but sit in a
   Bootstrap column sized as a fraction of the row, so on a narrow desktop window
   the pair of them was wider than the column and painted straight over the page
   size box beside it. Wrapping is the honest answer: a second short line beats a
   button sitting on top of a control. min-width:0 is what lets the column shrink
   at all — without it the overflow has nowhere to go but leftwards, over its
   neighbour. */
.list-filter-actions {
    min-width: 0;
    flex-wrap: wrap;
    row-gap: 6px;
}

/* ── The toolbar under the filter bar ─────────────────────────
   Saved views and Export CSV, side by side on one line. They were a line each:
   the saved-views panel brought its own bar with it, so anything added beside it
   started a third. The bar belongs to the page now, not to what sits on it. */

.list-toolbar {
    display: flex;
    align-items: center;
    gap: 18px;
    flex-wrap: wrap;
    border-top: 1px solid var(--border);
    margin-top: 6px;
    padding-top: 8px;
}

/* A <summary> among the toolbar's toggles, so it takes their colour and loses the
   default marker along with them — see .list-more-toggle. */
.list-export,
.list-export:hover {
    text-decoration: none;
}

.list-export:hover { color: var(--navy); }

/* Left-aligned like the saved views panel beside it: both toggles sit at the left
   of the toolbar, and a right-aligned panel would hang off towards the middle of
   the card. Wide enough for the longer label and its count on one line. */
.list-export-panel {
    left: 0;
    right: auto;
    min-width: 240px;
}

/* Shaped like .list-views-link, which is the other list of choices on this
   toolbar: label left, count right, the whole row a target. */
.list-export-option {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 7px;
    border-radius: var(--radius-sm);
    font-size: 13px;
    color: var(--text);
    text-decoration: none;
}

.list-export-option:hover {
    background: var(--surface-muted);
    color: var(--navy);
    text-decoration: none;
}

.list-export-note {
    margin: 6px 0 0;
    padding: 0 7px;
    font-size: 11.5px;
    color: var(--text-subtle);
}

/* ── Reordering the columns ───────────────────────────────────
   The rows of the Columns panel can be dragged, and where a row sits is where its
   column sits — see wwwroot/js/list-columns.js. The grip and the arrows are
   rendered hidden and unhidden by that script, so nothing here has to hide them. */

.list-columns-row {
    display: flex;
    align-items: center;
    gap: 2px;
    border-radius: var(--radius-sm);
}

/* The label is the whole of the row's hit area for ticking, so it takes the space
   the grip and the arrows do not. */
.list-columns-row > .list-popover-option {
    flex: 1;
    min-width: 0;
}

.list-columns-grip {
    display: flex;
    align-items: center;
    padding: 0 2px;
    color: var(--text-subtle);
    cursor: grab;
    font-size: 13px;
}

.list-columns-row.is-dragging {
    opacity: 0.45;
    background: var(--surface-muted);
}

.list-columns-move {
    display: flex;
    flex-direction: column;
}

/* Two arrows stacked in the height of one row, so the keyboard route to the same
   move costs the panel no extra line. */
.list-columns-arrow {
    display: block;
    border: 0;
    background: none;
    padding: 0 3px;
    line-height: 1;
    font-size: 9px;
    color: var(--text-subtle);
    cursor: pointer;
}

.list-columns-arrow:hover { color: var(--navy); }

.list-columns-note {
    margin: 6px 0 0;
    padding: 0 7px;
    font-size: 11.5px;
    color: var(--text-subtle);
}

.list-columns-reset {
    display: block;
    width: 100%;
    margin-top: 8px;
    padding: 5px 7px;
    border: 0;
    border-radius: var(--radius-sm);
    background: none;
    text-align: left;
    font-size: 12px;
    color: var(--text-muted);
    cursor: pointer;
}

.list-columns-reset:hover {
    background: var(--surface-muted);
    color: var(--navy);
}

/* ── Dragging a column wider ──────────────────────────────────
   The grab area on a heading's right-hand edge. It overhangs the border by
   3px each way so the target is 7px wide without the line itself moving,
   which is the difference between a grip you catch first time and one you
   chase. */
.adman-table thead th { position: relative; }

.col-grip {
    position: absolute;
    top: 0;
    right: -3px;
    width: 7px;
    height: 100%;
    cursor: col-resize;
    user-select: none;
    touch-action: none;
    z-index: 1;
}

/* Shown on approach rather than painted all the time: eleven standing lines
   down a grid is a table that reads as a spreadsheet. */
.col-grip::after {
    content: "";
    position: absolute;
    top: 20%;
    left: 3px;
    width: 1px;
    height: 60%;
    background: var(--navy);
    opacity: 0;
    transition: opacity 0.12s;
}

.col-grip:hover::after { opacity: 1; }

/* Whatever the pointer happens to be over mid-drag, it is resizing a column,
   and nothing under it should be selecting text or offering a hand cursor. */
body.is-column-resizing,
body.is-column-resizing * {
    cursor: col-resize !important;
    user-select: none !important;
}

/* Only once there are widths to honour — see the note in _OrderListResults.
   Without them fixed layout would share the page out in equal columns, which
   is not what any of these grids want. The overflow is what fixed layout
   needs to mean anything: a cell whose content is wider than its column would
   otherwise push the column back out. */
.adman-table-sized { table-layout: fixed; }

.adman-table-sized th,
.adman-table-sized td { overflow: hidden; }

/* ── Saved views ──────────────────────────────────────────────
   A named filter set. It sits on the toolbar rather than in the filter bar because
   saving is a POST and its form cannot be nested inside the bar's GET form. */

/* Wider than the column chooser and left-aligned: these hold names somebody has
   typed, not a fixed vocabulary, and a name clipped at 180px is not a name. */
.list-views-panel {
    left: 0;
    right: auto;
    min-width: 300px;
}

.list-views-row {
    display: flex;
    align-items: center;
    gap: 6px;
    /* These are links to click, not text to copy, and a stray drag across the
       panel selected two of them — which reads exactly like two saved views
       both being marked as the one you are on. */
    user-select: none;
}

.list-views-link {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1;
    min-width: 0;
    padding: 6px 7px;
    border-radius: var(--radius-sm);
    font-size: 13px;
    color: var(--text);
    text-decoration: none;
}

.list-views-link span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.list-views-link:hover {
    background: var(--surface-muted);
    color: var(--navy);
    text-decoration: none;
}

/* The one you are already looking at. Marked rather than hidden or disabled: it is
   still a link worth having, and a panel that drops the current view would read as
   having lost it. */
.list-views-row.is-current .list-views-link {
    background: var(--navy-tint);
    color: var(--navy);
    font-weight: 600;
}

.list-views-empty,
.list-views-note {
    margin: 0;
    font-size: 11.5px;
    color: var(--text-subtle);
}

.list-views-empty { padding: 2px 7px 4px; }

/* Saving sits at the top of the panel, above the rule, because that is what
   opening it is nearly always for — coming back to a saved view is one click on
   its name wherever in the panel it sits, but the box you type into cannot be
   pushed off the bottom by eight rows that grew above it. */
.list-views-save { padding: 2px 7px 0; }

.list-views-save .form-label {
    margin-bottom: 4px;
    font-size: 11.5px;
}

/* Small deliberately. This is a name and a button, not a form: at the filter
   bar's full 40px they were the largest thing in a panel whose actual content is
   a list of one-line links. */
.list-views-save-row {
    display: flex;
    gap: 6px;
}

.list-views-save-row .form-control {
    min-height: 0;
    height: 30px;
    padding: 4px 9px;
    font-size: 12.5px;
}

.list-views-save-row .btn-adman {
    min-height: 0;
    padding: 4px 14px;
    font-size: 12.5px;
}

/* --border-strong rather than --border: this one is doing a job, dividing what
   you are typing from what you have already saved, and in dark mode the lighter
   rule all but disappears. */
.list-views-rule {
    margin: 10px -8px 6px;
    opacity: 1;
    border: 0;
    border-top: 1px solid var(--border-strong);
}

/* The bin is one icon beside a name, not a button competing with it: bordered and
   boxed it read as the row's action rather than as the small thing you reach for
   only when tidying up. */
.list-views-delete {
    border: 0;
    background: none;
    padding: 4px 6px;
    line-height: 1;
    font-size: 12px;
    color: var(--text-subtle);
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.list-views-delete:hover {
    background: var(--surface-muted);
    color: var(--danger);
}

.list-views-note {
    margin-top: 8px;
    padding: 0 7px;
}

.list-views-error {
    font-size: 12px;
    color: var(--danger);
}

/* On a phone the bar is a single column and an absolutely positioned
   panel would sit over the results with nothing to anchor it to the
   right of. In flow it simply pushes the rows down, which at that width
   is what everything else on the page does too. */
@media (max-width: 767.98px) {
    .list-popover-panel {
        position: static;
        min-width: 0;
    }
}

/* The filter bar's own buttons sit in a row with .form-control and
   .form-select, which are 40px tall. .btn-sm is 30px, so a segmented
   period control built from it sat visibly short of the date boxes
   beside it. These take the full control height instead — same class
   set, one line of override, rather than a bespoke button. */
.report-filter-btn {
    min-height: var(--control-height);
    padding: 5px 14px;
    font-size: 12.5px;
}

/* combobox.js replaces a filter's <select> with a .picker-combo wrapper holding a button,
   so the widths that keep this row even have to sit on the column rather than on the
   control - the wrapper is what the row lays out once the upgrade has run. Either control
   then fills it, which also keeps the row even with no script, where the select is what
   is left. */
.report-filter-field > .form-select,
.report-filter-field > .picker-combo { width: 100%; }

/* Apply and Download CSV are a matched pair at opposite ends of the
   row, so they are sized by their box rather than by their text —
   otherwise "Apply" reads as a smaller, lesser button than the export
   beside it purely because the word is shorter. */
.report-filter-action {
    min-height: var(--control-height);
    min-width: 148px;
    padding: 5px 14px;
    font-size: 12.5px;
    justify-content: center;
}

/* ── County league table ──────────────────────────────────────
   A ranked row: position, name, share bar, figures. Reuses .bar-track
   and .bar-fill so it reads like every other breakdown on the site. */
.area-row {
    display: grid;
    grid-template-columns: 26px minmax(96px, 1fr) 2fr auto;
    align-items: center;
    gap: 10px;
    padding: 7px 0;
    border-bottom: 1px solid var(--border);
}

.area-row:last-child { border-bottom: none; }

.area-rank {
    font-size: 11px;
    font-weight: 600;
    color: var(--text-subtle);
    text-align: right;
}

.area-label {
    font-size: 12.5px;
    font-weight: 500;
    color: var(--text);
    text-decoration: none;
}

.area-label:hover, .area-label:focus-visible {
    color: var(--navy);
    text-decoration: underline;
}

.area-region {
    display: block;
    font-size: 10.5px;
    color: var(--text-subtle);
    font-weight: 400;
}

.area-value {
    font-size: 12px;
    color: var(--text-muted);
    text-align: right;
    white-space: nowrap;
}

.area-value strong {
    color: var(--text);
    font-weight: 600;
}

/* The Unrecognised row is data, not a warning — muted so it does not
   compete with the counties, but never hidden. */
.area-row-unplaced .area-label { color: var(--text-muted); font-style: italic; }

@media (max-width: 575.98px) {
    .area-row { grid-template-columns: 22px 1fr auto; }
    .area-row .bar-track { grid-column: 2 / -1; }
}

/* ── Refreshing ───────────────────────────────────────────────
   Anything being refreshed in place — the running total on the items step, the
   live lists on the orders, quotes and issues grids — is marked aria-busy while
   its request is in flight. Both scripts already set the attribute; this is what
   makes it visible, so a figure a moment behind does not read as a figure that is
   wrong. Deliberately faint and slow to appear: a fast refresh should not flash — and
   the grids now mark themselves busy on the keystroke rather than when the request goes
   out, so the wait is what keeps ordinary typing from strobing the rows. It matches the
   pill's delay below, so the fade and the words arrive together. */
[aria-busy="true"] {
    opacity: .55;
    transition: opacity .12s ease-in .35s;
}

/* The fade alone is easy to miss on a full-width grid, so a refresh that is taking a
   moment also says so in words. One pill, two placements:

     .live-list-busy       pinned over the top edge of the results it belongs to, for the
                           grids that refresh in place (live-list.js)
     .busy-pill-floating   fixed under the topbar, for a filter form that reloads the whole
                           page and has no in-place results to sit against (site.js)

   Both stay invisible until .35s in — a filter that answers quickly should not flash a
   spinner at anyone — and neither one moves the page when it appears: the first is
   absolutely positioned, the second fixed. */
.busy-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 12px;
    font-size: 12px;
    font-weight: 600;
    color: var(--navy);
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: 999px;
    box-shadow: var(--shadow-md);
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    transition: opacity .12s ease;
}

.busy-pill i {
    display: inline-block;
    animation: busy-pill-spin .9s linear infinite;
}

@keyframes busy-pill-spin {
    to { transform: rotate(360deg); }
}

/* The wrapper live-list.js puts around the results container, so the pill can be
   positioned against the rows rather than against the page. */
.live-list-wrap {
    position: relative;
}

.live-list-busy {
    position: absolute;
    top: -12px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 3;
}

/* Shown as soon as it is in the document: an element that has only just been appended has
   no earlier opacity to animate from, so a transition on it does nothing and it appears at
   once regardless. The .35s wait is therefore kept in script for this one — see site.js —
   rather than pretended at here. */
.busy-pill-floating {
    position: fixed;
    /* Top centre of the window. There is no desktop top bar — pages carry their own
       heading, left-aligned — so the middle of that line is empty on every page, and on
       mobile the only thing up there is the sidebar toggle, over on the left. */
    top: 14px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 1080;
    opacity: 1;
}

/* The live-list pill is in the document from the start, so it can be faded properly. The
   delay lives on the shown state only: coming in it waits, going out it goes. */
.live-list-wrap.is-busy .live-list-busy {
    opacity: 1;
    transition-delay: .35s;
}

/* Less movement asked for: the words still say "updating", without the spin. */
@media (prefers-reduced-motion: reduce) {
    .busy-pill i { animation: none; }
}

/* ── Payment step: copy buttons ──────────────────────────────
   The row of "copy this to the clipboard" buttons at the top of the
   Payment card. .copy-source is the field each button copies from:
   pushed off-screen rather than display:none, because the clipboard
   fallback in copy-field.js copies a selection and a hidden field
   cannot be selected. Not .visually-hidden — that clips rather than
   moves, and the selection goes with the clip in some browsers. */
.pay-copy {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
}

.pay-copy-label {
    color: var(--text-muted);
    font-size: .8125rem;
}

.copy-source {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    opacity: 0;
}

/* ── Order details: payment awaiting funds ───────────────────
   The panel on the payment card where the office records a bank
   transfer, cash or cheque arriving. Amber rather than red: nothing
   has gone wrong, the money simply has not landed yet. */
.pay-outstanding {
    border: 1px solid var(--border);
    border-left: 3px solid #B4741A;
    border-radius: 6px;
    background: var(--surface-muted);
    padding: 12px;
}

/* The customer's PO document, on the invoiced-on-account line. A
   hairline under the heading rather than a box of its own, so it
   reads as part of the one line and not a second thing to deal with. */
.pay-po-file {
    border-top: 1px solid var(--border);
    min-height: 40px;
}

/* ── Payment step: amount options ────────────────────────────
   Deposit / Paid in full / Other on the order wizard's Payment step.
   Buttons rather than radios: nothing here is posted, and the amount
   box below stays editable after one is clicked. Built from the same
   tokens as .btn-adman-outline so the group reads as part of the form
   rather than as three actions competing with Continue. */
.pay-preset-group {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.pay-preset {
    flex: 1 1 140px;
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 8px 12px;
    text-align: left;
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    color: var(--text);
    cursor: pointer;
    transition: border-color 0.12s ease, background 0.12s ease;
}

.pay-preset:hover:not(:disabled) {
    background: var(--surface-muted);
}

.pay-preset:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px var(--navy-ring);
}

/* Lit by aria-pressed rather than a class, so the state a screen reader
   is told is the same one the eye is shown — one attribute, not two
   that can drift. */
.pay-preset[aria-pressed="true"] {
    border-color: var(--navy);
    background: var(--navy-tint);
    box-shadow: inset 0 0 0 1px var(--navy);
}

.pay-preset:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}

.pay-preset-label {
    font-size: 12.5px;
    font-weight: 600;
}

.pay-preset-figure {
    font-size: 12px;
    color: var(--text-muted);
}

.pay-preset[aria-pressed="true"] .pay-preset-figure { color: var(--navy); }

/* ── Save-as-you-go note ──────────────────────────────────────
   The line that says what the last automatic save did. Shared by Products > Details, where the
   note replaced nine Save buttons, and by every wizard step, where it sits beside a Continue
   button that is still there.

   Quiet when it says nothing happened, loud only when something was refused: a screen that saves
   on every keystroke cannot flash green each time without becoming wallpaper. */
.autosave-note { font-size: 12.5px; color: var(--text-subtle); }
.autosave-busy { opacity: .6; }
.autosave-ok   { color: var(--success, #2e7d32); }
.autosave-bad  { color: var(--danger); font-weight: 600; }
/* A refused save: the note reads as an alert rather than a caption, and the row's controls are
   outlined for a few seconds (products-autosave.js) so it cannot be mistaken for a save. */
.autosave-note.autosave-bad {
    display: block; margin-top: 4px; padding: 6px 10px; border-radius: 6px;
    background: color-mix(in srgb, var(--danger) 14%, transparent);
}
.autosave-refused select,
.autosave-refused .picker-combo-btn,
.autosave-refused button[aria-haspopup] {
    outline: 2px solid var(--danger); outline-offset: 1px;
}

/* The internal note's own Save button, on all four boxes that show that field. Hidden only where
   the class is on <html>, which _Layout.cshtml puts there in <head> — before first paint, or a tick
   shows beside every note on the orders list for a second and then disappears — and takes back off
   if note-autosave.js turns out not to have run. Without that script the button is the only way to
   save a note.

   Done in CSS rather than by setting hidden on each button so that a row rendered afterwards is
   covered too: the two list cells sit inside a results table that live-list.js replaces wholesale
   as the filters are typed into. */
.js-note-autosave [data-note-save] { display: none; }

/* ── Sage company search ─────────────────────────────────
   Suggestions under the Company box on the Customer step, plus the chip
   naming the account the order will be invoiced to. Borrows the combobox
   panel's geometry so the two read as the same control; its parent needs
   position-relative, which the view sets. */
.sage-company-panel {
    position: absolute;
    z-index: 20;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    min-width: 240px;
    max-height: 300px;
    overflow-y: auto;
    padding: 4px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    background: var(--surface);
    box-shadow: 0 10px 28px rgba(0,0,0,0.14);
}

.sage-company-row {
    display: flex;
    align-items: baseline;
    gap: 8px;
    width: 100%;
    padding: 7px 10px;
    border: 0;
    border-radius: calc(var(--radius) - 2px);
    background: none;
    text-align: left;
    font-size: 13px;
    cursor: pointer;
}

.sage-company-row:hover,
.sage-company-row.is-active { background: var(--surface-muted); }

.sage-company-number {
    margin-left: auto;
    color: var(--text-subtle);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
}

/* The account the company name points at rather than one the search matched:
   "HSE Waterford" naming the HSE's own account. It sits first and looks it,
   because a row bearing no resemblance to what was typed needs to say why it
   is there. Brand navy rather than a colour that reads as a warning — this is
   a suggestion, and taking it is the ordinary outcome. */
.sage-company-row.is-recommended { background: var(--navy-tint); }

.sage-company-recommended {
    padding: 1px 6px;
    border-radius: 999px;
    background: var(--navy);
    color: #fff;
    font-size: 11px;
    font-weight: 600;
    white-space: nowrap;
}

/* An account on hold is still findable — hiding it only gets a duplicate
   opened — so it is marked rather than left out. */
.sage-company-hold {
    padding: 1px 6px;
    border-radius: 999px;
    background: var(--surface-muted);
    color: var(--danger);
    font-size: 11px;
    font-weight: 600;
    white-space: nowrap;
}

/* Not "no results": a customer with no Sage account is the ordinary case. */
.sage-company-none {
    padding: 8px 10px;
    color: var(--text-subtle);
    font-size: 13px;
}

.sage-company-chip {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 6px;
    padding: 6px 10px;
    border-radius: var(--radius);
    background: var(--surface-muted);
    font-size: 12px;
}

/* The company box no longer says what the account says. It stays bound on purpose — see
   sage-company-search.js — so this has to be loud enough that nobody misses it, without looking
   like an error the form will refuse. */
.sage-company-chip.is-mismatch {
    background: var(--st-pending-bg);
    border: 1px solid var(--alert-warning-border);
    color: var(--st-pending-fg);
}

.sage-company-chip.is-mismatch .bi-exclamation-triangle-fill { color: var(--st-pending-fg); }

/* A real button here, not the underlined link the matching state wears — this is the state where
   somebody has to be able to find it. */
.sage-company-chip.is-mismatch .sage-company-clear {
    padding: 3px 10px;
    border-radius: var(--radius);
    border: 1px solid var(--st-pending-fg);
    color: var(--st-pending-fg);
    text-decoration: none;
}

.sage-company-clear {
    margin-left: auto;
    padding: 0;
    border: 0;
    background: none;
    color: var(--navy);
    font-size: 12px;
    font-weight: 600;
    text-decoration: underline;
    cursor: pointer;
}

/* ── Returning-customer suggestion ──────────────────────
   Sits above the delivery address on the Customer step and offers the
   Sage account a returning customer already has. Deliberately quieter
   than the mismatch warning beside it: this is an offer, not a problem,
   and it appears while somebody is still typing. */
/* The "who is billed" line above the matches. It was --text-subtle at 12px, i.e. the quietest
   thing on the box, while saying the one thing a rep has to take in before pressing anything on
   it. Readable now, with the account itself in the body colour. */
.sage-match-note {
    margin: 0 0 8px;
    color: var(--text-muted);
    font-size: 12.5px;
    line-height: 1.5;
}

.sage-match-note strong { color: var(--text); }

/* One offer, as a card. The buttons used to sit inline after the sentence and wrapped onto their
   own line at most widths anyway, landing wherever the text happened to run out — so they have a
   row of their own now, and the record they act on is readable above them. */
.sage-match {
    padding: 10px 12px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    background: var(--surface-muted);
    font-size: 13px;
}

.sage-match + .sage-match { margin-top: 8px; }

.sage-match-head {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 8px;
}

.sage-match-who { font-weight: 600; }

/* Why this customer came back, set apart from their name so the two do not read as one phrase. */
.sage-match-why {
    margin-left: auto;
    color: var(--text-subtle);
    font-size: 12px;
}

.sage-match-from {
    margin-top: 2px;
    color: var(--text-subtle);
    font-size: 12px;
}

/* What the match is built on, field by field. Three states, because "different" and "nothing to
   compare" are not the same thing and the old one-line reason could not tell them apart. */
.sage-match-basis {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 5px;
    margin-top: 6px;
}

.sage-match-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 1px 7px;
    border: 1px solid var(--border);
    border-radius: 999px;
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    white-space: nowrap;
}

.sage-match-chip .bi { font-size: 10px; }

/* The page's own navy, not a green.
   It was --st-delivered-*, borrowed from the seven-state badge palette — the colour an order wears
   when it has been Delivered. That palette says "this document is in this state", which is not
   what a chip on a suggestion is saying at all, and none of it is otherwise on this screen, so it
   read as a colour from somewhere else.

   Navy is the accent this step already uses for the section captions and the accept button, so a
   field that agrees now looks like part of the page. Deliberately without a hover or a pointer:
   navy is also the action colour here, and a chip is a statement rather than something to press. */
.sage-match-chip.is-agrees {
    border-color: var(--navy-ring);
    background: var(--navy-tint);
    color: var(--navy);
}

/* Amber, not red. A name that disagrees is the couples case this exists to catch — it is the
   reason to look, not an error. */
.sage-match-chip.is-differs {
    border-color: var(--alert-warning-border);
    background: var(--st-pending-bg);
    color: var(--st-pending-fg);
}

.sage-match-chip.is-blank {
    background: var(--surface);
    color: var(--text-subtle);
}

/* A button rather than an underlined link. It opens a panel on the page — it does not go
   anywhere — and as blue underlined text among two other blue underlined things it read as one of
   the several links on this box rather than as the control it is. Quieter than .sage-match-use,
   because seeing the detail is not the decision; using the customer is. */
.sage-match-toggle {
    margin-top: 8px;
    padding: 4px 10px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--text-muted);
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
}

.sage-match-toggle:hover {
    border-color: var(--navy);
    color: var(--navy);
}

.sage-match-detail {
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid var(--border);
}

/* Two columns on anything but a phone: the label, then the held value with what it would do to
   the box beside it. */
.sage-match-fields {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 2px 12px;
    margin: 0;
    font-size: 12px;
}

.sage-match-fields dt {
    color: var(--text-subtle);
    font-weight: 500;
}

.sage-match-fields dd {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: 0;
}

.sage-match-value { font-weight: 500; }

.sage-match-fill,
.sage-match-same,
.sage-match-kept {
    padding: 0 6px;
    border-radius: 999px;
    font-size: 11px;
    line-height: 18px;
    white-space: nowrap;
}

.sage-match-fill { background: var(--navy-tint); color: var(--navy); }
.sage-match-same { background: var(--surface); color: var(--text-subtle); }

/* The one worth noticing: nothing typed is ever overwritten, so this says which of the two values
   is being kept. Warning colours rather than error — it is a choice, not a fault. */
.sage-match-kept {
    background: var(--st-pending-bg);
    color: var(--st-pending-fg);
    white-space: normal;
}

.sage-match-docs { margin-top: 8px; }

.sage-match-docs-head {
    color: var(--text-subtle);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
}

.sage-match-docs ul {
    margin: 2px 0 0;
    padding-left: 18px;
    font-size: 12px;
}

.sage-match-actions {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 10px;
}

.sage-match-use {
    padding: 4px 12px;
    border: 1px solid var(--navy);
    border-radius: var(--radius);
    background: none;
    color: var(--navy);
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
}

.sage-match-use:hover { background: var(--navy-tint); }

/* What a hidden match folds down to: who it was, and the way back. The row keeps its contents
   hidden rather than dropping them, so Show costs nothing and asks the server nothing. */
.sage-match-collapsed {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    color: var(--text-muted);
}

.sage-match-collapsed > span {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sage-match-show {
    flex-shrink: 0;
    padding: 0;
    border: 0;
    background: none;
    color: var(--navy);
    font-size: 12px;
    font-weight: 600;
    text-decoration: underline;
    cursor: pointer;
}

/* Also a button now, and matched to the toggle rather than to "use": Hide and See-what-Adman-holds
   are both ways of managing the box, while using the customer is the one thing that changes the
   order. Kept lighter than both so it never competes with the offer it sits beside. */
.sage-match-dismiss {
    padding: 4px 10px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: none;
    color: var(--text-muted);
    font-size: 12px;
    font-weight: 500;
    cursor: pointer;
}

.sage-match-dismiss:hover {
    border-color: var(--border-strong);
    color: var(--text);
}

/* ── Wizard form sections ─────────────────────────────────────
   The Customer step is five cards rather than the one it used to be, and this is what holds
   them to a single column. The width lived in an inline style="max-width:820px" on the card
   itself, which meant the step's measure was set in the markup and could only be changed in one
   view at a time.

   940px rather than 820: at 820 a two-across row gave each field about 390px, and the county
   combobox and the country list are both wider than that reads comfortably. It is still a
   measure, not the full page — a form read top to bottom wants a column, and stretching these
   rows across a 27" monitor would put the labels and their boxes acres apart. */
.form-stack {
    max-width: 940px;
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* The section headings, scoped to .form-stack so the .card-header everywhere else in the app is
   untouched.

   These are signposts through one form, not titles of five separate things. At the app's usual
   card weight -- 14px semibold, full padding, a rule under it -- five of them stacked read as
   five documents, and competed with the field labels for attention. A small muted caption says
   "you are here" and then gets out of the way. */
.form-stack > .card > .card-header {
    padding: 11px 20px 9px;
    border-bottom: 0;
}

.form-stack > .card > .card-header .card-title {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--text-subtle);
}

/* The body carries the padding the header gave up, so the first field is not tight under it. */
.form-stack > .card > .card-body {
    padding-top: 4px;
}

/* .card sets overflow:hidden globally, to keep its children inside its rounded corners. On this
   form that clipped the email check's message the moment it became an overlay: the box hangs
   below the field it explains, which on the last field of a section is past the card's own edge,
   so the sentence was sliced off mid-line.

   Safe to lift here. Nothing in these five cards relies on the clip — no child sits against an
   edge, and the header shares the card's own background — and the combobox menus that open near
   the bottom of a section had the same problem. */
.form-stack > .card {
    overflow: visible;
}

/* The returning-customer panel's reserved band.

   sage-customer-suggest.js owns #sageMatchSuggest completely — it writes the rows into its
   innerHTML and empties it again on hide — so the panel cannot hold anything of ours. This is
   the wrapper around it instead.

   Why reserve the space at all: the panel sits between the contact boxes that trigger it and the
   delivery address, and it appears on blur. Without a band held open, tabbing out of the phone
   box made the whole address card jump down the page, under the cursor of a rep already reaching
   for Address line 1. The hint holds the height when there is no match — which is most of the
   time — so the common case now moves nothing at all, and it doubles as the only place the
   feature is explained. */
.form-suggest-slot {
    margin-top: 16px;
}

/* A quiet note, not a panel. It began as a dashed, tinted box, which gave a line that says
   "nothing has happened yet" the same visual weight as the fields around it — two boxes of
   explanation stacked under one column was the bulk complained about. Now it is one small muted
   line: it still holds the band open, and it stops shouting. */
.form-suggest-hint {
    display: flex;
    align-items: baseline;
    gap: 6px;
    margin: 0;
    padding: 0 2px;
    color: var(--text-subtle);
    font-size: 12px;
    line-height: 1.4;
}

.form-suggest-hint .bi {
    font-size: 12px;
}

/* The hint steps aside the moment the panel has something to say, so the two are never both on
   screen explaining the same thing. :has() rather than a class toggled from the script, because
   the script's contract is that it owns the panel and knows nothing about what surrounds it —
   adding a line to it here would be a second place to keep in step. */
.form-suggest-slot:has(> #sageMatchSuggest:not(.d-none)) .form-suggest-hint {
    display: none;
}

/* The invoice address fields, revealed by the tick above them.

   An inset region rather than the .card-inside-a-.card this replaced: a nested card carried the
   same border, radius and shadow as the five real sections, so an optional sub-block read with
   exactly the weight of the step around it. */
.billing-fields {
    margin-top: 14px;
    padding: 16px;
    border: 1px solid var(--border);
    border-left: 3px solid var(--navy);
    border-radius: var(--radius);
    background: var(--surface-page);
}

/* The same inset, holding the address Sage owns. Shown rather than described, but unmistakably
   not yours to type in: the left edge goes grey rather than navy, and the boxes are disabled. */
.billing-fields.is-locked {
    border-left-color: var(--border-strong);
}

.billing-fields.is-locked .form-control:disabled {
    background: var(--surface-muted);
    color: var(--text-muted);
    cursor: default;
}

/* The dialling-code suggestion under a phone box.

   One row: which country it thinks this is, the button that switches to it, and a dismiss. The
   sentence explaining the reasoning is on the row's title — it took a second line, and on this
   step a second line of explanation under one field was the whole complaint.

   An overlay, like the email message, so appearing costs the page no height. Unlike that one it
   is NOT hover-gated: it carries buttons the rep has to travel to, and an offer that vanished
   when the pointer left would be both unreachable and undiscoverable. Only the reason hides.

   Opaque and shadowed because it covers real content. `top: auto` leaves it at its static
   position, directly under the control it is about. */
/* In the flow, always — the opposite of .email-domain-warning above, and deliberately so.
   The email note is a transient explanation: it appears under the pointer and goes away, so
   floating over what follows costs nothing. This one persists and carries the buttons that answer
   it, so floating would leave it sitting on top of a number for as long as it is up. On a stack of
   secondary numbers that is exactly the fault it was reported for. A transient explanation floats;
   an actionable offer takes the space. */
.phone-region-suggest {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 6px;
    padding: 5px 8px 5px 10px;
    border-radius: var(--radius-sm);
    background: var(--st-pending-bg);
    border: 1px solid var(--alert-warning-border);
    box-shadow: var(--shadow-md);
    color: var(--st-pending-fg);
    font-size: 12px;
    white-space: nowrap;
}

/* Between the extra-phone rows it keeps the list's own spacing rather than adding its own. */
.extra-contacts-rows .phone-region-suggest {
    margin-top: 0;
}

/* Truncates rather than wrapping to a second line, which is the one thing this row must not do.
   The full label is on the title alongside the reason. */
.phone-region-label {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.phone-region-use {
    flex-shrink: 0;
    padding: 2px 9px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--st-pending-fg);
    background: none;
    color: var(--st-pending-fg);
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
}

.phone-region-use:hover {
    background: var(--st-pending-fg);
    color: var(--surface);
}

/* A bare × rather than "Keep +353". The label it replaces is on its title and aria-label, and at
   one row the words were competing with the button that matters. */
.phone-region-dismiss {
    flex-shrink: 0;
    width: 20px;
    padding: 0;
    border: 0;
    background: none;
    color: var(--st-pending-fg);
    font-size: 15px;
    line-height: 1;
    opacity: 0.7;
    cursor: pointer;
}

.phone-region-dismiss:hover { opacity: 1; }

.billing-locked-note {
    display: flex;
    align-items: baseline;
    gap: 6px;
    margin: 0;
    color: var(--text-muted);
    font-size: 12.5px;
    line-height: 1.45;
}

.billing-locked-note .bi { color: var(--text-subtle); font-size: 12px; }

@media (max-width: 575.98px) {
    .sage-match-fields { grid-template-columns: 1fr; }
    .sage-match-fields dd { margin-bottom: 4px; }
    .sage-match-why { margin-left: 0; }
}

/* A company off Adman's own documents rather than a Sage account — no number to show, so it shows
   the evidence instead. See sage-company-search.js. */
.sage-company-seen {
    margin-left: auto;
    color: var(--text-subtle);
    font-size: 11px;
}

/* Loud on purpose. The consequence — no Sage account, so this order is invoiced directly and a new
   account is not created for them — is the one thing about the row somebody must not skim past.
   Amber rather than red: invoicing a company directly is ordinary, it just has to be unmissable. */
.sage-company-direct {
    padding: 0 7px;
    border: 1px solid var(--alert-warning-border);
    border-radius: 999px;
    background: var(--st-pending-bg);
    color: var(--st-pending-fg);
    font-size: 10px;
    font-weight: 700;
    line-height: 17px;
    letter-spacing: .05em;
    text-transform: uppercase;
    white-space: nowrap;
}

/* ── Print ────────────────────────────────────────────────────
   These pages get printed for meetings. Drop the controls, keep the
   figures, and let the charts out of their scrolling box. */
/* ── Branches: which of a partner's shops a sale came from ─────────────────────────
   Eighteen rows of shop, code, account and two buttons. Everything here is about taking weight
   out of the repeat: the row says its name once, the code sits under it rather than beside it,
   and the buttons that are rarely pressed stop competing with the one that is. */
.branch-table .branch-name { font-weight: 600; }

.branch-table .branch-code {
    margin-top: 1px;
    font-size: 11.5px;
    color: var(--text-subtle);
    /* A column of codes reads as a column when the digits line up. */
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.2px;
}

/* The exception the column exists for: a shop invoiced to an account of its own rather than its
   chain's. B&Q's six northern shops are the whole reason this is configurable, and six marked
   edges answer "which ones" faster than eighteen dropdowns can be read. Inset rather than a
   border, so the cell's text stays on the same line as every other row's. */
.branch-table tr.branch-elsewhere td:first-child {
    box-shadow: inset 3px 0 0 var(--navy);
}

.branch-actions {
    display: flex;
    gap: 6px;
    align-items: center;
    justify-content: flex-end;
}

/* Close and Delete are rare and irreversible-ish; Save is neither. Quieting the two keeps the
   row's one ordinary action the one that looks like an action. Both still pass focus-visible
   and keep their full hit area. */
.branch-table .branch-quiet {
    border-color: transparent;
    color: var(--text-muted);
}

.branch-table .branch-quiet:hover:not(:disabled) {
    border-color: var(--border-strong);
    color: var(--text);
}

.branch-delete-slot {
    display: inline-block;
    width: var(--control-height-sm);
}

.branch-table .branch-delete:hover:not(:disabled) {
    border-color: var(--danger);
    color: var(--danger);
}

/* Shown by branch-account.js once the account is changed. The rule is here rather than left to
   the [hidden] attribute alone because .d-flex on the form would otherwise win it back. */
.branch-table .branch-save[hidden] { display: none !important; }

/* ── Billing rules: one shed on the left, the codes it maps to on the right ──────────────
   Two columns rather than two cards. What a partner is invoiced for a shed is one fact, and
   splitting it by how many codes it took made the screen read as two settings. */
.billing-map td { vertical-align: top; }

.billing-codes {
    list-style: none;
    margin: 0;
    padding: 0;
}

.billing-codes li {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 0;
    border-bottom: 1px solid var(--border);
}

.billing-codes li:last-child { border-bottom: 0; }

/* The name gives way first: a code, a price and the way to remove it all have to stay legible,
   and the name is the one of the four that can be guessed from the code. */
.billing-code-name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 13px;
}

.billing-code-price {
    flex-shrink: 0;
    font-size: 13px;
    font-variant-numeric: tabular-nums;
}

/* What the mapping comes to against Adman's own price - the figure the whole arrangement rests
   on, so it sits under the codes rather than in a footer row away from them. */
.billing-total {
    margin-top: 8px;
    font-size: 12px;
    color: var(--text-muted);
}

@media (max-width: 700px) {
    .billing-codes li { flex-wrap: wrap; }
    .billing-code-name { flex-basis: 100%; white-space: normal; }
}


/* ── An extras purchase order attached to no order ───────────────────────────
   Loud on purpose, and the loudest thing on any page it appears on. An
   accessory with no shed behind it is a real document a real customer is
   waiting for, but it is also the one that falls down the back of the desk:
   nobody is chasing it, no order references it, and it is invisible on every
   list that counts sheds. So it says so on every screen it appears on.

   Built from the existing tokens rather than fresh hexes, which is what keeps
   it legible in dark mode - a hard-coded navy panel with hard-coded cream text
   inverts into something unreadable, and this banner exists to be read. */
.extras-unlinked {
    display: flex;
    gap: 12px;
    align-items: flex-start;
    background: var(--navy);
    color: #FFFFFF;
    border-radius: 8px;
    padding: 14px 16px;
    margin-bottom: 16px;
    border-left: 6px solid var(--danger);
}

.extras-unlinked .extras-unlinked-title {
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    font-size: 13px;
    margin-bottom: 2px;
}

.extras-unlinked p { margin: 0; font-size: 14px; opacity: 0.92; }

.extras-unlinked a {
    color: #FFFFFF;
    text-decoration: underline;
    white-space: nowrap;
}

/* Still waiting on somebody rather than settled - quieter, because this one is
   an ordinary piece of outstanding work and the banner above is not. */
.extras-undecided {
    border-left: 6px solid var(--navy);
    background: var(--surface-muted);
    color: var(--text);
    border-radius: 8px;
    padding: 12px 16px;
    margin-bottom: 16px;
}

/* ── Extras-only PO: "Which order are these for?" (Views/PurchaseOrders/LinkOrder.cshtml) ──
   Kept above .extras-link-why below, which is an unclosed block in this file. */
.link-summary .card-body {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px 32px;
    font-size: 14px;
}
.link-label {
    font-size: 11.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: var(--text-muted);
    margin-bottom: 2px;
}
.link-muted { color: var(--text-muted); }
.link-accessories { margin: 0; padding-left: 18px; }

.link-search { display: flex; gap: 8px; margin-bottom: 16px; }
.link-search .form-control { flex: 1 1 auto; min-width: 0; }

.link-empty { margin: 0 0 16px; color: var(--text-muted); font-size: 14px; }

.link-cards { display: grid; gap: 10px; margin-bottom: 16px; }
.link-card { display: block; margin: 0; cursor: pointer; position: relative; }
.link-card-input {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
    pointer-events: none;
}
.link-card-body {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 12px 14px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-lg);
    background: var(--surface);
    font-size: 14px;
    transition: border-color 0.12s, background-color 0.12s, box-shadow 0.12s;
}
.link-card:hover .link-card-body { border-color: var(--navy); }
.link-card-input:checked + .link-card-body {
    border-color: var(--navy);
    background: var(--navy-tint);
    box-shadow: 0 0 0 1px var(--navy);
}
.link-card-input:focus-visible + .link-card-body { box-shadow: 0 0 0 3px var(--navy-ring); }
.link-card-top { display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap; }
.link-card-number { font-size: 15px; }
.link-card-pills { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.link-card-pills .badge-status::before { display: none; }
.link-card-kind::before { display: none; }
/* Once one is chosen, link-order.js hides the rest and shows the Change button. */
.link-card[hidden] { display: none; }
.link-change-row { margin: -6px 0 16px; }
.link-change {
    background: none;
    border: 0;
    padding: 0;
    color: var(--navy);
    font-size: 14px;
    text-decoration: underline;
    cursor: pointer;
}
.link-change:focus-visible { outline: 0; box-shadow: 0 0 0 3px var(--navy-ring); border-radius: 2px; }

.link-address { border: 0; padding: 0; margin: 0 0 16px; min-width: 0; }
.link-address legend { float: none; width: auto; font-size: 14px; font-weight: 600; margin-bottom: 8px; }
.link-choice { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.link-choice label { margin: 0; cursor: pointer; position: relative; }
.link-choice input { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }
.link-choice span {
    display: block;
    padding: 10px 14px;
    text-align: center;
    font-size: 14px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-lg);
    background: var(--surface);
}
.link-choice label:hover span { border-color: var(--navy); }
.link-choice input:checked + span {
    border-color: var(--navy);
    background: var(--navy-tint);
    box-shadow: 0 0 0 1px var(--navy);
    font-weight: 600;
}
.link-choice input:focus-visible + span { box-shadow: 0 0 0 3px var(--navy-ring); }

.link-actions { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
.link-actions .btn-adman[disabled] { opacity: 0.5; cursor: not-allowed; }
.link-back { color: var(--text-muted); font-size: 14px; }

.link-notincore { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.link-notincore-title { margin: 0; font-weight: 600; font-size: 14px; }
.link-notincore .form-control { flex: 1 1 200px; min-width: 0; max-width: 280px; }

@media (max-width: 700px) {
    .link-summary .card-body { grid-template-columns: 1fr; }
    .link-choice { grid-template-columns: 1fr; }
    .link-search .btn-adman { padding-left: 12px; padding-right: 12px; }
    .link-notincore .form-control { max-width: none; flex-basis: 100%; }
}

/* Why an order is being suggested - "same Eircode, same phone number". Small
   and muted: it is evidence for a person, not a heading. */
.extras-link-why {
    font-size: 12px;
    color: var(--text-muted);
}

/* ── Replacing a purchase order: the comparison ────────────────────────────────
   Markup: Views/Orders/ComparePurchaseOrder.cshtml and _ProposedLineChange.cshtml.
   Behaviour: js/resource-share.js supplies the "accept everything" tick, and nothing else —
   the right-hand column is driven entirely by :has() below.

   The screen answers one question: is that the right shed. It answered it twice before, first as
   prose and then as three lists of changes, and both read as work. It is now what it always was —
   the order as it stands, the order as it would be, and a tick between them — so the question is
   answered by reading across rather than by assembling the answer from a list of deltas.

   The whole shed is on both sides, including the lines nothing is happening to. A column that
   showed only the differences would be a list of changes wearing two columns. */

.po-sbs {
    display: grid;
    /* Order now · what the purchase order asks for · the tick · the order after. The tick sits
       between the document and the result because that is the decision it makes: whether what the
       store asked for reaches the order. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 3rem minmax(0, 1fr);
    align-items: stretch;
}

/* ── The two headings ── */
.po-sbs-head {
    display: contents;
}

.po-sbs-col-head {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 12px 16px;
    background: var(--surface-muted);
    border-bottom: 1px solid var(--border);
}

/* The result is tinted, so where the eye should land survives a glance. Colour is not the only
   carrier — all three columns are labelled in words. */
.po-sbs-col-head.is-incoming {
    background: var(--navy-tint);
}

/* The store's own document. Held apart from both order columns because it is a different kind of
   thing: their paperwork, not ours, and the one column no tick on this screen can alter. */
.po-sbs-col-head.is-document {
    border-left: 1px solid var(--border);
    border-right: 1px solid var(--border);
}

.po-sbs-col-label {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--text-muted);
}

/* The store's own reference: the number the shop quotes down the phone, and the one a reissue
   actually changes. Adman's own number goes quietly underneath. */
.po-sbs-col-ref {
    font-size: 16px;
    font-weight: 700;
    color: var(--text);
    overflow-wrap: anywhere;
}

.po-sbs-col-meta {
    font-size: 12px;
    color: var(--text-muted);
}

.extras-no-parent {
    margin: 24px 0;
    padding: 12px 16px;
    border: 1px dashed var(--border);
    border-radius: 8px;
}

.extras-no-parent > summary {
    cursor: pointer;
    color: var(--text-muted);
    font-size: 14px;
}

@media (max-width: 700px) {
    .extras-unlinked { flex-direction: column; }
    .extras-unlinked a { white-space: normal; }
}

/* ── A line, read across ── */
.po-sbs-row {
    display: contents;
}

.po-sbs-cell {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 10px 16px;
    border-bottom: 1px solid var(--border);
    min-width: 0;
}

.po-sbs-row:last-child .po-sbs-cell,
.po-sbs-row:last-child .po-sbs-gutter { border-bottom: 0; }

.po-sbs-now { background: var(--navy-tint); }

/* Same separation on every row as on the heading, so the middle column reads as one band down the
   page rather than as part of whichever order column it happens to touch. */
.po-sbs-po {
    border-left: 1px solid var(--border);
    border-right: 1px solid var(--border);
}

/* Nothing is happening to this line. Faded back so the rows that are doing something carry the
   page, but still readable — the point of showing the whole shed is that it can be read. */
.po-sbs-row.is-same .po-sbs-line-name { color: var(--text-muted); font-weight: 500; }

/* The shed itself. Tinted rather than outlined, because a row cannot carry a border on three
   separate grid children without the corners going wrong. */
.po-sbs-row.is-shell .po-sbs-was,
.po-sbs-row.is-shell .po-sbs-po,
.po-sbs-row.is-shell .po-sbs-now,
.po-sbs-row.is-shell .po-sbs-gutter {
    box-shadow: inset 0 3px 0 var(--danger);
}

.po-sbs-line {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
}

.po-sbs-line-name {
    font-weight: 600;
    color: var(--text);
    overflow-wrap: anywhere;
}

.po-sbs-line-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    color: var(--text-muted);
}

.po-sbs-qty,
.po-sbs-money {
    font-variant-numeric: tabular-nums;
}

.po-sbs-qty { font-weight: 600; color: var(--text); }

/* A line that is not there on this side. An em dash rather than an empty cell, so a row that has
   lost something reads as deliberate rather than as a rendering fault. */
.po-sbs-none {
    color: var(--text-subtle);
    font-size: 18px;
    line-height: 1.2;
}

/* Named, not valued: a money change on a retail order, where the figures may not be shown. */
.po-sbs-suppressed {
    font-size: 12px;
    color: var(--text-muted);
    font-style: italic;
}

.po-sbs-badges {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
}

/* ── The gutter ── */
.po-sbs-gutter {
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: 12px 0;
    border-bottom: 1px solid var(--border);
}

.po-sbs-gutter .form-check-input { margin: 0; cursor: pointer; }

/* ── What the right-hand side says ──
   The whole point of the layout: the right column shows what pressing the button would leave
   behind, and follows the tick as it is clicked. Done with :has() rather than a script, the same
   way .picker-chip lights up, so the two can never fall out of step. */
.po-sbs-row:has(.po-sbs-tick:checked) .po-sbs-when-not { display: none; }

/* Scoped to rows that actually carry a tick. Without :has(.po-sbs-tick) this also matched the rows
   nothing is happening to — they have no tick, so they can never have a ticked one — and blanked
   the right-hand side of every unchanged line on the order.

   Watching .po-sbs-tick rather than .pick on purpose: .pick is the master tick's business and a
   direct-sale line deliberately does not carry it, but its right-hand column still has to follow
   its own box. */
.po-sbs-row:has(.po-sbs-tick):not(:has(.po-sbs-tick:checked)) .po-sbs-when-applied { display: none; }

/* A row whose change has been turned down: the right side is back to saying what the left says,
   so the tint is dropped to make that obvious at a glance. */
.po-sbs-row:has(.po-sbs-tick):not(:has(.po-sbs-tick:checked)) .po-sbs-now { background: transparent; }

/* What the store wanted, on a row where it has been turned down. Without it the right-hand
   column simply shows what the order already has, and somebody reading it cannot tell whether the
   store asked for nothing or whether they themselves declined it. Shown only where there is a
   different proposal to report. */
.po-sbs-declined {
    font-size: 12px;
    color: var(--text-subtle);
}

.po-sbs-declined .po-sbs-declined-value { text-decoration: line-through; }

/* The direct extras, below the grid. Three tracks become two: the store's new document has a
   column up there and these lines are on no store document at all, so a dash in it would read as
   an omission rather than as the fact that the shop never sold them. */
.po-sbs-extras {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 3rem minmax(0, 1fr);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
}

.po-sbs-extras .po-sbs-col-head { padding: 8px 14px; }

.po-sbs-extras .po-sbs-gutter .po-sbs-col-label { text-align: center; }

.po-sbs-note {
    grid-column: 1 / -1;
    padding: 8px 16px;
    font-size: 12.5px;
    color: var(--text-muted);
    background: var(--surface-muted);
    border-bottom: 1px solid var(--border);
}

/* ── Replace the purchase order: the compact compare screen ──
   Markup: Views/Orders/ComparePurchaseOrder.cshtml. Behaviour: js/compare-purchase-order.js.
   A short list of tickable sentences, the whole order behind a toggle, and one line of
   housekeeping. The grid rules above are reused for the table, in a read-only three-column form. */

.pr-warnings {
    display: flex;
    gap: 10px;
    align-items: flex-start;
    padding: 8px 14px;
    margin-bottom: 12px;
    font-size: 13.5px;
}

.pr-warnings > .bi { margin-top: 2px; }

.pr-warnings ul {
    margin: 0;
    padding: 0;
    list-style: none;
}

.pr-changes { padding: 6px 16px; }

.pr-list {
    margin: 0;
    padding: 0;
    list-style: none;
}

.pr-list > li { padding: 8px 0; border-bottom: 1px solid var(--border); }
.pr-list > li:last-child { border-bottom: 0; }

.pr-change {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    margin: 0;
    cursor: pointer;
    overflow-wrap: anywhere;
}

.pr-change .form-check-input { margin-top: 3px; flex: none; cursor: pointer; }

.pr-note {
    margin: 2px 0 0 28px;
    font-size: 12.5px;
    color: var(--text-muted);
}

.pr-note-block { margin: 8px 0; }

.pr-none {
    margin: 8px 0;
    color: var(--text-muted);
}

/* The whole-order table, closed until asked for. */
.pr-whole {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
}

.pr-whole > summary {
    padding: 10px 16px;
    cursor: pointer;
    font-weight: 600;
    color: var(--text-muted);
}

.pr-whole[open] > summary { border-bottom: 1px solid var(--border); }

/* Three columns: the tick column is not drawn, because the ticks are in the list above. */
.po-sbs-readonly { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr); }

.po-sbs-readonly .po-sbs-gutter { display: none; }

/* Said once, in the list above, so not on every row. */
.po-sbs-readonly .po-sbs-suppressed { display: none; }

/* A row whose tick is off follows the order as it stands. The :has() rules above do this where the
   box is inside the row; here the box is in the list, so the script marks the row instead. */
.po-sbs-row.is-off .po-sbs-when-applied { display: none; }
.po-sbs-row.is-off .po-sbs-now { background: transparent; }
.po-sbs-row:not(.is-off)[data-sync] .po-sbs-when-not { display: none; }

.pr-also {
    margin: 0 0 14px;
    font-size: 14px;
    color: var(--text-muted);
}

.pr-inline {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin: 0 10px 0 6px;
    color: var(--text);
    cursor: pointer;
}

.pr-inline .form-check-input { margin: 0; cursor: pointer; }

/* ── The candidate picker ──
   Markup: Views/Orders/ReplacePurchaseOrder.cshtml. One card per candidate rather than a table
   row, because the reasons it is ranked where it is are chips, and chips in a table cell wrap
   badly and read as an afterthought. */
.po-candidate {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    margin-bottom: 8px;
}

.po-candidate:hover { border-color: var(--border-strong); }

/* The likeliest one, and only when it actually scored — a "likeliest" on a row that matched
   nothing would be a guess wearing a rosette. */
.po-candidate.is-likeliest { border-color: var(--navy); background: var(--navy-tint); }

.po-candidate-main {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

/* What kind of document this row is. Spelled out rather than left to the number's shape: an
   order is 6020901 and a purchase order is 8020003, which nobody should have to know. */
.po-candidate-kind {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.po-candidate-ref {
    font-size: 16px;
    font-weight: 700;
    color: var(--text);
}

.po-candidate-meta {
    font-size: 12px;
    color: var(--text-muted);
}

.po-candidate-reasons {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-top: 2px;
}

@media (max-width: 760px) {
    /* Two columns cannot be read side by side on a phone, so the row becomes what it is underneath:
       was, then the tick, then would be — in that order, with the labels doing the work the
       column headings did. */
    .po-sbs { grid-template-columns: 1fr; }

    .po-sbs-head { display: none; }

    .po-sbs-row {
        display: block;
        border-bottom: 1px solid var(--border);
        padding: 8px 0;
    }

    .po-sbs-cell { border-bottom: 0; padding: 4px 16px; }

    .po-sbs-cell::before {
        font-size: 11px;
        font-weight: 700;
        letter-spacing: .04em;
        text-transform: uppercase;
        color: var(--text-muted);
    }

    .po-sbs-was::before { content: "Order now"; }
    .po-sbs-po::before  { content: "Purchase order asks for"; }
    .po-sbs-now::before { content: "Order after"; }

    /* The vertical rules only separate columns, and at this width there are none. */
    .po-sbs-po { border-left: 0; border-right: 0; }

    .po-sbs-gutter {
        justify-content: flex-start;
        padding: 4px 16px;
        border-bottom: 0;
    }

    .po-candidate { grid-template-columns: 1fr; }

    .po-sbs-readonly { grid-template-columns: 1fr; }

    /* The table's columns are the two purchase orders and the result, not "order now". */
    .po-sbs-readonly .po-sbs-was::before { content: "Old purchase order"; }
    .po-sbs-readonly .po-sbs-po::before  { content: "New purchase order"; }
    .po-sbs-readonly .po-sbs-now::before { content: "Order after"; }

    .pr-inline { display: flex; margin: 6px 0 0; }
}

@media print {
    .report-no-print,
    .chart-tooltip { display: none !important; }

    .card { break-inside: avoid; box-shadow: none; border: 1px solid #ccc; }

    .card-body[style*="overflow-x"] { overflow: visible !important; }

    /* Bars are the data — without this they print as empty outlines. */
    .chart-bar rect,
    .bar-fill { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
}

/* ── Special adaptations ──────────────────────────────────────
   The cards on the quote wizard's Adaptations step, the Review step and
   both Details pages — see Views/Shared/_SpecialAdaptations.cshtml. Every
   colour is a token, so the dark theme follows without a second block. */

.adapt-list { display: flex; flex-direction: column; gap: 12px; }

.adapt-card {
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--surface);
    overflow: hidden;
}

.adapt-card-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    padding: 10px 14px;
    background: var(--surface-muted);
    border-bottom: 1px solid var(--border);
}

.adapt-card-title {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    font-weight: 600;
    color: var(--navy);
    min-width: 0;
}

/* The number a rep and an admin use to talk about the same modification,
   so it reads as a label rather than as part of the sentence. */
.adapt-number {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: var(--navy);
    color: #fff;
    font-size: 12px;
    font-weight: 700;
    flex: none;
}

.adapt-card-actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.adapt-card-actions form { display: inline; }

.adapt-card-body { padding: 12px 14px; }

/* pre-line, and not wrap: the description is a 4,000-character free-text
   field and a rep's line breaks are how they separate one instruction from
   the next. Collapsing them runs the whole thing into one paragraph. */
.adapt-text {
    white-space: pre-line;
    margin: 0;
    overflow-wrap: anywhere;
}

.adapt-locked { margin: 8px 0 0; font-size: 13px; }

.adapt-warning {
    padding: 8px 10px;
    border-radius: 6px;
    background: var(--st-onhold-bg);
    color: var(--st-onhold-fg);
    font-size: 13px;
}

.adapt-pictures {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    padding: 0 14px 12px;
}

.adapt-picture { margin: 0; width: 140px; }

/* contain, not cover: a bespoke modification is judged by its whole
   outline, and cropping a portrait phone photo to a square hides the part
   the customer photographed it for. */
.adapt-picture img {
    width: 140px;
    height: 110px;
    object-fit: contain;
    background: var(--surface-muted);
    border: 1px solid var(--border);
    border-radius: 6px;
    display: block;
}

.adapt-picture figcaption {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    font-size: 12px;
    color: var(--text-muted);
    padding-top: 2px;
}

.adapt-picture-remove {
    border: none;
    background: none;
    color: var(--text-muted);
    padding: 0 2px;
    line-height: 1;
    cursor: pointer;
}

.adapt-picture-remove:hover { color: var(--danger); }

/* ── The add panel ──
   Set apart from the cards above it, so "what is already on the quote" and
   "what I am about to add" do not read as one list. */
.adapt-add {
    border: 1px dashed var(--border);
    border-radius: 10px;
    padding: 14px;
    margin-top: 14px;
    background: var(--surface-muted);
}

.adapt-add-title { font-weight: 600; color: var(--navy); margin-bottom: 8px; }

/* What a useful description says. The whole complaint about this screen was
   that it was a bare box with no hint of what to write in it. */
.adapt-prompts {
    margin: 0 0 10px;
    padding-left: 18px;
    font-size: 13px;
    color: var(--text-muted);
}

.adapt-prompts li { margin-bottom: 2px; }

/* Thumbnails of what has just been picked, before the form is submitted —
   so a rep can see they attached the right photo, and the wrong one is
   caught before it is on the quote. Filled in by adaptations.js. */
.adapt-previews { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }

/* Nothing picked yet, so no strip and no margin. It is empty on first paint and stays empty
   unless somebody attaches something, and those 8px were enough to push the whole Drawing column
   8px out of step with the Sage code column beside it on the review panel — label, control and
   help text all sitting at different heights, because the row aligns on its bottom edge. */
.adapt-previews:empty { display: none; }

.adapt-previews img {
    width: 92px;
    height: 72px;
    object-fit: contain;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 6px;
    display: block;
}

/* ── Retailer notice: "What the retailer now says" ─────────────
   One compact block per shed. Colour only on the short inline note, never a filled box, so it
   reads the same in light and dark. */
.nc-list { padding: 4px 16px; }

.nc-shed {
    padding: 12px 0;
    border-bottom: 1px solid var(--border);
}

.nc-shed:last-child { border-bottom: 0; }

.nc-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 6px 12px;
}

.nc-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; }
.nc-sep { color: var(--text-muted); }
.nc-status { color: var(--text-muted); font-size: 12px; }

.nc-changes { margin: 8px 0 0; line-height: 1.55; }
.nc-changes .nc-sep { margin: 0 6px; }
.nc-muted { color: var(--text-muted); font-size: 13px; }

.nc-note { margin: 6px 0 0; font-size: 13px; font-weight: 600; }
.nc-note-bad  { color: #c0392b; }
.nc-note-warn { color: #b7791f; }
.nc-note-info { color: #0b7a96; }

:root[data-theme="dark"] .nc-note-bad  { color: #ff8a7a; }
:root[data-theme="dark"] .nc-note-warn { color: #f0b84a; }
:root[data-theme="dark"] .nc-note-info { color: #5fd0e8; }

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .nc-note-bad  { color: #ff8a7a; }
    :root:not([data-theme="light"]) .nc-note-warn { color: #f0b84a; }
    :root:not([data-theme="light"]) .nc-note-info { color: #5fd0e8; }
}

.nc-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 10px;
}

.nc-leave { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 8px; margin: 0; }
.nc-leave .form-control { width: 15rem; max-width: 100%; }

.nc-quiet { color: var(--text-muted); text-decoration: none; padding-left: 4px; padding-right: 4px; }
.nc-quiet:hover { color: var(--text); text-decoration: underline; }

/* A settled shed: one muted line. */
.nc-done {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 10px;
    padding: 8px 0;
    border-bottom: 1px solid var(--border);
    color: var(--text-muted);
    font-size: 13px;
}

.nc-done:last-child { border-bottom: 0; }
.nc-done-note { min-width: 0; overflow-wrap: anywhere; }

/* ── Section help ──────────────────────────────────────────────
   A closed "What is this?" under a card header: one short paragraph of plain explanation, muted so
   it never competes with the card. */
.section-help {
    padding: 10px 16px 0;
    font-size: 13px;
    color: var(--text);
}

/* A pill that reads as a control: "?" badge, label, and a chevron that turns when open (Sean: the
   plain-text version was "hidden away"). */
.section-help > summary {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    list-style: none;
    cursor: pointer;
    user-select: none;
    padding: 3px 10px 3px 4px;
    border: 1px solid color-mix(in srgb, var(--navy) 45%, var(--border));
    border-radius: 999px;
    background: color-mix(in srgb, var(--navy) 10%, transparent);
    color: var(--text);
    font-weight: 600;
    font-size: 12.5px;
}
.section-help > summary::-webkit-details-marker { display: none; }
.section-help > summary::before {
    content: "?";
    display: inline-flex; align-items: center; justify-content: center;
    width: 18px; height: 18px; border-radius: 50%;
    background: var(--navy); color: #fff; font-size: 11px; font-weight: 800;
}
.section-help > summary::after {
    content: "";
    width: 6px; height: 6px; margin-left: 2px;
    border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
    transform: rotate(-45deg); transition: transform .15s ease;
}
.section-help[open] > summary::after { transform: rotate(45deg); }
.section-help > summary:hover { background: color-mix(in srgb, var(--navy) 18%, transparent); }
.section-help > summary:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }

.section-help > p {
    margin: 8px 0 4px;
    padding: 10px 14px;
    line-height: 1.55;
    max-width: 80ch;
    border-left: 3px solid var(--navy);
    border-radius: 6px;
    background: color-mix(in srgb, var(--navy) 8%, var(--surface-muted));
}

.section-help-text {
    margin: 0;
    padding: 8px 14px;
    font-size: 12.5px;
    color: var(--text-muted);
    border-bottom: 1px solid var(--border);
}
