/* WarehousePro — corporate black & red theme, v3 (token-driven design system) */

:root {
    /* ---------- BRAND (UNCHANGED — do not edit these six lines) ---------- */
    --red: #c8102e;
    --red-dark: #9c0c24;
    --red-glow: rgba(200, 16, 46, .35);
    --red-light: #fdecee;
    --black: #101014;
    --black-2: #17171d;
    --black-3: #26262e;

    /* ---------- BRAND, ADDITIVE ---------- */
    --red-tint: rgba(200, 16, 46, .08);   /* was hardcoded in .kpi/.scan-box/.sidebar glows */
    --red-tint-2: rgba(200, 16, 46, .16);

    /* ---------- SURFACES (existing three + promoted hardcodes) ---------- */
    --grey-bg: #f1f2f5;        /* UNCHANGED */
    --grey-line: #dcdee3;      /* UNCHANGED */
    --grey-mid: #8a8d95;       /* UNCHANGED */
    --white: #ffffff;          /* UNCHANGED */
    --surface: var(--white);
    --surface-2: #fafafb;      /* row hover + expanded colspan rows */
    --surface-3: var(--grey-bg);/* inline code chips, readonly inputs, kv payload */
    --surface-inset: #f7f7f9;  /* goods-in serial panel, inset wells */
    --line-soft: #ecedf0;      /* table td rule */
    --track: #eceef1;          /* gauge track, pick-progress track, .badge-dispatched bg */

    /* ---------- TEXT ---------- */
    --text: #1c1d22;           /* UNCHANGED — primary */
    --text-2: #40434c;         /* secondary body / field labels */
    --text-3: #55585f;         /* tertiary body copy */
    --text-4: #4a4d55;         /* long-form lede copy */
    --on-dark: #c9cad1;        /* sidebar nav text */
    /* Sidebar section headings. WAS #5a5d68, which measures 2.89:1 against the
       sidebar's #101014 — below the 4.5:1 needed for text, and it is real text
       (OPERATIONS / STOCK / OFFICE / SYSTEM). Aliased to --grey-mid, 5.72:1,
       which passes while staying well dimmer than the nav links themselves
       (--on-dark, 11.24:1) so the heading/link hierarchy is unchanged.
       Single consumer: .nav-section. */
    --on-dark-dim: var(--grey-mid);

    /* ---------- STATUS (UNCHANGED) ---------- */
    --ok: #1d7a3e;
    --ok-bg: #e3f4e9;
    --warn: #b96a00;
    --warn-bg: #fdf1e0;
    --info: #1f4e96;           /* NEW — was hardcoded in .flash-info */
    --info-bg: #e8eff9;        /* NEW */

    /* ---------- SPACING SCALE ---------- */
    --sp-0: 0;
    --sp-05: 2px;
    --sp-1: 4px;
    --sp-15: 6px;
    --sp-2: 8px;
    --sp-25: 10px;
    --sp-3: 12px;
    --sp-35: 14px;
    --sp-4: 16px;
    --sp-5: 20px;
    --sp-6: 24px;
    --sp-7: 32px;
    --sp-8: 40px;
    --sp-9: 56px;
    --sp-10: 72px;

    /* ---------- TYPE SCALE ---------- */
    --font-sans: "Segoe UI", system-ui, -apple-system, Arial, sans-serif;
    --font-mono: Consolas, "SF Mono", ui-monospace, monospace;

    --fs-100: 9px;      /* .brand-tag */
    --fs-150: 10px;     /* .nav-section, .bc-star */
    --fs-200: 11px;     /* table th, .badge */
    --fs-250: 11.5px;   /* .kpi-label, .bc-cmd, .qr-payload */
    --fs-300: 12px;     /* .kpi-sub, .app-foot */
    --fs-400: 12.5px;   /* field labels, .scan-hint, .btn-sm, .user-chip */
    --fs-450: 13px;     /* .tab-btn (was an undefined var() fallback) */
    --fs-500: 13.5px;   /* .btn, nav links, .quickscan */
    --fs-550: 14px;     /* form control text, .btn-ms (MS brand exception) */
    --fs-600: 14.5px;   /* BASE (body) */
    --fs-650: 15.5px;   /* h3, .card-collapse summary h2, .topbar h1 (mobile) */
    --fs-700: 16px;     /* mobile control text (anti-zoom floor), .card h2, .scan-box input */
    --fs-800: 18.5px;   /* .topbar h1 */
    --fs-900: 20px;     /* .detail-title, .scan-box input */
    --fs-1000: 30px;    /* .kpi-num */

    --lh-tight: 1.25;
    --lh-snug: 1.4;
    --lh-base: 1.55;
    --lh-relaxed: 1.75;

    --fw-normal: 400;
    --fw-medium: 500;
    --fw-semi: 600;
    --fw-bold: 700;
    --fw-black: 800;

    --ls-tight: .2px;
    --ls-caps: .7px;
    --ls-caps-wide: 1.4px;

    /* ---------- RADIUS SCALE ---------- */
    --radius-xs: 4px;
    --radius-sm: 6px;
    --radius-md: 8px;
    --radius-lg: 10px;
    --radius-xl: 14px;
    --radius-2xl: 18px;
    --radius-pill: 999px;
    --radius: var(--radius-lg);          /* ALIAS — existing rules keep working */

    /* ---------- ELEVATION TIERS ---------- */
    --elev-0: none;
    --elev-1: 0 1px 2px rgba(16,16,20,.05);
    --elev-2: 0 1px 2px rgba(16,16,20,.06), 0 4px 14px rgba(16,16,20,.05);
    --elev-3: 0 2px 6px rgba(16,16,20,.08), 0 10px 30px rgba(16,16,20,.08);
    --elev-4: 0 6px 16px rgba(16,16,20,.12), 0 22px 56px rgba(16,16,20,.12);
    --elev-5: 0 30px 80px rgba(0,0,0,.5);          /* login card on dark */
    --shadow: var(--elev-2);             /* ALIAS — existing rules keep working */
    --shadow-lift: var(--elev-3);        /* ALIAS */

    /* ---------- FOCUS RINGS ---------- */
    --ring: 0 0 0 3px rgba(200,16,46,.22);
    --ring-strong: 0 0 0 3px rgba(200,16,46,.38);
    --ring-dark: 0 0 0 3px rgba(16,16,20,.16);

    /* ---------- BORDER / LINE WIDTHS ---------- */
    --bw-hair: 1px;
    --bw-1: 1.5px;
    --bw-2: 2px;
    --bw-3: 3px;
    --bw-accent: 4px;
    --border: var(--bw-hair) solid var(--grey-line);
    --border-soft: var(--bw-hair) solid var(--line-soft);
    --border-strong: var(--bw-2) solid var(--grey-line);

    /* ---------- MOTION ---------- */
    --dur-1: 90ms;    /* colour-only micro transitions */
    --dur-2: 150ms;   /* default hover / focus */
    --dur-3: 240ms;   /* drawers, panels */
    --dur-4: 400ms;   /* gauge + progress fills */
    --ease-out: cubic-bezier(.22, .61, .36, 1);
    --ease-in-out: cubic-bezier(.4, 0, .2, 1);
    --ease-emphasis: cubic-bezier(.2, .9, .3, 1);
    --t-fast: var(--dur-1) var(--ease-out);
    --t-base: var(--dur-2) var(--ease-out);
    --t-slow: var(--dur-3) var(--ease-out);

    /* ---------- CONTROL GEOMETRY ---------- */
    --ctrl-pad-y: 9px;
    --ctrl-pad-x: 11px;
    --ctrl-min-h: 44px;
    --ctrl-radius: var(--radius-sm);
    --ctrl-border: var(--border);
    --check-size: 18px;
    --check-size-lg: 20px;
    --chevron-w: 12px;
    --chevron-h: 8px;

    /* ---------- CONTROL GLYPHS (data URIs — colour is baked in) ---------- */
    --chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.6 6 6.4 11 1.6' fill='none' stroke='%238a8d95' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    --chevron-red: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.6 6 6.4 11 1.6' fill='none' stroke='%23c8102e' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    --tick: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='10' viewBox='0 0 12 10'%3E%3Cpath d='M1 5.1 4.4 8.4 11 1.6' fill='none' stroke='%23ffffff' stroke-width='2.1' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    --dash: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='2' viewBox='0 0 10 2'%3E%3Cpath d='M1 1h8' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");

    /* ---------- Z-INDEX SCALE (documents existing values) ---------- */
    --z-topbar: 20;
    --z-suggest: 30;   /* autocomplete panel: clears the topbar, stays under the drawer */
    --z-overlay: 55;
    --z-drawer: 60;
    --z-toast: 100;

    /* app has no dark mode — without this, native controls (<select> popups,
       date pickers, etc.) render in OS dark mode against our light surfaces */
    color-scheme: light;
}

/* ---------- DESIGNER-LANE ADDITIONS beyond the exact §1.2 block ----------
   Three hardcoded colours in the pre-existing stylesheet were not among the
   nine neutrals §1.1 lists for promotion (they were missed by the scout
   sweep). AC-11 requires zero raw hex outside :root/@keyframes/.btn-ms, so
   they are promoted here rather than left as violations. See LOCKED report
   for the full justification. */
:root {
    --black-4: #141419;   /* sidebar gradient stop, was hardcoded */
    --black-5: #23232b;   /* login-wrap radial gradient stop, was hardcoded */
    --status-live: #2ecc71; /* scanner-status "connected" dot, was hardcoded */

    /* ---------- ACCESSIBILITY & TOUCH (phase 1 redesign) ---------- */
    /* Focus-visible outline on dark surfaces. The red --ring glows are
       tuned for light cards and vanish against the sidebar, so a white
       outline is used there instead — same pattern as .scan-bar buttons. */
    --ring-on-dark: 0 0 0 2px var(--white);
    /* Minimum touch target: WCAG 2.5.5 recommends 44px; 40px is the practical
       floor on dense toolbars (Apple HIG / Material both accept 40-44). */
    --touch-min: 40px;
    /* Sidebar focus accent bar width. */
    --focus-bar: 3px;
}

/* ---------- LAYOUT WIDTHS — deliberately none ----------
   A --content-max shell measure was tried here and REMOVED at the owner's
   explicit request: they want the app full-bleed on wide monitors. Do not
   reintroduce a max-width, a centred container, or growing inline gutters on
   .topbar / .content / .app-foot. The shell spans the window at every width;
   the only measures in this file are the prose ones (68ch on .card-lede and
   .prose), which are unrelated and stay. */

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
        scroll-behavior: auto !important;
    }
}

* { margin: 0; padding: 0; box-sizing: border-box; }

/* every native <summary> disclosure triangle is an affordance — always pointer */
summary { cursor: pointer; }

body {
    font-family: var(--font-sans);
    background: var(--grey-bg);
    color: var(--text);
    font-size: var(--fs-600);
}

.app { display: flex; min-height: 100vh; }

/* ---- sidebar ---- */
.sidebar {
    width: 236px; flex-shrink: 0;
    background: linear-gradient(180deg, var(--black) 0%, var(--black-4) 100%);
    color: var(--on-dark);
    display: flex; flex-direction: column;
    position: sticky; top: 0; height: 100vh;
    overflow-y: auto;
    scrollbar-width: thin;                      /* Firefox */
    scrollbar-color: var(--red) var(--black-2);
}
.sidebar::-webkit-scrollbar { width: 6px; }
.sidebar::-webkit-scrollbar-track { background: var(--black-2); }
.sidebar::-webkit-scrollbar-thumb { background: var(--red); border-radius: var(--radius-sm); }
.sidebar::-webkit-scrollbar-thumb:hover { background: var(--red-dark); }
.brand {
    padding: var(--sp-4) var(--sp-35) var(--sp-35);
    border-bottom: var(--bw-hair) solid var(--black-3);
    text-align: center;
}
.brand-logo {
    display: block; background: var(--white); border-radius: var(--radius-lg);
    padding: var(--sp-25) var(--sp-35);
    box-shadow: 0 0 18px var(--red-glow);
}
.brand-logo img { width: 100%; height: auto; display: block; }
.logo-full { max-width: 100%; height: auto; display: block; }
.brand-tag {
    margin-top: var(--sp-2); font-size: var(--fs-100); text-transform: uppercase;
    letter-spacing: 1.1px; color: var(--grey-mid); white-space: nowrap;
}

.sidebar nav { padding: var(--sp-25) var(--sp-25) var(--sp-35); flex: 1; }
.nav-section {
    font-size: var(--fs-150); text-transform: uppercase; letter-spacing: var(--ls-caps-wide);
    color: var(--on-dark-dim); padding: var(--sp-35) var(--sp-3) var(--sp-15);
}
.sidebar nav a {
    display: flex; align-items: center; gap: var(--sp-25);
    padding: var(--sp-2) var(--sp-3); margin-bottom: 1px;
    color: var(--on-dark); text-decoration: none;
    border-radius: var(--radius-sm); font-weight: var(--fw-medium); font-size: var(--fs-500);
    border-left: var(--bw-3) solid transparent;
    transition: background-color var(--t-fast), color var(--t-fast);
}
.sidebar nav a:hover { background: var(--black-3); color: var(--white); }
.sidebar nav a.active {
    background: var(--black-3); color: var(--white);
    border-left-color: var(--red);
    box-shadow: inset 0 0 20px var(--red-tint);
}
.nav-icon { width: 20px; text-align: center; color: var(--red); font-size: var(--fs-400); }
.nav-badge {
    margin-left: auto; background: var(--red); color: var(--white);
    font-size: var(--fs-250); font-weight: var(--fw-black); border-radius: var(--radius-pill); padding: 1px var(--sp-15);
}

/* My TechHub remains visually separate from the existing warehouse sections. */
.sidebar .nav-section-mytechhub,
.sidebar .nav-mytechhub .nav-icon { color: #f0c63d; }
.sidebar .nav-mytechhub .nav-icon svg { width: 20px; height: 20px; display: block; }
.sidebar .nav-mytechhub .nav-badge { background: #f0c63d; color: #211b07; }
.sidebar nav a.nav-mytechhub.active {
    color: #fff2bc;
    border-left-color: #f0c63d;
    background: #302a17;
    box-shadow: none;
}

.sidebar-foot { padding: var(--sp-35) var(--sp-4); border-top: var(--bw-hair) solid var(--black-3); font-size: var(--fs-300); }
.scanner-status { display: flex; align-items: center; gap: var(--sp-2); color: var(--grey-mid); }
.scanner-status .dot {
    width: var(--sp-2); height: var(--sp-2); border-radius: 50%;
    background: var(--status-live); box-shadow: 0 0 8px var(--status-live);
    animation: pulse 2.4s infinite;
}
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .45; } }

/* ---- main / topbar ---- */
.main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.topbar {
    background: var(--surface); border-bottom: var(--bw-3) solid var(--red);
    padding: var(--sp-3) var(--sp-6);
    display: flex; align-items: center; justify-content: space-between; gap: var(--sp-35);
    position: sticky; top: 0; z-index: var(--z-topbar);
    box-shadow: var(--elev-2);
}
.topbar h1 { font-size: var(--fs-800); font-weight: var(--fw-bold); letter-spacing: var(--ls-tight); white-space: nowrap; }
.topbar-right { display: flex; align-items: center; gap: var(--sp-3); }
.quickscan input {
    width: 230px; padding: var(--sp-2) var(--sp-3);
    border: var(--border); border-radius: var(--ctrl-radius);
    font-size: var(--fs-500);
}
.quickscan input:focus { outline: none; border-color: var(--red); box-shadow: var(--ring); }
.clock { font-family: var(--font-mono); font-size: var(--fs-250); color: var(--grey-mid); white-space: nowrap; }
.user-chip {
    background: var(--black); color: var(--white);
    padding: var(--sp-15) var(--sp-35); border-radius: var(--radius-pill); font-size: var(--fs-400); font-weight: var(--fw-semi);
    white-space: nowrap;
}

.content { padding: var(--sp-6) var(--sp-6); flex: 1; }
.app-foot {
    padding: var(--sp-35) var(--sp-6); color: var(--grey-mid); font-size: var(--fs-300);
    border-top: var(--border);
}

/* ---- cards / grids ---- */
.card {
    background: var(--surface);
    border: var(--border-soft);
    border-radius: var(--radius-lg);
    box-shadow: var(--elev-2);
    padding: var(--sp-5);
    margin-bottom: var(--sp-5);
}
.card h2 {
    font-size: var(--fs-700); font-weight: var(--fw-bold);
    margin-bottom: var(--sp-35);
    letter-spacing: var(--ls-tight); color: var(--black);
    display: flex; align-items: center; gap: var(--sp-2);
}
.card h2::before { content: ""; width: var(--bw-accent); height: 16px; background: var(--red); border-radius: var(--radius-xs); flex-shrink: 0; }
/* uppercase dropped from .card h2 above, so the child no longer needs to opt back out of it */
.card h2 .h2-right { margin-left: auto; font-weight: var(--fw-medium); }
.card-accent { border-left: var(--bw-accent) solid var(--red); }   /* replaces emails.php inline */

/* h3 previously had no rule at all and rendered at the browser default
   (~17px bold) — LARGER than .card h2 above it. Sits between .card h2 and
   body copy in the scale. */
h3 {
    font-size: var(--fs-650); font-weight: var(--fw-semi);
    color: var(--text); line-height: var(--lh-snug);
    margin-bottom: var(--sp-2);
}
.card + .card { margin-top: 0; }
.card-narrow { max-width: 960px; }
.card-narrow.tight { max-width: 760px; }

.grid { display: grid; gap: var(--sp-5); }
.grid > * { min-width: 0; }                      /* stop wide tables blowing grids out */
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 1150px) { .grid-2, .grid-3 { grid-template-columns: minmax(0, 1fr); } }

/* ---- KPI tiles ---- */
.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--sp-35); margin-bottom: var(--sp-5); }
.kpis > * { min-width: 0; }
/* Every tile used to wear a 3px solid-black cap and an --elev-2 shadow, so
   seven of them read as seven equally loud objects and the red ones had
   nothing left to shout over. The default tile is now quiet — hairline border,
   --elev-1 — and emphasis is spent only on the exception. */
.kpi {
    background: var(--surface); border-radius: var(--radius-lg);
    box-shadow: var(--elev-1); padding: var(--sp-4) var(--sp-4);
    border: var(--border-soft);
    transition: transform var(--t-base), box-shadow var(--t-base), border-color var(--t-base);
}
.kpi:hover { transform: translateY(-2px); box-shadow: var(--elev-2); border-color: var(--grey-line); }
/* THE RED RULE: red means "outstanding work, and the count is not zero".
   It is never applied to a standing total (lines in stock, weight on hand,
   money to date) — those are facts, not exceptions, and a tile that is always
   red signals nothing. Pages emit it conditionally: class="kpi <?= $n ? 'red' : '' ?>"
   (vans.php:114-115 and index.php already do). The cap is an inset shadow, not
   a thicker border, so an alerting tile never shifts its content relative to
   the calm tiles beside it. */
.kpi.red {
    border-color: var(--red);
    box-shadow: inset 0 var(--bw-3) 0 0 var(--red), var(--elev-1);
}
.kpi.red:hover { box-shadow: inset 0 var(--bw-3) 0 0 var(--red), var(--elev-2); }
.kpi a { text-decoration: none; color: inherit; }
.kpi .kpi-num { font-size: var(--fs-1000); font-weight: var(--fw-black); color: var(--black); line-height: var(--lh-tight); }
.kpi.red .kpi-num { color: var(--red); }
.kpi .kpi-label { font-size: var(--fs-250); text-transform: uppercase; letter-spacing: .8px; color: var(--grey-mid); margin-top: var(--sp-1); }
.kpi .kpi-sub { font-size: var(--fs-300); color: var(--grey-mid); margin-top: var(--sp-1); }

/* ---- grid: stop short cards stretching to a tall sibling ----
   ROUND 2 ADD. Paste near the .grid rules (style.css ~line 335-339).
   The default .grid is `align-items: stretch`, so in the dashboard's grid-3 row
   the two short chart cards (Dispatch by Carrier, Warehouse Capacity) were being
   pulled to the height of the tall "Stock by Customer" card, leaving ~300px of
   dead white inside them. This opt-in modifier lets each card take its natural
   height instead. Applied in index.php as `<div class="grid grid-3 grid-top">`.
   (I could not edit .grid-3 in style.css directly — file ownership — so this is a
   separate modifier class rather than a change to .grid-3 itself.) */
.grid-top { align-items: start; }

/* =====================================================================
   PAGE LAYOUT — vertical rhythm, KPI bands, weighted rows
   ---------------------------------------------------------------------
   Purely additive. Where a rule has to beat a generic one that lives
   FURTHER DOWN the file (the @media .kpis / .kpi overrides near the end),
   it wins on specificity via a compound selector (.kpis.kpis-4), never by
   being moved after it — so nothing above or below here is reordered and
   pending merges into this file stay clean.
   ===================================================================== */

/* ---- shell gutters ----------------------------------------------------
   The growing inline padding that used to sit here is gone (see LAYOUT WIDTHS
   in :root). .topbar, .content and .app-foot each already declare --sp-6 as
   their own horizontal padding, so all three share one gutter natively and
   the footer's text lands on the same left edge as the cards above it with no
   rule needed here at all. */

/* ---- rhythm: one gap value between two cards, wherever they sit --------
   .card carries margin-bottom:--sp-5 and .grid carries gap:--sp-5, so a grid
   that wrapped to two rows (every .grid-2 / .grid-3 below 1150px) put 40px
   between stacked cards and 20px between side-by-side ones — the same page
   breathing at two different rates. The gap now owns spacing inside a grid,
   and the grid owns spacing after itself. */
.grid { margin-bottom: var(--sp-5); }
.grid > .card { margin-bottom: 0; }

/* A weighted row: one dominant card and one supporting card, instead of two
   equal boxes. This is what gives the dashboard a primary area — .grid-2 is
   still there for rows that genuinely are a pair of equals.
   align-items:start is baked in: a 2:1 row is exactly where a short card
   would otherwise be stretched to a tall neighbour. */
.grid-2-1 { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); align-items: start; }

/* ---- dashboard card field ----
   Cards here have wildly different natural heights (a 2-row work queue beside a
   capacity meter; a 6-row customer table beside a 3-row ageing list). In a grid,
   the row is as tall as its tallest card, so the short ones left holes — measured
   106px and 154px at 1440. Multi-column flow packs each card at its own height
   and balances the columns, so there are no gaps to leave.
   break-inside keeps a card whole; the margin is the gutter (column-gap handles
   the horizontal side). */
.dash-field { columns: 2; column-gap: var(--sp-5); }
.dash-field > .card {
    break-inside: avoid;
    -webkit-column-break-inside: avoid;
    margin-bottom: var(--sp-5);
}
@media (max-width: 1100px) { .dash-field { columns: 1; } }

@media (max-width: 1150px) { .grid-2-1 { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 900px)  { .grid { margin-bottom: var(--sp-25); } }

/* ---- KPI bands ------------------------------------------------------
   THE ORPHAN FIX. `repeat(auto-fit, minmax(160px, 1fr))` derives the column
   count from the available width, so the tile count and the column count are
   set by two unrelated things and only line up by luck. With SEVEN tiles they
   never can: 7 is prime, so no column count between 2 and 6 divides it — 6
   columns leaves 1 orphan, 3 leaves 1, 2 leaves 1. Picking a different
   minmax() only moves which width is broken.
   So the count stops being emergent. Tiles are grouped into bands by meaning
   ("today's work" / "what is on hand"), each band declares its own explicit
   column count, and each band's size (4 and 3) has a whole divisor at every
   breakpoint: 4 -> 4 | 4 | 2, and 3 -> 3 | 3 | 3. A full last row is now a
   property of the markup, not of the viewport.
   Bare .kpis (the other seven pages) keeps auto-fit and is untouched. */
.kpi-band { margin-bottom: var(--sp-5); }
.kpi-band > .kpis { margin-bottom: 0; }
.kpi-band-label {
    font-size: var(--fs-150); font-weight: var(--fw-bold);
    text-transform: uppercase; letter-spacing: var(--ls-caps-wide);
    color: var(--grey-mid);
    margin-bottom: var(--sp-2);
}

.kpis.kpis-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.kpis.kpis-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 560px) {
    .kpis.kpis-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }   /* 4 = 2 x 2 */
    .kpis.kpis-3 { grid-template-columns: minmax(0, 1fr); }              /* 3 = 3 x 1 */
}

/* The secondary band. Standing totals are reference, not news, so they get a
   flatter tile, a smaller figure, and the figure and its label on ONE line —
   the hierarchy has to be legible without reading a word of the labels, and an
   inline stat still reads right when a 1920px monitor makes these tiles 470px
   wide, where a stacked figure would leave them looking half-empty.
   Scoped to .kpis-quiet, which only the dashboard's second band uses: the
   other seven pages' bare .kpis are untouched, and the tile DOM is unchanged
   so a shared KPI helper can still render it. */
.kpis-quiet .kpi { box-shadow: none; padding: var(--sp-3) var(--sp-35); }
.kpis-quiet .kpi:hover { transform: none; box-shadow: var(--elev-1); }
.kpis-quiet .kpi > a { display: flex; align-items: baseline; gap: var(--sp-25); }
.kpis-quiet .kpi-num { font-size: var(--fs-800); }
.kpis-quiet .kpi-label { margin-top: 0; }
/* text-align can't centre flex items, so the inline stat needs its own
   centring to line up with the centred primary tiles on small screens. */
