﻿/* hm33-001: the HarmonyTMS look. Loaded AFTER Bootstrap so it wins.
   THE RULE THAT MATTERS MOST (hm118 widens it beyond colour): every
   colour AND every radius, spacing, type-scale and density value is
   declared once in the :root block below and referenced everywhere else
   through var(). No literal hex — and no literal px for those concerns —
   may appear outside :root, in this file or in any view, because a
   future settings page will re-theme at runtime by emitting an
   overriding :root block, and only that. Values that are deliberately
   NOT themeable stay literal below with a comment saying why (hairline
   border widths, the 3px identity edges, component-fit sizes, layout).
   Bootstrap here is 5.1.0, which compiles its component colours into
   literals (no --bs-btn-* variables), so components are overridden with
   explicit rules below rather than variable overrides. */

:root {
    /* ---- colour ------------------------------------------------------ */
    --hm-band: #14303A;
    --hm-band-eyebrow: #8CA6B2;
    --hm-band-subtitle: #A8BAC6;
    --hm-accent: #A85A2B;
    --hm-accent-hover: #8E4A22;
    --hm-accent-tint: #FBF1EA;
    --hm-bg: #F7F9FA;
    --hm-surface: #FFFFFF;
    --hm-border: #E4E9ED;
    --hm-text: #16232B;
    --hm-text-muted: #4A5A66;
    --hm-success-tint: #E7F4EC;
    --hm-success-text: #186B41;
    --hm-warning-tint: #FCF2DF;
    --hm-warning-text: #8A5800;
    --hm-danger-tint: #FAE9E8;
    --hm-danger-text: #A32118;
    --hm-neutral-tint: #EEF1F3;
    --hm-neutral-text: #4A5A66;
    /* hm134-001: two NEW families for the board's load-type chips. The
       existing palette had no room for two more meanings: success/
       warning/neutral belong to status, danger/warning/success edges to
       urgency, and accent's pale warm tint (#FBF1EA) sits nearly on top
       of warning's (#FCF2DF) at chip size. Blue and violet exist nowhere
       else in the palette, so a dispatcher never has to ask whether a
       colour means status, urgency or type. */
    --hm-info-tint: #E8F1F8;
    --hm-info-text: #1F5D8A;
    --hm-violet-tint: #F0EBF7;
    --hm-violet-text: #5D4B8C;
    /* hm239-002 (Oscar): TEAL — "ready at the terminal", a green that
       is deliberately NOT the success green: success means work DONE
       (stops, Completed); teal means the outside world says GO. Same
       green-family read at a glance, distinct on the same screen. */
    --hm-teal-tint: #D9F2EE;
    --hm-teal-text: #0C6E62;
    /* hm253-002: the map's PAIRING palette — eight distinct hues that
       tie a load's stops together on the map tab (burgundy first,
       Oscar's example). DATA-VIS series colours, deliberately NOT the
       meaning families: pairing is arbitrary, meaning stays on the
       rings (danger = do now, teal = ready). A user palette may re-map
       these too, same no-two-alike discipline. */
    --hm-map-1: #7A1F3D;
    --hm-map-2: #1D4ED8;
    --hm-map-3: #047857;
    --hm-map-4: #B45309;
    --hm-map-5: #6D28D9;
    --hm-map-6: #0E7490;
    --hm-map-7: #BE185D;
    --hm-map-8: #4D7C0F;
    /* hm136-001: the driver portal's BLUE — Depot is blue and Oscar's
       drivers came from Depot; a familiar colour costs no time at a
       gate. The hex lives HERE (the one :root block, per hm118); the
       .portal-shell block below only REMAPS --hm-accent to these, so
       the dispatch app never sees them and a future runtime theme can
       restyle the portal by overriding these three like any token. */
    --hm-portal-accent: #1D6FB8;
    --hm-portal-accent-hover: #175A96;
    --hm-portal-accent-tint: #E8F1F8;

    /* ---- type scale (hm117: 26/16/14/13/11, size does the ranking) --- */
    --hm-font-title: 26px;      /* page titles, the band title */
    --hm-font-heading: 16px;    /* section headings; money inputs borrow it */
    --hm-font-body: 14px;       /* body, controls, form labels, nav */
    --hm-font-table: 13px;      /* table cells, row actions, notes */
    --hm-font-small: 12px;      /* nested stop lines, timelines, seq bubbles */
    --hm-font-caption: 11px;    /* the uppercase caption family: table headers, eyebrows, badges */
    --hm-tracking-caption: 0.06em;
    --hm-weight-normal: 400;
    --hm-weight-medium: 500;
    /* hm193-006: Oscar's ruling supersedes the old "500, never heavier"
       cap for ONE use — DATA values on load Details read genuinely bold.
       Everything else keeps 400/500. */
    --hm-weight-bold: 700;
    --hm-leading-body: 1.5;
    --hm-leading-loose: 1.8;    /* nested stop lines and timelines */

    /* ---- spacing scale (the only six steps: 4/8/12/16/24/32) --------- */
    --hm-space-1: 4px;
    --hm-space-2: 8px;
    --hm-space-3: 12px;
    --hm-space-4: 16px;
    --hm-space-5: 24px;
    --hm-space-6: 32px;

    /* ---- radius (hm122 REVERSES hm33's sharp-corners rule: Oscar
       prefers the rounder look of the system this replaces) ----------- */
    --hm-radius: 8px;           /* controls, badges, notes */
    --hm-radius-lg: 12px;       /* cards, dropdowns, table wrappers */

    /* ---- elevation (hm122 REVERSES the no-box-shadow rule: true for a
       card on a list page, false for a dialog — a dialog needs to look
       lifted). Full shadow values live HERE so no colour literal ever
       leaves :root; the rgba is --hm-text's 22,35,43. The INSET accent
       edges (nav, tabs, board rows) are edges, not shadows — untouched. */
    --hm-shadow-card: 0 1px 2px rgba(22, 35, 43, 0.05), 0 2px 8px rgba(22, 35, 43, 0.07);
    --hm-shadow-lift: 0 4px 12px rgba(22, 35, 43, 0.09), 0 16px 40px rgba(22, 35, 43, 0.13);
    /* hm156-003: the dialog backdrop — the band's rgb at half strength,
       living HERE for the same reason the shadows do: no colour literal
       leaves :root. */
    --hm-backdrop: rgba(20, 48, 58, 0.55);

    /* ---- density ----------------------------------------------------- */
    --hm-row-height: 34px;      /* data-table body rows */
    --hm-row-head-height: 30px; /* data-table header row */
    --hm-control-pad-y: 6px;    /* inputs and buttons — control height derives from this */
    --hm-control-pad-x: 12px;

    /* ---- hm380: the board's PROGRESS ladders (Oscar + the rats) ------- */
    /* "From 1 to 3 or 4, light green until dark green; pickups and
       deliveries opposite colours." Deliveries climb one family, pickups
       the opposite; four steps from just-started to all-done; the text
       twin is the family's dark shade. The values are ThemeCustom's own
       derivation of #3A8F4E (a grass green, deliberately NOT success
       green — a done stop's chip stays its own thing inside a delivery
       row) and #5D4B8C (the pickup violet), so a user who picks the same
       base on Appearance gets exactly this ladder; "plain rows" there
       sets every step to the surface. */
    --hm-progress-d-1: #EBF4ED;
    --hm-progress-d-2: #CEE3D3;
    --hm-progress-d-3: #ACD0B5;
    --hm-progress-d-4: #89BC95;
    --hm-progress-d-text: #1D4827;
    --hm-progress-p-1: #EFEDF4;
    --hm-progress-p-2: #D6D2E2;
    --hm-progress-p-3: #BBB3CF;
    --hm-progress-p-4: #9E93BA;
    --hm-progress-p-text: #2E2646;
}

/* ---- base ------------------------------------------------------------ */
/* hm122-001: surfaces carry a soft shadow now (--hm-shadow-card on
   cards, --hm-shadow-lift on dialog-class surfaces like the auth card
   and dropdowns) — reversing hm33's no-box-shadow rule, which was true
   for a card on a list page and false for a dialog. System font stack
   stays: this may eventually run somewhere without internet access. */

body {
    background-color: var(--hm-bg);
    color: var(--hm-text);
    font-size: var(--hm-font-body);
    font-weight: var(--hm-weight-normal);
    line-height: var(--hm-leading-body);
}

/* hm117-004: the type scale, widened — 26/16/14/13/11. At 20px a page
   title barely outranked a field label and nothing anchored the eye;
   weights stay 400/500 only, so SIZE does the ranking. */
h1, .h3 {
    font-size: var(--hm-font-title);
    font-weight: var(--hm-weight-medium);
}

h2, .h5 {
    font-size: var(--hm-font-heading);
    font-weight: var(--hm-weight-medium);
}

/* Only weights 400 and 500 exist in this design — never heavier. */
b, strong, th, dt, .fw-bold, h1, h2, h3, h4, h5, h6 {
    font-weight: var(--hm-weight-medium);
}

a {
    color: var(--hm-accent);
}

a:hover {
    color: var(--hm-accent-hover);
}

.text-muted {
    color: var(--hm-text-muted) !important;
}

/* ---- Bootstrap 5.1 component overrides (explicit rules; see header) --- */

.btn {
    font-size: var(--hm-font-body);
    border-radius: var(--hm-radius);
}

/* hm118-001: control density through the pad tokens — Bootstrap 5.1's own
   values restated (0.375rem/0.75rem = 6px/12px), so nothing moves today;
   a theme tightens or loosens every control here. The sm/lg variants are
   excluded on purpose: they are Bootstrap's relative steps, not ours. */
.btn:not(.btn-sm):not(.btn-lg) {
    padding: var(--hm-control-pad-y) var(--hm-control-pad-x);
}

.btn-primary {
    background-color: var(--hm-accent);
    border-color: var(--hm-accent);
}

.btn-primary:hover,
.btn-primary:focus,
.btn-primary:active,
.btn-primary.active,
.btn-primary:disabled,
.btn-primary.disabled {
    background-color: var(--hm-accent-hover);
    border-color: var(--hm-accent-hover);
}

.form-control,
.form-select {
    font-size: var(--hm-font-body);
    border-radius: var(--hm-radius);
    border-color: var(--hm-border);
    color: var(--hm-text);
}

.form-control:not(.form-control-sm):not(.form-control-lg) {
    padding: var(--hm-control-pad-y) var(--hm-control-pad-x);
}

/* The select keeps Bootstrap's literal RIGHT padding — that is the arrow
   gutter (2.25rem), component fit, not density. */
.form-select:not(.form-select-sm):not(.form-select-lg) {
    padding-top: var(--hm-control-pad-y);
    padding-bottom: var(--hm-control-pad-y);
    padding-left: var(--hm-control-pad-x);
}

/* hm122-002: a THIN focus ring — Bootstrap's default is a thick 0.25rem
   halo; one hairline-class ring in the accent reads as focus without
   shouting. The 1px is ring width, hairline family, not a theme value. */
.form-control:focus,
.form-select:focus {
    border-color: var(--hm-accent);
    box-shadow: 0 0 0 1px var(--hm-accent);
    outline: 0;
}

/* hm117-001: form labels are normal case at body size — the 2026-08-08
   intake critique ("fine for table headers, exhausting on a form") made
   real. WEIGHT distinguishes the label from the value, not size and
   letter-spacing. The 11px uppercase treatment survives ONLY where it
   belongs: table headers, eyebrows, badges, metric captions — the label
   style stopped being one rule shared by forms and tables. */
.form-label {
    font-size: var(--hm-font-body);
    font-weight: var(--hm-weight-medium);
    color: var(--hm-text);
    margin-bottom: var(--hm-space-1);
}

.card {
    border-radius: var(--hm-radius-lg);
    border-color: var(--hm-border);
}

/* hm170: THE GLOBAL DRESS — Bootstrap's stock vocabulary restyled once,
   so every screen not yet rebuilt on the system components stops
   reading as 2010 WITHOUT touching its markup. A bridge, not the
   destination: a screen's real upgrade is the component rebuild
   (Customers/Edit is the template — banded header, data-table); a
   rebuilt screen never uses these classes, so the two layers cannot
   fight. */
.card {
    border: 0;
    box-shadow: var(--hm-shadow-card); /* the hm122 surface rule, applied to stock cards */
}

.card-header {
    /* the section-card heading, not Bootstrap's grey strip — mirrors
       banded-card__band--compact */
    background-color: var(--hm-surface);
    border-bottom: 1px solid var(--hm-border); /* hairline */
    font-size: var(--hm-font-heading);
    font-weight: var(--hm-weight-medium);
    color: var(--hm-text);
    padding: var(--hm-space-3) var(--hm-space-4);
}

.card-body {
    padding: var(--hm-space-4);
}