@media (max-width: 900px) { .kpis-quiet .kpi > a { justify-content: center; } }

/* ---- charts ---- */
.chart { width: 100%; height: auto; display: block; }

/* Bar chart + its legend + its data table, as one block. */
.chart-block { display: flex; flex-direction: column; gap: var(--sp-2); }
.chart-block > .chart-legend { justify-content: flex-start; margin-top: 0; }

.chart-donut { max-width: 220px; margin: var(--sp-1) auto 0; }

.chart-legend { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); justify-content: center; margin-top: var(--sp-3); font-size: var(--fs-250); color: var(--text-2); }
.chart-legend i { display: inline-block; width: 10px; height: 10px; border-radius: var(--radius-xs); margin-right: var(--sp-15); vertical-align: baseline; }
.chart-legend strong { color: var(--text); font-variant-numeric: tabular-nums; }
.chart-legend em { font-style: normal; color: var(--grey-mid); }

/* Collapsible day-by-day figures — the table view every chart is paired with. */
.chart-data { margin-top: var(--sp-1); }
.chart-data > summary { cursor: pointer; list-style: none; font-size: var(--fs-400); color: var(--text-2); padding: var(--sp-1) 0; }
.chart-data > summary::-webkit-details-marker { display: none; }
.chart-data > summary::before { content: "▸"; color: var(--red); font-weight: var(--fw-black); margin-right: var(--sp-15); }
.chart-data[open] > summary::before { content: "▾"; }
.chart-data table { margin-top: var(--sp-1); }

/* Prominent single figure above a meter (warehouse capacity). Proportional
   figures, not tabular — this is a display number, not a column. */
.chart-hero { display: flex; align-items: baseline; gap: var(--sp-2); margin-bottom: var(--sp-2); }
.chart-hero strong { font-size: var(--fs-1000); font-weight: var(--fw-black); line-height: 1; color: var(--text); }
.chart-hero span { font-size: var(--fs-400); color: var(--text-3); }

/* ---- meters & horizontal bars ---- */
.gauge { margin: var(--sp-25) 0; }
.gauge-track { background: var(--track); border-radius: var(--radius-md); height: 14px; overflow: hidden; }
.gauge-fill { height: 100%; border-radius: var(--radius-md); transition: width var(--dur-4) var(--ease-out); }
.gauge-label { display: flex; justify-content: space-between; align-items: baseline; gap: var(--sp-3); font-size: var(--fs-250); margin-top: var(--sp-1); color: var(--text-2); }
.gauge-label strong { color: var(--text); font-variant-numeric: tabular-nums; white-space: nowrap; }
.gauge-label em { font-style: normal; color: var(--grey-mid); }

.gauge-lg .gauge-track { height: 18px; }

/* A list of labelled bars: zone fill, carrier split, charge types. */
.hbars { margin-top: var(--sp-3); }
.hbars .gauge { margin: 0 0 var(--sp-25); }
.hbars .gauge:last-child { margin-bottom: 0; }
.hbars .gauge-track { height: 10px; }

/* =====================================================================
   FORMS
   ---------------------------------------------------------------------
   REPAIR: the opening marker of this header was lost in an earlier merge,
   leaving an orphan closing marker mid-stylesheet. That is not
   cosmetic — the CSS parser read `===== *​/ .form-grid` as one selector
   prelude — the row of `=` signs plus the orphan marker plus `.form-grid` —
   and threw the whole first rule away, so `.form-grid` has had NO
   `display: grid` at all and every two-column form in the app (goods-in,
   settings, customers…) has been rendering as a plain block stack. The
   header is restored, which restores the rule. Comment markers now balance
   149/149; before this edit the file had 148 openers and 149 closers.
   ===================================================================== */
.form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--sp-35) var(--sp-4); }
.form-grid .full { grid-column: 1 / -1; }
.form-grid.cols-1 { grid-template-columns: 1fr; }
@media (max-width: 900px) { .form-grid { grid-template-columns: 1fr; } }
.form-row-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-35) var(--sp-4); }
@media (max-width: 900px) { .form-row-3 { grid-template-columns: 1fr; } }

/* The label title is the one unclassed <span> inside a .field. Every other
   <span> MUST carry a class — the :not([class]) guard means it can never
   match .check/.check-text/.scan-hint/etc, so no specificity fight is
   possible with sibling component classes. (Replaces the old greedy
   `label.field span` selector — see §2.b.4 of the build contract.) */
.field { display: block; font-size: var(--fs-400); font-weight: var(--fw-semi); color: var(--text-2); }
.field > span:not([class]) {
    display: block;
    margin-bottom: var(--sp-15);
    font-size: var(--fs-400);
    font-weight: var(--fw-semi);
    color: var(--text-2);
    line-height: var(--lh-snug);
}
.field-note { color: var(--text-3); font-weight: var(--fw-medium); }

/* WAS: ... background: #fff; }   <-- shorthand wipes background-image */
input[type=text], input[type=number], input[type=email], input[type=password],
input[type=date], select, textarea {
    width: 100%;
    padding: var(--ctrl-pad-y) var(--ctrl-pad-x);
    border: var(--ctrl-border);
    border-radius: var(--ctrl-radius);
    font-size: var(--fs-550);
    font-family: inherit;
    color: var(--text);
    background-color: var(--surface);      /* longhand — REQUIRED */
    transition: border-color var(--t-fast), box-shadow var(--t-fast), background-color var(--t-fast);
}
input[readonly], textarea[readonly] {
    background-color: var(--surface-3);
    color: var(--text-2);
    cursor: default;
}

/* The select skin is applied by element selector ONLY. It must never require a
   wrapper element or a JS init call: selects are injected at runtime by
   engineer.php (#add-row), cloned by asn.php (.asn-line) and repopulated by
   app.js (#carrier -> #service) and goods-in.php (#project_id). Anything that
   needs per-element setup WILL be missed by one of those paths. */
select:not([multiple]):not([size]) {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    padding-right: calc(var(--ctrl-pad-x) + var(--chevron-w) + var(--sp-2));
    background-image: var(--chevron);
    background-repeat: no-repeat;
    background-position: right var(--ctrl-pad-x) center;
    background-size: var(--chevron-w) var(--chevron-h);
    cursor: pointer;
}
select:not([multiple]):not([size]):hover:not(:disabled) {
    border-color: var(--grey-mid);
    background-image: var(--chevron-red);
}
select:not([multiple]):not([size]):focus,
select:not([multiple]):not([size]):focus-visible {
    background-image: var(--chevron-red);
}
/* placeholder / unchosen state — progressive, degrades to normal text colour */
select:has(> option[value=""]:checked),
select:required:invalid {
    color: var(--grey-mid);
}
select:disabled {
    background-color: var(--surface-3);
    color: var(--grey-mid);
    cursor: not-allowed;
    opacity: 1;                            /* Safari dims disabled selects otherwise */
}
/* option colour must be reset — the placeholder rule above is inherited */
select > option, select > optgroup { color: var(--text); }
select > optgroup { font-weight: var(--fw-bold); }

input:focus, select:focus, textarea:focus {
    outline: none;
    border-color: var(--red);
    box-shadow: var(--ring);
}

@media (forced-colors: active) {
    select:not([multiple]):not([size]) {
        border: var(--bw-hair) solid ButtonBorder;
        background-image: none;
        padding-right: var(--ctrl-pad-x);
    }
    input:focus, select:focus, textarea:focus { outline: 2px solid Highlight; }
}


/* ---- themed checkbox / radio ---- */
input[type=checkbox], input[type=radio] {
    -webkit-appearance: none;
    appearance: none;
    flex: 0 0 auto;
    display: inline-block;
    width: var(--check-size);
    height: var(--check-size);
    margin: 0;
    padding: 0;
    border: var(--bw-1) solid var(--grey-line);
    background-color: var(--surface);
    background-repeat: no-repeat;
    background-position: center;
    vertical-align: middle;
    cursor: pointer;
    transition: background-color var(--t-fast), border-color var(--t-fast), box-shadow var(--t-fast);
}
input[type=checkbox] { border-radius: var(--radius-xs); }
input[type=radio]    { border-radius: 50%; }

input[type=checkbox]:hover:not(:disabled),
input[type=radio]:hover:not(:disabled) {
    border-color: var(--red);
    box-shadow: 0 0 0 3px var(--red-tint);
}

input[type=checkbox]:checked {
    background-color: var(--red);
    border-color: var(--red);
    background-image: var(--tick);
    background-size: 12px 10px;
}
input[type=checkbox]:indeterminate {
    background-color: var(--red);
    border-color: var(--red);
    background-image: var(--dash);
    background-size: 10px 2px;
}
input[type=radio]:checked {
    background-color: var(--red);
    border-color: var(--red);
    box-shadow: inset 0 0 0 3.5px var(--surface);
}

input[type=checkbox]:focus-visible,
input[type=radio]:focus-visible {
    outline: var(--bw-2) solid var(--red);
    outline-offset: 2px;
    box-shadow: none;
}
input[type=checkbox]:disabled, input[type=radio]:disabled {
    background-color: var(--surface-3);
    border-color: var(--grey-line);
    cursor: not-allowed;
}
input[type=checkbox]:disabled:checked { background-color: var(--grey-mid); border-color: var(--grey-mid); }

@media (forced-colors: active) {
    input[type=checkbox], input[type=radio] {
        border-color: CanvasText;
        background-color: Canvas;
    }
    input[type=checkbox]:checked, input[type=radio]:checked { background-color: Highlight; border-color: Highlight; }
    input[type=checkbox]:focus-visible, input[type=radio]:focus-visible { outline-color: CanvasText; }
}

/* ---- .check — checkbox/radio + description compound ---- */
.check {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-2);
    margin-bottom: 0;
    font-weight: var(--fw-medium);
    line-height: var(--lh-base);
    cursor: pointer;
}
.check > input[type=checkbox], .check > input[type=radio] { margin-top: 1px; }
.check > .check-text {
    display: block;
    margin-bottom: 0;
    font-size: var(--fs-400);
    font-weight: var(--fw-medium);
    color: var(--text-2);
}
.check.check-inline { display: inline-flex; align-items: center; white-space: nowrap; }
.check.check-lg > input { width: var(--check-size-lg); height: var(--check-size-lg); }
.check.check-lg > .check-text { font-size: var(--fs-500); font-weight: var(--fw-semi); color: var(--text); }
.check:hover > .check-text { color: var(--text); }
.check:has(> input:disabled) { cursor: not-allowed; opacity: .6; }

/* ---- radio groups ---- */
.check-group { display: flex; flex-direction: column; gap: var(--sp-2); }
.check-group.inline { flex-direction: row; flex-wrap: wrap; gap: var(--sp-2) var(--sp-5); align-items: center; }
.check-group-label { display: block; font-size: var(--fs-400); font-weight: var(--fw-semi); color: var(--text-2); margin-bottom: var(--sp-15); }

/* elevated "card" variant, for when a radio choice deserves weight */
.check-card {
    display: flex; align-items: flex-start; gap: var(--sp-25);
    padding: var(--sp-3) var(--sp-35);
    border: var(--border); border-radius: var(--radius-md);
    background: var(--surface); cursor: pointer;
    transition: border-color var(--t-base), box-shadow var(--t-base), background-color var(--t-base);
}
.check-card:hover { border-color: var(--grey-mid); }
.check-card:has(> input:checked) { border-color: var(--red); background: var(--red-light); box-shadow: var(--ring); }
.check-card:has(> input:focus-visible) { outline: var(--bw-2) solid var(--red); outline-offset: 2px; }

.panel-inset { background: var(--surface-inset); border: var(--border-soft); border-radius: var(--radius-md); padding: var(--sp-25) var(--sp-35); align-self: start; }
.panel-inset > .scan-hint { margin-top: var(--sp-15); }

.form-actions { margin-top: var(--sp-4); display: flex; gap: var(--sp-25); align-items: center; flex-wrap: wrap; }

/* mobile control-geometry override — the chevron would overlap the text otherwise */
@media (max-width: 900px) {
    input[type=text], input[type=number], input[type=email], input[type=password],
    input[type=date], select, textarea {
        padding: 11px var(--sp-3);
        font-size: var(--fs-700);          /* 16px — stops iOS/Android zoom */
    }
    select:not([multiple]):not([size]) {
        padding-right: calc(var(--sp-3) + var(--chevron-w) + var(--sp-2));
        background-position: right var(--sp-3) center;
    }
    input.mono, textarea.mono {
        font-size: var(--fs-700);          /* 16px — stops iOS/Android zoom, .mono's .92em would otherwise shrink it */
    }
}

a {
    color: var(--red-dark);
    text-decoration-color: color-mix(in srgb, var(--red) 34%, transparent);
    text-underline-offset: .16em;
}
a:hover {
    color: var(--red);
    text-decoration-color: currentColor;
}

/* =====================================================================
   SCAN STATION — focused operational overlay
   ===================================================================== */
body[data-page='scan'] .content > * {
    position: relative;
    z-index: 1;
}

.scan-station-overlay {
    position: relative;
    overflow: hidden;
    padding: clamp(24px, 3.5vw, 54px);
    border: 1px solid rgba(255, 255, 255, .12);
    border-top: 4px solid var(--red);
    border-radius: var(--radius-lg);
    background: linear-gradient(145deg, rgba(28, 28, 36, .97), rgba(10, 10, 15, .98));
    box-shadow: 0 26px 80px rgba(0, 0, 0, .36);
    animation: scan-overlay-in 320ms var(--ease-out) both;
}

.scan-station-overlay::after {
    content: "";
    position: absolute;
    width: 460px;
    height: 460px;
    right: -180px;
    top: -260px;
    border-radius: 50%;
    background: rgba(200, 16, 46, .2);
    filter: blur(32px);
    pointer-events: none;
}

.scan-station-hero {
    position: relative;
    z-index: 1;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--sp-3) var(--sp-5);
    align-items: end;
    margin-bottom: var(--sp-5);
}

.scan-station-kicker {
    display: inline-block;
    margin-bottom: var(--sp-2);
    color: #ff6680;
    font-size: var(--fs-200);
    font-weight: var(--fw-black);
    letter-spacing: .16em;
    text-transform: uppercase;
}

.scan-station-copy h1 {
    margin: 0;
    color: var(--white);
    font-size: clamp(34px, 4vw, 62px);
    font-weight: var(--fw-black);
    letter-spacing: -.045em;
    line-height: .98;
}

.scan-station-copy p,
.scan-station-help {
    margin: var(--sp-2) 0 0;
    color: var(--white);
}

.scan-station-ready {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
    color: var(--white);
    font-size: var(--fs-250);
    font-weight: var(--fw-bold);
    border: 1px solid rgba(255, 255, 255, .18);
    border-radius: var(--radius-pill);
    background: rgba(255, 255, 255, .06);
}

.scan-station-ready i {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--ok);
    box-shadow: 0 0 0 5px rgba(29, 122, 62, .17), 0 0 18px rgba(47, 210, 100, .48);
}

.scan-station-box {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--sp-2);
    padding: var(--sp-2);
    border: 1px solid rgba(255, 255, 255, .2);
    border-radius: var(--radius-lg);
    background: var(--white);
    box-shadow: 0 0 0 5px rgba(200, 16, 46, .15), 0 18px 48px rgba(0, 0, 0, .32);
}

.scan-station-box input {
    min-width: 0;
    padding: 18px 20px;
    color: var(--black);
    font-size: clamp(19px, 2vw, 27px);
    font-weight: var(--fw-bold);
    letter-spacing: .04em;
    border: 0;
    box-shadow: none;
}

.scan-station-box input:focus {
    outline: 0;
    box-shadow: none;
}

.scan-station-submit {
    padding: 0 var(--sp-5);
    color: var(--white);
    font-weight: var(--fw-black);
    border: 0;
    border-radius: var(--radius-md);
    background: linear-gradient(135deg, #df1639, #a30c28);
    cursor: pointer;
}

.scan-station-submit:hover { background: var(--red-dark); }

.scan-action-grid {
    position: relative;
    z-index: 1;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--sp-4);
}

.scan-action-card {
    display: grid;
    grid-template-columns: 150px minmax(0, 1fr);
    min-height: 190px;
    overflow: hidden;
    color: var(--white);
    text-decoration: none;
    border: 1px solid rgba(255, 255, 255, .14);
    border-radius: var(--radius-lg);
    background: linear-gradient(145deg, var(--black-3), #16161d);
    box-shadow: var(--elev-3);
    transition: transform var(--t-base), border-color var(--t-base), background-color var(--t-base);
}

.scan-action-card-primary {
    background: linear-gradient(145deg, #cf1233, #8e0a22);
    border-color: #f03d5b;
}

.scan-action-card:hover {
    color: var(--white);
    border-color: var(--red);
    transform: translateY(-3px);
}

.scan-action-art {
    display: grid;
    place-items: center;
    padding: var(--sp-3);
    background: rgba(255, 255, 255, .055);
}

.scan-action-art svg,
.scan-not-found-art svg {
    width: 100%;
    height: auto;
    fill: none;
    stroke: var(--white);
    stroke-width: 3;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.scan-action-art .scan-art-accent,
.scan-not-found-art .scan-art-accent {
    stroke: #ff4f6d;
    stroke-width: 5;
}

.scan-action-card-primary .scan-art-accent { stroke: var(--white); }

.scan-action-copy {
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-width: 0;
    padding: var(--sp-4);
}

.scan-action-copy strong {
    color: var(--white);
    font-size: var(--fs-750);
    line-height: var(--lh-tight);
}

.scan-action-copy small {
    margin-top: var(--sp-2);
    color: var(--white);
    font-size: var(--fs-350);
    line-height: var(--lh-relaxed);
}

.scan-action-copy b {
    margin-top: auto;
    padding-top: var(--sp-3);
    color: var(--white);
    font-size: var(--fs-250);
}

.scan-not-found {
    display: grid;
    grid-template-columns: 74px minmax(0, 1fr) auto;
    gap: var(--sp-3);
    align-items: center;
    margin-top: var(--sp-4);
    padding: var(--sp-3);
    color: var(--white);
    border: 1px solid rgba(255, 255, 255, .15);
    border-left: 4px solid var(--red);
    border-radius: var(--radius-lg);
    background: var(--black-2);
}

.scan-not-found-art { display: block; width: 64px; }
.scan-not-found strong,
.scan-not-found small { display: block; color: var(--white); }
.scan-not-found small { margin-top: var(--sp-1); }

body[data-page='scan'] .content > .card {
    margin-top: var(--sp-4);
    color: var(--white);
    background: linear-gradient(145deg, var(--black-2), var(--black));
    border-color: rgba(255, 255, 255, .12);
    border-top: 3px solid var(--red);
}

body[data-page='scan'] .content > .card h2,
body[data-page='scan'] .content > .card .detail-title,
body[data-page='scan'] .content > .card .detail-sub,
body[data-page='scan'] .content > .card .kv dt,
body[data-page='scan'] .content > .card .kv dd { color: var(--white); }

body[data-page='scan'] .content > .card table.list th {
    color: var(--white);
    background: var(--black-3);
    border-color: rgba(255, 255, 255, .14);
}

body[data-page='scan'] .content > .card table.list td {
    color: var(--white);
    background: transparent;
    border-color: rgba(255, 255, 255, .1);
}

body[data-page='scan'] .content > .card table.list tr:hover td { background: var(--black-3); }
body[data-page='scan'] .content > .card a:not(.btn) { color: var(--white); }

body[data-page='dashboard'].is-leaving-for-scan .main,
body[data-page='dashboard'].is-leaving-for-scan .sidebar {
    opacity: 0;
    filter: blur(12px);
    transform: scale(.985);
    transition: opacity 220ms ease, filter 220ms ease, transform 220ms ease;
}

body[data-page='dashboard'].is-leaving-for-scan::after {
    content: "";
    position: fixed;
    z-index: 9999;
    inset: 0;
    pointer-events: none;
    background: #09090d;
    animation: scan-route-cover 220ms ease both;
}

.scan-station-overlay .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

@keyframes scan-overlay-in {
    from { opacity: 0; transform: translateY(14px) scale(.992); }
    to { opacity: 1; transform: none; }
}

@keyframes scan-route-cover {
    from { opacity: 0; }
    to { opacity: 1; }
}

@media (max-width: 1180px) {
    .scan-action-grid { grid-template-columns: 1fr; }
    .scan-action-card { grid-template-columns: 130px minmax(0, 1fr); min-height: 156px; }
}

@media (prefers-reduced-motion: reduce) {
    .scan-station-overlay,
    body[data-page='dashboard'].is-leaving-for-scan::after { animation: none; }
    .scan-action-card { transition: none; }
}

/* =====================================================================
   GOODS IN — desktop receiving console
   ===================================================================== */
body[data-page='goods-in'] .content {
    background:
        radial-gradient(circle at 88% 0%, rgba(200, 16, 46, .09), transparent 28%),
        var(--grey-bg);
}

.gi-console {
    width: 100%;
    animation: gi-console-in 280ms var(--ease-out) both;
}

.gi-console-hero {
    display: grid;
    grid-template-columns: 210px minmax(0, 1fr);
    gap: clamp(24px, 4vw, 58px);
    align-items: center;
    overflow: hidden;
    margin-bottom: var(--sp-5);
    padding: clamp(28px, 4vw, 58px);
    color: var(--white);
    border-top: 4px solid var(--red);
    border-radius: var(--radius-lg);
    background:
        radial-gradient(circle at 88% 5%, rgba(224, 24, 61, .48), transparent 34%),
        linear-gradient(145deg, #17171f, #09090d);
    box-shadow: 0 22px 60px rgba(16, 16, 20, .2);
}

.gi-console-art {
    display: grid;
    place-items: center;
    aspect-ratio: 1.18;
    padding: var(--sp-4);
    border: 1px solid rgba(255, 255, 255, .15);
    border-radius: var(--radius-lg);
    background: rgba(255, 255, 255, .055);
}

.gi-console-art svg {
    width: 100%;
    fill: none;
    stroke: var(--white);
    stroke-width: 3;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.gi-console-art .gi-art-accent { stroke: #ff4968; stroke-width: 5; }

.gi-console-kicker,
.gi-summary-kicker {
    display: block;
    margin-bottom: var(--sp-2);
    color: #ff6680;
    font-size: var(--fs-200);
    font-weight: var(--fw-black);
    letter-spacing: .16em;
    text-transform: uppercase;
}

.gi-console-copy h1 {
    margin: 0;
    color: var(--white);
    font-size: clamp(32px, 4vw, 58px);
    font-weight: var(--fw-black);
    letter-spacing: -.045em;
    line-height: 1;
}

.gi-console-copy > p {
    max-width: 760px;
    margin: var(--sp-3) 0 0;
    color: var(--white);
    font-size: var(--fs-550);
    line-height: var(--lh-relaxed);
}

.gi-console-signals {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    margin-top: var(--sp-4);
}

.gi-console-signals span {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
    color: var(--white);
    font-size: var(--fs-200);
    font-weight: var(--fw-bold);
    border: 1px solid rgba(255, 255, 255, .18);
    border-radius: var(--radius-pill);
    background: rgba(255, 255, 255, .06);
}

.gi-console-signals i {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--ok);
    box-shadow: 0 0 0 4px rgba(29, 122, 62, .18);
}

.gi-workspace {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 340px;
    gap: var(--sp-5);
    align-items: start;
}

.gi-form-main {
    display: grid;
    gap: var(--sp-4);
    min-width: 0;
}

.gi-form-main > .form-section {
    margin: 0;
    padding: clamp(22px, 2.3vw, 34px);
    border: 1px solid var(--grey-line);
    border-top: 3px solid var(--red);
    border-radius: var(--radius-lg);
    background: var(--white);
    box-shadow: var(--elev-2);
}

.gi-form-main > .form-section + .form-section {
    margin-top: 0;
    padding-top: clamp(22px, 2.3vw, 34px);
}

body[data-page='goods-in'] .gi-section-title {
    display: flex;
    gap: var(--sp-3);
    align-items: flex-start;
    margin-bottom: var(--sp-4);
    color: var(--black);
    font-size: var(--fs-650);
    letter-spacing: var(--ls-tight);
    text-transform: none;
}

body[data-page='goods-in'] .gi-section-title > span:first-child {
    display: grid;
    flex: 0 0 34px;
    place-items: center;
    width: 34px;
    height: 34px;
    color: var(--white);
    font-size: var(--fs-200);
    letter-spacing: 0;
    border-radius: var(--radius-sm);
    background: var(--red);
}

body[data-page='goods-in'] .gi-section-title > span:last-child { display: block; }
body[data-page='goods-in'] .gi-section-title small {
    display: block;
    margin-top: var(--sp-1);
    color: var(--text-3);
    font-size: var(--fs-300);
    font-weight: var(--fw-normal);
    letter-spacing: 0;
    line-height: var(--lh-base);
}

body[data-page='goods-in'] .field > span:first-child {
    color: var(--black);
    font-weight: var(--fw-bold);
}

.gi-codes {
    padding: var(--sp-3);
    border: 1px solid rgba(200, 16, 46, .18);
    border-radius: var(--radius-md);
    background: #fff6f8;
}

.gi-booking-rail {
    position: sticky;
    top: 84px;
}

.gi-booking-summary {
    overflow: hidden;
    padding: var(--sp-4);
    color: var(--white);
    border-top: 4px solid var(--red);
    border-radius: var(--radius-lg);
    background:
        radial-gradient(circle at 100% 0%, rgba(200, 16, 46, .32), transparent 38%),
        linear-gradient(145deg, var(--black-2), var(--black));
    box-shadow: 0 20px 48px rgba(16, 16, 20, .24);
}

.gi-booking-summary h2 {
    margin: 0 0 var(--sp-4);
    color: var(--white);
    font-size: var(--fs-750);
}

.gi-booking-summary dl { margin: 0; }
.gi-booking-summary dl > div {
    display: grid;
    grid-template-columns: 104px minmax(0, 1fr);
    gap: var(--sp-2);
    padding: var(--sp-2) 0;
    border-bottom: 1px solid rgba(255, 255, 255, .11);
}

.gi-booking-summary dt {
    color: #ff8ba0;
    font-size: var(--fs-150);
    font-weight: var(--fw-black);
    letter-spacing: var(--ls-caps);
    text-transform: uppercase;
}

.gi-booking-summary dd {
    min-width: 0;
    margin: 0;
    color: var(--white);
    font-size: var(--fs-300);
    font-weight: var(--fw-bold);
    overflow-wrap: anywhere;
}

.gi-booking-route {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    margin: var(--sp-4) 0;
}

.gi-booking-route span {
    color: var(--white);
    font-size: var(--fs-150);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
}

.gi-booking-route span.is-ready { color: #ff6680; }
.gi-booking-route i { flex: 1; height: 1px; background: rgba(255, 255, 255, .24); }

.gi-book-submit {
    width: 100%;
    min-height: 54px;
    font-size: var(--fs-550);
    background: linear-gradient(135deg, #df1639, #a30c28);
    box-shadow: 0 10px 26px rgba(200, 16, 46, .28);
}

.gi-booking-summary > p {
    margin: var(--sp-3) 0 0;
    color: var(--white);
    font-size: var(--fs-200);
    line-height: var(--lh-relaxed);
}

@keyframes gi-console-in {
    from { opacity: 0; transform: translateY(12px); }
    to { opacity: 1; transform: none; }
}

@media (max-width: 1180px) {
    .gi-workspace { grid-template-columns: 1fr; }
    .gi-booking-rail { position: static; }
    .gi-booking-summary dl { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--sp-4); }
}

@media (max-width: 760px) {
    .gi-console-hero { grid-template-columns: 1fr; }
    .gi-console-art { display: none; }
    .gi-booking-summary dl { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) {
    .gi-console { animation: none; }
}

/* ---- scan input (big) ---- */
.scan-box input {
    width: 100%; padding: var(--sp-4) var(--sp-4); font-size: var(--fs-700);
    font-family: var(--font-mono); letter-spacing: 1px;
    border: var(--bw-2) solid var(--black); border-radius: var(--radius-lg);
}
.scan-box input:focus { outline: none; border-color: var(--red); box-shadow: var(--ring-strong), 0 0 0 6px var(--red-tint); }
.scan-hint { font-size: var(--fs-400); color: var(--text-3); margin-top: var(--sp-2); line-height: var(--lh-base); }

/* =====================================================================
   SCANNER FEEDBACK — is it live, did it catch that, did it work
   ---------------------------------------------------------------------
   The scan field is the primary control of the whole app and gave no signal
   that it was listening. Three states, all carried by ONE small indicator
   dot at the end of the field, so nothing moves in the operator's reading
   path and nothing competes with the value being scanned:

       idle       grey, still     not focused
       armed      red, pulsing    focused and waiting for a scan
       captured   green, still    the field has a value — it caught something

   The pulse REUSES the sidebar's existing @keyframes pulse (2.4s, opacity
   only, already shipped on .scanner-status .dot) so the app has one "live"
   vocabulary rather than two, and it is the only continuous motion here —
   every other cue is a one-shot transition. This is an all-day tool.

   :has(input) guard: devices.php and scanner-setup.php reuse .scan-box as a
   plain <div> around printed setup barcodes, with no field in it at all.
   */
.scan-box:has(input) { position: relative; }
.scan-box:has(input)::after {
    content: "";
    position: absolute;
    top: 50%;
    right: var(--sp-4);
    width: var(--sp-2);
    height: var(--sp-2);
    margin-top: calc(var(--sp-2) / -2);
    border-radius: 50%;
    /* The dot sits over the field's own white background, not the page, so it
       is measured against --surface: --grey-line would be 1.30:1 and simply
       absent, --grey-mid is 3.34:1 and reads as a deliberate "off". */
    background: var(--grey-mid);
    pointer-events: none;
    transition: background-color var(--t-base), box-shadow var(--t-base);
}
/* ARMED */
.scan-box:has(input:focus)::after {
    background: var(--red);
    box-shadow: 0 0 8px var(--red-glow);
    animation: pulse 2.4s infinite;
}
/* CAPTURED. Scoped to fields that actually declare a placeholder:
   :not(:placeholder-shown) is vacuously true on a field without one, which
   would light the dot green on load. Every scan field in the app has one. */
.scan-box:has(input[placeholder]:focus:not(:placeholder-shown))::after {
    background: var(--ok);
    box-shadow: 0 0 8px var(--ok);
    animation: none;
}

/* ---- the result of the lookup ----
   Whether a scan resolved is decided on the server, and comes back as a
   flash on the next page — so the scanner echoes that outcome once, on
   arrival, then stops. The colour is set as a plain declaration and the
   motion is layered on top, which is what keeps this meaningful under
   prefers-reduced-motion: the global block collapses both animations to
   .001ms, and the green/red border still states the outcome. */
@keyframes scan-accept {
    0%   { box-shadow: 0 0 0 0 var(--ok-bg); }
    40%  { box-shadow: 0 0 0 5px var(--ok-bg); }
    100% { box-shadow: 0 0 0 0 transparent; }
}
@keyframes scan-reject {
    0%, 100% { transform: translateX(0); }
    25%      { transform: translateX(calc(var(--sp-1) * -1)); }
    75%      { transform: translateX(var(--sp-1)); }
}
body:has(.flash-success) .scan-box input {
    border-color: var(--ok);
    animation: scan-accept var(--dur-4) var(--ease-out) 1;
}
/* translateX is on the INPUT, never on .scan-box or .ac-wrap: a transform on
   either of those would become the containing block for .ac-panel and
   re-anchor every suggestion list on the page. */
body:has(.flash-error) .scan-box input {
    border-color: var(--red);
    animation: scan-reject var(--dur-3) var(--ease-in-out) 1;
}

/* ---- scan bar: the primary action, promoted out of a card ----------------
   Quick Scan used to be a .card in a stretch grid beside the 331px activity
   chart, so it was inflated to match and carried 119px of dead white below
   its buttons. Aligning the row would have left a short empty box next to a
   tall one; the honest fix is that scanning is not a peer of a chart. It is
   the first thing every operator does, so it becomes a full-width command
   strip above everything, and the row it vacated becomes a real pair.
   */
.scan-bar {
    display: flex; align-items: center; flex-wrap: wrap;
    gap: var(--sp-3) var(--sp-4);
    padding: var(--sp-35) var(--sp-4);
    margin-bottom: var(--sp-5);
    background: linear-gradient(180deg, var(--black-2) 0%, var(--black) 100%);
    border-left: var(--bw-accent) solid var(--red);
    border-radius: var(--radius-lg);
    box-shadow: var(--elev-2);
}
/* Capped, not greedy: at 1440 a `flex: 1 1 340px` field grew to ~700px, which is
   far more room than a barcode needs and made the bar read as a giant black slab. */
.scan-bar-field { flex: 0 1 clamp(240px, 38vw, 460px); min-width: 0; }
.scan-bar .scan-box input {
    padding: var(--sp-3) var(--sp-35);
    border-color: var(--black-3);
}
.scan-bar-actions { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
/* .btn-ghost is built for a light card, and the first pass only fixed its
   LABEL colour — the button still had no visible edge, so the two secondary
   actions read as floating white text rather than controls. Measured against
   the bar's darkest stop (#101014); 3:1 is the WCAG non-text threshold for a
   UI component boundary:
       WAS  border --black-3  #26262e   1.26:1   FAIL — effectively no border
       NOW  border --grey-mid #8a8d95   5.72:1   PASS
            label  --white    #ffffff  18.98:1   PASS
            hover  border --white      18.98:1   PASS
   Hierarchy is unchanged: "+ Goods In" keeps the solid red fill (3.23:1) and
   is still the only filled button; these two are outlined, not invisible.
   Hover deliberately brightens the EDGE rather than the fill — a red border
   on the --black-3 fill measures only 2.55:1, so the earlier hover would have
   failed the same test the base state just did. */
.scan-bar .btn-ghost {
    color: var(--white);
    background: var(--black-3);
    border-color: var(--grey-mid);
}
.scan-bar .btn-ghost:hover {
    color: var(--white);
    background: var(--black-3);
    border-color: var(--white);
    box-shadow: 0 3px 12px var(--red-glow);
}
/* --ring and --ring-strong are red glows tuned for light cards. Composited
   over the bar they measure 1.14:1 and 1.35:1 — invisible, so focus was
   effectively unindicated on this surface. The dark bar gets a white outline
   instead (18.98:1), which is the pattern the checkbox/radio focus styles in
   this file already use. It covers the scan field too, which is what makes
   the auto-focused field visibly "armed" the moment the page loads. */
.scan-bar .btn:focus-visible,
.scan-bar .scan-box input:focus,
.scan-bar .scan-box input:focus-visible {
    outline: var(--bw-2) solid var(--white);
    outline-offset: 2px;
}

@media (max-width: 900px) {
    .scan-bar {
        padding: var(--sp-25); gap: var(--sp-25);
        margin-bottom: var(--sp-25); border-radius: var(--radius-md);
    }
    .scan-bar-field { flex: 1 1 100%; }
    .scan-bar-actions { width: 100%; }
    .scan-bar-actions .btn { flex: 1 1 auto; }
}

/* =====================================================================
   TABLES
   ===================================================================== */
.table-wrap { overflow-x: auto; }
.table-wrap.capped { max-height: 480px; overflow-y: auto; }   /* replaces billing.php inline */

table.list { width: 100%; border-collapse: collapse; }
table.list th {
    position: sticky; top: 0; z-index: 1;
    text-align: left; font-size: var(--fs-200);
    text-transform: uppercase; letter-spacing: var(--ls-caps);
    color: var(--grey-mid); font-weight: var(--fw-bold);
    padding: var(--sp-2) var(--sp-25);
    border-bottom: var(--border-strong);
    background: var(--surface);
    cursor: pointer; user-select: none; white-space: nowrap;
    transition: color var(--t-fast);
}
table.list th:hover { color: var(--red); }
/* sort affordance — driven purely by the data-dir attribute app.js ALREADY writes.
   Pseudo-element content is not part of textContent, so the sort comparator is unaffected. */
table.list th:not(:empty)::after {
    content: "\2195"; margin-left: var(--sp-15);
    font-size: var(--fs-100); opacity: 0; color: var(--grey-mid);
    transition: opacity var(--t-fast), color var(--t-fast);
}
table.list th:not(:empty):hover::after { opacity: .5; }
table.list th[data-dir]::after { opacity: 1; color: var(--red); }
table.list th[data-dir="asc"]::after  { content: "\25B2"; }
table.list th[data-dir="desc"]::after { content: "\25BC"; }

table.list td { padding: var(--sp-25); border-bottom: var(--border-soft); vertical-align: middle; }
table.list tr:hover td { background: var(--surface-2); }
table.list tr:last-child td { border-bottom: none; }
table.list .num { text-align: right; font-variant-numeric: tabular-nums; }
td.row-expand { background: var(--surface-2); }   /* replaces asn/shipments colspan inline */

/* .mono was declared twice in the legacy stylesheet — the second (chip-style)
   declaration silently won everywhere, so every barcode in every table
   rendered as a grey chip. Collapsed to one declaration; `.chip` is now an
   opt-in for payload/command strings only, never applied to table barcodes. */
/* Left as a relative .92em (not a token): .mono is nested in wildly different
   contexts — plain body text, table cells, .kpi-num (30px), badges — and a
   single absolute token can't serve all of them without regressing the ones
   that already look right. The two known bad-compounding spots (mobile input
   zoom floor at :574, table.list .mono at :923) are already patched with
   explicit absolute overrides; leave the base rule relative. */
.mono { font-family: var(--font-mono); font-size: .92em; }
.mono.chip { background: var(--surface-3); padding: 1px var(--sp-15); border-radius: var(--radius-xs); }

/* ---- badges ---- */
.badge {
    display: inline-flex; align-items: center; gap: var(--sp-1);
    padding: var(--sp-05) var(--sp-25);
    border-radius: var(--radius-pill);
    font-size: var(--fs-200); font-weight: var(--fw-bold);
    letter-spacing: .3px; white-space: nowrap; line-height: var(--lh-base);
    box-shadow: inset 0 0 0 1px rgba(16,16,20,.05);
}
/* names frozen — PHP interpolates badge-<status> / badge-<type> */
.badge-in_stock, .badge-arrived, .badge-open   { background: var(--ok-bg);   color: var(--ok); }
.badge-part, .badge-picking, .badge-expected   { background: var(--warn-bg); color: var(--warn); }
.badge-dispatched, .badge-cancelled            { background: var(--track);   color: var(--text-3); }
.badge-type                                    { background: var(--red-light); color: var(--red-dark); }
.badge-project { background: var(--black); color: var(--white); text-decoration: none; transition: background-color var(--t-base); }
.badge-project:hover { background: var(--red); }

/* =====================================================================
   BUTTONS
   ===================================================================== */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-15);
    padding: var(--sp-25) var(--sp-5);
    border: var(--bw-1) solid transparent;
    border-radius: var(--ctrl-radius);
    cursor: pointer; text-decoration: none;
    font-family: inherit; font-size: var(--fs-500); font-weight: var(--fw-bold);
    letter-spacing: var(--ls-tight); line-height: var(--lh-tight);
    background: var(--red); color: var(--white);
    box-shadow: var(--elev-1);
    transition: background-color var(--t-base), box-shadow var(--t-base),
                transform var(--dur-1) var(--ease-out), border-color var(--t-base), color var(--t-base);
}
.btn:hover  { background: var(--red-dark); box-shadow: 0 3px 12px var(--red-glow), var(--elev-1); }
.btn:active { transform: translateY(1px); box-shadow: var(--elev-0); }
.btn:focus-visible { outline: none; box-shadow: var(--ring-strong), var(--elev-1); }
.btn:disabled, .btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; box-shadow: none; transform: none; }

.btn-dark { background: var(--black); }
.btn-dark:hover { background: var(--black-3); box-shadow: 0 3px 12px rgba(16,16,20,.28), var(--elev-1); }
.btn-dark:focus-visible { box-shadow: var(--ring-dark), var(--elev-1); }

.btn-ghost { background: transparent; color: var(--text); border-color: var(--grey-line); box-shadow: none; }
.btn-ghost:hover { border-color: var(--red); color: var(--red); background: var(--red-light); box-shadow: none; }
.btn-ghost:focus-visible { box-shadow: var(--ring); }

.btn-sm { padding: var(--sp-15) var(--sp-3); font-size: var(--fs-400); }

/* ---- tabs ---- */
.tabs { display: flex; gap: var(--sp-1); flex-wrap: wrap; border-bottom: var(--border-strong); margin-bottom: var(--sp-5); }
.tab-btn {
    position: relative; background: none; border: none; cursor: pointer;
    padding: var(--sp-3) var(--sp-4); margin-bottom: calc(-1 * var(--bw-2));
    font-family: inherit; font-size: var(--fs-450); font-weight: var(--fw-bold);
    text-transform: uppercase; letter-spacing: var(--ls-caps);
    color: var(--grey-mid);
    display: flex; align-items: center; gap: var(--sp-15); white-space: nowrap;
    transition: color var(--t-base);
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}
.tab-btn::after {
    content: ""; position: absolute; left: var(--sp-3); right: var(--sp-3); bottom: 0;
    height: var(--bw-2); background: var(--red); border-radius: var(--bw-2) var(--bw-2) 0 0;
    transform: scaleX(0); transform-origin: center;
    transition: transform var(--t-base);
}
.tab-btn:hover { color: var(--black); background: var(--surface-2); }
.tab-btn:focus-visible { outline: var(--bw-2) solid var(--red); outline-offset: -2px; }
.tab-btn[aria-selected="true"] { color: var(--red); }
.tab-btn[aria-selected="true"]::after { transform: scaleX(1); }
.tab-btn .tab-icon { color: var(--red); font-weight: var(--fw-normal); }
.tab-panel[hidden] { display: none; }

/* ---- setup barcodes ---- */
.bc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: var(--sp-35); }
.bc {
    border: var(--border); border-radius: var(--radius-md);
    padding: var(--sp-3) var(--sp-3) var(--sp-35); background: var(--surface);
    display: flex; flex-direction: column;
    transition: border-color var(--t-base), box-shadow var(--t-base);
}
.bc:hover { border-color: var(--red); box-shadow: var(--elev-3); }
.bc svg { width: 100%; height: auto; display: block; }
.bc-title { font-weight: var(--fw-bold); font-size: var(--fs-500); margin-top: var(--sp-2); display: flex; align-items: center; gap: var(--sp-15); }
.bc-cmd { font-family: var(--font-mono); font-size: var(--fs-250); color: var(--grey-mid); margin-top: var(--sp-05); word-break: break-all; }
.bc-hint { font-size: var(--fs-300); color: var(--text-3); line-height: var(--lh-base); margin-top: var(--sp-15); }
.bc-star { background: var(--black); color: var(--white); font-size: var(--fs-150); font-weight: var(--fw-black); border-radius: var(--radius-pill); padding: 1px var(--sp-15); letter-spacing: .4px; }
.bc-help { margin-top: var(--sp-15); }
.bc-help summary { font-size: var(--fs-250); color: var(--grey-mid); cursor: pointer; list-style: none; font-weight: var(--fw-semi); }
.bc-help summary::-webkit-details-marker { display: none; }
.bc-help summary::before { content: "▸ "; color: var(--red); }
.bc-help[open] summary::before { content: "▾ "; }
/* ---- devices: tidy long-form copy ---- */
.card-lede { color: var(--text-4); line-height: var(--lh-base); margin: -4px 0 var(--sp-35); max-width: 68ch; }
.device-note { margin: -6px 0 var(--sp-4); }
.prose { color: var(--text-4); line-height: var(--lh-relaxed); max-width: 68ch; }
ol.steps-compact { line-height: var(--lh-base); }
ol.steps-compact li { margin-bottom: var(--sp-1); }
.card-collapse { margin-top: var(--sp-1); border-top: var(--border); padding-top: var(--sp-1); }
.card-collapse > summary { cursor: pointer; list-style: none; }
.card-collapse > summary::-webkit-details-marker { display: none; }
.card-collapse > summary h2 { display: inline; font-size: var(--fs-650); }
.card-collapse > summary::before { content: "▸"; color: var(--red); font-weight: var(--fw-black); margin-right: var(--sp-15); }
.card-collapse[open] > summary::before { content: "▾"; }

/* ---- QR config block ---- */
.qr-config { display: grid; grid-template-columns: 300px 1fr; gap: var(--sp-6); align-items: start; }
.qr-panel { text-align: center; border: 1px dashed var(--grey-line); border-radius: var(--radius-lg); padding: var(--sp-4); background: var(--surface); }
.qr-panel img { width: 100%; max-width: 260px; height: auto; display: block; margin: 0 auto; }
.qr-payload {
    font-family: var(--font-mono); font-size: var(--fs-250); line-height: var(--lh-base);
    background: var(--surface-3); border-radius: var(--radius-sm); padding: var(--ctrl-pad-y) var(--ctrl-pad-x);
    word-break: break-all; text-align: left; margin-top: var(--sp-3); color: var(--text-2);
}
@media (max-width: 1000px) { .qr-config { grid-template-columns: 1fr; } }

/* ---- steps ---- */
ol.steps { margin: 0 0 0 18px; line-height: var(--lh-relaxed); }
ol.steps li { margin-bottom: var(--sp-15); padding-left: var(--sp-1); }
ol.steps li::marker { color: var(--red); font-weight: var(--fw-black); }

/* ---- flash toasts ---- */
.toasts { position: fixed; top: 70px; right: 22px; z-index: var(--z-toast); width: min(430px, 90vw); }
.flash {
    padding: var(--sp-3) var(--sp-4); border-radius: var(--radius-md); margin-bottom: var(--sp-25);
    font-weight: var(--fw-semi); border-left: var(--bw-accent) solid;
    box-shadow: var(--elev-3);
    display: flex; justify-content: space-between; gap: var(--sp-3); align-items: flex-start;
    animation: slidein .25s var(--ease-out);
    background: var(--surface);
}
.flash.flat { box-shadow: none; }
.flash.is-out { opacity: 0; transform: translateX(30px); transition: opacity var(--dur-4) var(--ease-out), transform var(--dur-4) var(--ease-out); }
@keyframes slidein { from { transform: translateX(30px); opacity: 0; } to { transform: none; opacity: 1; } }
.flash-success { color: var(--ok); border-color: var(--ok); }
.flash-error   { color: var(--red-dark); border-color: var(--red); }
.flash-info    { color: var(--info); border-color: var(--info); }
.flash-warning { color: var(--warn); border-color: var(--warn); }
.flash a { color: inherit; }
.flash-x { background: none; border: none; cursor: pointer; color: inherit; font-size: var(--fs-500); opacity: .6; }
.flash-x:hover { opacity: 1; }

/* ---- item detail ---- */
.detail-head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--sp-5); flex-wrap: wrap; }
.detail-title { font-size: var(--fs-900); font-weight: var(--fw-black); }
.detail-sub { color: var(--grey-mid); margin-top: var(--sp-1); line-height: var(--lh-relaxed); }    /* was 1.9, closest token — negligible on a one-line subtitle */
.kv { display: grid; grid-template-columns: var(--kv-col, 150px) 1fr; row-gap: var(--sp-25); font-size: var(--fs-550); }
.kv-sm { --kv-col: 110px; }
.kv-lg { --kv-col: 170px; }
.kv dt { color: var(--grey-mid); font-weight: var(--fw-semi); }
.kv dd { font-weight: var(--fw-medium); }
.barcode-wrap { text-align: center; padding: var(--sp-25); background: var(--surface); border: 1px dashed var(--grey-line); border-radius: var(--radius-md); }

.basket-count {
    background: var(--red); color: var(--white); border-radius: 50%;
    padding: var(--sp-05) var(--sp-2); font-size: var(--fs-300); font-weight: var(--fw-black);
}

/* ---- toolbar ---- */
.toolbar { display: flex; gap: var(--sp-25); align-items: center; margin-bottom: var(--sp-4); flex-wrap: wrap; }
.toolbar form { display: flex; gap: var(--sp-25); align-items: center; flex-wrap: wrap; }
.toolbar input, .toolbar select { width: auto; }
.toolbar select { min-width: 160px; }
.spacer { flex: 1; }

.empty { color: var(--grey-mid); padding: var(--sp-6); text-align: center; }
.empty.pad-lg { padding: var(--sp-8) var(--sp-6); }

/* ---- locations: collapsible per-zone groups ---- */
details.zone-group { border: var(--border); border-radius: var(--radius-md); margin-bottom: var(--sp-25); overflow: hidden; }
details.zone-group > summary { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-25) var(--sp-35); cursor: pointer; background: var(--surface-inset); list-style: none; }
details.zone-group > summary::-webkit-details-marker { display: none; }
details.zone-group > summary::before { content: '▸'; color: var(--grey-mid); transition: transform var(--t-slow); font-size: var(--fs-200); }
details.zone-group[open] > summary::before { transform: rotate(90deg); }
details.zone-group > summary h2 { display: inline; font-size: inherit; margin: 0; }
details.zone-group > summary .zone-letter { font-weight: var(--fw-bold); font-size: var(--fs-550); }
details.zone-group > summary .zone-stats { margin-left: var(--sp-1); }
details.zone-group > summary .zone-print { margin-left: auto; }
details.zone-group > table.list,
details.zone-group > .table-wrap { margin: 0; border: 0; border-top: var(--border); border-radius: 0; }
details.zone-group > .table-wrap > table.list { margin: 0; border: 0; border-radius: 0; }

/* progress bar (orders picking) */
.pick-progress { background: var(--track); border-radius: var(--radius-sm); height: 10px; overflow: hidden; min-width: 90px; }
.pick-progress > div { height: 100%; width: var(--pct); background: var(--ok); border-radius: var(--radius-sm); transition: width var(--dur-4) var(--ease-out); }

/* ---- label preview (label-preview.php) ---- */
.lp-toolbar { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-4); }
.lp-toolbar .lp-label { margin-right: var(--sp-1); font-weight: var(--fw-semi); }
.lp-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 430px), 1fr)); gap: var(--sp-4); }
.lp-cell { min-width: 0; margin: 0; }
.lp-cell h2 { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.lp-meta { display: flex; flex-wrap: wrap; gap: var(--sp-15) var(--sp-3); margin: 0 0 var(--sp-25); color: var(--text-2); font-size: var(--fs-400); }
.lp-printers { margin: 0 0 var(--sp-3); padding-left: var(--sp-5); }
.lp-printers li + li { margin-top: var(--sp-1); }
.lp-warning { display: block; width: fit-content; max-width: 100%; margin: var(--sp-2) 0; padding: var(--sp-15) var(--sp-25); border: var(--bw-hair) solid var(--warn); border-radius: var(--radius-sm); background: var(--warn-bg); color: var(--warn); font-size: var(--fs-400); font-weight: var(--fw-semi); }
.lp-broken { border-left: var(--bw-accent) solid var(--red); }
.lp-broken ul { margin-bottom: 0; }
.lp-canvas { overflow: auto; padding: var(--sp-1); }
.lp-media { overflow: hidden; position: relative; background: var(--surface); outline: var(--bw-hair) solid var(--grey-mid); outline-offset: 0; flex: none; }
.lp-zoom { transform-origin: top left; transform: scale(var(--lp-zoom)); }
.lp-rot { transform-origin: center; }
.lp-media iframe { display: block; border: 0; background: var(--surface); }
.lp-limit { position: absolute; top: 0; bottom: 0; left: calc(101.6mm * var(--lp-zoom)); z-index: 2; border-left: var(--bw-2) dashed var(--red); pointer-events: none; }
.lp-no-sample { display: flex; align-items: center; justify-content: center; box-sizing: border-box; padding: var(--sp-5); background: var(--surface-inset); text-align: center; }
@media (max-width: 700px) {
    .lp-grid { grid-template-columns: 1fr; }
}

/* ---- login page ---- */
.login-wrap {
    min-height: 100vh; display: flex; align-items: center; justify-content: center;
    background: radial-gradient(1100px 600px at 20% 0%, var(--black-5) 0%, var(--black) 55%);
}
.login-card {
    width: min(400px, 92vw); background: var(--surface); border-radius: var(--radius-xl);
    padding: var(--sp-7) var(--sp-6); box-shadow: var(--elev-5);
    border-top: var(--bw-accent) solid var(--red);
}
.login-card .brand { border: none; padding: 0 0 var(--sp-4); }
.login-demo {
    margin-top: var(--sp-4); font-size: var(--fs-300); color: var(--text-3);
    background: var(--grey-bg); border-radius: var(--radius-md); padding: var(--sp-25) var(--sp-3); line-height: var(--lh-relaxed);
}

/* --grey-mid (#8a8d95) is tuned for the DARK sidebar, where it measures 5.71:1.
   The login card is white, and these two reuse it on that light surface:
       .brand-tag  #8a8d95 on #ffffff = 3.32:1   FAIL (9px uppercase, needs 4.5:1)
       .login-demo #8a8d95 on #f1f2f5 = 2.97:1   FAIL
   --text-3 (#55585f) gives 7.4:1 and 6.6:1 respectively. --grey-mid keeps its
   passing dark-sidebar use untouched; only the light-card context is overridden. */
.login-card .brand-tag { color: var(--text-3); }

/* Microsoft sign-in — Microsoft's brand guidelines: their colours, not ours. */
.btn-ms {
    display: flex; align-items: center; justify-content: center; gap: 10px;
    width: 100%; background: #fff; color: #1b1b1b;
    border: 1px solid #8c8c8c; border-radius: 7px;
    padding: 11px 16px; font-size: var(--fs-550); font-weight: 600;
    text-decoration: none;
}
.btn-ms:hover { background: #f3f3f3; box-shadow: none; }
.btn-ms svg { flex-shrink: 0; }
.login-or {
    display: flex; align-items: center; gap: var(--sp-25);
    margin: var(--sp-4) 0 var(--sp-1); color: var(--grey-mid); font-size: var(--fs-250);
}
.login-or::before, .login-or::after {
    content: ""; flex: 1; height: 1px; background: var(--grey-line);
}

/* ---- mobile navigation ---- */
.nav-burger {
    display: none;
    background: var(--black); color: var(--white); border: none;
    font-size: var(--fs-900); padding: var(--sp-2) var(--sp-3); border-radius: var(--radius-md); cursor: pointer;
    flex-shrink: 0;
}
.nav-overlay {
    display: none; position: fixed; inset: 0; z-index: var(--z-overlay);
    background: rgba(10, 10, 14, .55); backdrop-filter: blur(2px);
}
body.nav-open .nav-overlay { display: block; }

/* ---- responsive: tablets & phones (incl. the L4 handheld) ---- */
@media (max-width: 900px) {
    .nav-burger { display: block; }

    .sidebar {
        position: fixed; top: 0; left: 0; height: 100vh; height: 100dvh;
        transform: translateX(-105%);
        transition: transform var(--t-slow);
        z-index: var(--z-drawer); width: 250px;
        box-shadow: 8px 0 40px rgba(0,0,0,.5);
    }
    body.nav-open .sidebar { transform: none; }

    .topbar { padding: var(--sp-25) var(--sp-3); gap: var(--sp-2); }
    .topbar h1 { font-size: var(--fs-650); white-space: normal; line-height: var(--lh-tight); }
    .quickscan, .clock { display: none; }          /* scan pages have their own big input */
    .user-chip { padding: var(--sp-1) var(--sp-25); font-size: var(--fs-200); max-width: 90px; overflow: hidden; text-overflow: ellipsis; }

    .content { padding: var(--sp-25) var(--sp-2); }
    .card { padding: var(--sp-3) var(--sp-25); margin-bottom: var(--sp-25); border-radius: var(--radius-md); }
    .card h2 { font-size: var(--fs-700); margin-bottom: var(--sp-25); }   /* same token as desktop — no separate mobile type scale */
    .grid, .grid-2, .grid-3 { gap: var(--sp-25); }

    /* desktop-only heavyweight cards (charts etc.) */
    .m-hide { display: none !important; }

    /* KPIs become a compact stat strip, not a wall of cards */
    .kpis { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-15); margin-bottom: var(--sp-25); }
    /* border-top-width dropped: the tile no longer has a heavyweight cap to
       thin out, and the red alert cap is now an inset shadow that needs no
       per-breakpoint adjustment. */
    .kpi { padding: var(--sp-2) var(--sp-15); text-align: center; border-radius: var(--radius-md); }
    .kpi:hover { transform: none; }
    .kpi .kpi-num { font-size: var(--fs-800); }
    .kpi .kpi-label { font-size: var(--fs-100); letter-spacing: .4px; margin-top: var(--sp-05); }

    /* tables: fit naturally; wide ones scroll inside the card */
    .card { overflow-x: auto; -webkit-overflow-scrolling: touch; }
    /* ...but overflow-x forces the BLOCK axis to compute to `auto` as well, so
       the card also clips anything leaving it vertically — including the
       autocomplete panel, which opens downward past the card's bottom edge.
       That broke the new dropdown on phones, which is the one device the
       engineers actually use.
       Moving the scroll to .table-wrap was the obvious fix and is wrong: only
       4 of the 24 table-bearing pages wrap their tables (billing, engineer,
       stocktake, vans), so a blanket move would leave 20 pages overflowing the
       viewport on a phone. Exempting every card that CONTAINS a suggest field
       is also more than needed — 11 cards do, and 2 of those hold a table.
       So the clip is lifted only while a panel is actually open: app.js sets
       aria-expanded="true" on the field when it opens the panel (app.js:371)
       and back to "false" when it closes it (app.js:256). Wide tables keep
       their horizontal scroll on every page, in every other moment.
       Known cost: a card scrolled part-way sideways snaps back to the left
       when a dropdown inside it opens. Exactly one card in the app has both a
       wide table and an autocomplete field (settings.php, "Printers"). */
    table.list { min-width: 0; }
    table.list th { position: static; white-space: nowrap; padding: var(--sp-15) var(--sp-15); font-size: var(--fs-150); }
    table.list td { padding: var(--sp-2) var(--sp-15); font-size: var(--fs-550); }
    table.list .mono { white-space: nowrap; font-size: var(--fs-300); }
    .badge { font-size: var(--fs-150); padding: var(--sp-05) var(--sp-15); }
    .scan-hint { font-size: var(--fs-200); }

    /* touch-friendly controls */
    .btn { padding: 11px var(--sp-4); }
    .btn-sm { padding: var(--sp-2) var(--sp-3); }
    .scan-box input { padding: var(--sp-35) var(--sp-3); font-size: var(--fs-700); }
    .form-actions .btn { flex: 1 1 auto; text-align: center; }

    /* tabs scroll sideways rather than wrapping into a tall stack */
    .tabs { flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
    .tabs::-webkit-scrollbar { display: none; }
    .tab-btn { padding: var(--sp-25) var(--sp-3); font-size: var(--fs-250); }
    .bc-grid { grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: var(--sp-25); }
    .qr-config { gap: var(--sp-35); }

    .detail-head { flex-direction: column; }
    .barcode-wrap { width: 100%; }
    .barcode-wrap svg { max-width: 100%; height: auto; }
    .kv { grid-template-columns: 110px 1fr; }
    .toolbar form { width: 100%; }
    .toolbar input, .toolbar select { flex: 1 1 140px; min-width: 0; }
    .toasts { top: 58px; right: var(--sp-25); left: var(--sp-25); width: auto; }
    /* Footer gutter matched to .content's mobile gutter (--sp-2) and the
       centring dropped, so the footer text sits on the same left edge as the
       cards at EVERY width rather than only on desktop. It was the one piece
       of chrome that disagreed with the column it closes. */
    .app-foot { font-size: var(--fs-150); padding: var(--sp-3) var(--sp-2); }
}

@media (max-width: 480px) {
    .kpis { grid-template-columns: repeat(2, 1fr); }
    .chart-legend { font-size: var(--fs-200); gap: var(--sp-15) var(--sp-25); }
    .login-card { padding: var(--sp-6) var(--sp-4); }
    /* .track-hero h1 moved here — the whole component now lives in track.php,
       which carries its own 480px override. */
}

/* ---- print ---- */
@media print {
    .sidebar, .topbar, .app-foot, .toasts, .nav-burger, .nav-overlay, .no-print { display: none !important; }
    .content { padding: 0; }
    body { background: var(--white); }
    .card { box-shadow: none; }
}

/* =====================================================================
   UTILITY CLASSES — exact catalogue, §3.2 of the build contract.
   No lane may invent additional utility class names.
   ===================================================================== */
/* layout / flow */
.u-inline        { display: inline; }
.u-block         { display: block; }
.u-row           { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.u-row > input:not([type=checkbox]):not([type=radio]):not(.input-xs):not(.input-sm):not(.input-md):not(.input-lg) { flex: 1 1 0; width: auto; min-width: 0; }
.u-row-tight     { display: flex; align-items: center; gap: var(--sp-1); flex-wrap: wrap; }
.u-row-inline    { display: inline-flex; align-items: center; gap: var(--sp-1); }
.u-row-top       { align-items: flex-start; }
.u-justify-center{ justify-content: center; }
.u-justify-end   { justify-content: flex-end; }
.u-gap-1         { gap: var(--sp-1); }
.u-gap-2         { gap: var(--sp-2); }
.u-gap-3         { gap: var(--sp-3); }
.u-full          { width: 100%; }
.u-w-auto        { width: auto; }
.u-hidden        { display: none; }              /* PHP-conditional rendering ONLY — never on a JS-toggled node */

/* text */
.u-nowrap        { white-space: nowrap; }
.u-upper         { text-transform: uppercase; }
.u-center        { text-align: center; }
.u-right         { text-align: right; }
.u-break         { word-break: break-all; }
.u-break-word    { overflow-wrap: anywhere; }
.u-text-xs       { font-size: var(--fs-250); }
.u-text-sm       { font-size: var(--fs-400); }
.u-text-md       { font-size: var(--fs-600); }
.u-text-lg       { font-size: var(--fs-700); }
.u-lh-loose      { line-height: var(--lh-relaxed); }
.text-mid        { color: var(--grey-mid); }
.text-2          { color: var(--text-2); }
.text-warn       { color: var(--warn); }
.text-ok         { color: var(--ok); }
.text-red        { color: var(--red); }
.u-fw-medium     { font-weight: var(--fw-medium); }
.u-fw-bold       { font-weight: var(--fw-bold); }

/* spacing (scale-snapped: 10/14/18/22 round to 8/12/16/20/24) */
/* DESIGNER-LANE ADDITION — the closed §3.2 catalogue has no padding utility;
   the printer edit-row <td colspan> needs padding-top:0 to sit flush under
   its parent row. Named to match the existing u-mt-* / u-mb-* convention. */
.u-pt-0 { padding-top: 0; }
.u-pt-4 { padding-top: var(--sp-4); }
.u-m-0  { margin: 0; }
.u-mt-0 { margin-top: 0; }      .u-mb-0 { margin-bottom: 0; }
.u-mt-1 { margin-top: var(--sp-1); }   .u-mb-1 { margin-bottom: var(--sp-1); }
.u-mt-2 { margin-top: var(--sp-2); }   .u-mb-2 { margin-bottom: var(--sp-2); }
.u-mt-3 { margin-top: var(--sp-3); }   .u-mb-3 { margin-bottom: var(--sp-3); }
.u-mt-4 { margin-top: var(--sp-4); }   .u-mb-4 { margin-bottom: var(--sp-4); }
.u-mt-5 { margin-top: var(--sp-5); }   .u-mb-5 { margin-bottom: var(--sp-5); }
.u-mt-6 { margin-top: var(--sp-6); }   .u-mb-6 { margin-bottom: var(--sp-6); }
.u-on-dark { color: var(--on-dark) !important; }

/* compact inputs inside tables and per-row forms */
input.input-xs { width: 72px; }
input.input-sm { width: 96px; }
input.input-md { width: 120px; }
input.input-lg { width: 160px; }
input.code-input { text-transform: uppercase; letter-spacing: .4px; font-family: var(--font-mono); }

/* table column widths */
.col-check { width: 36px; }
.col-xs    { width: 70px; }
.col-sm    { width: 110px; }
.col-md    { width: 130px; }
.col-lg    { width: 180px; }
.col-w     { width: var(--col-w); }              /* proportional one-offs: style="--col-w:78%" */

/* small components extracted from inline styles */
.img-frame   { border: var(--border); border-radius: var(--radius-xs); }
.img-fluid   { max-width: 100%; height: auto; display: block; }
.cap-full    { color: var(--red); font-weight: var(--fw-bold); }
.is-aged     { color: var(--red); }



/* =============================================================================
   MOBILE — paste into the existing @media (max-width: 720px) block.
   The three dashboard chart cards no longer carry .m-hide, so this is what
   makes them usable on a phone: the plot is dropped (its 10px axis text is
   unreadable at that width) and the legend totals plus the figures table,
   which are always in the DOM, carry the data instead.
   ============================================================================= */
@media (max-width: 720px) {
    .chart-svg { display: none; }
    .chart-data > summary { padding: var(--sp-2) 0; font-size: var(--fs-450); }
    .chart-block > .chart-legend { gap: var(--sp-15) var(--sp-3); }
    .chart-legend { font-size: var(--fs-200); gap: var(--sp-15) var(--sp-25); }
    .chart-donut { max-width: 180px; }
    .chart-hero strong { font-size: var(--fs-900); }
}


/* ============================================================================
   GOODS IN / GOODS OUT — workstream 4a/4b
   Append after the FORM CONTROLS block in style.css (it extends .form-grid,
   .field, .check and .badge). Tokens only: no raw hex, no raw px font-size.
   ========================================================================== */

/* ---- form sections ----
   The booking form was 14 fields in one flat grid. It is now four labelled
   groups — Stock details / Quantity / Location / Reference — separated by a
   hairline rather than by four separate cards, so the page stays one screen. */
.form-section + .form-section {
    margin-top: var(--sp-5);
    padding-top: var(--sp-5);
    border-top: var(--border-soft);
}
.form-section-title {
    font-size: var(--fs-250);
    font-weight: var(--fw-bold);
    letter-spacing: var(--ls-caps);
    text-transform: uppercase;
    color: var(--grey-mid);
    margin-bottom: var(--sp-3);
}

/* Quantity · Unit · Weight · Label copies on one row. Mirrors .form-row-3. */
.form-row-4 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-35) var(--sp-4); }
@media (max-width: 1100px) { .form-row-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 900px)  { .form-row-4 { grid-template-columns: minmax(0, 1fr); } }

/* A read-only control is driven by something else on the page (the quantity by
   the scan count, label copies by serial tracking) — it must not look editable. */
.field input[readonly] {
    background: var(--surface-3);
    color: var(--text-2);
    cursor: not-allowed;
}

/* ---- serial capture (Goods In) ----
   Replaces the .panel-inset card-in-a-card. The checkbox is now an ordinary
   inline field row; only the live capture area gets a surface of its own. */
.serial-toggle { display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap; }
.serial-toggle .scan-hint { margin-top: var(--sp-0); }

.serial-capture {
    background: var(--surface-inset);
    border: var(--border-soft);
    border-left: var(--bw-accent) solid var(--red);
    border-radius: var(--radius-md);
    padding: var(--sp-3) var(--sp-35);
}
.serial-capture[hidden] { display: none; }

.serial-capture-bar { display: flex; align-items: center; gap: var(--sp-25); flex-wrap: wrap; }
.serial-capture-bar input {
    flex: 1 1 220px;
    min-width: 0;
    text-transform: uppercase;
    letter-spacing: .4px;
}
.serial-count {
    margin-left: auto;
    font-size: var(--fs-400);
    font-weight: var(--fw-semi);
    color: var(--text-2);
    white-space: nowrap;
}
/* The running count is the number the operator is watching — give it size. */
.serial-count strong {
    font-size: var(--fs-900);
    font-weight: var(--fw-black);
    color: var(--red);
    margin-right: var(--sp-1);
    line-height: var(--lh-tight);
}

/* Unscoped deliberately: the status element moved to #gi-code-status inside the
   "Product barcode" field and is no longer inside a .serial-capture, so the old
   descendant scope silently stopped matching — a duplicate-scan warning rendered
   in the same grey as a good scan. The colour IS the signal here. */
.scan-hint.is-ok    { color: var(--ok); font-weight: var(--fw-semi); }
.scan-hint.is-error { color: var(--red); font-weight: var(--fw-semi); }

/* Captured product codes stay in hidden form fields; the visible list is an
   intentionally compact confirmation grid and never exposes raw barcodes. */
.serial-list {
    list-style: none;
    margin: var(--sp-2) var(--sp-0) var(--sp-0);
    padding: var(--sp-0);
    max-height: 220px;
    overflow-y: auto;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
    gap: var(--sp-1);
}
.serial-list:empty { display: none; }
.serial-row {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    background: rgba(255, 255, 255, .82);
    border: 1px solid rgba(22, 163, 74, .22);
    border-radius: var(--radius-pill);
    padding: var(--sp-1) var(--sp-1) var(--sp-1) var(--sp-2);
}
.serial-row:first-child { border-color: rgba(22, 163, 74, .38); box-shadow: var(--elev-1); }
.serial-n {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    width: 1.35rem;
    height: 1.35rem;
    text-align: center;
    font-size: var(--fs-250);
    font-weight: var(--fw-bold);
    color: #fff;
    background: var(--ok);
    border-radius: 50%;
    font-variant-numeric: tabular-nums;
}
.serial-value { flex: 1 1 auto; min-width: 0; color: var(--text); font-weight: var(--fw-semi); }
.serial-del { flex: 0 0 auto; padding: var(--sp-0) var(--sp-2); }

/* ---- unit-counted dispatch lines (Goods Out) ---- */
.badge-units { background: var(--red-light); color: var(--red-dark); }
.badge-whole { background: var(--surface-3); color: var(--text-3); }

.serial-chips {
    list-style: none;
    margin: var(--sp-15) var(--sp-0) var(--sp-0);
    padding: var(--sp-0);
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1) var(--sp-15);
}
.serial-chips li {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    background: var(--surface-3);
    border-radius: var(--radius-pill);
    padding: var(--sp-05) var(--sp-2);
    font-size: var(--fs-300);
}
.serial-chip-x {
    border: 0;
    background: none;
    padding: var(--sp-0);
    cursor: pointer;
    color: var(--grey-mid);
    font-size: var(--fs-300);
    line-height: 1;
}
.serial-chip-x:hover { color: var(--red); }
.serial-chip-x:focus-visible { outline: var(--bw-2) solid var(--red); outline-offset: 2px; border-radius: var(--radius-xs); }