.list-group-item {
    border-color: var(--hm-border);
    font-size: var(--hm-font-body);
    padding: var(--hm-space-3) var(--hm-space-4);
}

/* the old page titles — <h1 class="h3"> everywhere pre-band; title
   size, never heavier than medium (the weight rule) */
h1.h3 {
    font-size: var(--hm-font-title);
    font-weight: var(--hm-weight-medium);
}

/* stock .table aligned to the data-table's voice: 13px cells, caption
   headers, hairlines, hover instead of zebra (no zebra is a system
   rule — hover is the row cue) */
.table {
    font-size: var(--hm-font-table);
    color: var(--hm-text);
}

.table > :not(caption) > * > * {
    padding: var(--hm-space-2) var(--hm-space-3);
    border-bottom-color: var(--hm-border);
}

.table thead th {
    font-size: var(--hm-font-caption);
    text-transform: uppercase;
    letter-spacing: var(--hm-tracking-caption);
    color: var(--hm-text-muted);
    font-weight: var(--hm-weight-medium);
    border-bottom-color: var(--hm-border);
}

.table-striped > tbody > tr:nth-of-type(odd) > * {
    --bs-table-accent-bg: transparent;
}

.table tbody tr:hover > * {
    background-color: var(--hm-neutral-tint);
}

/* hm102-002 (supersedes hm101-003's shrink-on-scroll): THE NAV is the one
   floating element — pinned at the viewport top, one constant 46px row
   (brand, links, search), identical at rest and pinned so nothing ever
   jumps. Sticky adds nothing above the first row at rest — the anti-goal
   is DepotMeister's 900px of chrome. The table header is ordinary now;
   one floating thing, not two.
   hm102-004: sticky lives on the HEADER, not the .navbar inside it — a
   sticky element floats only within its parent's box, and the bare
   <header> wrapper is exactly nav-height, so sticky on the navbar had
   zero room and scrolled away with its wrapper. The header's parent is
   the body, whose scroll IS the viewport scroll. If a future shell ever
   wraps this in a transformed/overflow ancestor, fall back to
   position: fixed plus body padding of the bar's height. */
body > header {
    position: sticky;
    top: 0;
    z-index: 1030; /* Bootstrap's layer contract, not a theme value */
}

.navbar {
    border-color: var(--hm-border) !important;
    padding-top: var(--hm-space-1);
    padding-bottom: var(--hm-space-1);
}

.navbar .navbar-brand {
    padding-top: 0;
    padding-bottom: 0;
}

.navbar-nav .nav-link {
    color: var(--hm-text-muted);
    font-size: var(--hm-font-body);
}

.navbar-nav .nav-link:hover {
    color: var(--hm-text);
}

/* hm33-002: the active nav item wears a 3px accent edge. Inset shadow, not
   border, so activation never shifts the layout. The 3px is identity, not
   density — deliberately literal, a theme does not thin the edge. */
.navbar-nav .nav-link.active {
    color: var(--hm-text);
    box-shadow: inset 0 -3px 0 var(--hm-accent);
}

/* hm36: module dropdowns — Bootstrap 5.1 compiles its own colours in, so
   the active item would light up template-blue without these. */
.dropdown-menu {
    border-color: var(--hm-border);
    border-radius: var(--hm-radius-lg);
    font-size: var(--hm-font-body);
    /* hm122-001: a floating element is dialog-class — it lifts. */
    box-shadow: var(--hm-shadow-lift);
}

.dropdown-header {
    font-size: var(--hm-font-caption);
    text-transform: uppercase;
    letter-spacing: var(--hm-tracking-caption);
    color: var(--hm-text-muted);
}

.dropdown-item.active,
.dropdown-item:active {
    background-color: var(--hm-accent-tint);
    color: var(--hm-text);
}

/* hm172-002: FLY-OUT SUBMENUS (Oscar's explicit ask, overruling
   hm38-001's no-flyout stance): a parent row opens its child menu to
   the RIGHT on hover (or keyboard focus); the parent itself clicks
   through to its group's main page — which is also the touch fallback,
   where hover does not exist. Bootstrap 5.1 has no submenu of its own,
   so this is the theme's. */
.dropdown-submenu {
    position: relative;
}

.dropdown-submenu > .dropdown-menu {
    position: absolute;
    top: 0;
    left: 100%;
    display: none;
    margin-top: 0;
}

.dropdown-submenu:hover > .dropdown-menu,
.dropdown-submenu:focus-within > .dropdown-menu {
    display: block;
}

.dropdown-submenu__parent {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--hm-space-2);
}

/* the right-pointing caret is the flyout's affordance */
.dropdown-submenu__parent::after {
    content: "\203A";
    color: var(--hm-text-muted);
    line-height: 1;
}

/* hm122-002: the site.js password Show/Hide toggle belongs to its FIELD,
   not to the button family — the input's border and colours, so the
   group reads as one control. Styled by class, so EVERY password box the
   enhancer decorates gets it, anywhere in the app. */
.btn.password-toggle {
    border: 1px solid var(--hm-border); /* hairline — matches the input */
    background-color: var(--hm-surface);
    color: var(--hm-text-muted);
    font-size: var(--hm-font-table);
}

.btn.password-toggle:hover {
    background-color: var(--hm-neutral-tint);
    color: var(--hm-text);
    border-color: var(--hm-border);
}

.btn.password-toggle:focus {
    box-shadow: 0 0 0 1px var(--hm-accent); /* the thin ring again */
    border-color: var(--hm-accent);
}

/* hm122-002: the auth shell — centres the sign-in card in the viewport
   instead of pinning it under the top. The offsets are layout (viewport
   minus footer allowance), not theme values; 400px is component fit —
   the width a credential form reads best at. */
.auth-center {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: calc(100vh - 160px);
}

.auth-center .banded-card {
    width: 100%;
    max-width: 400px;
}

/* the 11px uppercase label, as a standalone utility */
.hm-eyebrow {
    font-size: var(--hm-font-caption);
    text-transform: uppercase;
    letter-spacing: var(--hm-tracking-caption);
}

.station-switcher-label {
    font-size: var(--hm-font-body);
    color: var(--hm-text-muted);
}

/* hm102-001: the board search inside the bar — sized here, not inline.
   Component fit, not a theme value. */
.nav-board-search {
    max-width: 220px;
}

/* hm37-004 (fix 4): catalog row actions are text links, not three boxed
   buttons per row — and Delete is red TEXT, not a bordered button that
   invites the click. */
.table-action {
    background: none;
    border: 0;
    padding: 0;
    margin-left: var(--hm-space-3);
    font-size: var(--hm-font-table);
    color: var(--hm-accent);
    text-decoration: none;
    cursor: pointer;
}

.table-action:hover {
    color: var(--hm-accent-hover);
    text-decoration: underline;
}

.table-action--danger {
    color: var(--hm-danger-text);
}

.table-action--danger:hover {
    color: var(--hm-danger-text);
}

/* hm39: intake stop tabs — colour-coded by the EXISTING pickup/return
   vocabulary (start = pickups, middle = customer-site, end = returns), so
   no new rules table and no new hex: each category maps to a theme
   variable via --tab-color. Deliberately NOT the status tints — these are
   positions, not statuses. */
/* hm167-001: screen-level tabs — the stop-tabs idiom in ONE colour:
   hairline underline row, muted labels, the active tab wearing the 3px
   accent edge (an edge, not a shadow — see the nav rule). For pages
   whose sections divide into rooms (the Customer editor). */
.page-tabs {
    display: flex;
    gap: var(--hm-space-1);
    border-bottom: 1px solid var(--hm-border); /* hairline — 1px is the definition, not a theme value */
    list-style: none;
    padding: 0;
    margin: 0;
}

.page-tab {
    background: none;
    border: 0;
    padding: var(--hm-space-2) var(--hm-space-3);
    font-size: var(--hm-font-body);
    color: var(--hm-text-muted);
    cursor: pointer;
    /* hm254-001: tabs may be real LINKS (the board's Board | Map) —
       same dress as the button form, and the icon sits in the text
       line. */
    display: inline-flex;
    align-items: center;
    gap: var(--hm-space-1);
    text-decoration: none;
}

a.page-tab:hover {
    color: var(--hm-text);
}

/* hm254-001: the tab's inline-SVG icon (the portal-tabs idiom, desk
   size) — stroke rides currentColor so active/muted colour it. */
.page-tab__icon {
    width: 16px;  /* component-fit glyph box */
    height: 16px;
    flex: none;
}

.page-tab.active {
    color: var(--hm-text);
    background-color: var(--hm-surface);
    box-shadow: inset 0 -3px 0 var(--hm-accent); /* the 3px identity edge, literal on purpose */
}

.stop-tabs {
    display: flex;
    gap: var(--hm-space-1);
    border-bottom: 1px solid var(--hm-border); /* hairline — 1px is the definition, not a theme value */
    list-style: none;
    padding: 0;
    margin: 0;
}

.stop-tab {
    background: none;
    border: 0;
    padding: var(--hm-space-2) var(--hm-space-3);
    font-size: var(--hm-font-body);
    color: var(--hm-text-muted);
    display: inline-flex;
    align-items: center;
    gap: var(--hm-space-2);
    cursor: pointer;
}

.stop-tab--start {
    --tab-color: var(--hm-accent);
    --tab-contrast: var(--hm-surface);
}

.stop-tab--middle {
    --tab-color: var(--hm-band);
    --tab-contrast: var(--hm-surface);
}

.stop-tab--end {
    --tab-color: var(--hm-band-eyebrow);
    --tab-contrast: var(--hm-band);
}

/* the 3px identity edge again — literal on purpose, see the nav rule */
.stop-tab.active {
    color: var(--hm-text);
    background-color: var(--hm-surface);
    box-shadow: inset 0 -3px 0 var(--tab-color);
}

/* the 20px number box is component fit — sized to its caption glyphs */
.stop-tab__num {
    width: 20px;
    height: 20px;
    border-radius: var(--hm-radius);
    background-color: var(--tab-color);
    color: var(--tab-contrast);
    font-size: var(--hm-font-caption);
    font-weight: var(--hm-weight-medium);
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* hm39-002: each tab carries its own state — a required field on an
   unselected tab must never be invisible. */
.stop-tab__state::before {
    content: "\25CB"; /* ○ incomplete */
    color: var(--hm-text-muted);
}

.stop-tab[data-state="complete"] .stop-tab__state::before {
    content: "\2713"; /* ✓ */
    color: var(--hm-success-text);
}

.stop-tab[data-state="error"] .stop-tab__state::before {
    content: "!";
    font-weight: var(--hm-weight-medium);
    color: var(--hm-danger-text);
}

/* money is money — one step above body (the heading size), digits aligned */
.money-input {
    font-size: var(--hm-font-heading);
    font-weight: var(--hm-weight-medium);
    font-variant-numeric: tabular-nums;
}

.money-total {
    font-size: var(--hm-font-body);
    color: var(--hm-text-muted);
    font-variant-numeric: tabular-nums;
}

/* the station control living inside the dark band — component fit */
.band-select {
    max-width: 260px;
}

.band-error {
    color: var(--hm-warning-tint);
    font-size: var(--hm-font-table);
}

/* ---- banded card (the signature component) ---------------------------- */
/* One component, two uses: the page header on list screens, and the
   wrapper for the sign-in card (as .banded-card--compact). */

/* hm117-003: FEWER BORDERS, MORE SPACE. 2010 divided with lines; the
   white surface against --hm-bg already reads as raised (the exact
   reasoning the no-box-shadow rule wrote down), so card OUTLINES are
   gone — banded card, table wrapper, metric tiles, detail and stop
   cards. What genuinely separates stays: table row hairlines, the
   hairline under a section heading, input borders (affordance), and
   dropdown edges (a floating element with no shadow needs one). */
.banded-card {
    background-color: var(--hm-surface);
    border-radius: var(--hm-radius-lg);
    overflow: hidden;
    box-shadow: var(--hm-shadow-card);
}

/* hm122-001: the card-level compact card is the AUTH surface (sign-in,
   setup) — dialog-class, so it lifts. */
.banded-card--compact {
    box-shadow: var(--hm-shadow-lift);
}

.banded-card__band {
    background-color: var(--hm-band);
    padding: var(--hm-space-4) var(--hm-space-5);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--hm-space-4);
}

.banded-card__eyebrow {
    font-size: var(--hm-font-caption);
    text-transform: uppercase;
    letter-spacing: var(--hm-tracking-caption);
    color: var(--hm-band-eyebrow);
    margin: 0 0 var(--hm-space-1);
}

.banded-card__title {
    /* hm117-004: the page title clearly outranks everything below it. */
    font-size: var(--hm-font-title);
    font-weight: var(--hm-weight-medium);
    color: var(--hm-surface);
    margin: 0;
}