/* ============================================================================
   Workstream 4b — text diet
   For the orchestrator to merge into assets/css/style.css.
   Every value below reuses an existing :root custom property — no raw hex,
   no raw px font sizes, no new colours introduced.
   ============================================================================ */

/* ---- inventory.php: distinguishable stock-type badges --------------------
   All four (soon five) stock types previously rendered with the single
   `.badge-type` class (red-light bg / red-dark text), so they were visually
   identical in the Type column. Each now gets its own token pairing drawn
   from the existing status/neutral palette — no hue is invented, and the
   set exactly covers the 5 keys in $STOCK_TYPES (includes/config.php). */
.badge-stype-item          { background: var(--track);      color: var(--text-3); }  /* Single Item — neutral grey, the default/no-frills case */
.badge-stype-carton        { background: var(--warn-bg);    color: var(--warn); }    /* Carton / Box */
.badge-stype-pallet_sealed { background: var(--red-light);  color: var(--red-dark); }/* Sealed Pallet — was the shared badge-type look, kept for the "heaviest" unit */
.badge-stype-pallet_mixed  { background: var(--ok-bg);      color: var(--ok); }      /* Mixed Pallet */
.badge-stype-bulk          { background: var(--black);      color: var(--white); }   /* Bulk Quantity */

/* ---- inventory.php: filter toolbar reads as filters + actions ------------
   The filter <form> already sits before the .spacer in .toolbar (actions
   render after it), but with no visual boundary every control read as one
   flat row of 8 equal-weight controls. Boxing just the filter cluster in
   the existing inset-surface treatment (same tokens as .qr-panel /
   .surface-inset elsewhere) makes the two groups legible at a glance
   without touching .toolbar's flex/wrap behaviour. */
.toolbar .filter-group {
    background: var(--surface-inset);
    border: var(--border);
    border-radius: var(--radius-md);
    padding: var(--sp-25) var(--sp-3);
}

/* ============================================================================
   BOOT STOCK — engineer.php (phone-first) and vans.php (office).
   Merge into assets/css/style.css. Every value comes from an existing :root
   token: no raw hex, no raw px font-size. The only bare pixel values are the
   44px minimum tap target (an accessibility floor, not a type or colour
   decision) and one 1px hairline that has no token of its own.
   ============================================================================ */

/* ---- shared: an item as a CARD, not a table row ---------------------------
   The engineer's surface used to be a six-column table on a 380px screen. Cards
   stack, wrap and give every action a full-width target, so nothing scrolls
   sideways and nothing needs a fingertip smaller than a fingertip. */
.van-list {
    display: grid;
    gap: var(--sp-3);
    grid-template-columns: minmax(0, 1fr);
    margin-top: var(--sp-2);
}
@media (min-width: 900px) {
    /* On a desk there is room for more than one van item at a time. */
    .van-list { grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); gap: var(--sp-4); }
}

.van-item {
    border: var(--border);
    border-left: var(--bw-accent) solid var(--red);
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: var(--elev-1);
    padding: var(--sp-35);
    min-width: 0;
}
.van-item-quiet { border-left-color: var(--grey-line); }

.van-item-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--sp-3);
}
.van-item-title { min-width: 0; }
.van-item-title strong {
    display: block;
    font-size: var(--fs-650);
    line-height: var(--lh-snug);
    color: var(--text);
    overflow-wrap: anywhere;
}
.van-item-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-15);
    margin-top: var(--sp-15);
    font-size: var(--fs-400);
    color: var(--grey-mid);
}
.van-item-meta .mono { overflow-wrap: anywhere; }

/* The number is what an engineer reads first — it stays big and never wraps. */
.van-qty {
    flex: 0 0 auto;
    text-align: right;
    line-height: var(--lh-tight);
}
.van-qty-num {
    display: block;
    font-size: var(--fs-900);
    font-weight: var(--fw-black);
    color: var(--black);
}
.van-qty-unit {
    display: block;
    font-size: var(--fs-250);
    text-transform: uppercase;
    letter-spacing: var(--ls-caps);
    color: var(--grey-mid);
}

.van-item-last {
    margin: var(--sp-25) 0 0;
    padding-top: var(--sp-2);
    border-top: var(--border-soft);
    overflow-wrap: anywhere;
}

/* ---- folded actions -------------------------------------------------------
   One tap opens one action; app.js is untouched — <details> does the work. */
.van-act { margin-top: var(--sp-2); }
.van-act-btn {
    display: block;
    width: 100%;
    min-height: 44px;                    /* tap target floor */
    padding: var(--sp-3) var(--sp-35);
    border: var(--border);
    border-radius: var(--ctrl-radius);
    background: var(--surface-inset);
    color: var(--text);
    font-size: var(--fs-550);
    font-weight: var(--fw-semi);
    text-align: left;
    cursor: pointer;
    list-style: none;
    transition: background var(--t-base), border-color var(--t-base);
}
.van-act-btn::-webkit-details-marker { display: none; }
.van-act-btn::after {
    content: "+";
    float: right;
    color: var(--red);
    font-weight: var(--fw-black);
}
.van-act[open] > .van-act-btn::after { content: "–"; }
.van-act-btn:hover { background: var(--red-tint); border-color: var(--red); }
.van-act-btn:focus-visible { outline: none; box-shadow: var(--ring); }

/* The action they came here to do reads as the primary one. */
.van-act-primary {
    background: var(--red);
    border-color: var(--red);
    color: var(--white);
}
.van-act-primary::after { color: var(--white); }
.van-act-primary:hover { background: var(--red-dark); border-color: var(--red-dark); }

.van-act-form { margin-top: var(--sp-2); }
.van-act-form .van-act-btn { text-align: left; }

/* ---- forms inside a card, on a phone -------------------------------------- */
.van-form {
    display: grid;
    gap: var(--sp-3);
    padding: var(--sp-35) 0 var(--sp-1);
}
.van-form .field { margin: 0; }
.van-form-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--sp-3);
}
/* Full-width controls: a half-width input is a half-sized target. */
.van-form input,
.van-form select { width: 100%; min-height: 44px; }
.van-submit { width: 100%; min-height: 44px; font-size: var(--fs-550); }

/* ---- the mismatch warning, at the point of entry -------------------------- */
.van-warn {
    border: var(--bw-1) solid var(--red);
    border-radius: var(--ctrl-radius);
    background: var(--red-light);
    color: var(--text);
    padding: var(--sp-25) var(--sp-3);
    font-size: var(--fs-400);
    line-height: var(--lh-snug);
}
.van-warn strong { color: var(--red-dark); }
.van-warn[hidden] { display: none; }
.van-warn-owner { font-weight: var(--fw-bold); }

.van-item-label {
    display: inline-block;
    margin-top: var(--sp-25);
    font-size: var(--fs-400);
    color: var(--grey-mid);
}

/* ---- lists that would otherwise have been tables -------------------------- */
.van-news-list,
.van-jobs,
.van-requests {
    list-style: none;
    margin: var(--sp-2) 0 0;
    padding: 0;
    display: grid;
    gap: var(--sp-2);
}
.van-news-list > li {
    padding: var(--sp-25) 0;
    border-bottom: var(--border-soft);
    font-size: var(--fs-500);
    line-height: var(--lh-snug);
}
.van-news-list > li:last-child { border-bottom: 0; }

.van-jobs > li,
.van-requests > li {
    padding: var(--sp-25) var(--sp-3);
    border: var(--border-soft);
    border-radius: var(--radius-md);
    background: var(--surface-2);
}
.van-jobs > li.van-job-mismatch {
    border-color: var(--red);
    background: var(--red-light);
}
.van-job-top {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--sp-3);
}
.van-job-top strong { min-width: 0; overflow-wrap: anywhere; }
.van-job-qty {
    flex: 0 0 auto;
    font-weight: var(--fw-bold);
    font-size: var(--fs-550);
}
.van-job-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-15);
    margin-top: var(--sp-1);
    font-size: var(--fs-400);
    color: var(--text-3);
}

/* ============================================================================
   OFFICE — vans.php
   ============================================================================ */

/* ---- fleet index ----------------------------------------------------------
   One row per engineer. The triage line is plain text with real separators, so
   it reads the same with or without this stylesheet. */
.van-triage {
    margin: var(--sp-2) 0 var(--sp-3);
    padding: var(--sp-25) var(--sp-3);
    border-left: var(--bw-3) solid var(--red);
    background: var(--red-tint);
    border-radius: var(--radius-sm);
    font-size: var(--fs-400);
    line-height: var(--lh-snug);
    color: var(--text-2);
}
.van-triage strong { color: var(--red-dark); }

/* Shown only when the database is missing the columns the boot-stock records need.
   It has to out-shout the figures underneath it, because those figures are the thing
   it is warning you not to trust. */
.van-schema-warn {
    border-left: var(--bw-accent) solid var(--red);
    background: var(--red-light);
    color: var(--text-2);
    font-size: var(--fs-500);
    line-height: var(--lh-snug);
}
.van-schema-warn strong { color: var(--red-dark); }

.van-index-name {
    font-weight: var(--fw-bold);
    font-size: var(--fs-550);
    text-decoration: none;
}
.van-index-name:hover { text-decoration: underline; }
/* A van with something outstanding is found by scanning, not by reading. */
.van-row-flag > td:first-child { box-shadow: inset var(--bw-3) 0 0 var(--red); }

/* ---- one engineer's page --------------------------------------------------- */
.van-profile .van-facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: var(--sp-3);
    margin: var(--sp-2) 0 var(--sp-3);
}
.van-facts dt {
    font-size: var(--fs-250);
    text-transform: uppercase;
    letter-spacing: var(--ls-caps);
    color: var(--grey-mid);
}
.van-facts dd {
    margin: var(--sp-05) 0 0;
    font-size: var(--fs-550);
    font-weight: var(--fw-semi);
    color: var(--text);
    overflow-wrap: anywhere;
}

/* The whole chain on one line: in → used → left → back → missing.
   Marked up as a definition list: each cell is a term and its value, so with no
   CSS at all it still reads "Pushed in / 6" on separate lines rather than
   colliding into "6Pushed in". Nothing here is load-bearing for legibility. */
.van-bar {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 1px;                              /* hairline rule between cells */
    margin: var(--sp-2) 0 var(--sp-3);
    background: var(--grey-line);
    border: var(--border);
    border-radius: var(--radius-md);
    overflow: hidden;
}
@media (max-width: 720px) {
    .van-bar { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.van-bar-cell {
    background: var(--surface);
    padding: var(--sp-25) var(--sp-3);
    text-align: center;
}
.van-bar-cell.is-bad { background: var(--red-light); }
.van-bar dt {
    font-size: var(--fs-250);
    text-transform: uppercase;
    letter-spacing: var(--ls-caps);
    color: var(--grey-mid);
}
.van-bar dd {
    margin: var(--sp-05) 0 0;
    font-size: var(--fs-800);
    font-weight: var(--fw-black);
    color: var(--black);
    line-height: var(--lh-tight);
}
.van-bar-cell.is-bad dd { color: var(--red); }

.van-flag {
    margin: 0 0 var(--sp-2);
    padding: var(--sp-25) var(--sp-3);
    border-left: var(--bw-3) solid var(--red);
    background: var(--red-light);
    border-radius: var(--radius-sm);
    font-size: var(--fs-400);
    line-height: var(--lh-snug);
    color: var(--text-2);
}

/* A row that does not reconcile is tinted, so it is found by scanning not reading. */
.van-row-bad > td { background: var(--red-light); }

.van-transfer-card {
    border-top: var(--bw-accent) solid var(--red);
}
.van-return-destination {
    display: grid;
    grid-template-columns: minmax(180px, 0.45fr) minmax(280px, 1fr);
    gap: var(--sp-1) var(--sp-3);
    align-items: end;
    max-width: 880px;
}
.van-return-destination label {
    grid-column: 1 / -1;
    margin: 0;
}
.van-return-destination select { width: 100%; }
.van-return-destination .scan-hint {
    margin: 0;
    align-self: center;
}
.van-return-check {
    width: 48px;
    text-align: center;
}
.van-return-check input {
    width: 19px;
    height: 19px;
    accent-color: var(--red);
    cursor: pointer;
}
.van-return-table label { cursor: pointer; }
.van-return-qty {
    width: 110px;
    text-align: right;
}
@media (max-width: 760px) {
    .van-return-destination { grid-template-columns: 1fr; }
    .van-return-destination label { grid-column: auto; }
}

/* =====================================================================
   AUTOCOMPLETE — [data-suggest] / [data-suggest-list], built by app.js
   -----------------------------------------------------------------
   Replaces every native <datalist> in the app. The browser drew those
   detached from the field: the stock one as a huge box floating mid-screen
   with one long concatenated label per row, the Goods In part-number one
   floating down across the Description / Quantity / Zone inputs. This panel
   is anchored to its input and matches its width.

   Two row shapes:
     .ac-row              stock search — description + barcode + muted meta line.
                          Opens only once a character is typed.
     .ac-row.ac-row-simple  pick-from-list dropdowns (zones, printers, part numbers,
                          IPs) — one line: value + optional muted note. These open
                          their FULL list on focus and render every row, so an
                          operator picks instead of scanning each unit in; the panel
                          (.ac-panel-simple) is taller and scrolls.

   THE PANEL IS NOT NESTED BESIDE ITS INPUT. app.js appends it to <body> and
   positions it `fixed` from the input's getBoundingClientRect(), repositioning
   on scroll (capture phase, so nested scroll boxes are heard), on resize, and
   closing it if the field scrolls out of sight.

   That is deliberate and it replaces the old anchoring contract. Anchored
   inside the flow, the panel was clipped by the first ancestor that scrolls:
   .table-wrap (overflow-x:auto) left 12px of a 420px panel visible on
   vans.php "Returns to book in", and .card does the same on phones. Lifting
   the clip per-container needs a new opt-out every time a field moves, and
   forcing overflow:visible on .table-wrap would let wide tables burst out of
   their card mid-scroll. Out of the flow, no ancestor's overflow — and no
   future transform, filter or contain — can reach it.
   */

/* A [data-suggest-list] field is a dropdown — it opens its whole list on focus —
   so it has to look like one. Same chevron, same geometry as the themed <select>,
   with the matching right padding so the glyph never sits under the typed value.
   background-image longhand, never the shorthand: that would wipe the
   background-color the base input rule sets.
   NOT applied to [data-suggest] scan fields — those are search boxes, not pickers. */
input[data-suggest-list] {
    padding-right: calc(var(--ctrl-pad-x) + var(--chevron-w) + var(--sp-2));
    background-image: var(--chevron);
    background-repeat: no-repeat;
    background-position: right var(--ctrl-pad-x) center;
    background-size: var(--chevron-w) var(--chevron-h);
}
input[data-suggest-list]:hover:not(:disabled),
input[data-suggest-list]:focus {
    background-image: var(--chevron-red);
}

/* position: fixed — viewport-relative, so no scrolling ancestor can clip it.
   top / left / width / max-height are all set inline by app.js from the input's
   rectangle; the values here are only the starting point and the CAP that
   place() measures against (it reads this max-height rather than hardcoding it,
   so changing the number below is enough to change the panel height). */
.ac-panel {
    display: block;
    position: fixed;
    top: 0;
    left: 0;
    z-index: var(--z-suggest, 30);
    max-height: 340px;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: var(--sp-1);
    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--elev-3);
}
.ac-panel[hidden] { display: none; }

/* flipped above the field when there is not enough room below — lift the shadow
   the other way so it still reads as sitting over the page */
.ac-panel.is-above { box-shadow: var(--elev-4); }

/* An open panel is a live UI affordance, never part of a printed page. */
@media print {
    .ac-panel { display: none !important; }
}

/* The dropdown mode renders every option (100+ parts on a big account), so it gets
   the taller window — enough rows visible at once to pick by eye rather than scroll
   blind. -webkit-overflow-scrolling keeps the flick smooth on the warehouse tablets. */
.ac-panel-simple {
    max-height: 420px;
    -webkit-overflow-scrolling: touch;
    scroll-padding: var(--sp-1);
}

.ac-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
        "desc code"
        "meta meta";
    gap: var(--sp-05) var(--sp-3);
    align-items: baseline;
    padding: var(--sp-2) var(--sp-25);
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: background var(--t-base), color var(--t-base);
}

.ac-desc {
    grid-area: desc;
    min-width: 0;
    color: var(--text);
    font-size: var(--fs-500);
    font-weight: var(--fw-medium);
    line-height: var(--lh-snug);
    overflow-wrap: anywhere;
}

.ac-code {
    grid-area: code;
    font-family: var(--font-mono);
    font-size: var(--fs-400);
    color: var(--text-3);
    white-space: nowrap;
}

.ac-meta {
    grid-area: meta;
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-15);
    font-size: var(--fs-400);
    color: var(--grey-mid);
    line-height: var(--lh-snug);
}
.ac-meta span + span::before {
    content: "·";
    margin-right: var(--sp-15);
    color: var(--grey-line);
}

/* ---- simple rows: zones, printer names, part numbers, LAN addresses ---- */
.ac-row-simple {
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas: "val note";
    align-items: baseline;
}
.ac-val {
    grid-area: val;
    font-family: var(--font-mono);
    font-size: var(--fs-500);
    font-weight: var(--fw-medium);
    color: var(--text);
    overflow-wrap: anywhere;
}
.ac-note {
    grid-area: note;
    font-size: var(--fs-400);
    color: var(--grey-mid);
    text-align: right;
    overflow-wrap: anywhere;
}
.ac-row-simple.is-active .ac-val { color: var(--red); }

/* matched substring, inside .ac-desc / .ac-code / .ac-val / .ac-note —
   <mark> defaults to yellow */
.ac-hit {
    background: var(--red-tint-2);
    color: var(--red);
    font-weight: var(--fw-semi);
    border-radius: var(--radius-xs);
    padding: 0 var(--sp-05);
}

.ac-row:hover,
.ac-row.is-active { background: var(--red-light); }
.ac-row.is-active .ac-desc { color: var(--text); font-weight: var(--fw-semi); }
.ac-row.is-active .ac-code { color: var(--red); }
.ac-row.is-active .ac-hit { background: transparent; padding: 0; }

/* truncation notice — not an option, so it must not look clickable */
.ac-more {
    display: block;
    padding: var(--sp-2) var(--sp-25);
    margin-top: var(--sp-1);
    border-top: var(--border-soft);
    color: var(--grey-mid);
    font-size: var(--fs-400);
    text-align: center;
    cursor: default;
}

/* No .quickscan or .toolbar overrides are needed any more. The topbar quick-scan
   panel used to need right-edge anchoring, and the orders toolbar field needed its
   flex sizing moved onto the wrapper; app.js now sets width and clamps left from
   the field's own rectangle, and it no longer wraps the input, so both are gone. */

@media (max-width: 900px) {
    .ac-panel { max-height: 56vh; }
    .ac-panel-simple { max-height: 62vh; }
    .ac-desc { font-size: var(--fs-550); }
    .ac-meta, .ac-code, .ac-more { font-size: var(--fs-400); }
}



/* ═══════════════════════════════════════════════════════════════════════════
   AI ASSISTANT
   A small tab pinned to the right edge, vertically centred — the "feedback tab"
   pattern — opening a chat panel that slides in from the right.

   Z-INDEX, chosen deliberately against the documented scale in :root
   (topbar 20, suggest 30, overlay 55, drawer 60, toast 100):

     --z-ai-backdrop: 26
     --z-ai-tab:      27
     --z-ai-panel:    28

   All three sit ABOVE page content and the topbar (20) but BELOW --z-suggest
   (30). That is the important one: the scan/search autocomplete now renders into
   <body> as position:fixed, and an operator mid-scan must never have a
   suggestion list hidden behind a chat window. Keeping the assistant under 30
   means the dropdown always wins. It also stays below the mobile nav overlay
   (55) and drawer (60), so opening the menu covers the assistant rather than
   fighting it, and below toasts (100) so flash messages are never obscured.

   Every value below comes from an existing :root token — no raw hex, no raw px
   font sizes.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
    --z-ai-backdrop: 26;
    --z-ai-tab: 27;
    --z-ai-panel: 28;
    --ai-panel-w: 380px;
}

/* ---------- the edge tab ----------
   VERTICAL by design. A horizontal tab pinned to the right edge can only grow
   leftwards over the page, and "Ask Ameco AI" was long enough to be clipped off
   the viewport. Turning the text to read down the tab keeps it narrow
   horizontally — the user asked for "little" — while giving the words as much
   room as they need.

   writing-mode alone is used, deliberately: it changes how TEXT flows without
   rotating the element box, so `transform` still works in ordinary screen axes
   and translateX() below still slides the tab out sideways from the right edge.
   A rotate() here would have flipped that axis. */
.ai-tab {
    position: fixed;
    right: 0;
    top: 50%;
    transform: translateY(-50%) translateX(var(--sp-2));   /* recessed until hovered */
    z-index: var(--z-ai-tab);

    writing-mode: vertical-rl;
    text-orientation: mixed;                                /* Latin reads top-to-bottom */

    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-15);

    padding: var(--sp-3) var(--sp-2);
    border: 0;
    /* Rounded on the LEFT only, so it reads as attached to the screen edge.
       Physical corners, unaffected by writing-mode. */
    border-radius: var(--radius-md) 0 0 var(--radius-md);
    background: var(--red);
    color: var(--white);
    font-family: var(--font-sans);
    font-size: var(--fs-400);
    font-weight: var(--fw-semi);
    letter-spacing: var(--ls-tight);
    line-height: var(--lh-tight);
    cursor: pointer;
    box-shadow: var(--elev-3);
    transition: transform var(--t-base), background var(--t-base), box-shadow var(--t-base);
}

/* The glyph would otherwise be rotated with the line — keep it upright. */
.ai-tab-icon {
    writing-mode: horizontal-tb;
    font-size: var(--fs-550);
    line-height: 1;
}

/* "little" — at rest the tab is just the ✦ mark on the edge of the screen; the
   words appear on hover/focus. In vertical writing mode the label's inline axis
   is VERTICAL, so it is collapsed with max-height, not max-width. */
.ai-tab-label {
    max-height: 0;
    overflow: hidden;
    white-space: nowrap;
    opacity: 0;
    transition: max-height var(--t-slow), opacity var(--t-base);
}

.ai-tab:hover,
.ai-tab:focus-visible {
    transform: translateY(-50%) translateX(0);             /* slides out to the edge */
    background: var(--red-dark);
    box-shadow: var(--elev-4);
}

/* Revealed length runs DOWN the tab. Sized for the longest realistic brand and
   still short enough that the tab never becomes a slab down the side. */
.ai-tab:hover .ai-tab-label,
.ai-tab:focus-visible .ai-tab-label {
    max-height: calc(var(--sp-10) + var(--sp-7));
    opacity: 1;
}

/* Short viewports: never let the tab dominate the screen. */
@media (max-height: 620px) {
    .ai-tab:hover .ai-tab-label,
    .ai-tab:focus-visible .ai-tab-label { max-height: var(--sp-10); }
}

.ai-tab:focus-visible { outline: none; box-shadow: var(--elev-4), var(--ring-strong); }

/* ---------- panel ---------- */
.ai-panel {
    position: fixed;
    top: var(--sp-4);
    right: var(--sp-4);
    bottom: var(--sp-4);
    z-index: var(--z-ai-panel);

    width: min(var(--ai-panel-w), calc(100vw - var(--sp-7)));
    display: flex;
    flex-direction: column;
    overflow: hidden;

    background: var(--surface);
    border: var(--border);
    border-radius: var(--radius-xl);
    box-shadow: var(--elev-4);
    font-family: var(--font-sans);
    font-size: var(--fs-500);
    color: var(--text);

    animation: ai-slide-in var(--dur-3) var(--ease-out);
}

.ai-panel[hidden] { display: none; }

@keyframes ai-slide-in {
    from { opacity: 0; transform: translateX(var(--sp-6)); }
    to   { opacity: 1; transform: translateX(0); }
}

.ai-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-2);
    padding: var(--sp-3) var(--sp-3) var(--sp-3) var(--sp-4);
    background: var(--black);
    color: var(--white);
    flex: 0 0 auto;
}

.ai-head-title {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    font-size: var(--fs-500);
    font-weight: var(--fw-semi);
    letter-spacing: var(--ls-tight);
}

.ai-head-mark { color: var(--red); font-size: var(--fs-650); line-height: 1; }

.ai-close {
    border: 0;
    background: transparent;
    color: var(--on-dark);
    font-size: var(--fs-550);
    line-height: 1;
    padding: var(--sp-1) var(--sp-15);
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: background var(--t-fast), color var(--t-fast);
}

.ai-close:hover { background: var(--black-3); color: var(--white); }
.ai-close:focus-visible { outline: none; box-shadow: var(--ring-strong); color: var(--white); }

/* ---------- which screen it is helping with ---------- */
.ai-scope {
    flex: 0 0 auto;
    padding: var(--sp-15) var(--sp-4);
    background: var(--black-2);
    color: var(--on-dark);
    font-size: var(--fs-300);
    line-height: var(--lh-snug);
    letter-spacing: var(--ls-tight);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.ai-scope[hidden] { display: none; }

/* ---------- context-aware starter prompts ---------- */
.ai-starters {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-15);
}

.ai-starter {
    border: var(--bw-hair) solid var(--red-tint-2);
    background: var(--surface);
    color: var(--red-dark);
    font-family: var(--font-sans);
    font-size: var(--fs-400);
    font-weight: var(--fw-medium);
    line-height: var(--lh-snug);
    padding: var(--sp-15) var(--sp-25);
    border-radius: var(--radius-pill);
    cursor: pointer;
    text-align: left;
    transition: background var(--t-fast), border-color var(--t-fast);
}

.ai-starter:hover { background: var(--red-tint); border-color: var(--red); }
.ai-starter:focus-visible { outline: none; box-shadow: var(--ring); border-color: var(--red); }

/* ---------- transcript ---------- */
.ai-log {
    flex: 1 1 auto;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: var(--sp-4);
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    background: var(--grey-bg);
}

.ai-msg {
    padding: var(--sp-25) var(--sp-3);
    border-radius: var(--radius-lg);
    line-height: var(--lh-base);
    max-width: 92%;
    word-wrap: break-word;
    overflow-wrap: anywhere;
}

.ai-msg p { margin: 0 0 var(--sp-2); }
.ai-msg p:last-child { margin-bottom: 0; }
.ai-msg ul { margin: 0 0 var(--sp-2); padding-left: var(--sp-5); }
.ai-msg ul:last-child { margin-bottom: 0; }
.ai-msg li { margin-bottom: var(--sp-1); }

.ai-msg ol { margin: 0 0 var(--sp-2); padding-left: var(--sp-5); }
.ai-msg ol:last-child { margin-bottom: 0; }

.ai-msg code {
    font-family: var(--font-mono);
    font-size: var(--fs-400);
    background: var(--surface-3);
    padding: 0 var(--sp-1);
    border-radius: var(--radius-xs);
}

/* Fenced code: scrolls sideways rather than stretching the panel. */
.ai-msg pre {
    margin: 0 0 var(--sp-2);
    padding: var(--sp-25);
    background: var(--surface-3);
    border: var(--border-soft);
    border-radius: var(--radius-sm);
    overflow-x: auto;
}

.ai-msg pre:last-child { margin-bottom: 0; }
.ai-msg pre code { background: none; padding: 0; white-space: pre; }

/* Headings inside an answer — deliberately close to body size; this is a chat
   panel, not a document, so a heading is a light emphasis, not a banner. */
.ai-msg .ai-h {
    margin: var(--sp-2) 0 var(--sp-1);
    font-size: var(--fs-550);
    font-weight: var(--fw-semi);
    line-height: var(--lh-snug);
    letter-spacing: var(--ls-tight);
}

.ai-msg .ai-h:first-child { margin-top: 0; }
.ai-msg .ai-h4, .ai-msg .ai-h5, .ai-msg .ai-h6 { font-size: var(--fs-500); color: var(--text-2); }

/* ---------- tables ----------
   The panel is ~380px. A table wider than that scrolls INSIDE this wrapper so
   the panel itself never grows and the page never scrolls sideways. */
.ai-table-wrap {
    margin: 0 0 var(--sp-2);
    max-width: 100%;
    overflow-x: auto;
    border: var(--border-soft);
    border-radius: var(--radius-sm);
    background: var(--surface);
}

.ai-table-wrap:last-child { margin-bottom: 0; }

.ai-table {
    border-collapse: collapse;
    width: 100%;
    font-size: var(--fs-400);
    line-height: var(--lh-snug);
}

.ai-table th,
.ai-table td {
    padding: var(--sp-15) var(--sp-2);
    text-align: left;
    vertical-align: top;
    white-space: nowrap;
    border-bottom: var(--border-soft);
}

.ai-table th {
    background: var(--surface-3);
    font-size: var(--fs-200);
    font-weight: var(--fw-semi);
    text-transform: uppercase;
    letter-spacing: var(--ls-caps);
    color: var(--text-2);
}

.ai-table tbody tr:last-child td { border-bottom: 0; }
.ai-table tbody tr:hover { background: var(--surface-2); }

/* On the user's own red bubble the table needs its own light surface to stay
   legible — red-on-red would fail contrast. */
.ai-msg-user .ai-table-wrap { background: var(--surface); color: var(--text); }

.ai-msg-user {
    align-self: flex-end;
    background: var(--red);
    color: var(--white);
    border-bottom-right-radius: var(--radius-xs);
}

.ai-msg-user code { background: var(--red-dark); color: var(--white); }

.ai-msg-bot {
    align-self: flex-start;
    background: var(--surface);
    border: var(--border);
    box-shadow: var(--elev-1);
    border-bottom-left-radius: var(--radius-xs);
}

/* ---------- notes: greeting, progress, errors ---------- */
.ai-note {
    align-self: flex-start;
    max-width: 92%;
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--radius-md);
    font-size: var(--fs-400);
    line-height: var(--lh-snug);
}

.ai-note-hello { background: var(--surface); border: var(--border); color: var(--text-3); }

.ai-note-status {
    background: var(--red-tint);
    border: var(--bw-hair) solid var(--red-tint-2);
    color: var(--red-dark);
    font-weight: var(--fw-medium);
    animation: ai-pulse var(--dur-4) var(--ease-in-out) infinite alternate;
}

@keyframes ai-pulse { from { opacity: .65; } to { opacity: 1; } }

.ai-note-error {
    background: var(--red-light);
    border: var(--bw-hair) solid var(--red);
    color: var(--red-dark);
    font-weight: var(--fw-medium);
    align-self: stretch;
    max-width: 100%;
}