.banded-card__subtitle {
    font-size: var(--hm-font-body);
    color: var(--hm-band-subtitle);
    margin: var(--hm-space-1) 0 0;
}

.banded-card__body {
    padding: var(--hm-space-5);
}

.banded-card--compact .banded-card__band {
    padding: var(--hm-space-3) var(--hm-space-4);
}

.banded-card--compact .banded-card__title {
    font-size: var(--hm-font-heading);
}

.banded-card--compact .banded-card__body {
    padding: var(--hm-space-4);
}

/* hm117-002: ONE dark band per page. Dark is punctuation, not wallpaper —
   the banded page header works because the rest of the page is pale, and
   screens stacking four dark section bands read as an admin template.
   Section cards (band-level --compact) go light: white surface, a quiet
   dark heading, a hairline under the heading doing the separating. The
   sign-in's dark band is CARD-level --compact and is untouched. A screen
   that genuinely needs a second dark band opts back in deliberately;
   none does today. */
.banded-card__band--compact {
    background-color: var(--hm-surface);
    border-bottom: 1px solid var(--hm-border); /* hairline */
    padding: var(--hm-space-3) var(--hm-space-5);
}

.banded-card__band--compact .banded-card__title {
    color: var(--hm-text);
    font-size: var(--hm-font-heading);
}

.banded-card__band--compact .banded-card__eyebrow {
    color: var(--hm-text-muted);
}

.banded-card__band--compact .banded-card__subtitle {
    color: var(--hm-text-muted);
}

/* ---- data table -------------------------------------------------------- */
/* The scroll wrapper IS the table's surface card. On phones it scrolls
   horizontally. hm102-002: the header is ORDINARY — it scrolls away with
   the page; the pinned element is the nav, and one floating thing is the
   budget. */

/* hm119-001: overflow-x at EVERY width now, not only phones — a long
   route line at a narrow desktop window otherwise pushed the whole PAGE
   into sideways scroll; the table scrolls inside its own card instead. */
.table-scroll {
    background-color: var(--hm-surface);
    border-radius: var(--hm-radius-lg);
    overflow-x: auto;
    box-shadow: var(--hm-shadow-card);
}

.data-table {
    width: 100%;
    margin: 0;
    font-size: var(--hm-font-table);
    border-collapse: collapse;
    color: var(--hm-text);
    /* digits line up down a column */
    font-variant-numeric: tabular-nums;
}

.data-table thead th {
    height: var(--hm-row-head-height);
    padding: 0 var(--hm-space-3);
    background-color: var(--hm-surface);
    font-size: var(--hm-font-caption);
    text-transform: uppercase;
    letter-spacing: var(--hm-tracking-caption);
    font-weight: var(--hm-weight-medium);
    color: var(--hm-text-muted);
    text-align: left;
    white-space: nowrap;
    border-bottom: 1px solid var(--hm-border); /* hairline */
}

/* hm525-001: every listing sorts by its headers (site.js marks them) — the
   pointer says so, the arrow says which way. Server-sorted headers keep
   their own links and never get the mark. */
.data-table thead th[data-sortable],
.table thead th[data-sortable] {
    cursor: pointer;
    user-select: none;
}

.data-table thead th[data-sortable]:hover,
.table thead th[data-sortable]:hover {
    color: var(--hm-text);
}

.data-table thead th[data-sortable][data-dir="asc"]::after,
.table thead th[data-sortable][data-dir="asc"]::after {
    content: " \25B2";
    font-size: 0.7em; /* relative to the caption: the glyph must stay smaller than its header */
}

.data-table thead th[data-sortable][data-dir="desc"]::after,
.table thead th[data-sortable][data-dir="desc"]::after {
    content: " \25BC";
    font-size: 0.7em; /* relative, as above */
}

.data-table thead th.text-end {
    text-align: right;
}

/* hm529-001: a flat-rate job hides the coded columns and shows the words — one class on the container, the markup never forks. */
.is-flat .flat-hide { display: none !important; }
.flat-only { display: none; }
.is-flat .flat-only { display: revert; }

/* hm528-001: a totals row'@

# ---------- Web: list, link page, PDF, mailer
Rep "HarmonyTMS.Web\Views\Repairs\Index.cshtml" '                    <td>@e.SizeType.Code</td>' '                    <td>@e.SizeType.Code@(e.Kind == RepairEstimateKinds.Flat ? " " : "")@if (e.Kind == RepairEstimateKinds.Flat) { <span class="chip chip--violet chip--sm" title="Flat-rate job — words and a price">FLAT</span> }</td>'
Rep "HarmonyTMS.Web\Views\Repairs\Link.cshtml" @'
        <table class="table mb-2">
            <thead><tr><th>#</th><th>Repair</th><th class="text-end">Hours</th><th class="text-end">Material</th></tr></thead>
            <tbody>
            @foreach (var line in Model.Lines.OrderBy(l => l.Sequence))
            {
                var where = string.Join(" ", new[] { line.LocationFace, line.LocationSegment }.Where(s => !string.IsNullOrWhiteSpace(s)));
                <tr>
                    <td>@line.Sequence</td>
                    <td>
                        <div>@line.ComponentCode / @line.DamageCode / @line.RepairCode@(where.Length == 0 ? "" : $" · {where}")@(line.Length is null ? "" : $" · {line.Length:0.##} × {line.Width:0.##} {Model.MeasureUnit}")</div>
                        @if (!string.IsNullOrWhiteSpace(line.Notes))
                        {
                            <div class="text-muted" style="font-size: var(--hm-font-small);">@line.Notes</div>
                        }
                    </td>
                    <td class="text-end">@line.LabourHours.ToString("0.00")</td>
                    <td class="text-end">@line.MaterialCost.ToString("N2")</td>
                </tr>
            }
            </tbody>
        </table>
        <div class="d-flex justify-content-between"><span class="text-muted">Labour (@Model.LabourRate.ToString("N2") @Model.Currency / h)</span><span>@totals.Labour.ToString("N2")</span></div>
        <div class="d-flex justify-content-between"><span class="text-muted">Material</span><span>@totals.Material.ToString("N2")</span></div> — the header's dress on a hairline above, values at table size, never a second dark band. */
.data-table tfoot th {
    height: var(--hm-row-height);
    padding: 0 var(--hm-space-3);
    border-top: 1px solid var(--hm-border); /* hairline */
    font-size: var(--hm-font-table);
    font-weight: var(--hm-weight-medium);
    color: var(--hm-text);
    text-align: left;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.data-table tfoot th.text-end {
    text-align: right;
}

.data-table tbody td {
    height: var(--hm-row-height);
    padding: 0 var(--hm-space-3);
    border-bottom: 1px solid var(--hm-border); /* hairline */
    vertical-align: middle;
    white-space: nowrap;
}

/* hm193-003 (Oscar): the intake ledger WRAPS instead of scrolling — a
   thicker row beats a mile-wide one; one row per email, always. Fixed
   layout pins every column so the 20-mail day and the 200-mail day
   read the same; Subject and Message take the slack and wrap. The
   widths are component fit (timestamp, station code, badge). */
.data-table--wrap {
    table-layout: fixed;
}

.data-table--wrap tbody td {
    height: auto;
    min-height: var(--hm-row-height);
    padding-top: var(--hm-space-2);
    padding-bottom: var(--hm-space-2);
    white-space: normal;
    overflow-wrap: break-word;
    vertical-align: top;
}

.data-table tbody tr:last-child td {
    border-bottom: 0;
}

/* no zebra striping — hover tint only */
.data-table tbody tr:hover td {
    background-color: var(--hm-neutral-tint);
}

/* hm460-001: a row that carries data-href opens something on click - the hand says so; the hover tint is the row's own. */
.data-table tr[data-href] { cursor: pointer; }

/* selected row, when a screen needs one */
.data-table tbody tr.is-selected td {
    background-color: var(--hm-accent-tint);
}

/* hm123-004: the board filter pills — token-owned, so the ACTIVE state
   is explicit and theme-controlled instead of borrowed from Bootstrap's
   button family: quiet surface at rest, filled accent when active. */
.filter-pill {
    display: inline-block;
    padding: var(--hm-space-1) var(--hm-space-3);
    border: 1px solid var(--hm-border); /* hairline */
    border-radius: var(--hm-radius);
    background-color: var(--hm-surface);
    color: var(--hm-text-muted);
    font-size: var(--hm-font-table);
    text-decoration: none;
}

.filter-pill:hover {
    background-color: var(--hm-neutral-tint);
    color: var(--hm-text);
    text-decoration: none;
}

.filter-pill--active {
    background-color: var(--hm-accent);
    border-color: var(--hm-accent);
    color: var(--hm-surface);
}

.filter-pill--active:hover {
    background-color: var(--hm-accent-hover);
    border-color: var(--hm-accent-hover);
    color: var(--hm-surface);
}

/* hm171-002 (second cut — Oscar: people are visual; Depot's strip is
   tinted AT REST, ours read as beige buttons): every board pill wears
   its meaning's colour family ALWAYS — the badge colour rule at pill
   size, tint background with the family's own darker text. ACTIVE adds
   the family-text ring (an inset box-shadow — layout-stable, the 3px
   edge's cousin) and medium weight; hover previews the ring. Never the
   accent fill here: these colours MEAN things (today green, the NOT
   SET amber, overdue red, the two type identities), and meaning must
   not be repainted by selection. */
.filter-pill--success { background-color: var(--hm-success-tint); border-color: var(--hm-success-tint); color: var(--hm-success-text); }
.filter-pill--warning { background-color: var(--hm-warning-tint); border-color: var(--hm-warning-tint); color: var(--hm-warning-text); }
.filter-pill--neutral { background-color: var(--hm-neutral-tint); border-color: var(--hm-neutral-tint); color: var(--hm-neutral-text); }
.filter-pill--danger { background-color: var(--hm-danger-tint); border-color: var(--hm-danger-tint); color: var(--hm-danger-text); }
.filter-pill--info { background-color: var(--hm-info-tint); border-color: var(--hm-info-tint); color: var(--hm-info-text); }
.filter-pill--violet { background-color: var(--hm-violet-tint); border-color: var(--hm-violet-tint); color: var(--hm-violet-text); }

.filter-pill--success:hover, .filter-pill--success.filter-pill--active { background-color: var(--hm-success-tint); box-shadow: inset 0 0 0 1px var(--hm-success-text); color: var(--hm-success-text); }
.filter-pill--warning:hover, .filter-pill--warning.filter-pill--active { background-color: var(--hm-warning-tint); box-shadow: inset 0 0 0 1px var(--hm-warning-text); color: var(--hm-warning-text); }
.filter-pill--neutral:hover, .filter-pill--neutral.filter-pill--active { background-color: var(--hm-neutral-tint); box-shadow: inset 0 0 0 1px var(--hm-neutral-text); color: var(--hm-neutral-text); }
.filter-pill--danger:hover, .filter-pill--danger.filter-pill--active { background-color: var(--hm-danger-tint); box-shadow: inset 0 0 0 1px var(--hm-danger-text); color: var(--hm-danger-text); }
.filter-pill--info:hover, .filter-pill--info.filter-pill--active { background-color: var(--hm-info-tint); box-shadow: inset 0 0 0 1px var(--hm-info-text); color: var(--hm-info-text); }
.filter-pill--violet:hover, .filter-pill--violet.filter-pill--active { background-color: var(--hm-violet-tint); box-shadow: inset 0 0 0 1px var(--hm-violet-text); color: var(--hm-violet-text); }

.filter-pill--active {
    font-weight: var(--hm-weight-medium);
}

/* the quiet gap between the urgency pills and the type pills — a
   spacer, not a rule: the two groups are one toolbar */
.board-pill-gap {
    display: inline-block;
    width: var(--hm-space-4);
}

/* ---- dispatch board (hm119/hm120) -------------------------------------- */
/* hm120-001: the worklist is a GRID of row-links, not a table. A table
   cannot be wrapped in a link (row-click would need JS and lose
   ctrl/middle-click), sizes its columns from cell contents (a longer
   container number shifts every column under the 20-second refresh),
   and cannot reflow for a narrow window. ONE grid-template-columns
   rule, shared by the header strip and every row, keeps them aligned
   by construction; adding a column later is one value here. The
   column widths are explicit AND literal on purpose — stability under
   refresh is the point, and they are component fit (sized to badge,
   org code, reference, container number, times); the route column
   takes the rest. Under width pressure a cell truncates with an
   ellipsis instead of shifting its column. */
.board-list {
    position: relative; /* hm479-001: the street-turn rail is absolute inside */
    background-color: var(--hm-surface);
    border-radius: var(--hm-radius-lg);
    overflow-x: auto;
    box-shadow: var(--hm-shadow-card);
}

/* hm479-001: the STREET-TURN RAIL — drawn by the board script in the
   map's pairing colours, pointer-inert. hm504-001 (Oscar: "the match
   lines separated on the left and/or right side — hard to see for a
   human"): TWO rails, 28px each (component fit: four 6px lanes plus the
   tick), odd turns down the LEFT edge, even turns down the RIGHT, so two
   turns sharing a row leave in opposite directions; the svg spans the
   list and the script places each lane at its own x. */
.board-rail {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    pointer-events: none;
    overflow: visible;
}

.board-rail__lit path {
    stroke-width: 4;
}

.board-list__row.board-row--partner {
    outline: 2px solid var(--hm-accent); /* the partner row under the pointer's pair */
    outline-offset: -2px;
}

/* The turn chip wears its pair's colour as text and a hairline ring on
   the surface (never a tint — eight colours would need eight tints);
   the pickup's chip is the press, the delivery's only names the partner. */
.chip--turn {
    background-color: var(--hm-surface);
    box-shadow: inset 0 0 0 1px currentColor; /* hairline ring */
    cursor: pointer;
    white-space: nowrap;
}

.chip--turn-quiet {
    cursor: default;
}

.board-list__head,
.board-list__row {
    display: grid;
    /* hm191-001: Depot's column order — # / order-ref / status /
       customer / container / route / appointment / actual. Component
       fit as before; the route takes the rest. */
    /* hm399-022: the ACTUAL column grew for the one stamp shape
       (2026-09-06 - 20:17 — never truncated: a clipped clock is a lie). */
    grid-template-columns: 32px 128px 84px 96px 136px minmax(300px, 1fr) 196px 132px;
    column-gap: var(--hm-space-3);
    padding-left: calc(var(--hm-space-3) + 28px); /* hm479-001 / hm504-001: room for the 28px street-turn rail on each edge */
    padding-right: calc(var(--hm-space-3) + 28px);
}

.board-list__head {
    height: var(--hm-row-head-height);
    align-items: center;
    font-size: var(--hm-font-caption);
    text-transform: uppercase;
    letter-spacing: var(--hm-tracking-caption);
    font-weight: var(--hm-weight-medium);
    color: var(--hm-text-muted);
    white-space: nowrap;
    border-bottom: 1px solid var(--hm-border); /* hairline */
}

/* hm193-001: sortable headers — links that dress as the header they
   replace; the ▲/▼ beside the active one is the whole indicator. */
.board-list__head .board-sort {
    color: inherit;
    text-decoration: none;
}

.board-list__head .board-sort:hover {
    color: var(--hm-accent);
}

/* hm120-002: the row IS the link — a real <a>, so ctrl-click,
   middle-click, hover URL and keyboard focus come free; styled to read
   as a row, not a link: no underline, inherited colour. Rows grow with
   the route. hm121-001: single-value cells centre against the full row
   height (one value floating at the top of a three-line row reads as
   detached); the route and the per-stop line columns top-align so
   their lines pair up. */
.board-list__row {
    align-items: center;
    padding-top: var(--hm-space-2);
    padding-bottom: var(--hm-space-2);
    border-bottom: 1px solid var(--hm-border); /* hairline */
    font-size: var(--hm-font-table);
    font-variant-numeric: tabular-nums;
    color: var(--hm-text);
    text-decoration: none;
}

.board-list__row:last-child {
    border-bottom: 0;
}

/* hm380: PROGRESS rows — the tint says how far the load is (step 1..4,
   done stops over real stops), the family says delivery (d) or pickup
   (p). Declared BEFORE :hover and the urgency rules on purpose: hover
   still answers, and DO NOW's danger wash (declared last) still wins —
   urgency is not progress. The route's number circles on the stops NOT
   yet done take the same ladder, so the row and its stops agree; a
   done stop keeps its green. The tokens are the user's (Appearance). */
.board-row--prog-d-1 { background-color: var(--hm-progress-d-1); }
.board-row--prog-d-2 { background-color: var(--hm-progress-d-2); }
.board-row--prog-d-3 { background-color: var(--hm-progress-d-3); }
.board-row--prog-d-4 { background-color: var(--hm-progress-d-4); }
.board-row--prog-p-1 { background-color: var(--hm-progress-p-1); }
.board-row--prog-p-2 { background-color: var(--hm-progress-p-2); }
.board-row--prog-p-3 { background-color: var(--hm-progress-p-3); }
.board-row--prog-p-4 { background-color: var(--hm-progress-p-4); }

.board-row--prog-d-1 .board-stop-box:not(.board-stop-box--done) .board-stop-box__num { background-color: var(--hm-progress-d-2); color: var(--hm-progress-d-text); }
.board-row--prog-d-2 .board-stop-box:not(.board-stop-box--done) .board-stop-box__num { background-color: var(--hm-progress-d-3); color: var(--hm-progress-d-text); }
.board-row--prog-d-3 .board-stop-box:not(.board-stop-box--done) .board-stop-box__num { background-color: var(--hm-progress-d-4); color: var(--hm-progress-d-text); }
.board-row--prog-d-4 .board-stop-box:not(.board-stop-box--done) .board-stop-box__num { background-color: var(--hm-progress-d-4); color: var(--hm-progress-d-text); }
.board-row--prog-p-1 .board-stop-box:not(.board-stop-box--done) .board-stop-box__num { background-color: var(--hm-progress-p-2); color: var(--hm-progress-p-text); }
.board-row--prog-p-2 .board-stop-box:not(.board-stop-box--done) .board-stop-box__num { background-color: var(--hm-progress-p-3); color: var(--hm-progress-p-text); }
.board-row--prog-p-3 .board-stop-box:not(.board-stop-box--done) .board-stop-box__num { background-color: var(--hm-progress-p-4); color: var(--hm-progress-p-text); }
.board-row--prog-p-4 .board-stop-box:not(.board-stop-box--done) .board-stop-box__num { background-color: var(--hm-progress-p-4); color: var(--hm-progress-p-text); }

/* On a tinted row the boxes' hairline-on-surface look (hm379-001)
   stays white against the tint — the contrast is the point. */

/* hover before the urgency rules below, so a tinted row keeps its tint
   (same source-order trick the table version relied on) */
.board-list__row:hover {
    background-color: var(--hm-neutral-tint);
    color: var(--hm-text);
    text-decoration: none;
}

.board-cell {
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.board-cell--route {
    min-width: 0;
    align-self: start;
}

/* hm121-001: the per-stop side columns (appointment, arrived) — one
   .board-line per stop, in the SAME enumeration order as the route
   block, so line i sits beside stop i. The min-height keeps an EMPTY
   line exactly one route-line tall (an empty block would collapse and
   pull the lines below out of register); route lines never wrap
   (hm120), so the pairing holds by construction. */
.board-cell--lines {
    min-width: 0;
    align-self: start;
}

.board-line {
    display: block;
    font-size: var(--hm-font-small);
    line-height: var(--hm-leading-loose);
    min-height: calc(var(--hm-font-small) * var(--hm-leading-loose));
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ---- hm191: the Depot-parity board pieces ------------------------------ */
/* Oscar's ruling (2026-08-19, the first dispatcher's verdict): same
   layout and colours as Depot's board. Supersedes hm123-002's no-wash
   and hm134-002's type chip FOR THIS SCREEN. */

.board-cell--num {
    color: var(--hm-text-muted);
    font-size: var(--hm-font-small);
}

/* The DO NOW flag — Depot's solid red badge, white text (surface token,
   never a literal). */
.board-donow {
    display: inline-block;
    background-color: var(--hm-danger-text);
    color: var(--hm-surface);
    font-size: var(--hm-font-caption);
    font-weight: var(--hm-weight-medium);
    letter-spacing: var(--hm-tracking-caption);
    border-radius: var(--hm-radius);
    padding: 0 var(--hm-space-2);
    margin-bottom: 2px; /* component fit: keeps the ref clear of the flag */
}

.board-ref {
    display: block;
    font-weight: var(--hm-weight-medium);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.board-sub {
    display: block;
    color: var(--hm-text-muted);
    font-size: var(--hm-font-caption);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.board-cell--ref,
.board-cell--customer,
.board-cell--container {
    min-width: 0;
}

/* hm399-022: the container cell's chips WRAP under the number — a long
   fact (out-gated + no pickup recorded) becomes two chips, never a
   chip running out of its cell. */
.board-cell--container .chip {
    display: inline-block;
    margin-top: var(--hm-space-1);
    white-space: nowrap;
}

/* The container is the hero — dispatchers think in container numbers. */
.board-cont__num {
    display: block;
    font-size: var(--hm-font-body);
    font-weight: var(--hm-weight-medium);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.board-status--progress { color: var(--hm-warning-text); }
.board-status--completed { color: var(--hm-success-text); }
.board-status--cancelled { color: var(--hm-danger-text); } /* hm353-002: dead, not done */

/* Depot's route boxes — numbered, tinted, the driver chip pushed right.
   Position tints deliberately AVOID the success family: green stays
   reserved for done-or-superseded (hm182/hm190). */
.board-stop-box {
    display: flex;
    align-items: center;
    gap: var(--hm-space-2);
    padding: 2px var(--hm-space-2); /* component fit: box breathing room */
    border-radius: var(--hm-radius);
    margin-bottom: var(--hm-space-1);
    /* hm319-006 (Oscar): one step down — the city now rides each box
       and the route must still fit any screen. */
    font-size: var(--hm-font-caption);
    color: var(--hm-text);
    white-space: nowrap;
}

/* hm379-001 (Oscar: "make the interface nicer", the rats' go for all):
   the route boxes LIGHTENED — the position family moved from the whole
   box onto the NUMBER CIRCLE (tint + family text), the box itself a
   hairline on the surface. Forty rows of tinted blocks were the
   loudest thing on the board; the numbers still say first / second /
   rest at a glance. DONE keeps the full success wash: done reads at
   a glance and that is the one colour that MEANS something here
   (hm190). Supersedes hm191's full-tint boxes for the position tiers
   only; CSS alone, one edit to revert. */
.board-stop-box {
    border: 1px solid var(--hm-border); /* hairline */
    background-color: var(--hm-surface);
}

.board-stop-box--first .board-stop-box__num { background-color: var(--hm-info-tint); color: var(--hm-info-text); }
.board-stop-box--second .board-stop-box__num { background-color: var(--hm-violet-tint); color: var(--hm-violet-text); }
.board-stop-box--rest .board-stop-box__num { background-color: var(--hm-neutral-tint); color: var(--hm-neutral-text); }
.board-stop-box--done {
    background-color: var(--hm-success-tint);
    border-color: var(--hm-success-tint);
}

.board-stop-box--done .board-stop-box__num { background-color: var(--hm-surface); color: var(--hm-success-text); }

.board-stop-box__num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;  /* component fit: a one-digit circle */
    height: 16px;
    border-radius: 50%;
    background-color: var(--hm-surface);
    font-size: var(--hm-font-caption);
    font-weight: var(--hm-weight-medium);
    color: var(--hm-text-muted);
    flex: 0 0 auto;
}

/* hm379-001: the board's refresh line — a 2px track that drains over
   the 20-second tick (replaces the ticking "Refreshing in 12s"). The
   height is component fit; the colours are the neutral family so it
   never competes with a status. */
.board-refresh {
    width: 96px; /* component fit: a short line, right-aligned in the filter row */
    height: 2px;
    border-radius: var(--hm-radius);
    background-color: var(--hm-border);
    overflow: hidden;
    align-self: center;
}

.board-refresh__bar {
    height: 100%;
    width: 100%;
    background-color: var(--hm-text-muted);
    transition: width 1s linear;
}

.board-stop-box__ref {
    color: var(--hm-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
}

.board-stop-box__driver {
    margin-left: auto;
    flex: 0 0 auto;
}

/* Depot's appointment box — bordered, and the warn state wears the
   danger family with the ⚠. */
.board-appt-box {
    display: inline-flex;
    align-items: center;
    gap: var(--hm-space-1);
    padding: 2px var(--hm-space-2); /* component fit, matches the route boxes */
    border: 1px solid var(--hm-border); /* hairline */
    border-radius: var(--hm-radius);
    font-size: var(--hm-font-small);
    white-space: nowrap;
}

.board-appt-box--warn {
    border-color: var(--hm-danger-text);
    background-color: var(--hm-danger-tint);
    color: var(--hm-danger-text);
    font-weight: var(--hm-weight-medium);
}

/* hm327-001: the location's LOUD flag — same danger family as the
   warn state; the STRICT flag word tells them apart. */
.board-appt-box--strict {
    border-color: var(--hm-danger-text);
    background-color: var(--hm-danger-tint);
    color: var(--hm-danger-text);
    font-weight: var(--hm-weight-medium);
}

.board-appt-box__flag {
    font-size: var(--hm-font-caption);
    letter-spacing: var(--hm-tracking-caption);
}

/* ---- hm192: Depot-parity load Details ----------------------------------- */

/* Depot's green completed stop card — the whole card wears the tint.
   Double class on purpose: .stop-card's surface background is declared
   LATER in this file, and at equal specificity later wins — this rule
   must outrank it, not out-position it. */
.stop-card.stop-card--completed {
    background-color: var(--hm-success-tint);
}

.stop-card__done {
    color: var(--hm-success-text);
    font-weight: var(--hm-weight-medium);
    font-size: var(--hm-font-small);
    margin-top: var(--hm-space-2);
}

/* hm193-005 (Oscar's template): the stop header as a 2×2 label/value
   grid — type/place + address on the first line, ref + apt on the
   second; the whole header opens the editor when the stop is open. */
.stop-head-grid {
    flex: 1 1 auto;
    min-width: 0;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
    column-gap: var(--hm-space-4);
    row-gap: 2px; /* component fit: two lines reading as one block */
    font-size: var(--hm-font-table);
}

.stop-head-grid__label {
    display: inline-block;
    min-width: 88px; /* component fit: the widest label, "Empty return :" */
    color: var(--hm-text-muted);
    font-size: var(--hm-font-caption);
    text-transform: uppercase;
    letter-spacing: var(--hm-tracking-caption);
}

/* hm194-003 (Oscar): the place NAME reads as bold as the other data —
   explicit, so no inherited label styling can quiet it. */
.stop-head-grid__name {
    font-weight: var(--hm-weight-bold);
    font-size: var(--hm-font-table);
    text-transform: uppercase;
}

.stop-card__header--fields {
    align-items: flex-start;
    padding-top: var(--hm-space-2);
    padding-bottom: var(--hm-space-2);
}

.stop-card__header--clickable {
    cursor: pointer;
}

/* hm306-002: the driver's chat chip — ONE fixed home, the header's
   right corner of every stop card, whatever the stop's type or state
   (the rodents' rule: never hunt for it). Right-anchored by the
   header's own flex; a touch more padding than a data chip because it
   is a BUTTON, not a value. */
.stop-card__chat {
    flex: none;
    margin-left: auto;
    align-self: flex-start;
    padding: var(--hm-space-1) var(--hm-space-2);
    text-decoration: none;
    cursor: pointer;
    white-space: nowrap;
}

.stop-card__chat:hover {
    background-color: var(--hm-accent);
    color: var(--hm-surface);
}

/* hm306-005: the board's driver chip doubles as the chat button — same
   spot it always held; hover says so. */
.board-stop-box__driver--chat {
    cursor: pointer;
}

.board-stop-box__driver--chat:hover {
    background-color: var(--hm-accent);
    color: var(--hm-surface);
}

.stop-card__header--clickable:hover {
    background-color: var(--hm-neutral-tint);
}

/* hm193-006 (Oscar, closing the day): on Details, DATA reads BOLD and
   UPPERCASE — values only; labels stay quiet captions. Display-level:
   storage already uppercases business text at the write door (hm96);
   this makes dates, times and names match on screen. Notes opt out via
   the --asis modifier — they are stored as typed and read as typed. */
.detail-grid__value {
    font-weight: var(--hm-weight-bold);
    text-transform: uppercase;
}

.detail-grid__value--asis {
    font-weight: var(--hm-weight-normal);
    text-transform: none;
}

.stop-head-grid > div {
    font-weight: var(--hm-weight-bold);
    text-transform: uppercase;
}

.stop-head-grid__label {
    font-weight: var(--hm-weight-normal);
}

.summary-row > span:not(.summary-row__label) {
    font-weight: var(--hm-weight-bold);
    text-transform: uppercase;
}

.stop-card__done {
    font-weight: var(--hm-weight-bold);
    text-transform: uppercase;
}

/* The timeline as event chips, Depot's row. */
.stop-card__events {
    display: flex;
    flex-wrap: wrap;
    gap: var(--hm-space-1);
    margin-top: var(--hm-space-2);
}

/* Depot's ORDER SUMMARY / CHARGES rows — label left, value right, a
   hairline between rows. */
.summary-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--hm-space-3);
    padding: var(--hm-space-1) 0;
    border-bottom: 1px solid var(--hm-border); /* hairline */
    font-size: var(--hm-font-table);
}

.summary-row:last-of-type {
    border-bottom: 0;
}

.summary-row__label {
    color: var(--hm-text-muted);
    flex: 0 0 auto;
}

/* The band's total slot — Depot puts the money top-right in green.
   Success TEXT, not tint: the compact band is a light surface (hm117). */
.charges-total {
    color: var(--hm-success-text);
    font-size: var(--hm-font-heading);
    font-weight: var(--hm-weight-medium);
    font-variant-numeric: tabular-nums;
}

/* ---- status badge ------------------------------------------------------ */
/* Tint background with its OWN darker text colour — never grey or black
   text on a tint. */

.status-badge {
    display: inline-block;
    /* 2px/16px are component fit: the badge is sized to its caption
       glyphs, not to the spacing scale — a theme resizes it through
       --hm-font-caption, not here. */
    padding: 2px var(--hm-space-2);
    border-radius: var(--hm-radius);
    font-size: var(--hm-font-caption);
    font-weight: var(--hm-weight-medium);
    text-transform: uppercase;
    letter-spacing: var(--hm-tracking-caption);
    line-height: 16px;
}

.status-badge--success {
    background-color: var(--hm-success-tint);
    color: var(--hm-success-text);
}

.status-badge--warning {
    background-color: var(--hm-warning-tint);
    color: var(--hm-warning-text);
}

.status-badge--danger {
    background-color: var(--hm-danger-tint);
    color: var(--hm-danger-text);
}

.status-badge--neutral {
    background-color: var(--hm-neutral-tint);
    color: var(--hm-neutral-text);
}

/* hm465-002: the Repairs statuses wear the info (approved) and violet (invoiced) families - same tint-plus-text rule as the others. */
.status-badge--info { background-color: var(--hm-info-tint); color: var(--hm-info-text); }
.status-badge--violet { background-color: var(--hm-violet-tint); color: var(--hm-violet-text); }

/* hm113-002: the informational badge — the accent tint pair, existing
   variables only. Built for the intake ledger's applied appointment
   changes: neither success (nothing entered), danger (nothing wrong),
   nor neutral grey (the actual complaint — it vanished). */
.status-badge--accent {
    background-color: var(--hm-accent-tint);
    color: var(--hm-accent);
}

/* ---- chip (hm123) — the FOURTH component ------------------------------- */
/* Depot's look: a VALUE sits in a small rounded tinted box instead of
   printing as bare text. Pale tint + the same family's darker text (the
   status badge's colour rule), control radius, and HORIZONTAL padding
   only — zero vertical padding on purpose: the text's own line box is
   the chip's height, so chips never grow a 34px table row or push a
   board line out of its hm121 register. Unlike the badge, a chip
   carries a value at context size in value case — uppercase captions
   stay the badge's job. Nest with --surface (a white box is the
   contrast on a tinted parent) and --sm (the smaller nested step). */
.chip {
    display: inline-flex;
    align-items: center;
    gap: var(--hm-space-2);
    min-width: 0;
    padding: 0 var(--hm-space-2);
    border-radius: var(--hm-radius);
    background-color: var(--hm-neutral-tint);
    color: var(--hm-neutral-text);
    white-space: nowrap;
}

.chip--success {
    background-color: var(--hm-success-tint);
    color: var(--hm-success-text);
}

.chip--warning {
    background-color: var(--hm-warning-tint);
    color: var(--hm-warning-text);
}

.chip--danger {
    background-color: var(--hm-danger-tint);
    color: var(--hm-danger-text);
}

.chip--accent {
    background-color: var(--hm-accent-tint);
    color: var(--hm-accent);
}

.chip--surface {
    background-color: var(--hm-surface);
    color: var(--hm-text);
}

/* hm134-001: the load-type pair — blue for Delivery, violet for Pickup,
   families no other component uses (see the :root note). */
.chip--info {
    background-color: var(--hm-info-tint);
    color: var(--hm-info-text);
}

.chip--violet {
    background-color: var(--hm-violet-tint);
    color: var(--hm-violet-text);
}

/* hm239-002: the terminal-says-go family. */
.chip--teal {
    background-color: var(--hm-teal-tint);
    color: var(--hm-teal-text);
}

.chip--muted {
    color: var(--hm-neutral-text);
}

.chip--sm {
    font-size: var(--hm-font-caption);
    padding: 0 var(--hm-space-1);
}

/* ---- the colour picker (hm301) ----------------------------------------- */
/* hm301-001: the gate's colour chips — hm290-003's "Sample RGB feeds the
   picker chips" finally built. A CONTROL, so it wears the input hairline
   (lines are for inputs, never card outlines); the checked chip takes the
   thin accent ring, the focus-ring rule's cousin. The radio itself hides —
   the chip IS the control. */
.color-pick {
    display: inline-flex;
    align-items: center;
    gap: var(--hm-space-1);
    padding: var(--hm-control-pad-y) var(--hm-control-pad-x);
    border: 1px solid var(--hm-border); /* control hairline — deliberately not themeable */
    border-radius: var(--hm-radius);
    background: var(--hm-surface);
    font-size: var(--hm-font-body);
    cursor: pointer;
    margin-bottom: 0;
}

.color-pick input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}

.color-pick:has(input:checked) {
    border-color: var(--hm-accent);
    box-shadow: inset 0 0 0 1px var(--hm-accent); /* the 1px ring, an edge not a shadow */
    background: var(--hm-accent-tint);
}

.color-pick:has(input:focus-visible) {
    box-shadow: inset 0 0 0 1px var(--hm-accent);
}

.color-pick__swatch {
    width: 14px;   /* component-fit size — deliberately literal */
    height: 14px;
    border-radius: 4px; /* swatch-fit — deliberately literal */
    border: 1px solid var(--hm-border); /* keeps WHITE visible on the surface */
    display: inline-block;
}

/* hm395-004 (Oscar: "the label AND the colour in the drop down"): the
   colour MENU — a native option cannot draw a square, so the list is
   ours: a button dressed as the select (the current square + name), a
   lifted list of square + name rows. The real select underneath is
   visually hidden, never removed — the form's value and the keyboard. */
.color-menu {
    position: relative;
    max-width: 320px; /* layout — a colour name never needs more */
}

.color-menu__select {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}

.color-menu__button {
    display: flex;
    align-items: center;
    gap: var(--hm-space-2);
    text-align: left;
    cursor: pointer;
}

.color-menu__list {
    position: absolute;
    z-index: 30;
    top: 100%;
    left: 0;
    right: 0;
    margin: var(--hm-space-1) 0 0;
    padding: var(--hm-space-1) 0;
    list-style: none;
    max-height: 320px; /* layout — about eleven rows, then it scrolls */
    overflow-y: auto;
    background: var(--hm-surface);
    border: 1px solid var(--hm-border); /* dropdown edge — a line for what separates */
    border-radius: var(--hm-radius);
    box-shadow: var(--hm-shadow-lift);
}

.color-menu__item {
    display: flex;
    align-items: center;
    gap: var(--hm-space-2);
    padding: var(--hm-control-pad-y) var(--hm-control-pad-x);
    font-size: var(--hm-font-body);
    cursor: pointer;
}

.color-menu__item:hover {
    background: var(--hm-accent-tint);
}

.color-menu__item.is-selected {
    background: var(--hm-accent-tint);
    font-weight: var(--hm-weight-medium);
}

.color-menu__swatch {
    flex: 0 0 auto;
    width: 18px;  /* component-fit size — deliberately literal */
    height: 18px;
    border-radius: 4px; /* swatch-fit — deliberately literal */
    border: 1px solid var(--hm-border); /* keeps WHITE visible on the surface */
}

/* hm396-003: the "Any colour" row's square — an empty, dashed box */
.color-menu__swatch--any {
    border-style: dashed;
    background: transparent;
}

.color-menu__button:disabled {
    opacity: 0.6;
    cursor: default;
}

/* hm136-001: the portal's scope — custom properties CASCADE, so
   remapping the accent family on the portal's <body> recolours every
   var(--hm-accent*) consumer inside it (buttons, links, focus rings)
   and nothing outside: the dispatch app renders under a different body
   and never enters this scope. Pure var() remapping — no literal here,
   the blues live in :root. */
.portal-shell {
    --hm-accent: var(--hm-portal-accent);
    --hm-accent-hover: var(--hm-portal-accent-hover);
    --hm-accent-tint: var(--hm-portal-accent-tint);
}

/* hm379-004 (Oscar's go, the 4 a.m. CN-gate case): the DRIVER PORTAL's
   NIGHT palette — the one place the "no dark mode" rule bends, and
   only here: the office app never renders under .portal-shell. A
   theme changes TOKENS only (hm118), so this is a second token block
   scoped to the night class — markup untouched, every component
   follows. The literals below are the night twins of the :root
   palette (this block and :root are the two homes of the portal's
   colour values; the readable tint/text pairs keep the badge rule:
   darker tints, lighter text, never grey on grey). Automatic 19:00–
   07:00 tenant-local, with the driver's own toggle (cookie) either
   way — the layout decides which class the body wears. */
.portal-shell--night {
    --hm-bg: #0E171D;
    --hm-surface: #17252D;
    --hm-border: #2B3B45;
    --hm-text: #E6ECF0;
    --hm-text-muted: #9EB0BB;
    --hm-band: #0A1216;
    --hm-band-eyebrow: #7E97A4;
    --hm-band-subtitle: #9DB0BC;
    --hm-accent: #4C97E0;
    --hm-accent-hover: #6AAAE8;
    --hm-accent-tint: #15304A;
    --hm-success-tint: #143A27;
    --hm-success-text: #7FD6A5;
    --hm-warning-tint: #3B2E0F;
    --hm-warning-text: #F2C46A;
    --hm-danger-tint: #421C19;
    --hm-danger-text: #F39B93;
    --hm-neutral-tint: #22313A;
    --hm-neutral-text: #B6C4CD;
    --hm-info-tint: #15304A;
    --hm-info-text: #8FC5EE;
    --hm-violet-tint: #2C2445;
    --hm-violet-text: #C5B4EA;
    --hm-teal-tint: #103B36;
    --hm-teal-text: #7FDBCD;
    --hm-shadow-card: 0 1px 2px rgba(0, 0, 0, 0.35), 0 2px 8px rgba(0, 0, 0, 0.4);
    --hm-shadow-lift: 0 4px 12px rgba(0, 0, 0, 0.45), 0 16px 40px rgba(0, 0, 0, 0.55);
    --hm-backdrop: rgba(0, 0, 0, 0.7);
    color: var(--hm-text);
    color-scheme: dark; /* native controls (date/time pickers, selects) follow */
}

/* The band title and the active tab paint "surface" on the accent —
   at night the surface is dark, so those two read the light text. */
.portal-shell--night .portal-band__title,
.portal-shell--night .portal-tabs__tab--active {
    color: var(--hm-text);
}

/* Bootstrap's controls compile their colours; at night the inputs and
   the outline buttons take the tokens explicitly. */
.portal-shell--night .form-control,
.portal-shell--night .form-select {
    background-color: var(--hm-surface);
    color: var(--hm-text);
    border-color: var(--hm-border);
}

.portal-shell--night .btn-outline-secondary {
    color: var(--hm-text);
    border-color: var(--hm-border);
}

.portal-shell--night .text-muted {
    color: var(--hm-text-muted) !important; /* Bootstrap's utility is !important itself */
}

/* The toggle — a quiet line under the page; the state word is the
   whole UI. */
.portal-night-toggle {
    text-align: center;
    padding: var(--hm-space-4) 0 var(--hm-space-6);
}

.portal-night-toggle button {
    background: none;
    border: 0;
    color: var(--hm-text-muted);
    font-size: var(--hm-font-small);
    padding: var(--hm-space-2) var(--hm-space-3);
}

/* ---- the stop editor dialog (hm156) ------------------------------------ */
/* A native <dialog> — showModal() centres it and paints ::backdrop for
   free, no Bootstrap JS. Dialog-class surface: lift shadow, card
   radius, NO outline border (hm117). Width is layout (component fit). */
.stop-edit-dialog {
    border: 0;
    border-radius: var(--hm-radius-lg);
    box-shadow: var(--hm-shadow-lift);
    padding: var(--hm-space-5);
    width: min(560px, calc(100vw - var(--hm-space-5)));
    background-color: var(--hm-surface);
    color: var(--hm-text);
}

.stop-edit-dialog::backdrop {
    background: var(--hm-backdrop);
}

/* ---- the found chat line (hm347) --------------------------------------- */
/* A search hit lands on its bubble: the 3px accent edge as an INSET ring
   (the nav's idiom — never shifts layout), eased in and out so the eye
   catches it; the popup removes the class after a few seconds. */
.chat-bubble--found {
    box-shadow: inset 0 0 0 3px var(--hm-accent), var(--hm-shadow-lift);
    transition: box-shadow 0.4s ease;
}

.chat-bubble--found mark {
    background-color: var(--hm-warning-tint);
    color: var(--hm-warning-text);
    padding: 0;
}

/* ---- gate photos (hm344) ----------------------------------------------- */
/* The pool picker and the unit gallery share one tile: a thumbnail in a
   card-radius box, the selected state an INSET accent ring (the nav's
   edge idiom — it cannot shift layout). Tile width and the 4:3 box are
   layout (component fit). The picker dialog is wider than the stop
   editor because a grid of pictures wants room. */
.photo-dialog {
    width: min(900px, calc(100vw - var(--hm-space-5)));
}

.photo-dialog__body {
    max-height: 60vh; /* layout — the dialog scrolls, the page does not */
    overflow-y: auto;
}

.photo-grid,
.photo-dialog__body {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); /* layout */
    gap: var(--hm-space-3);
}

.photo-batch {
    grid-column: 1 / -1;
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--hm-space-3);
    padding-top: var(--hm-space-2);
    border-top: 1px solid var(--hm-border); /* hairline — deliberately not themeable */
}

.photo-batch:first-child {
    border-top: 0;
    padding-top: 0;
}

.photo-batch__label {
    font-size: var(--hm-font-caption);
    letter-spacing: var(--hm-tracking-caption);
    text-transform: uppercase;
    color: var(--hm-text-muted);
}

.photo-tile {
    position: relative;
    display: block;
    margin: 0;
    border-radius: var(--hm-radius);
    overflow: hidden;
    background-color: var(--hm-neutral-tint);
    box-shadow: var(--hm-shadow-card);
    cursor: pointer;
}

.photo-tile img {
    display: block;
    width: 100%;
    aspect-ratio: 4 / 3; /* layout */
    object-fit: cover;
}

.photo-tile--selected {
    box-shadow: inset 0 0 0 3px var(--hm-accent); /* the 3px identity edge */
}

.photo-tile__check {
    position: absolute;
    top: var(--hm-space-2);
    left: var(--hm-space-2);
    width: 20px; /* component fit */
    height: 20px;
    accent-color: var(--hm-accent);
}

.photo-tile__x {
    position: absolute;
    top: var(--hm-space-1);
    right: var(--hm-space-1);
    width: 24px; /* component fit */
    height: 24px;
    padding: 0;
    border: 0;
    border-radius: var(--hm-radius);
    background-color: var(--hm-surface);
    color: var(--hm-danger-text);
    font-size: var(--hm-font-heading);
    line-height: 1;
    opacity: 0.85;
}

.photo-strip {
    display: flex;
    flex-wrap: wrap;
    gap: var(--hm-space-2);
}

.photo-strip:empty {
    display: none;
}

.photo-strip__thumb {
    width: 72px; /* component fit */
    height: 54px;
    object-fit: cover;
    border-radius: var(--hm-radius);
    box-shadow: var(--hm-shadow-card);
}

/* ---- the busy veil (hm222) --------------------------------------------- */
/* The portal's hm162-003 "Submitting, please wait…" veil, generalized to
   the dispatch plane: _Layout raises it on ANY form post (a form opts out
   with data-no-busy; a known-slow form names its wait with data-busy).
   Translucency is opacity on the surface token — no rgba literal leaves
   :root. z-index 1090 sits above Bootstrap's fixed stack (1030–1080);
   top-layer <dialog>s still paint over it by design. */
.busy-veil {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 1090; /* above Bootstrap's fixed/dropdown/modal stack — component fit */
}

.busy-veil--show {
    display: block;
}

.busy-veil__scrim {
    position: absolute;
    inset: 0;
    background-color: var(--hm-surface);
    opacity: 0.75;
}

.busy-veil__message {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
}

.busy-veil__message .chip {
    font-size: var(--hm-font-heading);
    padding: var(--hm-space-3) var(--hm-space-5);
    box-shadow: var(--hm-shadow-lift);
}

/* ---- driver portal list (hm146) ---------------------------------------- */
/* The 2010-tablet complaint: the portal was hairlines and grey on white
   with its blue doing no visible work. Now the blue anchors the page —
   the job title rides a BLUE BAND (the banded-card idea one plane over,
   in the driver's colour), the card drops its outline for the hm117
   no-border + shadow look, each stop carries a sequence circle (the
   Details page's stop-card__seq, portal-tinted), and the NEXT stop
   wears the 3px inset identity edge so what-to-do-now pops. Done rows
   sink to the bg tint with a success-tinted circle. Everything through
   the scoped tokens — inside .portal-shell, --hm-accent IS the blue. */
.portal-band {
    background-color: var(--hm-accent);
    border-radius: var(--hm-radius-lg);
    padding: var(--hm-space-4);
    box-shadow: var(--hm-shadow-card);
}

.portal-band__title {
    margin: 0;
    font-size: var(--hm-font-heading);
    font-weight: var(--hm-weight-medium);
    color: var(--hm-surface);
}

.portal-card {
    background-color: var(--hm-surface);
    border-radius: var(--hm-radius-lg);
    box-shadow: var(--hm-shadow-card);
    overflow: hidden; /* rows clip to the rounded corners */
}

.portal-stop {
    display: block;
    padding: var(--hm-space-3) var(--hm-space-4);
    text-decoration: none;
    color: var(--hm-text);
}

.portal-stop + .portal-stop {
    border-top: 1px solid var(--hm-border); /* hairline — rows genuinely separate */
}

/* the 3px identity edge, the nav/board pattern — an edge, not a shadow */
.portal-stop--next {
    box-shadow: inset 3px 0 0 var(--hm-accent);
}

.portal-stop--done {
    background-color: var(--hm-bg);
    color: var(--hm-text-muted);
}

/* hm166-001: the portal's segmented tab bar — two equal rooms, the
   active one filled with the (scoped) accent. Surface text on the fill:
   the band-title pattern, no literal white. */
.portal-tabs {
    display: flex;
    background-color: var(--hm-surface);
    border-radius: var(--hm-radius);
    box-shadow: var(--hm-shadow-card);
    overflow: hidden; /* tabs clip to the rounded corners */
}

.portal-tabs__tab {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--hm-space-2);
    padding: var(--hm-space-3);
    color: var(--hm-text-muted);
    text-decoration: none;
    font-weight: var(--hm-weight-medium);
}