/* ---------- composer ---------- */
.ai-form {
    flex: 0 0 auto;
    display: flex;
    align-items: flex-end;
    gap: var(--sp-2);
    padding: var(--sp-3);
    background: var(--surface);
    border-top: var(--border);
}

.ai-input {
    flex: 1 1 auto;
    resize: none;
    max-height: 140px;
    padding: var(--ctrl-pad-y) var(--ctrl-pad-x);
    border: var(--ctrl-border);
    border-radius: var(--ctrl-radius);
    font-family: var(--font-sans);
    font-size: var(--fs-550);
    line-height: var(--lh-snug);
    color: var(--text);
    background: var(--white);
    transition: border-color var(--t-base), box-shadow var(--t-base);
}

.ai-input:focus { outline: none; border-color: var(--red); box-shadow: var(--ring); }
.ai-input:disabled { background: var(--surface-3); color: var(--grey-mid); }

.ai-send {
    flex: 0 0 auto;
    width: var(--sp-8);
    height: var(--sp-8);
    border: 0;
    border-radius: var(--ctrl-radius);
    background: var(--red);
    color: var(--white);
    font-size: var(--fs-650);
    line-height: 1;
    cursor: pointer;
    transition: background var(--t-base), opacity var(--t-base);
}

.ai-send:hover:not(:disabled) { background: var(--red-dark); }
.ai-send:focus-visible { outline: none; box-shadow: var(--ring-strong); }
.ai-send:disabled { opacity: .5; cursor: default; }

/* ---------- backdrop: mobile only ---------- */
.ai-backdrop { display: none; }
.ai-backdrop[hidden] { display: none; }

/* ---------- screen-reader-only label ---------- */
.ai-sr {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* ═══════════ mobile ═══════════
   A vertically centred edge tab would sit over content on a phone and is a
   thumb-hostile target, so below 720px it moves to the bottom-right corner,
   clear of the topbar burger (top-left) and of the drawer. The panel becomes a
   near-full-screen sheet with a dimmed backdrop. */
@media (max-width: 720px) {
    /* Back to a horizontal pill: there is no edge to hang off on a phone, and a
       vertical tab centred on the right would sit over content and be a poor
       thumb target. Bottom-right keeps it clear of the top-left nav burger. */
    .ai-tab {
        top: auto;
        bottom: var(--sp-5);
        right: var(--sp-4);
        transform: none;
        writing-mode: horizontal-tb;
        border-radius: var(--radius-pill);
        padding: var(--sp-3) var(--sp-4);
    }

    .ai-tab:hover,
    .ai-tab:focus-visible { transform: none; }

    /* No hover on touch, so the label is always shown — and the collapse axis
       is horizontal again now that writing-mode is back to normal. */
    .ai-tab-label,
    .ai-tab:hover .ai-tab-label,
    .ai-tab:focus-visible .ai-tab-label {
        max-height: none;
        max-width: none;
        opacity: 1;
    }

    .ai-panel {
        top: var(--sp-2);
        right: var(--sp-2);
        left: var(--sp-2);
        bottom: var(--sp-2);
        width: auto;
        border-radius: var(--radius-lg);
    }

    .ai-backdrop {
        display: block;
        position: fixed;
        inset: 0;
        z-index: var(--z-ai-backdrop);
        background: rgba(16, 16, 20, .45);
    }

    .ai-backdrop[hidden] { display: none; }

    /* 16px floor stops iOS zooming the page when the field takes focus. */
    .ai-input { font-size: var(--fs-700); }
}

/* ═══════════ reduced motion ═══════════
   The tab's hover state must still be VISIBLE without the slide, so the colour
   and label changes stay and only the movement is removed. */
@media (prefers-reduced-motion: reduce) {
    .ai-tab,
    .ai-tab-label,
    .ai-panel,
    .ai-note-status {
        transition: none;
        animation: none;
    }

    .ai-tab { transform: translateY(-50%) translateX(0); }
    .ai-tab-label { max-height: calc(var(--sp-10) + var(--sp-7)); opacity: 1; }
}

/* ---- delivery address lookup ----
   Results were rendered as inline buttons and ran together into one unreadable
   string ("…United KingdomSunderland, North East…"). One candidate per row. */
.deliver-results { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-15); margin-top: var(--sp-2); }
.deliver-result { text-align: left; white-space: normal; }
.deliver-suggest { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-15); margin-top: var(--sp-2); }

/* ---- Goods In: part number + product barcode on ONE line ----
   Two short identifier fields side by side, sized to their content rather than
   stretched across the card: a part number is ~14 chars and a MAC/EAN ~17, so
   full-width inputs were mostly empty space. */
.gi-ident-row {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));   /* full width, 50% each */
    gap: var(--sp-35) var(--sp-4);
    align-items: start;
}
@media (max-width: 900px) { .gi-ident-row { grid-template-columns: 1fr; } }

.gi-codes { margin-top: var(--sp-2); }

/* A stock line already on the order stays visible but recedes — hiding it would
   invite re-adding something you cannot see further down the page. */
.is-onorder > td { background: var(--surface-2); }

/* A pick-from-list panel is width-locked to its input by app.js, which is right for a
   full-width field but unreadable for a short one in a toolbar — "cable · 1 unit · RET…"
   wrapped to four lines. Give the list a floor of its own; it still never exceeds the
   viewport. Stock search panels are unaffected. */
.ac-panel-simple { min-width: min(26rem, calc(100vw - var(--sp-6))); }

/* =====================================================================
   SETTINGS — tab-grouped page (settings.php)

   Two additive classes, nothing else. The rebuilt page reuses the existing
   components wholesale: .tabs/.tab-btn/.tab-panel for the grouping, plus
   .card, .form-grid, .field, .kv, .list, .badge and .card-collapse exactly
   as every other page uses them.

   Existing :root tokens only — no raw hex, no raw px font sizes.
   ===================================================================== */

/* Settings are label/value pairs, not tabular data. On a wide monitor the
   two-column .form-grid stretches the full content width and throws each
   input a long way from the label that names it, which is most of why the
   Company block read as a scattered mess. Cap the measure on panels that
   are a lone stack of cards; panels laid out with .grid-2 already have
   their width bounded by the second column. */
.set-cap { max-width: 72rem; }

/* The per-printer "Edit <name>" disclosure that sits in its own row under
   each printer in the printers table. It was marked up as .scan-hint —
   the class for advisory text — so a control was rendered as a passive
   note, giving no hover, no focus ring and no open/closed affordance. */
.row-edit {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-15);
    cursor: pointer;
    list-style: none;
    margin-left: calc(var(--sp-2) * -1);
    padding: var(--sp-1) var(--sp-2);
    border-radius: var(--radius-sm);
    font-size: var(--fs-400);
    font-weight: var(--fw-medium);
    color: var(--text-3);
}
.row-edit::-webkit-details-marker { display: none; }
.row-edit::before {
    content: "\25B8";                    /* ▸ — rotates to ▾ when open */
    color: var(--red);
    line-height: 1;
    transition: transform .15s ease;
}
details[open] > .row-edit::before { transform: rotate(90deg); }
.row-edit:hover { color: var(--black); background: var(--surface-2); }
.row-edit:focus-visible { outline: var(--bw-2) solid var(--red); outline-offset: var(--sp-05); }

/* ===========================================================================
   Settings → System — live diagnostics panel
   ===========================================================================

   Deliberately tiny. The panel is built almost entirely from components that
   already exist: .kpi-band / .kpis.kpis-4 / .kpi / .kpi-num / .kpi-label /
   .kpi-sub for the figures, chart_gauge()'s .gauge markup for the meters,
   .kv for the detail rows, and .card-collapse for the folded reference
   section. Only two things had no existing equivalent, and they are below.

   Every value is an existing :root token — no raw hex, no raw px font sizes.
   =========================================================================== */

/* The stack of CPU / memory / disk meters.
   .gauge already carries its own vertical rhythm (margin: var(--sp-25) 0), so
   this only needs to separate the group from the KPI band above it and the
   host detail list below it. On a wide screen the meters sit two-up: three
   full-width bars in a row reads as a progress wizard rather than as three
   independent readings. */
.sys-meters {
    margin: var(--sp-2) 0 var(--sp-5);
}
@media (min-width: 900px) {
    .sys-meters {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 0 var(--sp-6);
    }
}

/* "live" — shown only while a poll is actually in flight.
   It is a state indicator, not decoration: if it never lights up, the panel is
   not refreshing, and that should be visible rather than inferred. Uses the
   status-ok colours because a polling panel is the healthy state, and it sits
   in the same optical slot as a .badge so it inherits that scale. */
.sys-live {
    display: inline-block;
    font-size: var(--fs-200);
    font-weight: var(--fw-semi);
    text-transform: uppercase;
    letter-spacing: var(--ls-caps);
    color: var(--ok);
    background: var(--ok-bg);
    border-radius: var(--radius-pill);
    padding: var(--sp-05) var(--sp-2);
    margin-right: var(--sp-2);
    vertical-align: middle;
}
.sys-live::before {
    content: "";
    display: inline-block;
    width: var(--sp-15);
    height: var(--sp-15);
    border-radius: var(--radius-pill);
    background: var(--ok);
    margin-right: var(--sp-15);
    vertical-align: baseline;
    animation: sys-pulse var(--dur-4) var(--ease-in-out) infinite alternate;
}
@keyframes sys-pulse {
    from { opacity: .35; }
    to   { opacity: 1; }
}
/* A pulsing dot is the one thing here that could bother someone who asked the
   system not to animate. The indicator still works — it just stops moving. */
@media (prefers-reduced-motion: reduce) {
    .sys-live::before { animation: none; }
}

/* The provenance line under the machine card. It is .field-note already; this
   only lifts it clear of the .kv block it follows. */
.sys-stamp {
    margin-top: var(--sp-3);
}

/* ═══════════ AI assistant: lock the page behind the panel ═══════════
   assets/js/ai.js adds `ai-open` to <body> on open() and removes it on close()
   (ai.js:399 / :431), but nothing has ever styled it — so the class was a no-op.
   It matters on narrow screens: the media query above turns .ai-panel into a
   full-bleed overlay (inset: var(--sp-2)) over a fixed .ai-backdrop, and without
   this the page underneath still scrolls behind the assistant — including under
   the operator's thumb while they are reading an answer.

   Scoped to the same breakpoint as the full-bleed panel: on a desktop the panel
   is a side rail and the page behind it SHOULD stay scrollable, so locking there
   would be a regression, not a fix. */
@media (max-width: 720px) {
    body.ai-open {
        overflow: hidden;
        /* iOS ignores overflow:hidden on <body> once a scroll is in flight;
           pinning the height is what actually stops the rubber-band. */
        height: 100dvh;
    }
}

/* =====================================================================
   PHASE 1 — OPERATIONAL COMMAND-CENTRE REDESIGN
   Accessible, reusable system improvements. Tokens only, no raw hex.
   All rules are additive — nothing above is reordered or overridden.
   ===================================================================== */

/* ---- skip link ----
   Off-screen until focused, then a visible bar at the top of the viewport.
   The first Tab press reveals it; activating it jumps focus to #main. */
.skip-link {
    position: absolute; left: var(--sp-2); top: var(--sp-2); z-index: var(--z-toast);
    transform: translateY(-150%);
    background: var(--black); color: var(--white);
    padding: var(--sp-2) var(--sp-4); border-radius: var(--radius-sm);
    font-size: var(--fs-500); font-weight: var(--fw-bold); text-decoration: none;
    transition: transform var(--t-base);
}
.skip-link:focus { transform: translateY(0); outline: var(--bw-2) solid var(--red); outline-offset: 2px; }

/* ---- visible sidebar focus treatment ----
   A keyboard user navigating the sidebar now sees a clear white outline
   ring plus a left accent bar, instead of the invisible red glow. */
.sidebar nav a:focus-visible {
    outline: none;
    box-shadow: var(--ring-on-dark);
    border-radius: var(--radius-sm);
}
.sidebar .brand-logo:focus-visible {
    outline: var(--bw-2) solid var(--white);
    outline-offset: 2px;
}
/* The burger button gets the same dark-surface focus treatment. */
.nav-burger:focus-visible {
    outline: var(--bw-2) solid var(--white);
    outline-offset: 2px;
}

/* ---- flash close button: safe touch target ----
   The close button was font-size-only with no minimum box, so it failed
   the 40px touch target floor. Padding + min dimensions fix that without
   changing its visual footprint (it sits in a flex row). */
.flash-x {
    min-width: var(--touch-min); min-height: var(--touch-min);
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: var(--radius-sm); flex-shrink: 0;
    transition: background-color var(--t-fast), opacity var(--t-fast);
}
.flash-x:hover { background: var(--surface-2); opacity: 1; }
.flash-x:focus-visible { outline: var(--bw-2) solid var(--red); outline-offset: 1px; }

/* ---- sortable table header focus + keyboard affordance ----
   The sort affordance pseudo-element already reacts to [data-dir]; this adds
   a visible focus ring for keyboard sorters and a cursor/role hint. */
table.list th[tabindex="0"] { cursor: pointer; }
table.list th:focus-visible {
    outline: none;
    box-shadow: inset 0 0 0 var(--bw-2) var(--red);
    border-radius: var(--radius-xs);
}
table.list th[tabindex="0"]:not([data-dir]):hover::after { opacity: .7; }

/* ---- responsive table wrapping ----
   .table-wrap already provides horizontal scroll. This card-friendly
   modifier lets a table sit inside a card without the card's own
   overflow-x clipping its sticky header. Used by wrapping pages that
   want the table flush to the card padding. */
.table-wrap { border-radius: var(--radius-md); }
.card > .table-wrap:first-child { margin: calc(-1 * var(--sp-5)) calc(-1 * var(--sp-5)) 0; }
.card > .table-wrap:first-child table.list th { border-radius: var(--radius-md) var(--radius-md) 0 0; }

/* ---- refined toolbar ----
   A modifier for toolbars that need a surface and a bottom divider, so the
   controls read as a cohesive bar rather than floating inputs. */
.toolbar.bar {
    background: var(--surface); border: var(--border-soft); border-radius: var(--radius-md);
    padding: var(--sp-25) var(--sp-35); box-shadow: var(--elev-1);
}

/* ---- refined card: flush header variant ----
   A card whose h2 sits flush (no top padding), for KPI/action summaries
   where the heading is the first thing. */
.card.flush-head { padding-top: 0; }
.card.flush-head > h2:first-child { padding-top: var(--sp-5); }

/* ---- refined empty state ----
   A structured empty state with an icon, heading and optional description,
   so a blank table or card reads as "nothing here yet" with guidance. */
.empty-state {
    display: flex; flex-direction: column; align-items: center; gap: var(--sp-2);
    padding: var(--sp-7) var(--sp-5); text-align: center; color: var(--text-3);
}
.empty-state .empty-icon { font-size: var(--fs-1000); color: var(--grey-mid); line-height: 1; }
.empty-state .empty-title { font-size: var(--fs-700); font-weight: var(--fw-bold); color: var(--text-2); }
.empty-state .empty-desc { font-size: var(--fs-400); color: var(--text-3); max-width: 48ch; line-height: var(--lh-base); }

/* ---- refined form-section: labelled divider ----
   .form-section already separates groups with a hairline. This modifier
   adds a section heading badge for sections that need a visible label. */
.form-section-label {
    display: inline-block; font-size: var(--fs-200); font-weight: var(--fw-bold);
    text-transform: uppercase; letter-spacing: var(--ls-caps); color: var(--red);
    margin-bottom: var(--sp-2);
}

/* ---- summary KPI row ----
   A horizontal strip of inline summary stats (figure + label pairs), for
   the top of a detail or report page. Lighter than .kpis — no cards. */
.summary-strip {
    display: flex; flex-wrap: wrap; gap: var(--sp-4) var(--sp-7);
    padding: var(--sp-35) var(--sp-4); margin-bottom: var(--sp-5);
    background: var(--surface); border: var(--border-soft); border-radius: var(--radius-md);
    box-shadow: var(--elev-1);
}
.summary-strip .summary-item { display: flex; flex-direction: column; gap: var(--sp-05); }
.summary-strip .summary-num { font-size: var(--fs-900); font-weight: var(--fw-black); color: var(--black); line-height: var(--lh-tight); }
.summary-strip .summary-label { font-size: var(--fs-250); text-transform: uppercase; letter-spacing: .8px; color: var(--grey-mid); }

/* ---- action bar ----
   A sticky bottom action bar for primary page actions (save, cancel), so
   they stay reachable on a long form without scrolling to the bottom. */
.action-bar {
    display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
    padding: var(--sp-3) var(--sp-4); margin-top: var(--sp-5);
    background: var(--surface); border-top: var(--border); border-radius: var(--radius-md);
    box-shadow: var(--elev-1);
}
.action-bar.sticky {
    position: sticky; bottom: 0; z-index: var(--z-topbar);
}
.action-bar .spacer { flex: 1; }

/* ---- safe touch targets ----
   Ensures clickable controls meet the 40px minimum. Applied to icon-only
   buttons and nav items that might fall below the floor. */
.btn-icon-only {
    min-width: var(--touch-min); min-height: var(--touch-min);
    padding: var(--sp-2); display: inline-flex; align-items: center; justify-content: center;
}

/* ---- mobile: touch target floor for nav links ---- */
@media (max-width: 900px) {
    .sidebar nav a { min-height: var(--touch-min); }
    .nav-burger { min-width: var(--touch-min); min-height: var(--touch-min); }
    .tab-btn { min-height: var(--touch-min); }
}


/* =====================================================================
   INVENTORY — inventory.php
   The page is a register: one dark readout band, one filter row, a status
   switch, then the table. Everything else is chrome, and chrome competes
   with the rows for attention, so there is none. No new colours; the whole
   block is tokens plus the two on-dark alphas the .scan-bar already uses.
   ===================================================================== */
/* `minmax(0, 1fr)`, not the implicit `auto`: a grid item's default min-width is
   min-content, so the table's min-content width was inflating this column and
   pushing the readout, the filter row and the page itself past the viewport at
   900–1060px. Pinning the track to 0 lets the shell clip instead. */
.inv { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-35); }

/* ---- readout band ----
   Same dark treatment and red edge as .scan-bar, so the two operational
   headers in the app read as one family rather than two inventions. */
.inv-readout {
    display: flex; align-items: center; flex-wrap: wrap;
    gap: var(--sp-3) var(--sp-6);
    padding: var(--sp-35) var(--sp-4);
    background: linear-gradient(180deg, var(--black-2) 0%, var(--black) 100%);
    border-left: var(--bw-accent) solid var(--red);
    border-radius: var(--radius-lg);
    box-shadow: var(--elev-2);
}
/* One line of copy, and only because "42 lines" alone doesn't say 42 of what. */
.inv-scope {
    flex: 1 0 100%; order: -1;
    color: var(--grey-mid); font-size: var(--fs-300);
    text-transform: uppercase; letter-spacing: var(--ls-caps);
}
.inv-figures { display: flex; align-items: baseline; gap: var(--sp-6); flex-wrap: wrap; min-width: 0; }
.inv-figure { display: flex; align-items: baseline; gap: var(--sp-2); white-space: nowrap; }
.inv-figure-num {
    color: var(--white); font-size: var(--fs-900); font-weight: var(--fw-black);
    line-height: var(--lh-tight); font-variant-numeric: tabular-nums;
}
.inv-figure-label { color: var(--on-dark); font-size: var(--fs-400); }
.inv-readout-actions { display: flex; align-items: center; gap: var(--sp-2); margin-left: auto; flex-wrap: wrap; }
/* .btn-ghost is built for a light card and disappears on this surface. Same
   fix, same measurements as .scan-bar .btn-ghost: --grey-mid edge (5.72:1
   against the band's darkest stop) with a white label. */
.inv-readout .btn-ghost {
    color: var(--white); background: var(--black-3); border-color: var(--grey-mid);
}
.inv-readout .btn-ghost:hover {
    color: var(--white); background: var(--black-3); border-color: var(--white);
    box-shadow: 0 3px 12px var(--red-glow);
}
/* Red focus glows composite to ~1.1:1 here — invisible. White outline instead,
   the same pattern .scan-bar and the checkbox focus styles already use. */
.inv-readout .btn:focus-visible { outline: var(--bw-2) solid var(--white); outline-offset: 2px; box-shadow: none; }

/* ---- filter row ----
   A grid, not a wrapping flex row: fixed tracks mean the controls line up in
   one disciplined row and drop to a deliberate 2-up then 1-up stack, instead
   of reflowing into a ragged staircase at every intermediate width. */
.inv-filters {
    display: grid;
    grid-template-columns: minmax(220px, 2.2fr) repeat(3, minmax(0, 1fr)) auto;
    gap: var(--sp-2) var(--sp-3);
    align-items: end;
    padding: var(--sp-3) var(--sp-35);
    background: var(--surface);
    border: var(--border-soft);
    border-radius: var(--radius-lg);
    box-shadow: var(--elev-1);
}
.inv-field { min-width: 0; display: grid; gap: var(--sp-1); }
.inv-label {
    font-size: var(--fs-250); font-weight: var(--fw-bold); color: var(--grey-mid);
    text-transform: uppercase; letter-spacing: var(--ls-caps);
}
.inv-filters input, .inv-filters select { width: 100%; }
.inv-filter-actions { display: flex; align-items: center; gap: var(--sp-2); }
.inv-filter-actions .btn { white-space: nowrap; }

/* ---- status switch ----
   Status is the filter that gets used most, so it is one click with the count
   already visible, not a fifth dropdown you have to open to learn anything. */