.portal-tabs__tab--active {
    background-color: var(--hm-accent);
    color: var(--hm-surface);
}
/* hm515-001 (Oscar: "these guys are very visual... pictures for ingates a
   nice green, for damages a red... by colour he knows where to click"):
   the yard phone's tabs wear a FAMILY each — the badge rule at tab size
   (tint at rest, the family's text; active = the family's text as the
   ground, surface text). Wayfinding colour on a four-room phone, not a
   status; the board's meaning families are untouched. */
.portal-tabs__tab--success { background-color: var(--hm-success-tint); color: var(--hm-success-text); }
.portal-tabs__tab--danger  { background-color: var(--hm-danger-tint);  color: var(--hm-danger-text); }
.portal-tabs__tab--info    { background-color: var(--hm-info-tint);    color: var(--hm-info-text); }
.portal-tabs__tab--violet  { background-color: var(--hm-violet-tint);  color: var(--hm-violet-text); }
.portal-tabs__tab--success.portal-tabs__tab--active { background-color: var(--hm-success-text); color: var(--hm-surface); }
.portal-tabs__tab--danger.portal-tabs__tab--active  { background-color: var(--hm-danger-text);  color: var(--hm-surface); }
.portal-tabs__tab--info.portal-tabs__tab--active    { background-color: var(--hm-info-text);    color: var(--hm-surface); }
.portal-tabs__tab--violet.portal-tabs__tab--active  { background-color: var(--hm-violet-text);  color: var(--hm-surface); }
/* hm568-001: the yard phone's Gate passes room wears the warning family (people waiting), with a count badge. */
.portal-tabs__tab--warning { background-color: var(--hm-warning-tint); color: var(--hm-warning-text); }
.portal-tabs__tab--warning.portal-tabs__tab--active { background-color: var(--hm-warning-text); color: var(--hm-surface); }
.portal-tabs__badge {
    display: inline-block; min-width: 1.4em; padding: 0 var(--hm-space-1); margin-left: var(--hm-space-1);
    border-radius: 999px; /* a pill: layout, not a token */
    background-color: var(--hm-danger-text); color: var(--hm-surface);
    font-size: var(--hm-font-small); line-height: 1.4em; text-align: center; font-weight: var(--hm-weight-medium);
}
.portal-tabs__badge[hidden] { display: none; }

/* hm516-001 (Oscar: "some nice small icons"): the STACKED tab — the icon
   over the label, big enough for a thumb and an eye at arm's length.
   32px is the icon box (component fit, like the badge glyph); the stroke
   rides currentColor so the family colours it. */
.portal-tabs--stacked .portal-tabs__tab {
    flex-direction: column;
    gap: var(--hm-space-1);
    padding: var(--hm-space-3) var(--hm-space-2);
    font-size: var(--hm-font-small);
    line-height: 1.2;
}
.portal-tabs--stacked .portal-tabs__tab svg {
    width: 32px;  /* component fit — the big icon box */
    height: 32px;
    stroke-width: 1.75;
}

.portal-tabs__tab svg {
    width: 16px;  /* component fit — the icon box, like the badge glyph */
    height: 16px;
}

/* hm471-001: the menu icons - inline SVG, one stroke, the item's own colour a shade quieter; 16px is the icon box (component fit, like the badge glyph), -3px sits it on the text's baseline. */
.dropdown-item .nav-icon { width: 16px; height: 16px; vertical-align: -3px; margin-right: var(--hm-space-2); opacity: 0.75; }
.dropdown-item.active .nav-icon, .dropdown-item:hover .nav-icon { opacity: 1; }

/* hm160-002: the loads LIST cards wear the load type as a wash —
   delivery green, pickup violet, empty move neutral. The board banned
   row tints (hm134: chips, never a wash) because a board row is one of
   forty; here the card IS the job and Oscar asked for exactly this —
   the day's mix readable from colour alone. Chips inside go --surface. */