.inv-status { display: flex; gap: var(--sp-1); flex-wrap: wrap; align-items: center; }
.inv-status-tab {
    display: inline-flex; align-items: center; gap: var(--sp-15);
    padding: var(--sp-15) var(--sp-3);
    border: var(--bw-hair) solid transparent; border-radius: var(--radius-pill);
    color: var(--text-3); text-decoration: none;
    font-size: var(--fs-400); font-weight: var(--fw-semi);
    transition: background-color var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.inv-status-tab:hover { color: var(--red); background: var(--red-light); }
.inv-status-tab.is-on { background: var(--black); border-color: var(--black); color: var(--white); }
.inv-status-tab:focus-visible { outline: none; box-shadow: var(--ring); color: var(--red); }
.inv-status-tab.is-on:focus-visible { box-shadow: var(--ring-dark); color: var(--white); }
.inv-status-n {
    font-variant-numeric: tabular-nums; font-size: var(--fs-250); font-weight: var(--fw-bold);
    padding: 1px var(--sp-15); border-radius: var(--radius-pill);
    background: var(--track); color: var(--text-3);
}
.inv-status-tab.is-on .inv-status-n { background: var(--black-3); color: var(--white); }
/* A dot carries the status colour so the tab reads at a glance without
   painting the whole pill — the badges in the rows already own that job. */
.inv-status-tab.is-in_stock::before,
.inv-status-tab.is-part::before,
.inv-status-tab.is-dispatched::before {
    content: ""; width: var(--sp-2); height: var(--sp-2); border-radius: 50%; flex-shrink: 0;
}
.inv-status-tab.is-in_stock::before   { background: var(--ok); }
.inv-status-tab.is-part::before       { background: var(--warn); }
.inv-status-tab.is-dispatched::before { background: var(--grey-mid); }

/* ---- results ----
   The table is the page. It gets the full width, one hairline shell, and the
   density of table.list unchanged — no card padding pushing rows apart. */
/* The shell is deliberately NOT wrapped in .table-wrap, and its overflow is
   `clip` on the x axis only.
     - `overflow: hidden` or a .table-wrap ancestor (either axis) makes this a
       scroll container, and a scroll container between the sticky th and the
       viewport silently kills `position: sticky` — the header would scroll
       away exactly when a long list needs it.
     - `overflow-x: clip` with `overflow-y: visible` is the one combination
       that clips the corners AND leaves the th sticky to the viewport.
   Above 1060px the table fits, so `clip` never actually clips anything; the
   901–1060px band is handled by its own rule below. */
.inv-table-shell {
    background: var(--surface); border: var(--border-soft);
    border-radius: var(--radius-lg); box-shadow: var(--elev-2);
    overflow-x: clip; overflow-y: visible;
}
/* 901–1060px: the seven columns no longer fit (their floor is ~773px against
   ~660px of content width). Clipping here would swallow the Actions column and
   with it the Out button, so this band scrolls instead. That reintroduces a
   scroll container and therefore loses the sticky header for this band only —
   a deliberate trade: a header that scrolls away is cosmetic, a dispatch
   button you cannot reach is not. */
@media (min-width: 901px) and (max-width: 1060px) {
    .inv-table-shell { overflow-x: auto; }
}
.inv-table { margin: 0; }
/* table.list th is nowrap globally, which makes the HEADER the widest thing in
   a narrow column ("Customer / Project" alone demanded 128px against 31px of
   actual data) and pushes the table past the viewport. Letting the labels wrap
   costs one extra header line and buys ~70px of column budget. */
.inv-table th { white-space: normal; }
/* table.list th is `sticky; top: 0; z-index: 1`, so on a long list it parks
   BEHIND the z-20 topbar and the column names are lost exactly when the list
   is long enough to need them. Scoped to this page: park it below the bar
   instead. The offset is the topbar's own geometry — --sp-3 padding top and
   bottom around a 39px control row — expressed in the same tokens, so it
   tracks if that padding is ever retuned. Raising z-index above the topbar
   was the alternative and is worse: the header would cover the quick-scan
   field. */
.inv-table thead th { top: var(--inv-th-top); }
.inv-table-shell { --inv-th-top: calc(39px + (2 * var(--sp-3)) + var(--bw-3)); }
.inv-table thead th:first-child { border-top-left-radius: var(--radius-lg); }
.inv-table thead th:last-child  { border-top-right-radius: var(--radius-lg); }
.inv-table tbody tr:last-child td:first-child { border-bottom-left-radius: var(--radius-lg); }
.inv-table tbody tr:last-child td:last-child  { border-bottom-right-radius: var(--radius-lg); }
.inv-table td { vertical-align: top; padding-top: var(--sp-3); padding-bottom: var(--sp-3); }
.inv-desc { font-weight: var(--fw-semi); }
.inv-table td.mono a { color: var(--text); text-decoration: none; font-weight: var(--fw-semi); }
.inv-table td.mono a:hover { color: var(--red); text-decoration: underline; }
/* Customer / project in one narrow column: the code is the identifier an
   operator scans for, the project tag rides beneath it rather than beside it
   so the column never widens past its share of the row. */
.inv-col-account, .inv-account { width: 1%; white-space: nowrap; }
.inv-cust { display: block; font-weight: var(--fw-bold); font-size: var(--fs-500); }
.inv-account .badge-project, .inv-account .scan-hint { display: inline-block; margin-top: var(--sp-05); max-width: 128px; overflow: hidden; text-overflow: ellipsis; vertical-align: bottom; }
.inv-qty { white-space: nowrap; font-weight: var(--fw-semi); }
.inv-unit { color: var(--text-3); font-weight: var(--fw-normal); font-size: var(--fs-400); }
.inv-col-do, .inv-do { width: 1%; text-align: right; }
.inv-do .btn + .btn { margin-left: var(--sp-1); }

/* ---- narrow desktop / tablet: 2-up filters, still one row of everything else ---- */
@media (max-width: 1180px) {
    .inv-filters { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .inv-field-search { grid-column: 1 / -1; }
    .inv-filter-actions { grid-column: 1 / -1; }
    .inv-filter-actions .btn { flex: 1 1 auto; }
}

/* ---- phone: the table becomes a stack of records ----
   A 7-column operational table cannot be read on a 380px screen, and sideways
   scrolling hides the actions. Each row becomes a labelled block instead, with
   the header supplied by data-label so the markup stays a real table (and the
   sort handler, which walks cells by index, is untouched). */
@media (max-width: 900px) {
    .inv { gap: var(--sp-25); }
    .inv-readout { padding: var(--sp-3); border-radius: var(--radius-md); }
    .inv-figures { gap: var(--sp-4); width: 100%; justify-content: space-between; }
    .inv-figure { flex-direction: column; gap: 0; }
    .inv-figure-num { font-size: var(--fs-800); }
    .inv-readout-actions { width: 100%; margin-left: 0; }
    .inv-readout-actions .btn { flex: 1 1 auto; }
    .inv-filters { grid-template-columns: minmax(0, 1fr); padding: var(--sp-25); border-radius: var(--radius-md); }
    .inv-status { overflow-x: auto; flex-wrap: nowrap; scrollbar-width: none; }
    .inv-status::-webkit-scrollbar { display: none; }
    .inv-status-tab { white-space: nowrap; }

    .inv-table-shell { background: none; border: 0; box-shadow: none; }
    .inv-table, .inv-table tbody, .inv-table tr, .inv-table td { display: block; width: auto; }
    .inv-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
    .inv-table tr {
        background: var(--surface); border: var(--border-soft);
        border-left: var(--bw-accent) solid var(--red);
        border-radius: var(--radius-md); box-shadow: var(--elev-1);
        padding: var(--sp-25); margin-bottom: var(--sp-25);
    }
    .inv-table td {
        display: grid; grid-template-columns: 8.5em minmax(0, 1fr); gap: var(--sp-25);
        align-items: baseline; padding: var(--sp-1) 0; border-bottom: 0; text-align: left;
    }
    .inv-table td::before {
        content: attr(data-label);
        color: var(--grey-mid); font-size: var(--fs-200); font-weight: var(--fw-bold);
        text-transform: uppercase; letter-spacing: var(--ls-caps);
    }
    .inv-table tr:hover td { background: none; }
    .inv-table td.num { text-align: left; }
    .inv-col-account, .inv-account, .inv-col-do, .inv-do { width: auto; white-space: normal; }
    .inv-cust { display: inline; }
    .inv-account .badge-project { margin-top: 0; margin-left: var(--sp-15); max-width: none; }
    /* Actions get the full width of the record rather than a labelled row —
       a thumb needs the target more than the eye needs the word "Actions". */
    .inv-table td.inv-do { display: flex; gap: var(--sp-2); padding-top: var(--sp-25); margin-top: var(--sp-1); border-top: var(--border-soft); }
    .inv-table td.inv-do::before { content: none; }
    .inv-do .btn { flex: 1 1 auto; min-height: var(--touch-min); }
}

/* =====================================================================
   DASHBOARD — operational command-centre theme
   ---------------------------------------------------------------------
   Scoped entirely to body[data-page='dashboard']: every selector below is
   prefixed with that attribute, so nothing here can reach another page.
   The existing global .dash-field / .kpi-band / .scan-bar / .card rules are
   untouched; this layer only ADDS the black/red operational treatment the
   brief asks for (crisp headers, constrained measure, dense tables,
   accessible focus) on top of them. No new dependencies; tokens only.
   ===================================================================== */

/* ---- operational treatment on every dashboard card ----------------------
   The Dashboard is intentionally dark: each card is a black operational
   surface with one red status edge. Data remains dense inside the same full-
   bleed shell; no card or table is width-capped or centred. */
body[data-page='dashboard'] .card {
    color: var(--white);
    background: linear-gradient(145deg, var(--black-2), var(--black));
    border-color: var(--black-3);
    border-top: var(--bw-accent) solid var(--red);
    box-shadow: var(--elev-3);
}
body[data-page='dashboard'] .card h2 { color: var(--white); }

body[data-page='dashboard'] .kpi-band-label {
    color: var(--red);
    border-bottom-color: var(--red);
}
body[data-page='dashboard'] .kpi {
    background: linear-gradient(145deg, var(--black-2), var(--black));
    border-top: var(--bw-2) solid var(--red);
    box-shadow: var(--elev-2);
}
body[data-page='dashboard'] .kpi > a,
body[data-page='dashboard'] .kpi-num { color: var(--white); }
body[data-page='dashboard'] .kpi-label { color: var(--white); }

/* Today and On Hand carry a deliberate red/black rhythm instead of seven identical tiles. */
body[data-page='dashboard'] .kpi-band-today .kpi:nth-child(odd),
body[data-page='dashboard'] .kpi-band-stock .kpi:nth-child(2) {
    background: linear-gradient(135deg, #db1437 0%, #9b0b25 100%);
    border-color: #ef3a59;
    box-shadow: 0 8px 24px rgba(200, 16, 46, .24);
}
body[data-page='dashboard'] .kpi-band-today .kpi:nth-child(even),
body[data-page='dashboard'] .kpi-band-stock .kpi:nth-child(odd) {
    background:
        radial-gradient(circle at 100% 0%, rgba(200, 16, 46, .34), transparent 48%),
        linear-gradient(145deg, var(--black-3), var(--black));
    border-color: rgba(239, 58, 89, .72);
}
body[data-page='dashboard'] .kpi-band-today .kpi:nth-child(odd) .kpi-label,
body[data-page='dashboard'] .kpi-band-stock .kpi:nth-child(2) .kpi-label {
    color: var(--white);
}

/* Dashboard KPIs stay grounded on hover: red state, no inherited card jump. */
body[data-page='dashboard'] .kpi:hover,
body[data-page='dashboard'] .kpi.red:hover {
    transform: none;
    border-color: var(--red);
    box-shadow: var(--elev-2);
}

body[data-page='dashboard'] .kpi > a:hover {
    color: var(--white);
}
body[data-page='dashboard'] .card .scan-hint,
body[data-page='dashboard'] .card .empty,
body[data-page='dashboard'] .card .chart-hero span,
body[data-page='dashboard'] .card .chart-legend,
body[data-page='dashboard'] .card .gauge-label,
body[data-page='dashboard'] .card .gauge-label em { color: var(--white); }

/* Keep operational figures legible and progress tracks dark on every dashboard card. */
body[data-page='dashboard'] .card .chart-hero strong,
body[data-page='dashboard'] .card .chart-legend strong,
body[data-page='dashboard'] .card .gauge-label strong { color: var(--white); }

body[data-page='dashboard'] .card .gauge-track {
    background: var(--black-3);
    border: var(--bw-hair) solid rgba(255, 255, 255, .14);
}
body[data-page='dashboard'] .card .btn-ghost {
    color: var(--white);
    background: var(--black-3);
    border-color: var(--grey-mid);
}
body[data-page='dashboard'] .card .btn-ghost:hover {
    color: var(--white);
    border-color: var(--red);
    background: var(--black-2);
}
/* Charts use white/red on dark cards; no low-contrast grey survives the page theme. */
body[data-page='dashboard'] .card .chart text { fill: var(--white); }
body[data-page='dashboard'] .card .chart line { stroke: rgba(255, 255, 255, .22); }
body[data-page='dashboard'] .card .chart-svg path[fill='#26262e'] { fill: var(--white); }
body[data-page='dashboard'] .card .chart-legend span:first-child i {
    background: var(--white) !important;
}
body[data-page='dashboard'] .card .chart-legend i {
    box-shadow: 0 0 0 1px rgba(255, 255, 255, .38);
}
body[data-page='dashboard'] .card .chart-legend em { color: var(--white); }
body[data-page='dashboard'] .card .chart-data > summary {
    color: var(--white);
    background: var(--black-3);
    border: var(--bw-hair) solid rgba(255, 255, 255, .16);
    border-radius: var(--radius-sm);
    padding: var(--sp-2) var(--sp-25);
}
body[data-page='dashboard'] .card .chart-data > summary:hover {
    color: var(--white);
    border-color: var(--red);
    background: var(--black-2);
}
body[data-page='dashboard'] .card .chart-data table th {
    color: var(--white);
    background: var(--black-3);
    border-color: rgba(255, 255, 255, .14);
}
body[data-page='dashboard'] .card .chart-data table td {
    color: var(--white);
    background: transparent;
    border-color: rgba(255, 255, 255, .1);
}
body[data-page='dashboard'] .card .chart-data table tr:hover td {
    color: var(--white);
    background: var(--black-2);
}
body[data-page='dashboard'] .card .chart-donut {
    shape-rendering: geometricPrecision;
    overflow: visible;
}
body[data-page='dashboard'] .card .chart-donut > circle {
    stroke-linecap: round;
}
body[data-page='dashboard'] .card .chart-donut > circle:first-of-type {
    stroke: var(--black-3);
}
body[data-page='dashboard'] .card .chart-donut > circle:nth-of-type(2) {
    stroke: var(--white);
}
body[data-page='dashboard'] .card .chart-donut > circle:nth-of-type(3) {
    stroke: var(--red);
}
body[data-page='dashboard'] .card.dash-hero { border-top-color: var(--red); }

/* ---- crisp card headings ------------------------------------------------
   Tightens the h2 rule line into a short red tick beside a slightly tighter
   heading, so each card title reads as a labelled panel rather than a page
   section. The global .card h2::before red bar is overridden only here. */
body[data-page='dashboard'] .card h2 {
    font-size: var(--fs-650);
    letter-spacing: var(--ls-tight);
    text-transform: none;
}
body[data-page='dashboard'] .card h2::before {
    width: 3px;
    height: 14px;
    border-radius: var(--radius-xs);
}

/* ---- Work Queue hero ----------------------------------------------------
   The hero is the operational lead. It gets a dark command header (the same
   black gradient + red edge as .scan-bar / .inv-readout, so the three
   operational headers read as one family), then the table beneath it on the
   light card surface so the rows stay dense and legible. */
body[data-page='dashboard'] .dash-hero {
    border-top: var(--bw-accent) solid var(--red);
}
body[data-page='dashboard'] .dash-hero-head {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    margin-bottom: var(--sp-4);
    padding: var(--sp-35) var(--sp-4);
    background: linear-gradient(180deg, var(--black-2) 0%, var(--black) 100%);
    border-radius: var(--radius-md);
}
/* The h2 inside the dark head loses its red ::before tick (the red is already
   carried by the card's top edge) and inverts to white so it reads on black. */
body[data-page='dashboard'] .dash-hero-head h2 {
    color: var(--white);
    margin-bottom: 0;
    font-size: var(--fs-700);
}
body[data-page='dashboard'] .dash-hero-head h2::before { display: none; }
body[data-page='dashboard'] .dash-hero-head .h2-right {
    margin-left: auto;
    display: flex;
    gap: var(--sp-2);
}
/* btn-ghost is built for a light card; on the dark head it gets the same
   outline treatment as .scan-bar .btn-ghost so it reads as a real control. */
body[data-page='dashboard'] .dash-hero-head .btn-ghost {
    color: var(--white);
    background: var(--black-3);
    border-color: var(--grey-mid);
}
body[data-page='dashboard'] .dash-hero-head .btn-ghost:hover {
    color: var(--white);
    background: var(--black-3);
    border-color: var(--white);
    box-shadow: 0 3px 12px var(--red-glow);
}
body[data-page='dashboard'] .dash-hero-head .btn:focus-visible {
    outline: var(--bw-2) solid var(--white);
    outline-offset: 2px;
    box-shadow: none;
}

/* ---- dense dashboard tables (.dash-table) -------------------------------
   Tightens the global table.list for the dashboard's compact registers.
   Shorter row padding, a zebra wash for scan-ability, and a header row that
   sits on the black/red operational palette. Scoped to .dash-table so every
   other table.list in the app is untouched. */
body[data-page='dashboard'] .dash-table {
    font-size: var(--fs-500);
}
body[data-page='dashboard'] .dash-table thead th {
    position: static;
    color: var(--white);
    background: var(--black-3);
    border-color: rgba(255,255,255,.12);
    border-bottom: var(--bw-2) solid var(--red);
    padding: var(--sp-2) var(--sp-25);
    font-size: var(--fs-200);
}
body[data-page='dashboard'] .dash-table td {
    color: var(--white);
    background: transparent;
    border-color: rgba(255,255,255,.1);
    padding: var(--sp-2) var(--sp-25);
    vertical-align: middle;
}
/* The global list-table hover is a light surface. Dashboard registers stay black. */
body[data-page='dashboard'] .dash-table tbody tr:hover td {
    color: var(--white);
    background: var(--black-2);
    box-shadow: none;
}

body[data-page='dashboard'] .dash-table a:not(.btn) { color: var(--white); }
body[data-page='dashboard'] .dash-table a:not(.btn):hover {
    color: var(--white);
    text-decoration-color: var(--red);
}
/* zebra striping for the registers — a black step keeps wide tables calm. */
body[data-page='dashboard'] .card:not(.dash-hero) .dash-table tbody tr:nth-child(even) td {
    background: var(--black-3);
}

/* ---- recent activity register ------------------------------------------
   This is the Dashboard's densest log. Give its time, scan code, action and
   quantity deliberate columns so the free-text detail can breathe. */
body[data-page='dashboard'] .dash-activity-table {
    table-layout: fixed;
}
body[data-page='dashboard'] .dash-activity-table .dash-activity-when {
    width: 118px;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
body[data-page='dashboard'] .dash-activity-table .dash-activity-barcode { width: 158px; }
body[data-page='dashboard'] .dash-activity-table .dash-activity-action { width: 86px; }
body[data-page='dashboard'] .dash-activity-table .dash-activity-qty { width: 58px; }
body[data-page='dashboard'] .dash-activity-table .dash-activity-detail {
    color: var(--white);
    overflow-wrap: anywhere;
}
body[data-page='dashboard'] .dash-activity-table .dash-activity-qty {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* Compact dashboard cards provide useful context without another wide table. */
body[data-page='dashboard'] .dash-summary-metrics {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--sp-2);
    margin-bottom: var(--sp-3);
}
body[data-page='dashboard'] .dash-summary-metrics a {
    min-width: 0;
    padding: var(--sp-2);
    color: var(--white);
    text-decoration: none;
    background: var(--black-3);
    border: var(--bw-hair) solid rgba(255, 255, 255, .12);
    border-radius: var(--radius-sm);
}
body[data-page='dashboard'] .dash-summary-metrics a:hover {
    color: var(--white);
    background: var(--black-2);
    border-color: var(--red);
}
body[data-page='dashboard'] .dash-summary-metrics strong {
    display: block;
    color: var(--white);
    font-size: var(--fs-800);
    font-weight: var(--fw-black);
    font-variant-numeric: tabular-nums;
    line-height: 1;
}
body[data-page='dashboard'] .dash-summary-metrics span {
    display: block;
    margin-top: var(--sp-1);
    color: var(--white);
    font-size: var(--fs-150);
    font-weight: var(--fw-bold);
    letter-spacing: var(--ls-caps);
    line-height: var(--lh-tight);
    text-transform: uppercase;
}
body[data-page='dashboard'] .dash-summary-card .form-actions {
    margin-top: var(--sp-3);
}

/* ---- work queue subhead rows -------------------------------------------
   The "Orders to pick (n)" / "Deliveries expected (n)" dividers. They are
   colspan rows, so they need their own treatment rather than inheriting the
   zebra wash or column padding. Reads as a labelled section, not a data row. */
body[data-page='dashboard'] .dash-table .dash-subhead td {
    background: var(--black);
    color: var(--white);
    font-size: var(--fs-200);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    letter-spacing: var(--ls-caps);
    padding: var(--sp-2) var(--sp-25);
    border-bottom: var(--bw-2) solid var(--red);
}
body[data-page='dashboard'] .dash-table .dash-subhead + tr td,
body[data-page='dashboard'] .dash-table .dash-subhead-gap + tr td {
    border-top: none;
}

/* ---- pick progress: show the fraction under the bar --------------------
   The work-queue progress cell now stacks the bar over its n/n label, so the
   exact picked count is visible without a tooltip. */
body[data-page='dashboard'] .dash-hero .pick-progress {
    margin-bottom: var(--sp-1);
    min-width: 80px;
}
body[data-page='dashboard'] .dash-hero td .u-text-xs {
    display: block;
    line-height: 1;
}

/* ---- KPI band operational framing --------------------------------------
   The band labels already read as section dividers. A thin black underline
   on the label makes the hierarchy crisp on the dashboard only. */
body[data-page='dashboard'] .kpi-band-label {
    padding-bottom: var(--sp-1);
    border-bottom: var(--bw-2) solid var(--black);
    margin-bottom: var(--sp-3);
}

/* ---- accessible focus on dashboard links/cards --------------------------
   KPI tiles are wrapped in <a>; ensure a visible focus ring on the dashboard
   so keyboard users see which tile is focused. The global .kpi has no focus
   style of its own. */
body[data-page='dashboard'] .kpi a:focus-visible {
    outline: var(--bw-2) solid var(--red);
    outline-offset: 3px;
    border-radius: var(--radius-lg);
}
/* dash-table links (barcodes) get a focus ring too */
body[data-page='dashboard'] .dash-table a:focus-visible {
    outline: var(--bw-2) solid var(--red);
    outline-offset: 2px;
    border-radius: var(--radius-xs);
}

/* ---- mobile: keep cards useful without nested scrollers -----------------
   The dash-field collapses to one column under 1100px (global rule). Below
   900px the card gets overflow-x:auto for wide tables (global mobile rule),
   which is enough — the dash-table is capped to a few narrow columns so it
   does not need its own inner scroller. The hero table is the widest, and at
   4 narrow columns it fits a 380px viewport without scrolling. Only tighten
   the hero head padding on phones so the controls do not crowd the title. */
@media (max-width: 900px) {
    body[data-page='dashboard'] .dash-hero-head {
        padding: var(--sp-25) var(--sp-3);
        flex-wrap: wrap;
    }
    body[data-page='dashboard'] .dash-hero-head .h2-right {
        width: 100%;
    }
    body[data-page='dashboard'] .dash-hero-head .btn-ghost {
        flex: 1 1 auto;
        text-align: center;
    }
    body[data-page='dashboard'] .dash-table td,
    body[data-page='dashboard'] .dash-table thead th {
        padding: var(--sp-15) var(--sp-2);
    }
}

/* =====================================================================
   OPERATIONAL WORKFLOW + PURPOSEFUL EMPTY STATES
   Shared by scanner-led pages so an idle station still explains the next
   action and never collapses into an unfinished white panel.
   ===================================================================== */
.command-workflow {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--sp-3);
    margin: var(--sp-4) 0;
}
.command-workflow-contained { margin-bottom: 0; }
.command-workflow-step {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: var(--sp-3);
    align-items: start;
    min-height: 118px;
    padding: var(--sp-4);
    border: var(--border);
    border-radius: var(--radius-lg);
    background: linear-gradient(145deg, var(--surface), var(--surface-2));
    box-shadow: var(--shadow-xs);
}
.command-workflow-step.is-active {
    border-color: color-mix(in srgb, var(--red) 55%, var(--grey-light));
    box-shadow: inset 0 var(--bw-3) 0 var(--red), var(--shadow-sm);
}
.command-workflow-number {
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    border-radius: var(--radius-md);
    background: var(--black);
    color: var(--white);
    font-family: var(--font-mono);
    font-size: var(--fs-200);
    font-weight: var(--fw-black);
}
.command-workflow-step.is-active .command-workflow-number { background: var(--red); }
.command-workflow-step strong {
    display: block;
    margin-bottom: var(--sp-1);
    color: var(--black);
    font-size: var(--fs-600);
}
.command-workflow-step p {
    margin: 0;
    color: var(--grey-dark);
    font-size: var(--fs-350);
    line-height: var(--lh-body);
}
.command-ready-panel {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: var(--sp-4);
    align-items: center;
    min-height: 150px;
    padding: var(--sp-5);
    overflow: hidden;
    border: var(--border-dark);
    border-radius: var(--radius-xl);
    background:
        radial-gradient(circle at 88% 15%, color-mix(in srgb, var(--red) 28%, transparent), transparent 34%),
        linear-gradient(135deg, var(--black), var(--black-3));
    color: var(--white);
    box-shadow: var(--shadow-lg);
}
.command-ready-panel-contained {
    margin: var(--sp-3) 0 var(--sp-4);
    border-radius: var(--radius-lg);
}
.command-ready-mark {
    display: grid;
    place-items: center;
    width: 72px;
    height: 72px;
    border: var(--bw-2) solid color-mix(in srgb, var(--red) 60%, var(--white));
    border-radius: var(--radius-lg);
    background: var(--red);
    color: var(--white);
    font-size: var(--fs-1000);
    font-weight: var(--fw-black);
    line-height: 1;
    box-shadow: 0 12px 30px color-mix(in srgb, var(--red) 28%, transparent);
}
.command-ready-copy { min-width: 0; }
.command-ready-kicker {
    display: block;
    margin-bottom: var(--sp-1);
    color: var(--red-light);
    font-size: var(--fs-150);
    font-weight: var(--fw-black);
    letter-spacing: var(--ls-caps);
    text-transform: uppercase;
}
.command-ready-copy > strong {
    display: block;
    color: var(--white);
    font-size: var(--fs-850);
    line-height: var(--lh-tight);
}
.command-ready-copy p {
    max-width: 68ch;
    margin: var(--sp-1) 0 0;
    color: var(--grey-light);
    line-height: var(--lh-body);
}
.command-ready-meta,
.command-ready-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--sp-2);
}
.command-ready-meta span {
    padding: var(--sp-2) var(--sp-3);
    border: var(--border-dark);
    border-radius: var(--radius-md);
    background: color-mix(in srgb, var(--white) 7%, transparent);
    color: var(--grey-light);
    font-size: var(--fs-250);
    white-space: nowrap;
}
.command-ready-meta strong { color: var(--white); }
.command-ready-actions .btn-ghost {
    border-color: var(--grey-dark);
    color: var(--white);
}
.command-ready-actions .btn-ghost:hover {
    border-color: var(--red);
    background: var(--black-2);
    color: var(--white);
}

.operational-empty {
    display: flex;
    min-height: 290px;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: var(--sp-6);
    border: var(--border-soft);
    border-radius: var(--radius-lg);
    background:
        radial-gradient(circle at 50% 0, color-mix(in srgb, var(--red) 8%, transparent), transparent 42%),
        var(--surface-2);
    text-align: center;
}
.operational-empty-mark {
    display: grid;
    place-items: center;
    width: 58px;
    height: 58px;
    margin-bottom: var(--sp-3);
    border-radius: var(--radius-lg);
    background: var(--black);
    color: var(--white);
    font-size: var(--fs-850);
    font-weight: var(--fw-black);
    box-shadow: inset 0 calc(var(--bw-3) * -1) 0 var(--red), var(--shadow-sm);
}
.operational-empty-kicker {
    color: var(--red-dark);
    font-size: var(--fs-150);
    font-weight: var(--fw-black);
    letter-spacing: var(--ls-caps);
    text-transform: uppercase;
}
.operational-empty > strong {
    margin-top: var(--sp-1);
    color: var(--black);
    font-size: var(--fs-700);
}
.operational-empty p {
    max-width: 48ch;
    margin: var(--sp-2) 0 0;
    color: var(--grey-dark);
    line-height: var(--lh-body);
}

/* Projects: purpose-built rows fit the half-width register without pretending
   six narrow table columns can communicate useful information. */
.project-register { border-top: var(--border); }
.project-register-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto auto;
    gap: var(--sp-3);
    align-items: center;
    padding: var(--sp-3);
    border-bottom: var(--border-soft);
    background: var(--surface);
    transition: background var(--t-fast), box-shadow var(--t-fast);
}
.project-register-row:last-child { border-bottom: 0; }
.project-register-row:hover {
    background: var(--surface-2);
    box-shadow: inset var(--bw-3) 0 0 var(--red);
}
.project-register-identity { min-width: 0; }
.project-register-identity .mono {
    display: inline-block;
    margin-right: var(--sp-2);
    color: var(--red-dark);
    font-weight: var(--fw-black);
}
.project-register-identity > strong {
    overflow: hidden;
    color: var(--black);
    text-overflow: ellipsis;
    white-space: nowrap;
}
.project-register-identity small {
    display: block;
    margin-top: var(--sp-05);
    color: var(--grey-mid);
    font-size: var(--fs-200);
    text-transform: uppercase;
    letter-spacing: var(--ls-caps);
}
.project-register-stock { text-align: right; }
.project-register-stock strong {
    display: block;
    color: var(--black);
    font-size: var(--fs-650);
    line-height: 1;
}
.project-register-stock span {
    color: var(--grey-mid);
    font-size: var(--fs-150);
    white-space: nowrap;
}

/* Reports: the five-column customer register needs the page width; the two
   supporting tools then sit side-by-side below it. */
body[data-page='reports'] .reports-stock-card table.list td:first-child { width: 46%; }
body[data-page='reports'] .reports-export-card h3 {
    margin-bottom: 0;
    color: var(--black);
    font-size: var(--fs-300);
    letter-spacing: var(--ls-caps);
    text-transform: uppercase;
}

/* =====================================================================
   DESKTOP WORK SURFACES — FORMS + TABLES
   The warehouse is a desktop operational system. These rules turn the
   shared primitives into deliberate work surfaces instead of loose fields
   and browser-like data grids. Specialist scanner/dashboard tables retain
   their purpose-built treatments through the exclusions below.
   ===================================================================== */

/* Form sections ------------------------------------------------------- */
.content .card form > .form-grid:not(.asn-line),
.content .card form > .form-row-3,
.content .card form > .form-row-4 {
    padding: var(--sp-4);
    border: var(--border);
    border-left: var(--bw-accent) solid var(--red);
    border-radius: var(--radius-lg);
    background:
        linear-gradient(135deg, color-mix(in srgb, var(--red) 4%, var(--surface-2)), var(--surface-2));
    box-shadow: inset 0 1px 0 var(--white), var(--elev-1);
}
.content .card form > .form-grid + .form-grid,
.content .card form > .form-grid + .form-row-3,
.content .card form > .form-grid + .form-row-4 {
    margin-top: var(--sp-3);
}
.content .card form .asn-line {
    padding: var(--sp-3);
    border: var(--border);
    border-radius: var(--radius-lg);
    background: var(--surface-2);
    box-shadow: var(--elev-1);
}
.content .card form .asn-line + .asn-line { margin-top: var(--sp-2); }
.content .field {
    min-width: 0;
    color: var(--text-2);
}
.content .field > span:not([class]) {
    margin-bottom: var(--sp-2);
    color: var(--grey-dark);
    font-size: var(--fs-200);
    font-weight: var(--fw-black);
    letter-spacing: var(--ls-caps);
    line-height: var(--lh-snug);
    text-transform: uppercase;
    transition: color var(--t-fast);
}
.content .field:focus-within > span:not([class]) { color: var(--red-dark); }
.content .field > span:not([class]) strong { color: inherit; }
.content input[type=text],
.content input[type=number],
.content input[type=email],
.content input[type=password],
.content input[type=date],
.content select,
.content textarea {
    min-height: var(--ctrl-min-h);
    border-width: var(--bw-1);
    border-color: var(--grey-line);
    background-color: color-mix(in srgb, var(--surface) 94%, var(--surface-2));
    box-shadow: inset 0 1px 2px color-mix(in srgb, var(--black) 5%, transparent);
}
.content textarea { min-height: calc(var(--ctrl-min-h) * 2.35); resize: vertical; }
.content input:hover:not(:disabled):not([readonly]),
.content select:hover:not(:disabled),
.content textarea:hover:not(:disabled):not([readonly]) {
    border-color: var(--grey-mid);
    background-color: var(--surface);
}
.content input:focus,
.content select:focus,
.content textarea:focus {
    border-color: var(--red);
    background-color: var(--surface);
    box-shadow: var(--ring), inset var(--bw-3) 0 0 var(--red);
}
.content input[readonly],
.content textarea[readonly],
.content select:disabled {
    border-style: dashed;
    background-color: var(--surface-3);
    box-shadow: none;
}
.content .card form > .form-actions {
    justify-content: flex-end;
    margin-top: var(--sp-4);
    padding-top: var(--sp-3);
    border-top: var(--border-soft);
}
.content .card form > .form-actions > .scan-hint,
.content .card form > .form-actions > .field-note { margin-right: auto; }
.content .card form > .form-actions .btn {
    min-width: calc(var(--sp-10) + var(--sp-9));
    justify-content: center;
}

/* Filter and action toolbars ----------------------------------------- */
.content .toolbar:has(input),
.content .toolbar:has(select) {
    gap: var(--sp-2);
    padding: var(--sp-3);
    border: var(--border);
    border-radius: var(--radius-lg);
    background: var(--surface-2);
    box-shadow: var(--elev-1);
}
.content .toolbar:has(input) input,
.content .toolbar:has(input) select,
.content .toolbar:has(select) input,
.content .toolbar:has(select) select {
    min-height: var(--sp-8);
    background-color: var(--surface);
}

/* Premium application registers ------------------------------------ */
body:not([data-page='dashboard']):not([data-page='scan']):not([data-page='inventory']):not([data-page='goods-in']) .content .table-wrap {
    border: var(--border);
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: var(--elev-1);
}
body:not([data-page='dashboard']):not([data-page='scan']):not([data-page='inventory']):not([data-page='goods-in']) .content table.list {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
}
body:not([data-page='dashboard']):not([data-page='scan']):not([data-page='inventory']):not([data-page='goods-in']) .content table.list th {
    position: sticky;
    top: 0;
    padding: var(--sp-25) var(--sp-3);
    border-bottom: var(--bw-3) solid var(--red);
    background: linear-gradient(180deg, var(--black-2), var(--black));
    color: var(--white);
    font-size: var(--fs-150);
    font-weight: var(--fw-black);
    letter-spacing: var(--ls-caps-wide);
    line-height: var(--lh-snug);
    white-space: normal;
}
body:not([data-page='dashboard']):not([data-page='scan']):not([data-page='inventory']):not([data-page='goods-in']) .content table.list th:first-child {
    border-top-left-radius: calc(var(--radius-lg) - var(--bw-hair));
}
body:not([data-page='dashboard']):not([data-page='scan']):not([data-page='inventory']):not([data-page='goods-in']) .content table.list th:last-child {
    border-top-right-radius: calc(var(--radius-lg) - var(--bw-hair));
}
body:not([data-page='dashboard']):not([data-page='scan']):not([data-page='inventory']):not([data-page='goods-in']) .content table.list th:hover,
body:not([data-page='dashboard']):not([data-page='scan']):not([data-page='inventory']):not([data-page='goods-in']) .content table.list th[data-dir] {
    color: var(--red-light);
    background: var(--black-2);
}
body:not([data-page='dashboard']):not([data-page='scan']):not([data-page='inventory']):not([data-page='goods-in']) .content table.list td {
    padding: var(--sp-3);
    border-bottom: var(--border-soft);
    background: var(--surface);
    color: var(--text-2);
    line-height: var(--lh-snug);
    transition: background-color var(--t-fast), color var(--t-fast), box-shadow var(--t-fast);
}
body:not([data-page='dashboard']):not([data-page='scan']):not([data-page='inventory']):not([data-page='goods-in']) .content table.list tr:nth-child(even) td {
    background: color-mix(in srgb, var(--surface-2) 72%, var(--surface));
}
body:not([data-page='dashboard']):not([data-page='scan']):not([data-page='inventory']):not([data-page='goods-in']) .content table.list tr:hover td {
    background: color-mix(in srgb, var(--red) 7%, var(--surface));
    color: var(--black);
}
body:not([data-page='dashboard']):not([data-page='scan']):not([data-page='inventory']):not([data-page='goods-in']) .content table.list tr:hover td:first-child {
    box-shadow: inset var(--bw-accent) 0 0 var(--red);
}
body:not([data-page='dashboard']):not([data-page='scan']):not([data-page='inventory']):not([data-page='goods-in']) .content table.list tr:last-child td { border-bottom: 0; }
body:not([data-page='dashboard']):not([data-page='scan']):not([data-page='inventory']):not([data-page='goods-in']) .content table.list td:first-child strong {
    color: var(--black);
}
body:not([data-page='dashboard']):not([data-page='scan']):not([data-page='inventory']):not([data-page='goods-in']) .content table.list td.num,
body:not([data-page='dashboard']):not([data-page='scan']):not([data-page='inventory']):not([data-page='goods-in']) .content table.list th.num {
    white-space: nowrap;
}
body:not([data-page='dashboard']):not([data-page='scan']):not([data-page='inventory']):not([data-page='goods-in']) .content table.list td:last-child:has(.btn),
body:not([data-page='dashboard']):not([data-page='scan']):not([data-page='inventory']):not([data-page='goods-in']) .content table.list td.u-nowrap {
    width: 1%;
    text-align: right;
    white-space: nowrap;
}
body:not([data-page='dashboard']):not([data-page='scan']):not([data-page='inventory']):not([data-page='goods-in']) .content table.list td:last-child .btn + .btn {
    margin-left: var(--sp-1);
}
body[data-page='reports'] .content table.list td:first-child,
body[data-page='reports'] .content table.list td.mono,
body[data-page='emails'] .content table.list td:first-child,
body[data-page='emails'] .content table.list td.mono {
    white-space: nowrap;
}

/* Staff register ----------------------------------------------------- */
.staff-register {
    display: grid;
    gap: var(--sp-3);
    padding: var(--sp-4);
    background: var(--surface-2);
}
.staff-register-row {
    display: grid;
    grid-template-columns: minmax(190px, .72fr) minmax(0, 1.28fr);
    gap: var(--sp-4);
    align-items: start;
    padding: var(--sp-4);
    border: var(--border);
    border-left: var(--bw-accent) solid var(--black);
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: var(--elev-1);
}
.staff-register-row:hover { border-left-color: var(--red); box-shadow: var(--elev-2); }
.staff-register-identity .mono {
    display: block;
    overflow-wrap: anywhere;
    color: var(--red-dark);
    font-weight: var(--fw-bold);
}
.staff-register-identity > strong {
    display: block;
    margin-top: var(--sp-1);
    color: var(--black);
    font-size: var(--fs-650);
}
.staff-register-badges {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1);
    margin-top: var(--sp-2);
}
.staff-register-controls { display: grid; gap: var(--sp-2); }
.staff-role-form,
.staff-password-form {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--sp-2);
    align-items: end;
}
.staff-toggle-form { display: flex; justify-content: flex-end; }
.staff-control-label { min-width: 0; }
.staff-control-label > span {
    display: block;
    margin-bottom: var(--sp-1);
    color: var(--grey-mid);
    font-size: var(--fs-150);
    font-weight: var(--fw-black);
    letter-spacing: var(--ls-caps);
    text-transform: uppercase;
}
.staff-control-label select,
.staff-control-label input { width: 100%; }

/* Customer account register ---------------------------------------- */
.account-register { display: grid; gap: var(--sp-3); }
.account-register-row {
    display: grid;
    grid-template-columns: minmax(230px, 1fr) auto;
    gap: var(--sp-3) var(--sp-4);
    align-items: center;
    padding: var(--sp-4);
    border: var(--border);
    border-left: var(--bw-accent) solid var(--black);
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: var(--elev-1);
}
.account-register-row:hover { border-left-color: var(--red); box-shadow: var(--elev-2); }
.account-register-identity { min-width: 0; }
.account-register-identity .mono {
    display: inline-block;
    margin-right: var(--sp-2);
    color: var(--red-dark);
    font-weight: var(--fw-black);
}
.account-register-identity > strong { color: var(--black); font-size: var(--fs-650); }
.account-register-identity > .scan-hint {
    display: block;
    overflow: hidden;
    margin-top: var(--sp-1);
    text-overflow: ellipsis;
    white-space: nowrap;
}
.account-register-metrics {
    display: grid;
    grid-template-columns: repeat(3, minmax(70px, 1fr));
    gap: var(--sp-2);
}
.account-register-metrics > span {
    min-width: 70px;
    padding: var(--sp-2);
    border-radius: var(--radius-md);
    background: var(--surface-2);
    text-align: right;
}
.account-register-metrics small {
    display: block;
    color: var(--grey-mid);
    font-size: var(--fs-100);
    font-weight: var(--fw-black);
    letter-spacing: var(--ls-caps);
    text-transform: uppercase;
}
.account-register-metrics strong { color: var(--black); font-size: var(--fs-650); }
.account-register-metrics em { color: var(--grey-mid); font-size: var(--fs-150); font-style: normal; }
.account-register-actions {
    grid-column: 1 / -1;
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--sp-2);
    padding-top: var(--sp-3);
    border-top: var(--border-soft);
}

/* Shipment register ------------------------------------------------- */
.shipment-register { display: grid; gap: var(--sp-3); }
.shipment-register-row {
    overflow: hidden;
    border: var(--border);
    border-left: var(--bw-accent) solid var(--black);
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: var(--elev-1);
}
.shipment-register-row.needs-attention { border-left-color: var(--red); }
.shipment-register-main {
    display: grid;
    grid-template-columns: minmax(105px, .6fr) minmax(125px, .9fr) minmax(170px, 1.35fr) auto;
    gap: var(--sp-3);
    align-items: center;
    padding: var(--sp-4);
}
.shipment-register-identity strong,
.shipment-register-route strong { display: block; color: var(--black); }
.shipment-register-date,
.shipment-register-route span {
    display: block;
    color: var(--grey-mid);
    font-size: var(--fs-250);
}
.shipment-register-tracking { min-width: 0; }
.shipment-register-tracking .scan-hint { display: block; margin-top: var(--sp-1); }
.shipment-register-metrics {
    display: grid;
    grid-template-columns: repeat(3, auto);
    gap: var(--sp-2);
}
.shipment-register-metrics span { min-width: 50px; text-align: right; }
.shipment-register-metrics strong { display: block; color: var(--black); font-size: var(--fs-600); }
.shipment-register-metrics small {
    color: var(--grey-mid);
    font-size: var(--fs-100);
    letter-spacing: var(--ls-caps);
    text-transform: uppercase;
}
.shipment-register-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-4) var(--sp-3);
    border-top: var(--border-soft);
    background: var(--surface-2);
}
.shipment-register-contents {
    padding: var(--sp-4);
    border-top: var(--border);
    background: var(--surface-2);
}

@media (max-width: 900px) {
    .command-workflow { grid-template-columns: 1fr; }
    .command-ready-panel { grid-template-columns: auto minmax(0, 1fr); }
    .command-ready-meta,
    .command-ready-actions { grid-column: 1 / -1; justify-content: flex-start; }
}

/* Goods In completion receipt: persistent until the operator dismisses it. */
.gi-receipt-backdrop {
    position: fixed;
    z-index: 12000;
    inset: 0;
    display: grid;
    place-items: center;
    padding: var(--sp-5);
    background: rgba(7, 7, 10, .82);
    backdrop-filter: blur(10px);
    animation: gi-receipt-backdrop-in 180ms ease both;
}

.gi-receipt-backdrop[hidden] {
    display: none;
}

/* McDonald's Goods In extras: .u-row is display:flex, which would beat the
   [hidden] attribute — the section must stay hidden until JS reveals it. */
.gi-mcd[hidden] {
    display: none;
}

/* Shared premium introduction for normal operational/list pages. Flagship
   screens retain their purpose-built heroes; this keeps the rest coherent. */
.app-page-hero {
    position: relative;
    isolation: isolate;
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--sp-4);
    min-height: 9.25rem;
    margin-bottom: var(--sp-5);
    padding: var(--sp-5) var(--sp-6);
    overflow: hidden;
    color: var(--on-dark);
    background:
        radial-gradient(circle at 86% 20%, color-mix(in srgb, var(--red) 34%, transparent), transparent 24rem),
        linear-gradient(135deg, var(--black) 0%, color-mix(in srgb, var(--black) 92%, var(--white)) 64%, color-mix(in srgb, var(--black) 82%, var(--red)) 100%);
    border: 1px solid color-mix(in srgb, var(--white) 8%, transparent);
    border-left: 4px solid var(--red);
    border-radius: var(--radius-xl);
    box-shadow: var(--elev-3);
}
.app-page-hero::after {
    content: '';
    position: absolute;
    z-index: -1;
    inset: 0;
    background-image: linear-gradient(color-mix(in srgb, var(--white) 2.5%, transparent) 1px, transparent 1px), linear-gradient(90deg, color-mix(in srgb, var(--white) 2.5%, transparent) 1px, transparent 1px);
    background-size: 2rem 2rem;
    mask-image: linear-gradient(90deg, transparent, var(--black) 55%);
}
.app-page-hero-icon {
    display: grid;
    place-items: center;
    width: 4.5rem;
    height: 4.5rem;
    color: var(--white);
    font-size: var(--fs-900);
    background: linear-gradient(145deg, var(--red), #740c20);
    border: 1px solid color-mix(in srgb, var(--white) 20%, transparent);
    border-radius: var(--radius-lg);
    box-shadow: 0 1rem 2.5rem color-mix(in srgb, var(--red) 28%, transparent);
}
.app-page-hero-copy > span {
    display: block;
    margin-bottom: var(--sp-1);
    color: color-mix(in srgb, var(--white) 48%, var(--red));
    font-size: var(--fs-250);
    font-weight: var(--fw-bold);
    letter-spacing: .13em;
    text-transform: uppercase;
}
.app-page-hero-copy h2 {
    margin: 0;
    color: var(--white);
    font-size: clamp(1.65rem, 2.4vw, 2.45rem);
    letter-spacing: -.035em;
}
.app-page-hero-copy p {
    max-width: 52rem;
    margin: var(--sp-1) 0 0;
    color: color-mix(in srgb, var(--white) 70%, transparent);
    font-size: var(--fs-400);
}
.app-page-hero-state {
    align-self: start;
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    padding: var(--sp-1) var(--sp-2);
    color: color-mix(in srgb, var(--white) 72%, transparent);
    font-size: var(--fs-250);
    font-weight: var(--fw-semi);
    background: color-mix(in srgb, var(--white) 6%, transparent);
    border: 1px solid color-mix(in srgb, var(--white) 10%, transparent);
    border-radius: var(--radius-pill);
}
.inv-figure-num.is-alert,
.inv-figure-label.is-alert { color: var(--red); }
.scan-bar-note { margin: calc(var(--sp-4) * -1) 0 var(--sp-4); }

/* Workers reuse the Dashboard's structural hero markup on specialist/admin
   pages. Give those headers the same dark operational treatment without
   darkening their tables, forms or documentation content below. */
body:not([data-page='dashboard']) .card.dash-hero {
    border-top: var(--bw-accent) solid var(--red);
}
body:not([data-page='dashboard']) .card.dash-hero > .dash-hero-head {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    margin: calc(var(--sp-5) * -1) calc(var(--sp-5) * -1) var(--sp-3);
    padding: var(--sp-35) var(--sp-4);
    color: var(--white);
    background: linear-gradient(135deg, var(--black-2), color-mix(in srgb, var(--black) 84%, var(--red)));
    border-radius: var(--radius-md) var(--radius-md) 0 0;
}
body:not([data-page='dashboard']) .card.dash-hero > .dash-hero-head h2 {
    margin: 0;
    color: var(--white);
}
body:not([data-page='dashboard']) .card.dash-hero > .dash-hero-head h2::before { display: none; }
body:not([data-page='dashboard']) .card.dash-hero > .dash-hero-head .h2-right {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1);
    margin-left: auto;
}
body:not([data-page='dashboard']) .card.dash-hero > .dash-hero-head .btn-ghost {
    color: var(--white);
    background: color-mix(in srgb, var(--white) 6%, transparent);
    border-color: color-mix(in srgb, var(--white) 22%, transparent);
}

.gi-receipt-backdrop.is-closing {
    opacity: 0;
    transition: opacity 160ms ease;
}

.gi-receipt-dialog {
    position: relative;
    width: min(920px, 100%);
    max-height: calc(100vh - 64px);
    overflow: auto;
    padding: clamp(28px, 4vw, 52px);
    color: var(--white);
    outline: 0;
    border: 1px solid rgba(255, 255, 255, .16);
    border-top: 5px solid var(--red);
    border-radius: var(--radius-lg);
    background:
        radial-gradient(circle at 95% 0%, rgba(200, 16, 46, .42), transparent 34%),
        linear-gradient(145deg, #1a1a22, #09090d);
    box-shadow: 0 34px 100px rgba(0, 0, 0, .56);
    animation: gi-receipt-dialog-in 220ms var(--ease-out) both;
}

.gi-receipt-close-icon {
    position: absolute;
    top: var(--sp-3);
    right: var(--sp-3);
    display: grid;
    place-items: center;
    width: 42px;
    height: 42px;
    padding: 0;
    color: var(--white);
    font-size: 28px;
    line-height: 1;
    border: 1px solid rgba(255, 255, 255, .22);
    border-radius: 50%;
    background: rgba(255, 255, 255, .07);
    cursor: pointer;
}

.gi-receipt-close-icon:hover { border-color: var(--red); background: rgba(200, 16, 46, .24); }

.gi-receipt-mark {
    width: 78px;
    margin-bottom: var(--sp-3);
}

.gi-receipt-mark svg {
    width: 100%;
    fill: rgba(200, 16, 46, .16);
    stroke: #ff4968;
    stroke-width: 5;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.gi-receipt-kicker {
    display: block;
    margin-bottom: var(--sp-2);
    color: #ff6680;
    font-size: var(--fs-200);
    font-weight: var(--fw-black);
    letter-spacing: .16em;
    text-transform: uppercase;
}

.gi-receipt-dialog h2 {
    margin: 0;
    color: var(--white);
    font-size: clamp(28px, 4vw, 46px);
    font-weight: var(--fw-black);
    letter-spacing: -.035em;
}

.gi-receipt-dialog > p {
    margin: var(--sp-2) 0 var(--sp-4);
    color: var(--white);
    font-size: var(--fs-500);
}

.gi-receipt-items {
    display: grid;
    gap: var(--sp-2);
    margin-bottom: var(--sp-4);
}

.gi-receipt-items a {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: var(--sp-3);
    align-items: center;
    padding: var(--sp-3);
    color: var(--white);
    text-decoration: none;
    border: 1px solid rgba(255, 255, 255, .15);
    border-radius: var(--radius-md);
    background: rgba(255, 255, 255, .055);
}

.gi-receipt-items a:hover { color: var(--white); border-color: var(--red); }
.gi-receipt-items span { color: #ff8ba0; font-size: var(--fs-150); font-weight: var(--fw-bold); text-transform: uppercase; }
.gi-receipt-items strong { color: var(--white); font-size: var(--fs-600); }
.gi-receipt-items b { color: var(--white); font-size: var(--fs-250); }

.gi-receipt-labels {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--sp-3);
}

.gi-receipt-label {
    display: grid;
    grid-template-columns: 46px minmax(0, 1fr);
    gap: 0 var(--sp-2);
    align-items: center;
    min-height: 108px;
    padding: var(--sp-3);
    color: var(--white);
    text-decoration: none;
    border: 1px solid rgba(255, 255, 255, .16);
    border-radius: var(--radius-md);
    background: var(--black-3);
}

.gi-receipt-label-primary {
    border-color: #ef3a59;
    background: linear-gradient(135deg, #d71336, #920a22);
}

.gi-receipt-label:hover { color: var(--white); border-color: #ff4968; transform: translateY(-2px); }
.gi-receipt-label > span { grid-row: 1 / 3; color: var(--white); font-size: 34px; }
.gi-receipt-label strong { color: var(--white); font-size: var(--fs-400); }
.gi-receipt-label small { color: var(--white); font-size: var(--fs-200); }

.gi-receipt-footer {
    display: flex;
    justify-content: flex-end;
    gap: var(--sp-2);
    margin-top: var(--sp-5);
    padding-top: var(--sp-4);
    border-top: 1px solid rgba(255, 255, 255, .13);
}

.gi-receipt-footer .btn-ghost {
    color: var(--white);
    background: var(--black-3);
    border-color: rgba(255, 255, 255, .24);
}

@keyframes gi-receipt-backdrop-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes gi-receipt-dialog-in { from { opacity: 0; transform: translateY(18px) scale(.985); } to { opacity: 1; transform: none; } }

@media (max-width: 760px) {
    .gi-receipt-labels { grid-template-columns: 1fr; }
    .gi-receipt-items a { grid-template-columns: 1fr; gap: var(--sp-1); }
    .gi-receipt-footer { flex-direction: column-reverse; }
    .gi-receipt-footer .btn { width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
    .gi-receipt-backdrop,
    .gi-receipt-dialog { animation: none; }
}