.portal-load--delivery {
    background-color: var(--hm-success-tint);
}

.portal-load--pickup {
    background-color: var(--hm-violet-tint);
}

.portal-load--empty {
    background-color: var(--hm-neutral-tint);
}

/* the card's centered type line — eyebrow treatment (uppercase caption),
   in the wash's own text colour so the word and the colour agree */
.portal-load__type {
    font-size: var(--hm-font-caption);
    font-weight: var(--hm-weight-medium);
    letter-spacing: var(--hm-tracking-caption);
    text-transform: uppercase;
    color: var(--hm-text-muted);
}

.portal-load--delivery .portal-load__type {
    color: var(--hm-success-text);
}

.portal-load--pickup .portal-load__type {
    color: var(--hm-violet-text);
}

/* the Details page's 22px sequence circle, portal-tinted (component fit;
   50% is a shape, not a radius step) */
.portal-stop__seq {
    flex: none;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: var(--hm-font-small);
    font-weight: var(--hm-weight-medium);
    background-color: var(--hm-accent-tint);
    color: var(--hm-accent);
}

.portal-stop--done .portal-stop__seq {
    background-color: var(--hm-success-tint);
    color: var(--hm-success-text);
}

/* hm135: the dev-only phone frame — 390×844, the iPhone 12–15 logical
   viewport and close to the Android mid-range, so what fits here fits a
   real phone. The dimensions are COMPONENT FIT (a phone, not a theme
   value) and stay literal, like the 3px edges. */
.phone-frame {
    width: 390px;
    height: 844px;
    border: 1px solid var(--hm-border); /* hairline */
    border-radius: var(--hm-radius-lg);
    background-color: var(--hm-surface);
    box-shadow: var(--hm-shadow-lift);
}

.phone-frame--empty {
    display: flex;
    align-items: center;
    justify-content: center;
}

/* hm97-003: an inline warning note for forms — the warning tints (never
   grey or black text on a tint), table-cell size, control radius. For
   text a user must read before filling a field in wrong; status-badge
   stays for one-word states. */
.warning-note {
    background-color: var(--hm-warning-tint);
    color: var(--hm-warning-text);
    border-radius: var(--hm-radius);
    padding: var(--hm-space-3) var(--hm-space-4);
    font-size: var(--hm-font-table);
}

.warning-note p {
    margin: 0 0 var(--hm-space-2);
}

.warning-note p:last-child {
    margin-bottom: 0;
}

/* hm101-002: board-row urgency — an edge (the nav's inset-shadow trick,
   hm33-002: it cannot shift the layout) plus a light tint, existing
   status tints only. Three tiers; a New load with a confirmed
   appointment gets nothing at all — if every row is coloured, nothing
   is. Applied to both of a load's rows so the block reads as one.
   hm102-003: the missing-info tier keeps its edge but NOT a row tint —
   the night 63 unconfirmed loads arrived, the whole board was one flat
   amber and the tint meant nothing; the per-row NOT SET badge says it.
   The 3px edges are identity, literal on purpose — see the nav rule.
   hm120-001: re-scoped from table cells to the grid row-link — one
   element per load now.
   hm123-002: the full-row WASH is gone — with most rows unconfirmed,
   nearly every row was tinted, so the tint signalled nothing and chips
   could not read as chips against a coloured row. Every tier keeps its
   3px edge (none lost its only signal: missing and calm were edge-only
   since hm102-003). The red edge remains the ONLY urgent marker — the
   colourblindness gap flagged at hm120-002 stands, deliberately
   unfixed. */
/* hm191-001: the DO NOW row gets Depot's full wash back — Oscar's
   ruling reverses hm123-002 for the urgent tier ONLY (missing and calm
   keep edge-only, so the wash stays a signal, not wallpaper). Declared
   after :hover, so the wash survives the pointer. */
.board-list__row.board-row--now {
    box-shadow: inset 3px 0 0 var(--hm-danger-text);
    background-color: var(--hm-danger-tint);
}

.board-list__row.board-row--missing {
    box-shadow: inset 3px 0 0 var(--hm-warning-text);
}

.board-list__row.board-row--calm {
    box-shadow: inset 3px 0 0 var(--hm-success-text);
}

/* hm120-003: ONE media query restacks a row for a narrow window — the
   SAME markup, a different template: reference, customer and status on
   the first line, the container under them, then the route, then the
   appointment beside arrived. The header strip hides — stacked rows
   label themselves. Not a separate mobile view. */
@media (max-width: 767.98px) {
    .board-list__head {
        display: none;
    }

    .board-list__row {
        grid-template-columns: 1fr auto auto;
        grid-template-areas:
            "ref    cust   status"
            "cont   cont   cont"
            "route  route  route"
            "appt   appt   arrived";
        row-gap: var(--hm-space-1);
    }

    /* hm191-001: the row number is desk furniture — stacked rows drop it. */
    .board-cell--num { display: none; }
    .board-cell--status { grid-area: status; }
    .board-cell--customer { grid-area: cust; }
    .board-cell--ref { grid-area: ref; }
    .board-cell--container { grid-area: cont; }
    .board-cell--route { grid-area: route; }
    .board-cell--appt { grid-area: appt; }
    .board-cell--arrived { grid-area: arrived; text-align: right; }

    /* hm121-001: stacked, the side columns sit BELOW the route, so the
       line-for-line register is gone and a blank spacer line is noise —
       drop them here; on the wide layout they hold the register. */
    .board-line--blank {
        display: none;
    }
}

/* hm55 / hm119-001: the stop lines inside the route cell — compact,
   quiet. (.board-stops, the old sub-row wrapper, died with the two-row
   board shape.) A line NEVER wraps — a wrapped line would read as an
   extra stop. */
.board-stop-line {
    display: flex;
    gap: var(--hm-space-3);
    font-size: var(--hm-font-small);
    color: var(--hm-text-muted);
    line-height: var(--hm-leading-loose);
    white-space: nowrap;
}

.board-stop-line .board-stop-line__type {
    color: var(--hm-text);
    min-width: 110px; /* component fit: the widest stop-type label */
}

/* hm120-001: under width pressure the LOCATION gives way with an
   ellipsis — sequence, type, the stop's reference and its driver stay
   whole, and the column never shifts. */
.board-stop-line__location {
    min-width: 0;
    flex: 0 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* hm56: load details — a definition grid for the load header and one
   card per stop, replacing the twelve-column table. Desk screen. */
.detail-card {
    background-color: var(--hm-surface);
    border-radius: var(--hm-radius-lg);
    padding: var(--hm-space-4);
    margin-bottom: var(--hm-space-4);
    box-shadow: var(--hm-shadow-card);
}

.detail-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr)); /* layout, not a theme value */
    gap: var(--hm-space-3) var(--hm-space-5);
}

.detail-grid__label {
    font-size: var(--hm-font-caption);
    text-transform: uppercase;
    letter-spacing: var(--hm-tracking-caption);
    color: var(--hm-text-muted);
}

.detail-grid__value {
    font-size: var(--hm-font-body);
    color: var(--hm-text);
}

.stop-card {
    background-color: var(--hm-surface);
    border-radius: var(--hm-radius-lg);
    margin-bottom: var(--hm-space-3);
    box-shadow: var(--hm-shadow-card);
}

.stop-card__header {
    display: flex;
    align-items: center;
    gap: var(--hm-space-3);
    padding: var(--hm-space-2) var(--hm-space-4);
    border-bottom: 1px solid var(--hm-border); /* hairline */
}

/* the 22px circle is component fit; 50% radius makes a circle — a shape,
   not a radius step, so it stays literal */
.stop-card__seq {
    flex: none;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--hm-font-small);
    font-weight: var(--hm-weight-medium);
    background-color: var(--hm-neutral-tint);
    color: var(--hm-neutral-text);
}

.stop-card__body {
    padding: var(--hm-space-3) var(--hm-space-4);
}

.stop-card__timeline {
    list-style: none;
    margin: var(--hm-space-3) 0 0;
    padding: 0;
    font-size: var(--hm-font-small);
    color: var(--hm-text-muted);
    line-height: var(--hm-leading-loose);
}

/* The quiet reveal: the event form opens on demand instead of shouting. */
.stop-card details > summary {
    cursor: pointer;
    font-size: var(--hm-font-table);
    color: var(--hm-accent);
    list-style: none;
}

.stop-card details > summary::-webkit-details-marker {
    display: none;
}

/* hm252 → hm253: the board's MAP tab. Each LOAD wears one pairing
   colour (--hm-map-N) on all its numbered stop pins and the line
   between them; MEANING stays on the rings — danger = do now, teal =
   ready at CN — and done stops mute. Sizes are component-fit literals
   (a map pin, like the badge's glyph box). */
.board-map {
    height: 560px; /* component-fit: the map's room under the pills */
    border-radius: var(--hm-radius-lg);
    box-shadow: var(--hm-shadow-card);
}

.map-pin-anchor {
    background: none;
    border: none;
}

/* The numbered stop pin — the route cell's numbered box, round, on a map. */
.map-pin {
    width: 22px;  /* component-fit: room for a sequence digit */
    height: 22px;
    border-radius: 50%;
    border: 2px solid var(--hm-surface); /* the white rim that lifts a pin off any tile */
    box-shadow: var(--hm-shadow-card);
    background: var(--hm-neutral-text);
    color: var(--hm-surface);
    font-size: var(--hm-font-caption);
    font-weight: var(--hm-weight-medium);
    display: flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
}

.map-pin--c1 { background: var(--hm-map-1); }
.map-pin--c2 { background: var(--hm-map-2); }
.map-pin--c3 { background: var(--hm-map-3); }
.map-pin--c4 { background: var(--hm-map-4); }
.map-pin--c5 { background: var(--hm-map-5); }
.map-pin--c6 { background: var(--hm-map-6); }
.map-pin--c7 { background: var(--hm-map-7); }
.map-pin--c8 { background: var(--hm-map-8); }

/* A finished (or superseded) stop stays in position, quietly. */
.map-pin--done {
    opacity: 0.35;
}

.map-pin--urgent {
    outline: 3px solid var(--hm-danger-text); /* DO NOW keeps its meaning */
    outline-offset: 1px;
}

.map-pin--ready {
    outline: 3px solid var(--hm-teal-text); /* the hm239 "terminal says GO" ring */
    outline-offset: 1px;
}

.map-pin__driver {
    display: inline-block;
    margin-top: 2px; /* component-fit: hugs its pin */
    padding: 0 var(--hm-space-1);
    background: var(--hm-surface);
    color: var(--hm-text);
    font-size: var(--hm-font-caption);
    font-weight: var(--hm-weight-medium);
    border-radius: var(--hm-radius);
    box-shadow: var(--hm-shadow-card);
    white-space: nowrap;
}

/* hm257-003: the truck dot — the DRIVER's last-known position with its
   age, a white chip so it never fights the coloured stop pins. */
/* hm539-001: the ELD truck pin — the fleet's last reported spot, an accent tint apart from the white live-phone dot. */
/* hm551-001: the Truck map's small truck glyph — no card, just the icon; the load rides the hover tooltip. */
/* hm553-001 (Oscar: "too tiny and mixes with color of the map"): the truck
   pin is a white round chip with a burgundy ring - nothing on an OSM tile is
   white-on-burgundy, so it stands off roads, parks and water alike. The
   SELECTED truck (its day path on show) fills. */
/* hm560-001: the Messages popup's signed-out veil - the dialog backdrop's colour, surface text. */
.session-gone { position: fixed; inset: 0; z-index: 1000; /* layout: above the popup's panes */ display: flex; align-items: center; justify-content: center; background: var(--hm-backdrop); color: var(--hm-surface); font-size: var(--hm-font-heading); text-align: center; line-height: var(--hm-leading-loose); }

/* hm557-001: the Driver days summary - a Loads count that opens its links in place. */
.loads-details > summary { cursor: pointer; color: var(--hm-accent); list-style: none; text-decoration: underline dotted; }
.loads-details > summary::-webkit-details-marker { display: none; }
.loads-details__list { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--hm-space-1); padding-top: var(--hm-space-1); max-width: 520px; /* layout: keeps a long list from stretching the column */ }
.loads-details__list .chip { white-space: nowrap; }

.map-trucklet {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;  /* component-fit: matches the divIcon's iconSize */
    height: 30px;
    border-radius: 50%;
    background: var(--hm-surface);
    color: var(--hm-map-1);
    border: 2px solid var(--hm-map-1); /* the identity ring, not a hairline */
    box-shadow: var(--hm-shadow-lift);
    line-height: 0;
    cursor: pointer;
}
.map-trucklet svg { width: 19px; height: 13px; /* component-fit icon */ }
.map-trucklet__wheel { fill: var(--hm-surface); stroke: currentColor; stroke-width: 1; }
.map-trucklet--on { background: var(--hm-map-1); color: var(--hm-surface); }
.map-trucklet--on .map-trucklet__wheel { fill: var(--hm-map-1); }
.map-eldtruck {
    background: var(--hm-accent-tint);
    color: var(--hm-accent);
}
.map-truck {
    display: inline-block;
    padding: 2px var(--hm-space-2); /* component-fit chip */
    background: var(--hm-surface);
    color: var(--hm-text);
    font-size: var(--hm-font-caption);
    font-weight: var(--hm-weight-medium);
    border-radius: var(--hm-radius);
    box-shadow: var(--hm-shadow-lift);
    white-space: nowrap;
}

/* hm356-002: the HOVER CARD — a load's facts on hover, a dialog-class
   surface (lift shadow, card radius) that opens under its trigger and
   stays while the pointer is on it (it is the trigger's child, so the
   :hover holds). --right flips it for triggers near the right edge. No
   JS: the card is rendered in the page and only shown. */
.hover-pop {
    position: relative;
    display: inline-block;
}
.hover-pop--block { display: block; }
.hover-pop__card {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 30; /* above timeline rows and chips */
    min-width: 300px; /* component-fit: the route line needs room */
    max-width: 440px; /* component-fit */
    padding: var(--hm-space-3);
    background: var(--hm-surface);
    color: var(--hm-text);
    font-size: var(--hm-font-small);
    line-height: var(--hm-leading-body);
    text-align: left;
    white-space: normal;
    border-radius: var(--hm-radius-lg);
    box-shadow: var(--hm-shadow-lift);
}
.hover-pop__card--right { left: auto; right: 0; }
.hover-pop:hover > .hover-pop__card,
.hover-pop:focus-within > .hover-pop__card { display: block; }
.hover-pop__head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--hm-space-2);
    margin-bottom: var(--hm-space-2);
    font-size: var(--hm-font-body);
}
.hover-pop__row {
    display: grid;
    grid-template-columns: 92px 1fr; /* label column: component-fit */
    gap: var(--hm-space-2);
    padding: 2px 0; /* component-fit */
}
.hover-pop__label {
    font-size: var(--hm-font-caption);
    letter-spacing: var(--hm-tracking-caption);
    text-transform: uppercase;
    color: var(--hm-text-muted);
    padding-top: 2px; /* component-fit: aligns the caption with the value's first line */
}
.hover-pop__warn { color: var(--hm-danger-text); font-weight: var(--hm-weight-medium); }
.hover-pop__foot {
    margin-top: var(--hm-space-2);
    padding-top: var(--hm-space-2);
    border-top: 1px solid var(--hm-border); /* hairline: deliberate */
}

/* hm399-011: the YARD BARS — the depot dashboard's three charts as
   markup, no chart library (the offline rule). A bar is a link: label,
   a track with the fill, the count in tabular numerals. The fill is
   the accent; the damaged chart's fill wears the danger family; the
   colour chart paints the colour's OWN sample inline (data, not theme
   — the same swatch the inventory shows). Track height is a component
   fit, kept literal on purpose. */
.yard-bars {
    display: flex;
    flex-direction: column;
    gap: var(--hm-space-2);
}

.yard-bar {
    display: grid;
    grid-template-columns: 6em 1fr 3em;
    align-items: center;
    gap: var(--hm-space-2);
    color: var(--hm-text);
    font-size: var(--hm-font-table);
    text-decoration: none;
}

.yard-bar:hover {
    color: var(--hm-accent);
    text-decoration: none;
}

.yard-bar__label {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.yard-bar__track {
    display: block;
    height: 14px; /* component fit: the bar's body, deliberately literal */
    border-radius: var(--hm-radius);
    background-color: var(--hm-neutral-tint);
    overflow: hidden;
}

.yard-bar__fill {
    display: block;
    height: 100%;
    border-radius: var(--hm-radius);
    background-color: var(--hm-accent);
}

.yard-bar__fill--danger {
    background-color: var(--hm-danger-text);
}

.yard-bar__count {
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-weight: var(--hm-weight-medium);
}

/* hm399-016: the pool picker's TWO PANES — the preview left, the list
   right (old Depot's picker, the rodents' list shape). The dialog is
   wide because a picture wants room; the pane split and the row grid
   are layout; the thumb box is a component fit. */
.photo-dialog {
    width: min(1200px, calc(100vw - var(--hm-space-5)));
}

.photo-dialog__panes {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 420px; /* layout */
    gap: var(--hm-space-4);
    align-items: start;
}

@media (max-width: 900px) { /* layout: the panes stack on a narrow screen */
    .photo-dialog__panes { grid-template-columns: minmax(0, 1fr); }
}

.photo-preview {
    min-height: 240px; /* layout */
    max-height: 70vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--hm-space-2);
    border-radius: var(--hm-radius-lg);
    background-color: var(--hm-neutral-tint);
    overflow: hidden;
}

.photo-preview img {
    max-width: 100%;
    max-height: calc(70vh - var(--hm-space-6));
    object-fit: contain;
    border-radius: var(--hm-radius);
}

.photo-preview__caption {
    font-size: var(--hm-font-small);
    color: var(--hm-text-muted);
    padding-bottom: var(--hm-space-2);
}

.photo-list {
    max-height: 70vh; /* layout — the list scrolls, the page does not */
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: var(--hm-space-1);
}

.photo-list__batch {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--hm-space-3);
    padding: var(--hm-space-2) 0 var(--hm-space-1);
    border-top: 1px solid var(--hm-border); /* hairline — deliberately not themeable */
}

.photo-list__batch:first-child {
    border-top: 0;
    padding-top: 0;
}

.photo-row {
    display: grid;
    grid-template-columns: 20px 72px 1fr 4em 24px; /* layout: tick · thumb · date · time · discard */
    align-items: center;
    gap: var(--hm-space-2);
    padding: var(--hm-space-1) var(--hm-space-2);
    border-radius: var(--hm-radius);
    font-size: var(--hm-font-table);
    font-variant-numeric: tabular-nums;
    cursor: pointer;
}

.photo-row:hover {
    background-color: var(--hm-neutral-tint);
}

.photo-row--current {
    background-color: var(--hm-accent-tint);
}

.photo-row--selected {
    box-shadow: inset 0 0 0 2px var(--hm-accent); /* the selection ring — layout-stable */
}

.photo-row__check {
    width: 20px; /* component fit */
    height: 20px;
    accent-color: var(--hm-accent);
}

.photo-row__thumb {
    width: 72px; /* component fit */
    height: 54px;
    object-fit: cover;
    border-radius: var(--hm-radius);
}

.photo-row__time {
    color: var(--hm-text-muted);
}

.photo-row__x {
    width: 24px; /* component fit */
    height: 24px;
    padding: 0;
    border: 0;
    border-radius: var(--hm-radius);
    background-color: transparent;
    color: var(--hm-danger-text);
    font-size: var(--hm-font-heading);
    line-height: 1;
}

/* hm399-019: the board's honest words — the UNPLANNED EMPTY leg, a warning-family route box with a dashed
   hairline so it reads as a hole in the plan, not a place. */

.board-stop-box--missing {
    border: 1px dashed var(--hm-warning-text); /* hairline, dashed: a gap */
    background-color: var(--hm-warning-tint);
    color: var(--hm-warning-text);
}

.board-stop-box--missing .board-stop-box__num {
    background-color: var(--hm-surface);
    color: var(--hm-warning-text);
}

/* hm399-024: the LIGHTBOX — a wide dialog, the picture on a dark stage
   (the band colour: the one dark surface the system already owns), the
   nav arrows floating on it, the thumbnail strip under it with the
   current one ringed. Sizes are layout / component fit. */
.lightbox {
    width: min(1100px, calc(100vw - var(--hm-space-5)));
}

.lightbox__stage {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 320px; /* layout */
    max-height: 70vh;
    border-radius: var(--hm-radius-lg);
    background-color: var(--hm-band);
    overflow: hidden;
}

.lightbox__stage img {
    max-width: 100%;
    max-height: 70vh;
    object-fit: contain;
}

.lightbox__nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 44px; /* component fit: a thumb-sized target */
    height: 64px;
    border: 0;
    border-radius: var(--hm-radius);
    background-color: var(--hm-surface);
    color: var(--hm-text);
    font-size: var(--hm-font-title);
    line-height: 1;
    opacity: 0.8;
    box-shadow: var(--hm-shadow-lift);
}

.lightbox__nav:hover { opacity: 1; }
.lightbox__nav--prev { left: var(--hm-space-3); }
.lightbox__nav--next { right: var(--hm-space-3); }

.lightbox__strip {
    display: flex;
    gap: var(--hm-space-2);
    margin-top: var(--hm-space-3);
    overflow-x: auto;
    padding-bottom: var(--hm-space-1);
}

.lightbox__strip img {
    width: 96px; /* component fit */
    height: 72px;
    object-fit: cover;
    border-radius: var(--hm-radius);
    cursor: pointer;
    flex: 0 0 auto;
}

.lightbox__strip img.is-current {
    box-shadow: inset 0 0 0 3px var(--hm-accent), 0 0 0 3px var(--hm-accent); /* the 3px identity edge */
}

/* hm443-001 (Oscar, laughing but serious: "make a pill blink and
   fluorescent when it is 4 hours approaching the cutoff, so we know
   that one is urgent"): the URGENT chip — the rail cutoff inside four
   hours. It alternates between the danger family's full text colour on
   a surface glyph and the warning tint (the brightest tint we own),
   once a second, steps so it snaps rather than fades. Tokens only, so a
   palette repaints it too. Reduced-motion users get the solid form. */
.chip--urgent {
    background-color: var(--hm-danger-text);
    color: var(--hm-surface);
    font-weight: var(--hm-weight-medium);
    animation: hm-urgent-blink 1s steps(2, jump-none) infinite;
}

@keyframes hm-urgent-blink {
    0%, 49% { background-color: var(--hm-danger-text); color: var(--hm-surface); }
    50%, 100% { background-color: var(--hm-warning-tint); color: var(--hm-danger-text); }
}

@media (prefers-reduced-motion: reduce) {
    .chip--urgent { animation: none; }
}

/* hm472-001: the office's driver-messages BUBBLE - fixed bottom-right, the accent
   family, the unread count in the danger family riding its shoulder. 48px is the
   touch-friendly button box and 1080 sits under the busy veil (1090) and over
   Bootstrap's fixed bar - component fit, not theme values. Hidden at zero. */
.chat-fab {
    position: fixed;
    right: var(--hm-space-5);
    bottom: var(--hm-space-5);
    z-index: 1080;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 48px;
    border-radius: 50%;
    background-color: var(--hm-accent);
    color: var(--hm-surface);
    box-shadow: var(--hm-shadow-lift);
    text-decoration: none;
}

.chat-fab:hover { background-color: var(--hm-accent-hover); color: var(--hm-surface); }

.chat-fab svg { width: 22px; height: 22px; }

.chat-fab__count {
    position: absolute;
    top: -6px;
    right: -6px;
    min-width: 22px;
    height: 22px;
    padding: 0 var(--hm-space-1);
    border-radius: 11px;
    background-color: var(--hm-danger-text);
    color: var(--hm-surface);
    font-size: var(--hm-font-caption);
    font-weight: var(--hm-weight-medium);
    line-height: 22px;
    text-align: center;
}
