/* ===========================================================================
   staff.css — HALCYON CLEARANCE — back-office (staff/admin) page-scoped styles
   ---------------------------------------------------------------------------
   Operator-grade console skin, redesigned to sit on the "working premium" system
   the marketing site now wears (Impeccable / Emil pass, 2026-07-13). Product
   register: a work tool, not a marketing page — fixed rem scale, restrained
   density, crisp scannable cards, one accent (amber) + ink + neutrals.

   Uses the shared design tokens from styles.css (:root --ink-900/--amber/--line/…);
   Hanken Grotesk (var(--display)) for figures + labels; system stack for prose;
   tabular-nums on every numeral. The old "Material Science" mono-instrument
   overlay is DELETED — it was the single biggest AI-slop tell.

   Every JS element id, class name, [hidden]-toggle and .is-active hook that
   staff.js binds to is preserved; this file re-skins, it renames nothing.
   ========================================================================= */

.bo-body {
  background: var(--canvas);
  /* --- back-office local scale + surfaces (semantic, 4pt base) --- */
  --bo-1: 4px; --bo-2: 8px; --bo-3: 12px; --bo-4: 16px;
  --bo-5: 24px; --bo-6: 32px; --bo-7: 48px;
  --bo-inset: #EAEEF3;            /* board column ground */
  --bo-inset-line: #dde4ec;
  --bo-card-line: #e3e8ee;
  --bo-elev-1: 0 1px 2px rgba(17,24,32,.05), 0 2px 6px -2px rgba(17,24,32,.08);
  --bo-elev-2: 0 14px 30px -18px rgba(17,24,32,.42);
  --bo-live: #1c7a43;            /* functional success green (matches pay-paid) */
  --bo-live-soft: #e3f6e8;
  /* status tokens (impeccable F6) — single source for the ok/warn/danger/info
     tints repeated across pills, gate-notes, doc-stats. Change here, not per-site.
     G50: values now shared from styles.css --status-ok/--status-warn/--status-danger. */
  --ok-bg: var(--status-ok-bg); --ok-ink: var(--status-ok-ink); --ok-line: var(--status-ok-line);
  --warn-bg: var(--status-warn-bg); --warn-ink: var(--status-warn-ink); --warn-line: var(--status-warn-line);
  --danger-bg: var(--status-danger-bg); --danger-ink: var(--status-danger-ink); --danger-line: var(--status-danger-line);
  --info-bg: #EAEEF3; --info-ink: var(--ink-900); --info-line: #D1DBE6;
  /* W2.4 — heights of the FIXED furniture stacked above the message centre, so
     .msg-centre can be sized as 100dvh minus a real stack (not a 300px guess).
     --bo-header-h = sticky .bo-shell-header (44px min + 22px pad ≈ 66);
     --bo-tabs-h   = .bo-tabs incl. its 24px bottom margin;
     --bo-viewhead-h = .view-head (kicker+title+sub) incl. its 24px bottom margin.
     A media query bumps the header var once its tabs wrap to a second row. */
  --bo-header-h: 66px;
  --bo-tabs-h: 71px;
  --bo-viewhead-h: 116px;
}
/* placeholder contrast (impeccable F10) — browser-default gray is borderline on white */
.bo-body ::placeholder { color: var(--slate); opacity: 1; }

/* =========================================================================
   HEADER — brand bar chrome specific to the console
   ========================================================================= */
.bo-tag {
  font-family: var(--display);
  font-size: .6875rem; font-weight: 800; letter-spacing: .1em; text-transform: uppercase;
  color: var(--amber-ink); background: var(--amber);
  padding: 3px 9px; border-radius: 6px; line-height: 1.2;
}
.bo-who {
  color: var(--ink-900); font-size: .86rem; font-weight: 600;
  display: inline-flex; align-items: center; gap: 8px;
}
#bo-user-email { font-variant-numeric: tabular-nums; }
.role-badge {
  font-family: var(--display);
  font-size: .62rem; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 999px;
  background: var(--slate-soft); color: var(--slate);
  border: 1px solid var(--line);
}
.role-badge.admin { background: var(--amber); color: var(--amber-ink); border-color: transparent; }

main > .container { padding-top: var(--bo-5); padding-bottom: var(--bo-7); }
.bo-panel { animation: bo-fade .3s var(--ease); }
@keyframes bo-fade { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* =========================================================================
   FULL-BLEED (T3/D1) — the back office is a work tool, not a marketing page;
   it escapes the 1180px .container cap from styles.css. Scoped to .bo-body
   only — styles.css itself is untouched, every other page keeps the cap.
   The drawer/doc-modal/lightbox are position:fixed and unaffected either way.
   R1 fix (measured 2026-07-24): max-width:none alone isn't enough — styles.css's
   `.container { margin: 0 auto }` survives the override, and while the message
   centre is open `.bo-msg-active main > .container` (below) becomes a FLEX ITEM
   of a column flex `main`. A flex item's auto cross-axis margins disable
   align-items:stretch (CSS Flexbox §8.3) — so instead of filling `main`'s
   1920px width, `.container` collapsed to its own shrink-to-fit content width
   (measured 860px) and the leftover space became the "auto" margins either
   side. Resetting margin:0 here removes that trap; it's a no-op everywhere
   .container is laid out as a normal block (max-width:none already makes the
   auto-centering math moot there), so nothing else on the page moves.
   ========================================================================= */
.bo-body .container { max-width: none; margin: 0; padding: 0 32px; }
@media (max-width: 768px) {
  .bo-body .container { padding: 0 24px; }
}

/* Mobile header: the console adds the signed-in email + role badge + Sign out to
   the shared .site-header, which overflows and overlaps the brand at phone widths.
   Scoped to .bo-body so the shared header on every other page is untouched. */
@media (max-width: 640px) {
  .bo-body .site-header .container { flex-wrap: wrap; row-gap: 8px; column-gap: 10px; }
  .bo-body .brand { flex: 1 1 auto; min-width: 0; }
  .bo-body .header-cta { flex: 1 1 100%; min-width: 0; margin-left: auto; display: inline-flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }
  .bo-body .bo-who { min-width: 0; flex: 1 1 auto; }
  .bo-body #bo-user-email { max-width: 60vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .bo-body .bo-tag { display: none; }   /* console context is already clear on mobile; frees the brand row */
}

/* =========================================================================
   NOT-OPEN + SIGN-IN + ACCESS-DENIED cards
   ========================================================================= */
.notopen-card, .signin-card {
  max-width: 560px; margin: var(--bo-6) auto;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-card);
  padding: var(--bo-6) var(--bo-6);
}
.signin-card { max-width: 430px; }
.notopen-icon {
  font-size: 1.5rem; width: 52px; height: 52px; border-radius: 14px;
  display: flex; align-items: center; justify-content: center;
  background: var(--slate-soft); border: 1px solid var(--line); margin-bottom: var(--bo-4);
}
.notopen-card h1, .signin-card h1 {
  color: var(--ink-900); margin: 0 0 var(--bo-3); font-size: 1.5rem; letter-spacing: -.02em;
}
.notopen-card p { color: var(--ink-soft); line-height: 1.6; margin-bottom: var(--bo-3); }
.notopen-sub { font-size: .9rem; }
.notopen-card code, .signin-card code {
  font-family: var(--mono); background: var(--slate-soft);
  padding: 1px 6px; border-radius: 6px; font-size: .84em; color: var(--ink-900);
}
.notopen-card .btn { margin-top: var(--bo-2); }
.signin-help { color: var(--ink-soft); margin-bottom: var(--bo-5); font-size: .92rem; }
.signin-help a, .notopen-card a[href], .msg-empty a { color: var(--ink-900); font-weight: 700; }
.signin-card .step-eyebrow { margin-bottom: var(--bo-2); }
.field input[type="password"] {
  width: 100%; padding: 12px 14px; border: 1.5px solid var(--line); border-radius: 10px;
  font: inherit; background: #fff; color: var(--ink);
  transition: border-color .15s var(--ease), box-shadow .15s var(--ease);
}
.field input[type="password"]:focus { border-color: var(--amber); outline: 2px solid var(--ink-900); outline-offset: 2px; box-shadow: 0 0 0 3px rgba(242,169,0,.18); }
.bo-auth-error { margin: 4px 0 var(--bo-4); }

/* =========================================================================
   VIEW HEAD — kicker + title band + LIVE dot (added static wrapper in html)
   ========================================================================= */
.view-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--bo-4); flex-wrap: wrap;
  padding-bottom: var(--bo-4); margin-bottom: var(--bo-5);
  border-bottom: 1px solid var(--line);
}
.view-kicker {
  display: block;
  font-family: var(--display); font-weight: 700;
  font-size: .72rem; letter-spacing: .11em; text-transform: uppercase;
  color: var(--steel); margin-bottom: 6px;
}
.view-title { font-size: 1.5rem; letter-spacing: -.02em; color: var(--ink-900); margin: 0; line-height: 1.05; }
.view-sub { font-size: .88rem; color: var(--ink-soft); margin: 6px 0 0; }
.live-dot {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--display); font-weight: 700; font-size: .68rem;
  letter-spacing: .08em; text-transform: uppercase; color: var(--bo-live);
  background: var(--bo-live-soft); border: 1px solid #bfe6c9;
  padding: 5px 11px 5px 9px; border-radius: 999px; white-space: nowrap;
}
.live-dot { position: relative; }
.live-dot::before {
  content: ""; width: 8px; height: 8px; border-radius: 50%;
  background: var(--bo-live);
}
/* pulse rides a composited transform ring (emil 7) — the old box-shadow spread
   keyframe repainted every frame forever; scale/opacity stay on the GPU. */
.live-dot::after {
  content: ""; position: absolute; left: 9px; top: 50%; margin-top: -4px;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--bo-live); opacity: .45; pointer-events: none;
  animation: live-ring 2s var(--ease) infinite;
}
@keyframes live-ring {
  0% { transform: scale(1); opacity: .45; }
  70%, 100% { transform: scale(2.4); opacity: 0; }
}

/* =========================================================================
   TABS
   ========================================================================= */
/* W6 S4 — a 7th tab (Payments) tipped this strip over 390px width, which
   used to widen the WHOLE PAGE sideways (the strip had no overflow scoping
   of its own). Scroll the strip itself instead — a standard mobile pattern,
   and zero visual change at any width the tabs already fit.
   -------------------------------------------------------------------------
   THAT SCROLL HID A WHOLE VIEW (design pass, 2026-07-26). At 390 the strip is
   751px of content in 342px of box, and #tab-payments sits at right=458 — past
   the end of the strip — with no fade, no cue, no sliver of the next tab. The
   entire Payments view was unreachable on a phone and nothing on screen said
   more tabs existed. A hidden affordance is not an affordance.

   So below 720px the strip WRAPS instead: every tab is on screen, nothing to
   discover. It is safe to wrap only there — --bo-tabs-h feeds .msg-centre's
   height calc, and that calc is itself scoped to >720px (below it the centre
   stacks at height:auto), so a taller tab block cannot mis-size anything.

   Between 720px and whatever width fits all seven tabs the strip can still
   scroll, so it also carries edge fades — toggled by staff.js on real
   scroll geometry (.has-more-start / .has-more-end), never permanently, so a
   strip that fits is never faded. Those fades are also what stops the Message
   centre's count badge being bisected by a hard edge mid-scroll. */
.bo-tabs {
  display: flex; gap: 2px; margin-bottom: var(--bo-5);
  border-bottom: 1px solid var(--line); overflow-x: auto; -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}
.bo-tab { flex: 0 0 auto; }
.bo-tabs.has-more-end {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 34px), transparent 100%);
          mask-image: linear-gradient(to right, #000 calc(100% - 34px), transparent 100%);
}
.bo-tabs.has-more-start {
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 34px);
          mask-image: linear-gradient(to right, transparent 0, #000 34px);
}
.bo-tabs.has-more-start.has-more-end {
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 34px, #000 calc(100% - 34px), transparent 100%);
          mask-image: linear-gradient(to right, transparent 0, #000 34px, #000 calc(100% - 34px), transparent 100%);
}
@media (max-width: 720px) {
  .bo-tabs {
    flex-wrap: wrap; overflow-x: visible; row-gap: 0;
    -webkit-mask-image: none; mask-image: none;
  }
  .bo-tab { padding: 11px 13px; font-size: .9rem; }
}
.bo-tab {
  border: none; background: none; font-family: var(--display); font-weight: 700;
  font-size: .95rem; color: var(--slate); letter-spacing: -.01em;
  padding: 11px 16px; cursor: pointer; border-bottom: 2.5px solid transparent;
  margin-bottom: -1px; transition: color .15s var(--ease), border-color .15s var(--ease);
}
.bo-tab:hover { color: var(--ink-900); }
.bo-tab.is-active { color: var(--ink-900); border-bottom-color: var(--amber); }
.tab-count {
  font-family: var(--display); font-variant-numeric: tabular-nums;
  background: var(--red); color: #fff; border-radius: 999px;
  font-size: .68rem; font-weight: 800; padding: 1px 7px; margin-left: 5px;
}

/* =========================================================================
   BOARD
   ========================================================================= */
/* TWO VIEWS: COLLECTIONS · SKIPS (2026-07-27). A clearance job is one movement;
   a skip goes out on one day and comes back on another, and the two do not
   share a column set. The switch is a tablist, sized to the same 44px effective
   target the board's other in-row controls use, and it sits ABOVE the legend
   because the legend's words change with it. */
.board-view-switch { display: flex; gap: 8px; margin-bottom: var(--bo-3); flex-wrap: wrap; }
.bvs-tab {
  font-family: var(--display); font-size: .78rem; font-weight: 800; letter-spacing: .02em;
  min-height: 38px; padding: 8px 14px; border-radius: 999px; border: 1px solid var(--line-2);
  background: #fff; color: var(--ink-soft); cursor: pointer;
  transition: background .15s var(--ease), color .15s var(--ease), border-color .15s var(--ease);
}
.bvs-tab:hover { background: var(--slate-soft, #EEF2F6); }
.bvs-tab.is-on { background: var(--ink-900); color: #fff; border-color: var(--ink-900); }
.bvs-count {
  display: inline-block; margin-left: 6px; min-width: 18px; padding: 0 6px; border-radius: 999px;
  background: var(--slate-soft, #EEF2F6); color: var(--ink-soft);
  font-size: .68rem; font-variant-numeric: tabular-nums;
}
.bvs-tab.is-on .bvs-count { background: rgba(255,255,255,.18); color: #fff; }
/* The one-row "book this skip's collection" editor, inside the Next-step cell.
   It reuses .board-link-actions for its layout and .board-link-action for its
   two buttons, so nothing about the tap targets or the spacing is re-invented —
   only the date input itself needs sizing. */
.skip-collect-form { align-items: center; }
.skip-collect-date {
  min-height: 34px; padding: 5px 8px; border-radius: 8px; border: 1px solid var(--line-2);
  font: inherit; font-size: .78rem; color: var(--ink-900); background: #fff;
  font-variant-numeric: tabular-nums;
}
.board-legend { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 14px; margin-bottom: var(--bo-4); }
.legend-title { font-family: var(--display); font-weight: 800; color: var(--ink-900); font-size: .95rem; }
.legend-note { font-size: .84rem; color: var(--ink-soft); }

/* =========================================================================
   METRICS STRIP (T6/D4) — quiet stat tiles, estate language: ink figures,
   tabular-nums, small-caps labels. Not a rainbow dashboard.
   ========================================================================= */
.board-metrics { display: flex; flex-direction: column; gap: var(--bo-2); margin-bottom: var(--bo-4); }
.metrics-row { display: flex; flex-wrap: wrap; gap: var(--bo-2); }
.metric-tile {
  flex: 1 1 130px; min-width: 130px; max-width: 220px;
  background: #fff; border: 1px solid var(--bo-card-line); border-radius: 11px;
  padding: 9px 13px; box-shadow: var(--bo-elev-1);
}
.mt-label {
  display: block; font-family: var(--display); font-weight: 800;
  font-size: .62rem; letter-spacing: .07em; text-transform: uppercase;
  color: var(--slate); margin-bottom: 4px;
}
.mt-value {
  display: block; font-family: var(--display); font-weight: 800;
  font-size: 1.22rem; color: var(--ink-900); font-variant-numeric: tabular-nums; line-height: 1.1;
}
.metric-tile-time .mt-value { font-size: 1.08rem; }
/* The six column tiles are a table of contents for the board; the money note is
   not one of them, so it gets a quiet ground instead of a card's white. */
.metric-tile.is-aside { background: var(--bo-inset, #f4f6f9); box-shadow: none; }
/* ATTENTION only raises its voice when it has something to say — a permanently
   red tile reading 0 is the alarm nobody looks at (design pass, 2026-07-26). */
.metric-tile.is-attention {
  background: var(--danger-bg); border-color: var(--danger-line);
}
.metric-tile.is-attention .mt-label,
.metric-tile.is-attention .mt-value { color: var(--danger-ink); }
.mt-money { font-weight: 800; }
.mt-sub {
  display: block; font-size: .74rem; color: var(--ink-soft);
  margin-top: 3px; font-variant-numeric: tabular-nums;
}

/* Work-queue table (v4, operator pick 2026-07-15 — option 2 of 5). One dense
   table grouped by job status; the kanban columns + v3 chip strip retired
   with the pick. The wrap owns any horizontal scroll on narrow screens; the
   page itself never scrolls sideways. */
.jobs-queue {
  background: #fff; border: 1px solid var(--line); border-radius: 14px;
  box-shadow: var(--bo-elev-1); padding: var(--bo-2) 0 var(--bo-3);
}
.jq-scroll { overflow-x: auto; }
.jobs-table { width: 100%; border-collapse: collapse; font-size: .89rem; }
.jobs-table thead th {
  font-family: var(--display); font-size: .66rem; font-weight: 800;
  letter-spacing: .07em; text-transform: uppercase; color: var(--slate);
  text-align: left; padding: 10px var(--bo-4); border-bottom: 2px solid var(--line);
  white-space: nowrap;
}
.jq-group th {
  font-family: var(--display); font-size: .68rem; font-weight: 800;
  letter-spacing: .07em; text-transform: uppercase; color: var(--ink-900);
  text-align: left; background: var(--bo-inset); padding: 6px var(--bo-4);
  border-top: 1px solid var(--bo-inset-line); border-bottom: 1px solid var(--bo-inset-line);
}
.jq-count {
  font-variant-numeric: tabular-nums; font-size: .68rem; font-weight: 800;
  color: var(--slate); background: #fff; border: 1px solid var(--bo-inset-line);
  border-radius: 999px; padding: 0 8px; margin-left: 4px; display: inline-block;
}
.job-row td { padding: 12px var(--bo-4); border-bottom: 1px solid var(--line-2); vertical-align: middle; }
.job-row { cursor: pointer; transition: background .15s var(--ease); }
.job-row:hover td { background: var(--slate-soft, #EEF2F6); }
.job-row:focus-visible { outline: 2px solid var(--ink-900); outline-offset: -2px; }
.job-row:active td { background: #E8EDF3; }
.jr-name { font-family: var(--display); font-weight: 700; color: var(--ink); letter-spacing: -.01em; }
/* A postcode and an ISO date are single TOKENS — breaking them ("NP19"/"8FG",
   "2026-08-"/"15") reads as a rendering fault, and the board had room to spare
   at 1280 when it did it (design pass, 2026-07-26). Nowrap, like .jr-price and
   .jr-chip already are; at 560 the table reflows to cards, where each of these
   sits on its own self-labelled line and cannot force the page sideways. */
.jr-pc { font-variant-numeric: tabular-nums; color: var(--ink-soft); white-space: nowrap; }
.jr-sched { font-family: var(--display); font-weight: 700; color: var(--ink-900); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* VALUE is column 6, not the last one — W6 S4 added a 7th (SLA/attention) and
   the right-align rule was still pointing at :last-child, so the SLA header sat
   right-aligned over a left-aligned column of chips and reason text. */
.jr-price, .jobs-table thead th:nth-child(6) { text-align: right; }
.jr-chip, .jobs-table thead th:nth-child(7) { text-align: left; }
.jr-price { font-family: var(--display); font-weight: 800; color: var(--ink-900); font-variant-numeric: tabular-nums; white-space: nowrap; }
.jq-none { padding: var(--bo-5) var(--bo-4); color: var(--slate); text-align: center; }
.jq-empty-note { font-size: .82rem; color: var(--slate); margin: var(--bo-3) var(--bo-4) 0; }
.col-empty { font-size: .82rem; color: var(--slate); padding: var(--bo-3) 4px; text-align: center; }

/* =========================================================================
   BOARD V2 (W6 S4) — SLA/window chips, ATTENTION reason chips, copy/chase
   link actions, and the Payments view (tiles + reconciliation strip + list).
   ========================================================================= */
.jr-chip, .jobs-table thead th:nth-last-child(1) { white-space: nowrap; }
.board-chip {
  display: inline-block; font-family: var(--display); font-weight: 800;
  font-size: .66rem; letter-spacing: .03em; padding: 3px 9px; border-radius: 999px;
  background: var(--slate-soft, #EEF2F6); color: var(--ink-soft); border: 1px solid var(--line-2);
  font-variant-numeric: tabular-nums;
}
.board-chip.is-over { background: var(--warn-bg); color: var(--warn-ink); border-color: var(--warn-line); }
/* An ATTENTION reason is a SENTENCE the writers produced — up to ~150 chars of
   real prose — not a status token. Inheriting .board-chip's 999px radius made a
   4–5 line pink blob with stadium arcs top and bottom: it read as a speech
   bubble, and at 390 it read as a speech bubble captioned "SLA" (design pass,
   2026-07-26). A note is a note: square-ish corners, sentence case, sentence
   weight, and enough width to be read rather than squeezed. The colour still
   carries the alarm; the geometry no longer pretends to be a badge. */
.board-chip.is-attention {
  display: block; background: var(--danger-bg); color: var(--danger-ink); border-color: var(--danger-line);
  white-space: normal; max-width: 34ch; text-align: left; line-height: 1.4;
  border-radius: 8px; padding: 6px 9px;
  font-size: .74rem; font-weight: 600; letter-spacing: 0; text-transform: none;
  font-variant-numeric: normal;
}
/* row-gap is 14px, not 6px, so the ::before hit-slop below cannot make two
   WRAPPED rows of actions overlap each other's targets. */
.board-link-actions { display: flex; gap: 14px 6px; margin-top: 8px; flex-wrap: wrap; }
/* ~30px visible control, ≥44px effective target — the same ::before expansion
   .ql-del and .chip-add already use. These are the primary chase-a-payment
   actions and the ones most likely to be tapped on a phone in a van, and they
   shipped at 22px with no slop (design pass, 2026-07-26). The horizontal slop is
   3px a side, exactly half the 6px flex gap, so two adjacent buttons meet
   without either stealing the other's taps. */
.board-link-action {
  position: relative;
  font-family: var(--display); font-size: .68rem; font-weight: 800; letter-spacing: .02em;
  min-height: 30px; padding: 6px 10px; border-radius: 8px; border: 1px solid var(--line-2);
  background: #fff; color: var(--ink-900); cursor: pointer; transition: background .15s var(--ease);
}
.board-link-action::before { content: ""; position: absolute; inset: -7px -3px; }
.board-link-action:hover { background: var(--slate-soft, #EEF2F6); }
.chase-badge {
  display: inline-block; margin-left: 4px; font-variant-numeric: tabular-nums;
  background: var(--warn-bg); color: var(--warn-ink); border-radius: 999px; padding: 0 6px; font-size: .64rem;
}

.hint { font-size: .82rem; color: var(--ink-soft); }

.payments-wrap { display: flex; flex-direction: column; gap: var(--bo-3); }
.recon-strip {
  background: var(--ok-bg); color: var(--ok-ink); border: 1px solid var(--ok-line);
  border-radius: 11px; padding: 10px var(--bo-4); font-size: .86rem;
}
.recon-strip.is-mismatch { background: var(--danger-bg); color: var(--danger-ink); border-color: var(--danger-line); }
/* NEUTRAL SKIN FOR "NOT A VERDICT" (REWORK 2). staff.js has emitted
   .is-unavailable since rework 1 for the admin-only / still-checking /
   couldn't-be-read states, but no rule ever matched it — so a strip that says
   "the reconciliation is admin-only" rendered on the GREEN reconciles skin,
   which reads as a clean bill of health to anyone scanning the page for
   colour. The 'partial' tone (a truncated read) shares this skin for the same
   reason: neither state is a pass, and neither is an alarm. */
.recon-strip.is-unavailable {
  background: var(--slate-soft, #EEF2F6); color: var(--ink-soft);
  border-color: var(--line);
}
.recon-flag { font-family: var(--display); font-weight: 800; }
.recon-flag.recon-ok { color: var(--ok-ink); }
.recon-strip.is-unavailable .recon-flag { color: var(--ink-900); }

/* THE PAYMENTS TABLE INHERITED NO CELL PADDING (rework 2, design pass). The
   cell rules on this skin are written for `.job-row td`, so the Payments
   table's cells had none at all — every column sat flush against the next,
   and once Amount was right-aligned it collided with "Sent via" outright
   ("£220.00 Copied out" read as one run). Same padding and rule the board's
   rows use, so the two tables read as one system. */
.pay-row td { padding: 12px var(--bo-4); border-bottom: 1px solid var(--line-2); vertical-align: middle; }
/* MONEY AND COUNTS LINE UP. Amount and Chased were left-aligned under
   right-ish headers with proportional figures, so the column read ragged and
   two amounts could not be compared down it — which is the one thing a money
   table is for. Same tabular-nums + right alignment .jr-price already uses. */
.pay-table th:nth-child(3), .pay-table th:nth-child(5) { text-align: right; }
.pay-table .pay-num {
  text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* status/payment pills — the D12 split, always both visible */
/* A 999px-radius badge CANNOT wrap: its background breaks into two separate
   stadium shapes stacked on top of each other, which reads as a rendering
   fault rather than a status ("PAYMENT" over "REQUESTED", design pass
   2026-07-26 — the same defect class as the ATTENTION chip's speech bubble
   above). A pill is a token; if it does not fit, the COLUMN gives way, not the
   token. Every pill label on this estate is two words at .66rem, so nowrap
   costs a few pixels of column and buys a badge that always looks like one. */
.pill {
  font-family: var(--display); white-space: nowrap;
  font-size: .66rem; font-weight: 800; letter-spacing: .04em; text-transform: uppercase;
  padding: 3px 9px; border-radius: 999px; border: 1px solid transparent; line-height: 1.25;
}
.pill.job    { background: var(--info-bg); color: var(--info-ink); border-color: var(--info-line); }
.pill.pay    { background: var(--warn-bg); color: var(--warn-ink); border-color: var(--warn-line); }
.pill.pay.paid, .pill.pay.deposit_confirmed { background: var(--ok-bg); color: var(--ok-ink); border-color: var(--ok-line); }
.pill.pay.waived { background: #eef1f4; color: #566; border-color: #dde4ec; }
/* NOTHING IS OWED, SO NOTHING IS AMBER. A job nobody has quoted yet, and a job
   the customer declined, are both `paymentStatus: unpaid` — but neither is
   waiting on money, and the amber "awaiting payment" skin argued with the very
   column heading the row sat under (design pass, 2026-07-26). payLabel() gives
   them honest words; this gives them an honest colour. */
.pill.pay.is-quiet { background: #eef1f4; color: #566; border-color: #dde4ec; }
.pill.job.completed { background: var(--ok-bg); color: var(--ok-ink); border-color: var(--ok-line); }
.pill.job.declined, .pill.job.cancelled { background: var(--danger-bg); color: var(--danger-ink); border-color: var(--danger-line); }

/* =========================================================================
   JOB VIEW — centred floating panel (v4, operator pick 2026-07-15: "when a
   job is selected open a central floating panel with the job view, esc to
   close"). Class names kept from the retired side drawer so the focus-trap /
   inert / Esc machinery in staff.js is untouched.
   ========================================================================= */
/* z-stack (2026-07-15, centred-panel fix): the sticky .site-header is z-150
   (styles.css), so every dialog layer sits ABOVE it — scrim 160 < panel 161 <
   doc-modal 180 < toast 190 < lightbox 300. The old drawer/doc-modal at 60/61/
   120 left the header floating undimmed over open dialogs. */
.drawer-scrim { position: fixed; inset: 0; background: rgba(17,24,32,.42); z-index: 160; backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); }
.drawer {
  position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(720px, calc(100vw - 32px)); max-height: min(88dvh, 920px);
  background: var(--card); z-index: 161; border-radius: 16px; overflow: hidden;
  box-shadow: 0 32px 80px -24px rgba(17,24,32,.55);
  display: flex; flex-direction: column;
  animation: drawer-in .24s var(--ease);
}
@keyframes drawer-in { from { transform: translate(-50%, calc(-50% + 14px)); opacity: 0; } to { transform: translate(-50%, -50%); opacity: 1; } }
/* exit fast, not absent (emil 9) — glide-in/teleport-out asymmetry fix */
.drawer.is-closing { animation: drawer-out .18s var(--ease) forwards; }
@keyframes drawer-out { to { transform: translate(-50%, calc(-50% + 10px)); opacity: 0; } }
.drawer-scrim.is-closing { animation: scrim-out .18s var(--ease) forwards; }
@keyframes scrim-out { to { opacity: 0; } }
.drawer-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: var(--bo-3);
  padding: var(--bo-5) var(--bo-5) var(--bo-4); border-bottom: 1px solid var(--line);
}
.drawer-head #d-ref { margin-bottom: 4px; }
.drawer-head h2 { color: var(--ink-900); font-size: 1.32rem; letter-spacing: -.02em; margin: 0; }
/* W5.3: the Close button measured 35px tall at phone widths — below the
   44px tap-target floor; .btn-sm's 44px bump elsewhere (.thread-compose) is
   per-surface, not global (styles.css owns .btn-sm and is out of scope). */
.drawer-head .btn-sm { min-height: 44px; }
/* W2.3 — body is the ONE scroll region and yields space to the pinned footer:
   flex:1 + min-height:0 lets it shrink inside the flex column (max-height set on
   .drawer) so .drawer-foot below always stays on-screen. */
.drawer-body { padding: var(--bo-4) var(--bo-5) var(--bo-7); overflow-y: auto; flex: 1 1 auto; min-height: 0; }

/* W2.3 — pinned action footer: a flex:none row below the scrolling body, inside
   .drawer's flex column, so the quote's primary actions never fall below the
   fold. Rounds off the drawer's bottom corners (the body clips at the radius). */
.drawer-foot {
  flex: none; display: flex; flex-wrap: wrap; gap: var(--bo-2);
  padding: var(--bo-3) var(--bo-5); border-top: 1px solid var(--line);
  background: var(--card); border-radius: 0 0 16px 16px;
}
.drawer-foot[hidden] { display: none; }
.drawer-foot .btn { flex: 1 1 auto; min-height: 44px; }

.d-section {
  border: 1px solid var(--line); border-radius: 12px; padding: var(--bo-4);
  margin-bottom: var(--bo-3); background: #fff;
}
.d-section h3 {
  font-family: var(--display); font-size: .72rem; font-weight: 800;
  letter-spacing: .08em; text-transform: uppercase; color: var(--slate);
  margin: 0 0 var(--bo-3);
}
.d-row { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--bo-3); font-size: .9rem; padding: 4px 0; }
.d-row + .d-row { border-top: 1px solid var(--line-2); }
.d-row dt { flex: none; color: var(--ink-soft); }
/* W5.3 (390/414 audit): a long unbroken value (email, long postcode string)
   has no natural break point — flex items don't shrink below their content's
   intrinsic width by default, so this silently overflowed past the drawer's
   edge and was CLIPPED (.drawer has overflow:hidden, so it wasn't even a
   visible scrollbar — the tail of the value just vanished). min-width:0 lets
   dd shrink to the row; overflow-wrap wraps the long token onto a 2nd line. */
.d-row dd { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; word-break: break-word; font-family: var(--display); font-weight: 700; color: var(--ink); text-align: right; font-variant-numeric: tabular-nums; }
.d-photos { display: flex; gap: var(--bo-2); flex-wrap: wrap; margin-top: var(--bo-3); }
.d-photos img { width: 74px; height: 74px; object-fit: cover; border-radius: 8px; border: 1px solid var(--line); cursor: pointer; }
.d-photos img:hover { border-color: var(--amber); }

/* evidence chain (driver jobPhotos, F1) — 5 phases with timestamps + missing */
.d-evidence .ev-count {
  font-family: var(--display); font-size: .78rem; font-weight: 800;
  font-variant-numeric: tabular-nums; color: var(--ink-900); margin: 0 0 var(--bo-3);
}
.ev-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(112px,1fr)); gap: var(--bo-2); }
.ev-tile { margin: 0; border: 1px solid var(--line); border-radius: 9px; overflow: hidden; background: #FCFDFE; }
.ev-tile img { display: block; width: 100%; aspect-ratio: 4/3; object-fit: cover; }
.ev-tile.filled img { cursor: pointer; }
.ev-tile.filled img:hover { opacity: .92; }
.ev-tile .ev-ph { display: flex; align-items: center; justify-content: center; aspect-ratio: 4/3; color: #b7c2ce; font-size: 1.3rem; background: repeating-linear-gradient(45deg, #F5F7F9, #F5F7F9 8px, #EEF2F6 8px, #EEF2F6 16px); }
.ev-tile figcaption { display: block; padding: 6px 8px; font-family: var(--display); font-size: .72rem; font-weight: 700; color: var(--ink-900); line-height: 1.3; }
.ev-tile figcaption span { display: block; font-family: var(--display); font-weight: 500; color: var(--ink-soft); font-size: .68rem; margin-top: 2px; font-variant-numeric: tabular-nums; }
.ev-tile figcaption .ev-miss { color: #b0392e; font-style: italic; }
.ev-tile.missing { border-style: dashed; }
.ev-tile.filled { border-color: #bfe6c9; }

/* W2.1 — admin evidence capture/upload control (the styled label is the button;
   the file input is visually hidden but keyboard-focusable). */
.ev-capture {
  display: flex; align-items: center; justify-content: center;
  margin: 0 8px 8px; min-height: 36px; padding: 7px 10px;
  font-family: var(--display); font-weight: 700; font-size: .72rem;
  color: var(--ink-900); background: #fff; border: 1px dashed #b7c2ce; border-radius: 8px;
  cursor: pointer; text-align: center; line-height: 1.2;
  transition: background .15s var(--ease), border-color .15s var(--ease);
}
.ev-capture:hover { background: var(--slate-soft); border-color: var(--slate); }
.ev-capture:focus-within { border-color: var(--amber); box-shadow: 0 0 0 3px rgba(242,169,0,.16); outline: 2px solid var(--ink-900); outline-offset: 2px; }
.ev-tile.filled .ev-capture { border-style: solid; }
/* visually-hidden file input — off-screen but still focusable/operable */
.ev-capture input[type="file"] { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* W2.1 — admin completion summary + printable-draft WTN block, under the grid */
.ev-completion { margin-top: var(--bo-4); padding-top: var(--bo-4); border-top: 1px solid var(--line); }
.ev-sub {
  font-family: var(--display); font-size: .72rem; font-weight: 800;
  letter-spacing: .08em; text-transform: uppercase; color: var(--slate);
  margin: 0 0 var(--bo-3);
}
.ev-cs-note { font-size: .78rem; color: var(--ink-soft); line-height: 1.5; margin: var(--bo-2) 0 0; }

/* quote builder */
/* The amount track was a flat 90px at EVERY width, which left ~67px of content
   box after padding and borders — enough for "165.00", not enough for
   "1165.50". A four-figure line (an ordinary multi-line probate clearance)
   rendered as "1165.5(" while the Total directly beneath it read £1165.50: the
   operator could not read back the figure they had just typed, and the field
   disagreed with the total on the same screen (design pass, 2026-07-26).
   112px carries seven tabular digits plus the decimal point with room to
   spare; the description track is minmax(0,1fr) so it, not the figure, is what
   gives up the space at 390. */
.ql-row { display: grid; grid-template-columns: minmax(0, 1fr) 112px 32px; gap: var(--bo-2); margin-bottom: var(--bo-2); }
/* min-width:0 is load-bearing, not tidiness. A grid item's automatic minimum
   size is its CONTENT's minimum, and an <input>'s is its default `size` — about
   170px. So `1fr` refused to go below that and the row overflowed its column
   track, clipping the amount field and the delete × against the drawer's right
   edge at 390px (checker LOW, 2026-07-25). */
.ql-row input { padding: 9px 10px; border: 1.5px solid var(--line); border-radius: 9px; font: inherit; min-width: 0; }
.ql-row input.ql-amt { font-family: var(--display); font-weight: 700; font-variant-numeric: tabular-nums; text-align: right; }
.ql-row input:focus { border-color: var(--amber); outline: 2px solid var(--ink-900); outline-offset: 2px; box-shadow: 0 0 0 3px rgba(242,169,0,.16); }
.ql-del { position: relative; border: 1px solid var(--line); background: #fff; border-radius: 9px; cursor: pointer; color: var(--red); font-weight: 800; transition: background .15s var(--ease), border-color .15s var(--ease); }
/* 32px visible button, ≥44px effective target (impeccable F9 — same pattern as
   .chip-act). The slop grows LEFTWARD only: .ql-del is the last grid column and
   sits flush with the row's right edge, so a symmetric `inset: -8px -6px` put
   6px of hit area outside the row and gave it 5px of scrollable overflow at
   390px — which is what clipped the amount field and the × against the drawer
   edge. Same 44px target, none of it off the edge. */
.ql-del::before { content: ""; position: absolute; inset: -8px 0 -8px -12px; }
.ql-del:hover { background: var(--danger-bg); border-color: var(--danger-line); }
.ql-tools { display: flex; flex-wrap: wrap; gap: 6px; margin: var(--bo-1) 0 var(--bo-3); }
.chip-add {
  position: relative;
  font-family: var(--display); font-weight: 700; font-variant-numeric: tabular-nums;
  border: 1px dashed #b7c2ce; background: #fff; border-radius: 999px;
  padding: 5px 12px; font-size: .8rem; color: var(--ink-900); cursor: pointer;
  transition: background .15s var(--ease), border-color .15s var(--ease);
}
/* ~29px visible chip, ≥44px effective target (impeccable F9 — same pattern as
   .ql-del/.chip-act; W5.3 audit found this one had no hit-slop yet). */
.chip-add::before { content: ""; position: absolute; inset: -8px -6px; }
.chip-add:hover { background: var(--slate-soft); border-color: var(--slate); }
/* `justify-content: space-between` only separates two items while there is
   free space between them. Once the right-hand side grew a second line of
   copy — "£165.00 (payment requested on send: £165.00)" — there was none left
   at 390px and the label welded itself to the figure: "Total£165.00" (checker
   LOW, 2026-07-25). A real `gap` is a floor that space-between cannot eat, and
   wrapping lets the pair stack rather than collide when even that will not fit. */
.ql-total {
  display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline;
  gap: 2px var(--bo-3);
  font-family: var(--display); font-weight: 800; color: var(--ink-900); font-size: 1.06rem;
  font-variant-numeric: tabular-nums; border-top: 1px solid var(--line); padding-top: var(--bo-3);
}
.ql-total > :last-child { min-width: 0; text-align: right; margin-left: auto; }
/* …and once the figure itself grew to four digits, wrapping produced a THIRD
   line carrying nothing but the closing bracket: "Total" / "£1165.50 (payment
   requested on send:" / "£1165.50)" (design pass, 2026-07-26). The parenthetical
   is one idea, so it breaks as one unit — inline-block makes it an unbreakable
   box that moves to its own line whole, and it never splits its own brackets. */
.ql-deposit {
  display: inline-block; white-space: nowrap;
  font-family: var(--display); font-size: .8rem; color: var(--ink-soft); font-weight: 600; font-variant-numeric: tabular-nums;
}

/* =========================================================================
   MANUAL TIME SLOT ON THE QUOTE FORM (W6 S2)
   -------------------------------------------------------------------------
   The slot is part of the quote, so it sits inside the quote section rather
   than in "Payment & scheduling" — but it is a different KIND of thing from a
   priced line, so it gets its own quiet well to sit in. Same well treatment as
   .d-manualpay above, deliberately: both are "a small form inside a section",
   and two different skins for the same idea is how a back office starts
   looking assembled rather than designed.

   The glance list is a HINT, not a control: it is type-scaled down, it uses
   the shared status tokens rather than new colours, and the only thing that
   raises its voice is a real paid clash (.gate-note.locked, already the
   estate's "this is blocked" skin).
   ========================================================================= */
/* GROUPING CARRIES THE MEANING (design pass, 2026-07-25). The slot well used to
   sit at the same distance from the total as the discount panel did, so it read
   as a third, unrelated form group — while its own first line says "The customer
   is given this with the price". Under payment model v2 the price and the slot
   are ONE promise: they go out together, and the payment window is measured
   against both. So the slot is pulled tight under the total and the discount
   panel is pushed away from it. Proximity, doing the job a divider would
   otherwise have to announce. */
.ql-slot {
  margin-top: var(--bo-2); padding: var(--bo-3);
  border: 1px solid var(--line); border-radius: 10px; background: var(--surface-2, #fafbfc);
}
.ql-slot + .d-discount { margin-top: var(--bo-5); }
.ql-slot-h {
  font-family: var(--display); font-weight: 800; font-size: .78rem;
  letter-spacing: .08em; text-transform: uppercase; color: var(--slate);
  margin: 0 0 6px;
}
.ql-slot > .docs-hint:first-of-type { margin-top: 0; }
/* Two fields side by side where there is room; stacked on a phone, where a
   44px date control and a 44px select cannot share a 390px row legibly. */
.ql-slot-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--bo-3); }
.ql-slot-fields .d-field { margin-bottom: 0; }
.slot-glance { margin-top: var(--bo-3); }
.slot-glance > .docs-hint { margin: 0 0 6px; }
.slot-glance-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.slot-glance-row {
  font-size: .82rem; line-height: 1.45; color: var(--ink-soft);
  padding: 6px 9px; border-radius: 8px; background: #fff; border: 1px solid var(--line);
  /* A long customer name + postcode must wrap, not push the drawer sideways
     (memory `layout-verify-real-data` — fixtures are always short). */
  overflow-wrap: anywhere;
}
.slot-glance-row strong { font-family: var(--display); font-variant-numeric: tabular-nums; color: var(--ink-900); }
.slot-glance-row.is-clash { border-color: var(--warn-line); background: var(--warn-bg); color: var(--warn-ink); }
.slot-tag {
  font-family: var(--display); font-weight: 700; font-size: .7rem;
  padding: 1px 7px; border-radius: 999px; border: 1px solid var(--line); white-space: nowrap;
}
/* ONE FACT, ONE COLOUR. This tag's label is the word "paid", and twelve rows
   above it the board renders the same fact as a green "paid ✓". Skinning it
   with the danger tokens put red "paid" and green "paid ✓" on one screen for
   one state (design pass, 2026-07-26). The class name (.locked) carries what
   was actually meant — this slot is blocked against you — but the CLASH is the
   row's job to say, and .slot-glance-row.is-clash already says it in amber. So
   the tag states the money fact in the estate's money colour, and the row it
   sits in states the consequence. */
.slot-tag.locked { background: var(--ok-bg); color: var(--ok-ink); border-color: var(--ok-line); }
.slot-tag.held { background: var(--slate-soft); color: var(--slate); }

/* deposit gate banner */
.gate-note { font-size: .86rem; border-radius: 10px; padding: 11px 13px; margin: var(--bo-3) 0; line-height: 1.5; }
.gate-note.locked { background: var(--warn-bg); color: var(--warn-ink); border: 1px solid var(--warn-line); }
.gate-note.open { background: var(--ok-bg); color: var(--ok-ink); border: 1px solid var(--ok-line); }

/* Manual payment recorder (W5.3, PAYMENT MODEL v2) — the cash / bank-transfer
   path, which is how most of today's real trade is settled. Grouped in its own
   bordered well so it reads as ONE action ("record what was handed over"),
   not as two loose fields sitting under the Send-payment-link button. */
.d-manualpay {
  margin-top: var(--bo-3); padding: var(--bo-3);
  border: 1px solid var(--line); border-radius: 10px; background: var(--surface-2, #fafbfc);
}
.d-manualpay .d-field:last-child { margin-bottom: 0; }
.gate-note code { font-family: var(--mono); font-size: .92em; }

/* =========================================================================
   MONEY SECTION (W5 back-office money tools) — the job's payment events, the
   extra-payment ask, and the admin refund panel. Everything here is derived
   from requests/{id}/payments/*, so the layout has to survive a job with one
   row and a job with six.
   ========================================================================= */
.view-head-actions { display: flex; align-items: center; gap: var(--bo-3); flex-wrap: wrap; }

/* styles.css .field styles text/tel/email/date but NOT number — the quick-job
   price field is a number input, and without this it renders as a bare
   unstyled box next to correctly-styled siblings. */
.field input[type="number"] {
  width: 100%; padding: 12px 14px; border: 1.5px solid var(--line); border-radius: 10px;
  font: inherit; background: #fff; color: var(--ink);
  transition: border-color .15s var(--ease), box-shadow .15s var(--ease);
}
.field input[type="number"]:focus { border-color: var(--amber); outline: 2px solid var(--ink-900); outline-offset: 2px; box-shadow: 0 0 0 3px rgba(242,169,0,.18); }

/* .form-msg is referenced by the driver-invite modal and the quick-job modal
   but was never defined for the back office (account.css owns the customer-
   side copy and staff.html does not load it) — so a validation message
   rendered as unstyled body text. Hidden until a message is set, matching the
   .show convention the invite modal already uses. */
.form-msg { display: none; margin: 0 0 var(--bo-3); padding: 10px 12px; border-radius: 9px; font-size: .88rem; }
.form-msg.show, .form-msg.err, .form-msg.bad, .form-msg.ok { display: block; }
.form-msg.err, .form-msg.bad { background: var(--danger-bg); color: var(--danger-ink); border: 1px solid var(--danger-line); }
.form-msg.ok { background: var(--ok-bg); color: var(--ok-ink); border: 1px solid var(--ok-line); }

.money-list { list-style: none; margin: 0 0 var(--bo-3); padding: 0; display: grid; gap: 2px; }
.money-row {
  display: grid; grid-template-columns: 1fr auto; gap: 2px var(--bo-3);
  align-items: baseline;
  padding: 9px 11px; border: 1px solid var(--line); border-radius: 9px;
  background: var(--surface-2, #fafbfc);
}
.money-what { font-family: var(--display); font-weight: 700; font-size: .88rem; color: var(--ink-900); }
.money-reason { font-family: var(--body, inherit); font-weight: 500; color: var(--ink-soft); }
.money-meta { grid-column: 1; font-size: .78rem; color: var(--ink-soft); }
.money-amt { grid-column: 2; grid-row: 1 / span 2; font-weight: 700; font-size: .95rem; color: var(--ink-900); align-self: center; }
.money-row.is-refund { background: var(--danger-bg); border-color: var(--danger-line); }
.money-row.is-refund .money-amt { color: var(--danger-ink); }

.money-sum {
  display: flex; justify-content: space-between; gap: var(--bo-3);
  padding: 7px 11px; font-size: .88rem; color: var(--ink-soft);
}
.money-sum .tnum { font-weight: 700; color: var(--ink-900); }
.money-sum .tnum.is-refund { color: var(--danger-ink); }
.money-sum.is-net {
  border-top: 1.5px solid var(--line); margin-top: 2px; padding-top: 10px;
  font-family: var(--display); font-weight: 700; color: var(--ink-900);
}

.money-panel {
  margin-top: var(--bo-3); padding: var(--bo-3);
  border: 1px solid var(--line); border-radius: 10px; background: var(--surface-2, #fafbfc);
}
.money-panel.is-danger { border-color: var(--danger-line); background: var(--danger-bg); }
.money-panel h4 {
  font-family: var(--display); font-weight: 700; font-size: .92rem;
  color: var(--ink-900); margin: 0 0 4px;
}
.money-panel .docs-hint { margin-top: 0; }
.money-panel .d-field:last-of-type { margin-bottom: var(--bo-3); }
.money-panel .btn { min-height: 44px; }

/* Board tag for a staff-entered offline job — quiet, never a warning colour:
   an offline job is normal trade, not an exception. */
.jr-tag {
  display: inline-block; margin-left: 6px; vertical-align: 1px;
  font-family: var(--display); font-weight: 700; font-size: .62rem;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--steel); background: var(--surface-2, #f1f3f5);
  border: 1px solid var(--line); border-radius: 999px; padding: 2px 7px;
}

/* W6 S6 — the UNSIZED board markers. Same quiet family as .jr-tag above and
   for the same reason: an unsized job is a normal booking, not an exception.
   'slot unpaid' is the one that leans warning-ward, because a held slot with
   no money behind it IS the thing the desk needs to act on. */
.jr-tag.is-unsized { color: var(--ink); background: var(--surface-2, #f1f3f5); }
.jr-tag.is-slotheld { color: var(--ok-ink, #1c6b3f); border-color: var(--ok-line, #b9dcc6); background: var(--ok-bg, #eef8f1); }
.jr-tag.is-slotunpaid { color: var(--danger-ink); border-color: var(--danger-line); background: var(--danger-bg); }

/* SERVICE LINE (2026-07-27) — the SKIP tag. Amber-inked so a skip job is
   findable at a glance down a column of clearances, but it is NOT a warning
   colour: a skip is ordinary trade, it is simply the less common of the two
   services. Only the non-default service is ever tagged, so a clearance row is
   unchanged (see the render note in staff.js). */
.jr-tag.is-skip { color: var(--amber-ink, #7a4a00); border-color: var(--amber, #e0a032); background: var(--amber-bg, #fdf4e3); }

/* OVERDUE IS THE ONE SKIP STATE THAT IS AN EXCEPTION (2026-07-27, checker C4).
   skipIsOverdue() in skip-lifecycle.js deliberately does NOT add a sixth stage —
   an overdue collection carries the same stage key as one due this morning, so
   the board's column set never moves. The difference is a LABEL ("Overdue") and
   this one modifier, which is the only place on the skip surface that reaches
   for the danger tokens: the business's asset is sitting on someone's drive past
   the day it was due back. Overriding all three properties, so it wins over the
   amber above regardless of source order. */
.jr-tag.is-skip.is-overdue { color: var(--danger-ink); border-color: var(--danger-line); background: var(--danger-bg); }

/* W6 S6 — the retention block inside the refund panel. A sub-panel rather than
   a peer: it MODIFIES the refund above it, it is not a second action. */
.money-subpanel { margin: var(--bo-3) 0; padding: var(--bo-3); border: 1px dashed var(--line); border-radius: 10px; background: var(--surface-2, #f7f8f9); }
.money-subpanel .docs-hint { margin: var(--bo-2) 0 0; }
.d-check { display: flex; align-items: flex-start; gap: 9px; font-size: .86rem; line-height: 1.4; color: var(--ink); cursor: pointer; min-height: 44px; padding: 6px 0; }
.d-check input { width: 20px; height: 20px; margin-top: 1px; flex: none; accent-color: var(--amber); }

.btn-warn { color: var(--danger-ink) !important; border-color: var(--danger-line) !important; background: var(--danger-bg) !important; }
.btn-warn:hover { background: #f6d5d1 !important; border-color: #e8a89f !important; }

.d-actions { display: flex; flex-wrap: wrap; gap: var(--bo-2); margin-top: var(--bo-3); }
.d-actions .btn { flex: 1 1 auto; }
/* W5.3: every action button in the detail sections (quote/deposit/driver/
   documents) is a .btn-sm — 35px tall at phone widths, below the 44px floor.
   Scoped to .d-section so the console's OTHER .btn-sm uses (header, message
   compose — already 44px via .thread-compose) are untouched. */
.d-section .btn-sm { min-height: 44px; }
/* minmax(0,1fr) is the same load-bearing fix .ql-row carries and for the same
   reason: a grid item's automatic minimum size is its CONTENT's minimum, and a
   <select>'s is its LONGEST OPTION. The refund picker's options name the
   payment and the refundable amount ("Slot booking fee £50.00 · £50.00
   refundable"), so the control sized itself to that string and blew straight
   through the money panel — 72px past it at 390, reaching 23px beyond the
   viewport edge, where the drawer scrolls vertically only and it could not even
   be scrolled to (design pass, 2026-07-26). Track pinned to zero minimum, and
   the control told to fill it rather than its content. */
.d-field { display: grid; grid-template-columns: minmax(0, 1fr); gap: 6px; margin-bottom: var(--bo-3); }
.d-field label { font-family: var(--display); font-weight: 700; font-size: .84rem; color: var(--ink); }
.d-field select, .d-field input { width: 100%; min-width: 0; max-width: 100%; min-height: 44px; padding: 9px 11px; border: 1.5px solid var(--line); border-radius: 9px; font: inherit; background: #fff; }
/* text-overflow only bites on a <select> once it is actually narrower than its
   option text — which is now the whole point of the rule above. */
.d-field select { text-overflow: ellipsis; }
.d-field select:focus, .d-field input:focus { border-color: var(--amber); outline: 2px solid var(--ink-900); outline-offset: 2px; box-shadow: 0 0 0 3px rgba(242,169,0,.16); }

/* FIELD-LEVEL REFUSAL (2026-07-27, checker C1) — the skip drawer's save can now
   REFUSE, and a refusal has to land where the mistake is. Sits directly under
   the control inside .d-field's single-column grid, so it pushes nothing
   sideways and cannot overflow the drawer. It is [hidden] until saveSkip() puts
   a sentence in it, so it costs an untouched form no height and a screen reader
   nothing (a hidden element is not in the accessibility tree, which is why the
   input can name it in aria-describedby from the start).
   The red border is on aria-invalid, NOT on :invalid — :invalid would paint the
   moment a half-typed date exists, i.e. while the operator is still typing it,
   which trains people to ignore the colour. This paints only when a save was
   actually refused, and saveSkip() clears it on the next attempt. */
.d-field-err { margin: 0; font-size: .82rem; font-weight: 600; line-height: 1.45; color: var(--danger-ink); }
.d-field input[aria-invalid="true"] { border-color: var(--danger-line); background: var(--danger-bg); }
.d-field input[aria-invalid="true"]:focus { border-color: var(--danger-ink); box-shadow: 0 0 0 3px rgba(138,32,24,.14); }

/* "Saving will remove it" — the notice above the skip edit form when a stored
   value is out of range and therefore cannot be prefilled. Warning-weight, not
   danger: nothing has gone wrong yet, but pressing Save will drop it, and the
   operator gets told before rather than after (checker C1, same class). */
.sk-warn {
  margin: 0 0 var(--bo-3); padding: 8px 10px; border-radius: 8px;
  font-size: .84rem; line-height: 1.45;
  color: var(--warn-ink, #7a4a00); background: var(--warn-bg, #fdf4e3);
  border: 1px solid var(--warn-line, #f0d9a8);
}
/* The remove-the-drop tickbox, shown only when a stamp is recorded but is not
   stored as a readable date. 44px tall so it clears the tap-target floor the
   rest of .d-field already holds. */
.sk-clear-line { display: flex; align-items: center; gap: 8px; min-height: 44px; font-size: .86rem; color: var(--ink); }
.sk-clear-line input[type="checkbox"] { width: 18px; height: 18px; min-height: 0; padding: 0; border: 0; background: none; }

/* discount-code management grid (Users-tab sibling, admin only) */
.d-field-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--bo-3); align-items: end; margin-bottom: var(--bo-3); }
.d-field-grid .btn { align-self: end; min-height: 44px; }
.dc-once-field label { display: flex; align-items: center; gap: 8px; min-height: 44px; }
.dc-once-field input[type="checkbox"] { width: 18px; height: 18px; }
.dc-status-badge { font-family: var(--display); font-weight: 700; font-size: .74rem; padding: 3px 9px; border-radius: 999px; border: 1px solid var(--line); }
.dc-status-badge.active { background: var(--ok-bg); color: var(--ok-ink); border-color: var(--ok-line); }
.dc-status-badge.inactive { background: var(--danger-bg); color: var(--danger-ink); border-color: var(--danger-line); }
.dc-status-badge.expired { background: var(--warn-bg); color: var(--warn-ink); border-color: var(--warn-line); }

/* discount-code panel inside the job-detail quote builder */
.d-discount { margin-top: var(--bo-3); padding-top: var(--bo-3); border-top: 1px dashed var(--line); }
.d-discount label { font-family: var(--display); font-weight: 700; font-size: .84rem; color: var(--ink); display: block; margin-bottom: 6px; }
.discount-row { display: flex; flex-wrap: wrap; gap: var(--bo-2); align-items: center; }
.discount-row input { flex: 1 1 160px; min-height: 44px; padding: 9px 11px; border: 1.5px solid var(--line); border-radius: 9px; font: inherit; background: #fff; }
.discount-row input:focus { border-color: var(--amber); outline: 2px solid var(--ink-900); outline-offset: 2px; box-shadow: 0 0 0 3px rgba(242,169,0,.16); }
.discount-status { font-size: .82rem; margin: 8px 0 0; line-height: 1.45; }
.discount-status.ok { color: var(--ok-ink); }
.discount-status.bad { color: var(--danger-ink); }

/* =========================================================================
   MESSAGE CENTRE — list + thread pane
   ========================================================================= */
/* W2.4 — height anchored to the REAL layout: the viewport minus the fixed
   furniture above the centre (header + tabs + view-head) plus the container's
   top pad (--bo-5) and the container's real bottom padding (--bo-7, 48px — the
   .bo-view padding-bottom). No min/max guesses — it genuinely tracks the window:
   tall screens no longer cap at 860px (acres of white), short screens no longer
   floor at 460px (page overflow). The panels' own overflow (.msg-list /
   .thread-scroll) handles content taller than this. */
.msg-centre {
  display: grid; grid-template-columns: 320px 1fr; gap: var(--bo-4);
  height: calc(100dvh - var(--bo-header-h) - var(--bo-tabs-h) - var(--bo-viewhead-h) - var(--bo-5) - var(--bo-7));
  min-height: 0;
}
/* 721–820px: the shell header wraps its tabs to a 2nd row (bo-shell.css @820),
   so the furniture above the still-two-column centre is taller. Below 720px the
   centre stacks (height:auto) and this no longer applies. */
@media (max-width: 820px) {
  .bo-body { --bo-header-h: 112px; }
}
/* W2.4 (structural fix) — while the message centre is open, lay the whole
   back-office shell out as ONE 100dvh flex column so the centre fills the exact
   remaining space BY CONSTRUCTION — no furniture arithmetic. The header, the
   console's OWN vertical padding (measured 64px top / 72px bottom), the tabs,
   the view-head and the post-<main> `.footnote` each take their natural height;
   `.msg-centre` absorbs the slack; its panels (.msg-list / .thread-scroll)
   scroll internally. That console padding + footnote is exactly what the old
   height calc never modelled — hence the constant ~136px page-scroll at every
   window height. Scoped to `.bo-msg-active` (staff.js adds it ONLY while the
   messages tab is active) AND to >720px (below that the centre stacks and
   scrolls as a normal mobile page), so Job board / Customers / Users / Day plan
   and the position:fixed drawer are entirely untouched. */
@media (min-width: 721px) {
  .bo-body.bo-msg-active { height: 100dvh; overflow: hidden; display: flex; flex-direction: column; }
  .bo-body.bo-msg-active main { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
  .bo-body.bo-msg-active main > .container { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
  .bo-body.bo-msg-active #console { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
  .bo-body.bo-msg-active #view-messages { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
  .bo-body.bo-msg-active .msg-centre { flex: 1 1 auto; height: auto; }
}
.msg-list { border: 1px solid var(--line); border-radius: 12px; overflow-y: auto; background: #fff; align-self: stretch; box-shadow: var(--bo-elev-1); }
.msg-row { display: block; width: 100%; text-align: left; border: none; border-bottom: 1px solid var(--line); background: none; padding: 12px 14px; cursor: pointer; font: inherit; transition: background .14s var(--ease); }
.msg-row:last-child { border-bottom: none; }
.msg-row:hover { background: var(--slate-soft); }
.msg-row.is-active { background: #EEF2F6; box-shadow: inset 3px 0 0 var(--amber); }
/* inset ring — .msg-list scrolls (overflow-y:auto forces x to auto too), so a
   positive outline-offset gets clipped at the left/right edge; -2px keeps the
   whole ring visible, an inset ring pattern shared with .job-row. */
.msg-row:focus-visible { outline: 2px solid var(--ink-900); outline-offset: -2px; }
.msg-row .mr-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.msg-row .mr-name { font-family: var(--display); font-weight: 700; color: var(--ink); font-size: .9rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.msg-row .mr-top .pill { flex: none; font-size: .6rem; padding: 2px 7px; }
.msg-row .mr-last { display: block; font-size: .8rem; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 4px; }
.msg-thread { border: 1px solid var(--line); border-radius: 12px; background: #fff; display: flex; flex-direction: column; box-shadow: var(--bo-elev-1); min-height: 0; }
.msg-empty { color: var(--slate); padding: var(--bo-6); text-align: center; }
.thread-scroll { flex: 1; min-height: 0; overflow-y: auto; padding: var(--bo-4); display: flex; flex-direction: column; gap: var(--bo-2); }
/* 620px cap ≈ 65ch (impeccable F4) — 80% of a wide-monitor pane is a 130ch ribbon */
.bubble { max-width: min(80%, 620px); padding: 9px 13px; border-radius: 13px; font-size: .9rem; line-height: 1.45; display: flex; flex-direction: column; gap: 6px; }
.bubble.them { background: var(--slate-soft); color: var(--ink); align-self: flex-start; border-bottom-left-radius: 4px; }
.bubble.me { background: var(--ink-900); color: #fff; align-self: flex-end; border-bottom-right-radius: 4px; }
.bubble .who { font-family: var(--display); font-size: .64rem; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; opacity: .7; }
.bubble .msg-body { white-space: pre-wrap; word-break: break-word; }
.bubble .msg-photo { display: block; max-height: 200px; max-width: 100%; width: auto; object-fit: cover; border-radius: 9px; cursor: pointer; }
.bubble.them .msg-photo { border: 1px solid var(--line); }

/* attachment preview chip (T5) — sits between the thread and the compose bar */
.attach-preview { padding: 8px var(--bo-3) 0; border-top: 1px solid var(--line); }
.attach-chip { position: relative; display: inline-flex; }
.attach-chip img { width: 56px; height: 56px; object-fit: cover; border-radius: 8px; border: 1px solid var(--line); display: block; }
.attach-chip .attach-remove {
  position: absolute; top: -7px; right: -7px; width: 20px; height: 20px; border-radius: 50%;
  background: var(--ink-900); color: #fff; border: 2px solid #fff; font-size: .62rem; line-height: 1;
  cursor: pointer; display: flex; align-items: center; justify-content: center;
}
/* the visible circle stays a small 20px badge (it sits on a 56px thumbnail);
   the real hit target grows to the 44px minimum via an invisible pseudo. */
.attach-chip .attach-remove::before { content: ""; position: absolute; inset: -12px; }
.attach-chip .attach-remove:hover { background: #293A4D; }

.thread-compose { display: flex; gap: var(--bo-2); border-top: 1px solid var(--line); padding: 12px var(--bo-3); align-items: center; }
.attach-preview + .thread-compose { border-top: none; }
/* one 44px band across input + attach + send (emil 14) */
.thread-compose .btn-sm { min-height: 44px; }
/* drawer thread box — was an inline style in staff.js (impeccable F8/F12);
   viewport-relative cap so tall monitors see more history */
.d-thread { max-height: clamp(200px, 32vh, 420px); border: 1px solid var(--line); border-radius: 10px; }
.thread-compose input[type="text"] { flex: 1; min-width: 0; padding: 12px 13px; border: 1.5px solid var(--line); border-radius: 10px; font: inherit; }
.thread-compose input[type="text"]:focus { border-color: var(--amber); outline: 2px solid var(--ink-900); outline-offset: 2px; box-shadow: 0 0 0 3px rgba(242,169,0,.16); }
.attach-btn {
  flex: none; width: 44px; height: 44px; border-radius: 10px; border: 1.5px solid var(--line);
  background: #fff; font-size: 1.05rem; cursor: pointer; display: flex; align-items: center; justify-content: center;
  transition: background .15s var(--ease), border-color .15s var(--ease);
}
.attach-btn:hover { background: var(--slate-soft); border-color: var(--slate); }
.attach-btn:focus-visible { outline: 2px solid var(--ink-900); outline-offset: 2px; }

/* =========================================================================
   DOCUMENTS (invoices + WTN) in the detail drawer
   ========================================================================= */
.docs-actions { display: flex; flex-wrap: wrap; gap: var(--bo-2); margin-bottom: var(--bo-2); }
.docs-hint { font-size: .8rem; color: var(--ink-soft); margin: 4px 0 var(--bo-2); line-height: 1.45; }
.doc-list { list-style: none; margin: 6px 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--bo-2); }
.doc-row { display: flex; align-items: center; justify-content: space-between; gap: var(--bo-3); border: 1px solid var(--line); border-radius: 10px; padding: 9px 11px; background: #fff; }
.dr-main { display: flex; align-items: center; gap: var(--bo-2); min-width: 0; }
.dr-ic { flex: none; font-size: 1rem; }
.dr-no { font-family: var(--display); font-weight: 700; font-size: .82rem; color: var(--ink); font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.doc-stat {
  flex: none; font-family: var(--display); font-size: .62rem; font-weight: 800;
  letter-spacing: .06em; text-transform: uppercase; padding: 2px 8px; border-radius: 999px;
  background: var(--info-bg); color: var(--info-ink); border: 1px solid var(--info-line);
}
.doc-stat.issued { background: var(--warn-bg); color: var(--warn-ink); border-color: var(--warn-line); }
.doc-stat.paid { background: var(--ok-bg); color: var(--ok-ink); border-color: var(--ok-line); }
.doc-stat.wtn { background: #eaf3ee; color: var(--ok-ink); border-color: #cfe6d7; }
.dr-act { display: flex; gap: 6px; flex: none; }
.chip-act {
  position: relative;
  font-family: var(--display); font-weight: 700;
  border: 1px solid #b7c2ce; background: #fff; border-radius: 999px;
  padding: 4px 11px; font-size: .76rem; color: var(--ink-900); cursor: pointer;
  transition: background .15s var(--ease), border-color .15s var(--ease);
}
.chip-act::before { content: ""; position: absolute; inset: -11px -2px; }
.chip-act:hover { background: var(--slate-soft); border-color: var(--slate); }
.chip-act:focus-visible { outline: 2px solid var(--ink-900); outline-offset: 2px; }

/* =========================================================================
   PHOTO LIGHTBOX (T2) — quote photos, evidence chain, message attachments.
   z-index 300, above doc-modal's 180.
   ========================================================================= */
.lb-modal { position: fixed; inset: 0; z-index: 300; display: flex; align-items: center; justify-content: center; }
.lb-modal[hidden] { display: none; }
/* entrance (emil 5) — modals grow into place instead of blinking on; the
   [hidden] toggle still owns visibility, the animation plays on un-hide */
.lb-modal:not([hidden]) .lb-scrim { animation: lb-scrim .18s var(--ease); }
.lb-modal:not([hidden]) .lb-stage { animation: lb-stage .2s var(--ease); }
@keyframes lb-scrim { from { opacity: 0; } }
@keyframes lb-stage { from { opacity: 0; transform: scale(.96); } }
.doc-modal:not([hidden]) { animation: lb-scrim .16s var(--ease); }
.lb-modal:focus { outline: none; }
.lb-scrim { position: absolute; inset: 0; background: rgba(17,24,32,.88); }
.lb-stage { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; gap: 12px; max-width: 92vw; }
.lb-imgwrap img { display: block; max-width: 92vw; max-height: 80vh; object-fit: contain; border-radius: 6px; box-shadow: 0 24px 70px rgba(0,0,0,.55); }
.lb-caption { margin: 0; color: #fff; font-family: var(--display); font-size: .84rem; text-align: center; opacity: .9; }
.lb-nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
  background: rgba(255,255,255,.14); color: #fff; border: none; width: 48px; height: 48px; border-radius: 50%;
  font-size: 1.7rem; line-height: 1; cursor: pointer; display: flex; align-items: center; justify-content: center;
  transition: background .15s var(--ease);
}
.lb-nav:hover { background: rgba(255,255,255,.26); }
.lb-nav:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; }
.lb-nav[hidden] { display: none; }
.lb-prev { left: 18px; }
.lb-next { right: 18px; }
.lb-close {
  position: absolute; top: 18px; right: 18px; z-index: 2;
  background: rgba(255,255,255,.14); color: #fff; border: none; width: 44px; height: 44px; border-radius: 50%;
  font-size: 1.05rem; cursor: pointer; transition: background .15s var(--ease);
}
.lb-close:hover { background: rgba(255,255,255,.26); }
.lb-close:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; }
@media (max-width: 600px) {
  .lb-nav { width: 44px; height: 44px; font-size: 1.4rem; }
  .lb-prev { left: 8px; }
  .lb-next { right: 8px; }
  .lb-close { top: 8px; right: 8px; }
}

.toast {
  position: fixed; left: 50%; bottom: var(--bo-5); transform: translateX(-50%);
  /* Lifts the toast off the drawer's pinned action row. At 390 the two landed
     on the same 60px of screen, so "Quote draft saved." covered the Save draft
     and Send quote buttons — a confirmation obscuring the control that produced
     it (design pass, 2026-07-25). Scoped to a drawer actually being open, so
     every other surface keeps the toast where it has always been.

     THE LIFT ALONE WAS NOT THE FIX (checker LOW, 2026-07-25). Those buttons sit
     inside a SCROLLABLE drawer, so the lift only clears them at one scroll
     position: hit-tested with document.elementFromPoint at 1280x900 with the
     drawer scrolled down, the toast still answered at the Send quote button's
     top-left corner. A geometric lift cannot be right at every scroll offset of
     a centred modal, so the guarantee is made where it actually belongs — a
     transient confirmation is not an interactive surface and must NEVER
     intercept a pointer. `pointer-events:none` makes the control underneath the
     hit-test winner and the click target at every scroll position; the lift
     stays because it also keeps the toast legible against the action row. */
  pointer-events: none;
  background: var(--ink-900); color: #fff; padding: 12px 20px; border-radius: 12px;
  font-family: var(--display); font-weight: 700; font-size: .9rem;
  z-index: 190; box-shadow: var(--bo-elev-2);
  animation: toast-in .24s var(--ease);
}
@keyframes toast-in { from { opacity: 0; transform: translate(-50%, 8px); } to { opacity: 1; transform: translate(-50%, 0); } }
/* +76px was a guess and it was 10px short: at 1280x900 the toast box still
   overlapped the Send quote button by ~80x10px (design pass, 2026-07-26).
   Guessing twice is not a fix, so the clearance is DERIVED. .drawer is a
   centred modal, so its bottom edge sits (100dvh − max-height)/2 above the
   viewport floor; .drawer-foot is pinned inside that edge at 12px + 44px +
   12px + 1px border = 69px. Clear both, plus a 12px breathing gap, and the
   toast cannot touch the action row at any viewport height. */
body:has(.drawer:not([hidden])) .toast {
  bottom: calc(((100dvh - min(88dvh, 920px)) / 2) + 69px + 12px);
}
.toast.is-leaving { animation: toast-out .18s var(--ease) forwards; }
@keyframes toast-out { to { opacity: 0; transform: translate(-50%, 6px); } }

/* =========================================================================
   PRESS FEEDBACK (emil 1) — nothing absorbs a click silently. Shallow scale
   on the big card, firmer on small chips; after the hover rules so it wins.
   ========================================================================= */
/* .job-row press feedback lives on its cells (background shift) — a transform
   on a <tr> breaks table row rendering, so no scale there. */
.chip-add:active, .chip-act:active, .ql-del:active,
.attach-btn:active, .lb-nav:active, .lb-close:active { transform: scale(.95); }
.msg-row:active { transform: scale(.995); }

/* =========================================================================
   RESPONSIVE
   ========================================================================= */
@media (max-width: 720px) {
  .msg-centre { grid-template-columns: 1fr; height: auto; max-height: none; }
  .msg-list { max-height: 240px; overflow-y: auto; }
  .msg-thread { min-height: 420px; }
  .view-head { align-items: flex-start; }
}
/* Phone — the 6-column table is wider than a phone; sideways-scrolling it clips
   Payment/Value. Reflow each job into a self-labelling CARD instead: thead off,
   status group = a full-width band, each row = a card with name + value on top
   and the rest as label:value lines (captions from the tds' data-label). */
@media (max-width: 560px) {
  .jq-scroll { overflow-x: visible; }
  .jobs-table, .jobs-table tbody { display: block; width: 100%; }
  .jobs-table thead { display: none; }

  .jobs-table tr.jq-group { display: block; margin: 14px 0 8px; }
  .jobs-table tr.jq-group th { display: flex; align-items: center; gap: 8px; padding: 0 2px 2px; border: none; }

  .jobs-table tr.job-row {
    display: grid; grid-template-columns: 1fr auto; align-items: baseline;
    column-gap: 12px; row-gap: 5px;
    background: var(--card); border: 1px solid var(--line); border-radius: 12px;
    padding: 12px 14px; margin-bottom: 10px; box-shadow: var(--shadow-card);
  }
  .jobs-table tr.job-row:hover { background: var(--slate-soft, #EEF2F6); }
  .jobs-table tr.job-row:hover td { background: transparent; }
  .job-row td { display: block; padding: 0; border: none; }

  .job-row .jr-name  { grid-column: 1; grid-row: 1; font-size: 1.04rem; }
  .job-row .jr-price { grid-column: 2; grid-row: 1; justify-self: end; text-align: right; }

  .job-row .jr-pc,
  .job-row td[data-label="Load"],
  .job-row td[data-label="Date"],
  .job-row td[data-label="Payment"] {
    grid-column: 1 / -1;
    display: flex; justify-content: space-between; align-items: center; gap: 12px;
    font-size: .9rem; color: var(--ink-soft);
  }
  .job-row td[data-label]:not(.jr-name):not(.jr-price)::before {
    content: attr(data-label); flex: none;
    color: var(--ink-3); font-weight: 600; font-size: .72rem;
    text-transform: uppercase; letter-spacing: .05em;
  }
  .job-row .jr-sched { color: var(--ink-900); }
  .job-row td[data-label="Payment"] .pill { margin-left: auto; }

  .jobs-table .jq-none { display: block; text-align: center; }

  /* PAYMENTS TABLE — same card reflow, and for the same reason (W6 S4 REWORK
     2, checker finding #8). `.jq-scroll` is overflow-x:visible up here because
     the BOARD's rows stop being a table at this width; the Payments view's
     six-column table did not, so it stayed a real table inside a wrapper that
     no longer scrolled and pushed the PAGE sideways by 27px with a realistic
     71-character customer name. Short fixture names measured 0px, which is
     exactly the shape memory `layout-verify-real-data` warns about. Customer
     name and amount hold the top line; the other four drop to self-labelling
     rows off the tds' existing data-label captions. */
  .jobs-table tr.pay-row {
    display: grid; grid-template-columns: 1fr auto; align-items: baseline;
    column-gap: 12px; row-gap: 5px;
    background: var(--card); border: 1px solid var(--line); border-radius: 12px;
    padding: 12px 14px; margin-bottom: 10px; box-shadow: var(--shadow-card);
  }
  .pay-row td { display: block; padding: 0; border: none; min-width: 0; }
  .pay-row td[data-label="Customer"] {
    grid-column: 1; grid-row: 1;
    font-family: var(--display); font-weight: 700; color: var(--ink-900);
    overflow-wrap: anywhere;
  }
  .pay-row td[data-label="Amount"] {
    grid-column: 2; grid-row: 1; justify-self: end; text-align: right;
    font-family: var(--display); font-weight: 800; color: var(--ink-900);
    font-variant-numeric: tabular-nums; white-space: nowrap;
  }
  .pay-row td[data-label="Status"],
  .pay-row td[data-label="Sent via"],
  .pay-row td[data-label="Chased"],
  .pay-row td[data-label="Deadline"] {
    grid-column: 1 / -1;
    display: flex; justify-content: space-between; align-items: center; gap: 12px;
    font-size: .9rem; color: var(--ink-soft); overflow-wrap: anywhere;
  }
  .pay-row td[data-label="Status"]::before,
  .pay-row td[data-label="Sent via"]::before,
  .pay-row td[data-label="Chased"]::before,
  .pay-row td[data-label="Deadline"]::before {
    content: attr(data-label); flex: none;
    color: var(--ink-3); font-weight: 600; font-size: .72rem;
    text-transform: uppercase; letter-spacing: .05em;
  }

  .drawer { width: calc(100vw - 16px); max-height: 94dvh; }
  /* same derivation as the desktop rule, against this width's max-height */
  body:has(.drawer:not([hidden])) .toast {
    bottom: calc(((100dvh - 94dvh) / 2) + 69px + 12px);
  }

  /* Documents: a long invoice number + status pill + TWO action chips (Mark
     paid + Print) fought over one 342px row — dr-main lost, squashing dr-no
     down to 1-2 characters ("T…") despite its own ellipsis rule (W5.3 audit,
     staff-detail-open-docs fixture). Let the row wrap: number+status keep
     their line, actions drop to a right-aligned line below. */
  .doc-row { flex-wrap: wrap; row-gap: 8px; }
  .dr-main { flex: 1 1 100%; }
  .dr-act { flex: 1 1 100%; justify-content: flex-end; }
}

/* =========================================================================
   REDUCED MOTION
   ========================================================================= */
/* decorative hover states gated off touch (emil 11) — sticky-hover guard;
   base values restored where a coarse pointer would false-positive them */
@media (hover: none) {
  .chip-add:hover { background: #fff; border-color: #b7c2ce; }
  .chip-act:hover { background: #fff; border-color: #b7c2ce; }
  .attach-btn:hover { background: #fff; border-color: var(--line); }
  .ql-del:hover { background: #fff; border-color: var(--line); }
  .lb-nav:hover, .lb-close:hover { background: rgba(255,255,255,.14); }
  .msg-row:hover { background: none; }
}

@media (prefers-reduced-motion: reduce) {
  .bo-panel, .drawer, .toast { animation: none; }
  .live-dot::before, .live-dot::after { animation: none; }
  .live-dot::after { display: none; }
  .drawer.is-closing, .drawer-scrim.is-closing, .toast.is-leaving { animation: none; }
  .lb-modal:not([hidden]) .lb-scrim, .lb-modal:not([hidden]) .lb-stage,
  .doc-modal:not([hidden]) { animation: none; }
  .chip-add:active, .chip-act:active, .ql-del:active,
  .attach-btn:active, .lb-nav:active, .lb-close:active, .msg-row:active { transform: none; }
}

/* =========================================================================
   TRACK H — USERS (role management, admin-only) + CUSTOMERS (CRM) views.
   Reuses the shared tokens + the .role-badge / .pill / .jq-scroll skins;
   renames nothing the JS binds to. 44px targets; both views usable at 390px.
   ========================================================================= */

/* --- role badges (extend the shared .role-badge base) --- */
.role-badge.r-admin    { background: var(--amber); color: var(--amber-ink); border-color: var(--amber); }
.role-badge.r-staff    { background: var(--info-bg); color: var(--info-ink); border-color: var(--info-line); }
.role-badge.r-driver   { background: var(--bo-live-soft); color: var(--bo-live); border-color: #bfe6c9; }
.role-badge.r-customer { background: var(--slate-soft); color: var(--slate); border-color: var(--line); }

/* --- Users table --- */
.users-wrap { margin-top: var(--bo-2); }
.users-table { width: 100%; border-collapse: collapse; font-size: .89rem; }
.users-table thead th {
  text-align: left; font-family: var(--display); font-weight: 700; font-size: .72rem;
  text-transform: uppercase; letter-spacing: .05em; color: var(--ink-3);
  padding: 0 var(--bo-4) 8px; border-bottom: 1px solid var(--line);
}
.users-table thead th:last-child { text-align: right; }
.users-table td { padding: 12px var(--bo-4); border-bottom: 1px solid var(--line-2); vertical-align: middle; }
.ur-email { font-variant-numeric: tabular-nums; color: var(--ink-900); font-weight: 600; word-break: break-word; }
.ur-name { color: var(--ink-soft); }
.ur-joined { color: var(--ink-soft); font-variant-numeric: tabular-nums; white-space: nowrap; }
.ur-set { text-align: right; }
.ur-setwrap { display: inline-flex; align-items: center; gap: 8px; justify-content: flex-end; }
.user-role-select {
  min-height: 44px; padding: 8px 11px; border: 1.5px solid var(--line); border-radius: 9px;
  font: inherit; background: #fff; color: var(--ink-900); text-transform: capitalize;
}
.user-role-select:focus { border-color: var(--amber); outline: 2px solid var(--ink-900); outline-offset: 2px; box-shadow: 0 0 0 3px rgba(242,169,0,.16); }
.user-role-select:disabled { background: var(--slate-soft); color: var(--slate); cursor: not-allowed; }
.user-save { min-height: 44px; }
.ur-note { display: block; margin-top: 5px; font-size: .72rem; color: var(--ink-3); }

/* --- Customers (CRM) --- */
.crm-wrap { margin-top: var(--bo-2); }
.crm-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.crm-item { background: var(--card); border: 1px solid var(--line); border-radius: 12px; box-shadow: var(--bo-elev-1); overflow: hidden; }
.crm-row {
  display: flex; align-items: center; gap: 14px; width: 100%;
  padding: 14px var(--bo-4); background: none; border: 0; text-align: left; cursor: pointer;
  min-height: 44px; font: inherit; color: inherit;
}
.crm-row:hover { background: var(--slate-soft, #EEF2F6); }
.crm-row:focus-visible { outline: 2px solid var(--ink-900); outline-offset: -2px; }
.crm-row[aria-expanded="true"] { background: var(--bo-inset); }
.crm-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.crm-name { font-family: var(--display); font-weight: 700; color: var(--ink-900); letter-spacing: -.01em; }
.crm-contact { font-size: .82rem; color: var(--ink-soft); font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crm-meta { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; flex: none; }
.crm-count { font-family: var(--display); font-weight: 700; font-size: .8rem; color: var(--ink); }
.crm-latest { font-size: .78rem; color: var(--ink-soft); display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.crm-history { border-top: 1px solid var(--line); padding: 6px var(--bo-4) 10px; display: grid; gap: 0; }
.crm-hrow {
  display: grid; grid-template-columns: auto auto 1fr auto auto auto; align-items: center; gap: 10px;
  padding: 9px 0; border-bottom: 1px solid var(--line-2); font-size: .82rem;
}
.crm-hrow:last-child { border-bottom: 0; }
.crm-hdate { color: var(--ink-soft); font-variant-numeric: tabular-nums; white-space: nowrap; }
.crm-href { font-family: var(--display); font-weight: 700; color: var(--ink-900); font-variant-numeric: tabular-nums; }
.crm-hload { color: var(--ink-soft); text-transform: capitalize; }
.crm-htotal { font-family: var(--display); font-weight: 800; color: var(--ink-900); font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }

/* --- Track H mobile (phone) --- */
@media (max-width: 560px) {
  .users-table, .users-table tbody { display: block; width: 100%; }
  .users-table thead { display: none; }
  .users-table tr.user-row {
    display: grid; grid-template-columns: 1fr auto; gap: 4px 12px; align-items: center;
    background: var(--card); border: 1px solid var(--line); border-radius: 12px;
    padding: 12px 14px; margin-bottom: 10px; box-shadow: var(--bo-elev-1);
  }
  .users-table td { display: block; padding: 0; border: 0; }
  .user-row .ur-email { grid-column: 1; grid-row: 1; }
  .user-row td[data-label="Role"] { grid-column: 2; grid-row: 1; justify-self: end; }
  .user-row .ur-name { grid-column: 1 / -1; }
  .user-row .ur-joined { grid-column: 1 / -1; font-size: .8rem; }
  .user-row .ur-set { grid-column: 1 / -1; text-align: left; margin-top: 6px; }
  .user-row .ur-setwrap { justify-content: flex-start; width: 100%; }
  .user-row .user-role-select { flex: 1 1 auto; }

  .crm-hrow { display: flex; flex-wrap: wrap; gap: 6px 10px; }
  .crm-hrow .crm-htotal { margin-left: auto; }
}

/* =========================================================================
   DRIVER INVITES (Driver-invite feature, 2026-07-24) — the "Add driver"
   modal (reuses .drawer/.drawer-scrim + .field from styles.css) and the
   invites list table (reuses the .users-table skin above).
   ========================================================================= */
#invite-modal { z-index: 161; }
.field select {
  width: 100%; max-width: 420px; font: inherit; padding: 11px 13px;
  border: 1.5px solid var(--line); border-radius: 9px; background: #fff; color: var(--ink);
}
.field select:focus { border-color: var(--amber); outline: 2px solid var(--ink-900); outline-offset: 2px; box-shadow: 0 0 0 3px rgba(242,169,0,.18); }
.inv-cat-grid { display: flex; flex-wrap: wrap; gap: 8px 14px; }
.inv-cat-opt { display: inline-flex; align-items: center; gap: 6px; font-size: .88rem; color: var(--ink); }
.inv-cat-opt input { margin: 0; }

.invite-result {
  margin-top: var(--bo-5); padding-top: var(--bo-4); border-top: 1px solid var(--line);
}
.invite-result h3 { font-size: 1.05rem; color: var(--ink-900); margin: 0 0 4px; }
.invite-result p { font-size: .86rem; color: var(--ink-soft); margin: 0 0 10px; }
.invite-link-row { display: flex; gap: 8px; }
.invite-link-row input {
  flex: 1 1 auto; min-width: 0; font: inherit; font-size: .82rem;
  padding: 10px 12px; border: 1.5px solid var(--line); border-radius: 9px;
  background: var(--slate-soft); color: var(--ink-900);
}
.invite-link-row .btn { flex: none; min-height: 44px; }

/* --- invites table (mirrors .users-table) --- */
.inv-status-pending   { background: var(--warn-bg); color: var(--warn-ink); border-color: var(--warn-line); }
.inv-status-consumed  { background: var(--ok-bg); color: var(--ok-ink); border-color: var(--ok-line); }
.inv-status-revoked,
.inv-status-expired   { background: var(--danger-bg); color: var(--danger-ink); border-color: var(--danger-line); }
.iv-actions { text-align: right; white-space: nowrap; }
.iv-actions .btn { min-height: 36px; padding: 6px 12px; font-size: .82rem; }
.iv-actions .btn + .btn { margin-left: 6px; }

@media (max-width: 560px) {
  .invite-link-row { flex-wrap: wrap; }
  .invite-link-row input { flex: 1 1 100%; }
}

/* =============================================================================
   DAY PLAN (Wave 4 Scheduling v1) — propose→approve route builder. Reuses the
   shared tokens + the .d-field / .gate-note / .btn skins; only the layout the
   ordered stop list and overflow rows genuinely need is new here.
   ========================================================================= */
.dp-controls { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--bo-3); margin-bottom: var(--bo-4); }
.dp-date-field { margin-bottom: 0; }
.dp-controls .btn { min-height: 44px; }
.dp-result { display: grid; gap: var(--bo-3); }
.dp-error { background: var(--danger-bg); color: var(--danger-ink); border: 1px solid var(--danger-line); border-radius: 10px; padding: 11px 13px; font-size: .88rem; }
.dp-badge { font-family: var(--display); font-weight: 700; font-size: .78rem; border-radius: 999px; padding: 5px 12px; justify-self: start; }
.dp-badge-warn { background: var(--warn-bg); color: var(--warn-ink); border: 1px solid var(--warn-line); }
.dp-badge-live { background: var(--ok-bg); color: var(--ok-ink); border: 1px solid var(--ok-line); }
.dp-van { border: 1px solid var(--line); border-radius: 12px; padding: var(--bo-4); background: #fff; }
.dp-van-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--bo-3); flex-wrap: wrap; margin-bottom: var(--bo-3); }
.dp-van-head h3 { font-family: var(--display); font-size: 1.02rem; font-weight: 800; color: var(--ink-900); margin: 0; }
.dp-van-meta { font-family: var(--display); font-size: .78rem; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.dp-driver-field { margin-bottom: var(--bo-3); }
.dp-stops { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.dp-stop { display: flex; align-items: flex-start; gap: var(--bo-2); padding: 8px 10px; border: 1px solid var(--line-2); border-radius: 9px; background: #FCFDFE; }
.dp-stop-no { flex: none; width: 24px; height: 24px; border-radius: 999px; background: var(--ink-900); color: #fff; font-family: var(--display); font-weight: 800; font-size: .78rem; display: flex; align-items: center; justify-content: center; font-variant-numeric: tabular-nums; }
.dp-stop-ic { flex: none; width: 24px; height: 24px; border-radius: 999px; background: var(--slate-soft); color: var(--slate); font-weight: 800; display: flex; align-items: center; justify-content: center; }
.dp-stop-tip { background: var(--slate-soft); }
.dp-stop-main { display: grid; gap: 2px; min-width: 0; }
.dp-stop-title { font-family: var(--display); font-weight: 700; color: var(--ink-900); font-size: .9rem; overflow-wrap: anywhere; }
.dp-stop-pc { font-weight: 600; color: var(--ink-soft); font-size: .84em; }
.dp-stop-sub { font-size: .8rem; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.dp-stop-failed { border-color: var(--danger-line); background: var(--danger-bg); }
.dp-fail-tag { color: var(--danger-ink); font-weight: 700; }
.dp-overflow h3 { font-family: var(--display); font-size: .82rem; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; color: var(--warn-ink); margin: 0 0 var(--bo-3); }
.dp-of-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.dp-of-list li { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--bo-2); padding: 8px 10px; border: 1px dashed var(--warn-line); border-radius: 9px; background: var(--warn-bg); font-size: .84rem; }
.dp-of-name { font-family: var(--display); font-weight: 700; color: var(--ink-900); }
.dp-of-reason { color: var(--warn-ink); font-size: .82rem; }
.dp-caption { font-size: .78rem; color: var(--slate); text-align: center; margin: 0; }
.dp-actions { display: flex; flex-wrap: wrap; gap: var(--bo-2); }
.dp-actions .btn { flex: 1 1 auto; min-height: 44px; }

/* =========================================================================
   W5 BACK OFFICE — R23 / R27 / R32 / R33
   -------------------------------------------------------------------------
   No new palette, no new typeface. Everything below is built from the tokens
   already in this file, because this is an operator console with a settled
   visual language and a second one would only make it harder to read.

   The single deliberate accent: the stage the job is AT wears a 3px amber
   left edge. Amber is this estate's one accent colour and it is spent, in
   this drawer, on exactly one thing — where you are.
   ========================================================================= */

/* --- R32: stages ---------------------------------------------------------
   .d-stage IS a .d-section (same card), so every existing skin inside it —
   .d-row, .gate-note, .money-panel, .ql-* — is untouched. */
.d-stage { padding: 0; overflow: hidden; }
.d-stage > summary {
  display: flex; align-items: baseline; gap: var(--bo-3);
  padding: var(--bo-3) var(--bo-4); cursor: pointer;
  list-style: none; user-select: none;
  border-left: 3px solid transparent;
  transition: background .14s var(--ease);
}
.d-stage > summary::-webkit-details-marker { display: none; }
.d-stage > summary:hover { background: var(--slate-soft); }
.d-stage > summary:focus-visible { outline: 2px solid var(--ink-900); outline-offset: -2px; }
.d-stage > summary h3 { margin: 0; flex: none; }
/* The fold's own answer. Right-aligned against the title so a column of
   folded stages reads as a column of values. */
.d-stage-state {
  margin-left: auto; text-align: right; min-width: 0;
  font-size: .82rem; color: var(--ink-soft); font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
/* The chevron. Drawn, not typed, so it rotates. */
.d-stage > summary::after {
  content: ""; flex: none; width: 8px; height: 8px; margin-left: 2px;
  border-right: 2px solid var(--slate); border-bottom: 2px solid var(--slate);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform .16s var(--ease);
}
.d-stage[open] > summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
.d-stage-body { padding: 0 var(--bo-4) var(--bo-4); }
/* later / now / done. Only "now" raises its voice. */
.d-stage.is-now > summary { border-left-color: var(--amber); }
.d-stage.is-now > summary h3 { color: var(--ink-900); }
.d-stage.is-done > summary h3 { color: var(--slate); }
/* A stage that is still ahead of the job is distinguished by the ABSENCE of
   the marker, never by dimming: an opacity veil over already-grey --slate
   heading text puts it under the contrast floor, and "you can't read it yet"
   is not the same message as "you don't need it yet". */
.d-stage.is-later > summary h3 { color: var(--slate); }
.d-stage.is-always > summary h3 { color: var(--ink-900); }
@media (prefers-reduced-motion: no-preference) {
  .d-stage[open] > .d-stage-body { animation: stage-in .16s var(--ease); }
}
@keyframes stage-in { from { opacity: 0; transform: translateY(-3px); } to { opacity: 1; transform: none; } }

/* --- R23: the lifecycle controls ---------------------------------------- */
.js-steps { display: grid; gap: var(--bo-2); }
.js-step { flex-direction: column; align-items: flex-start; text-align: left; line-height: 1.3; }
.js-step-hint {
  display: block; font-family: var(--display); font-weight: 600; font-size: .74rem;
  letter-spacing: 0; text-transform: none; opacity: .78; margin-top: 3px;
}
.js-exits { margin-top: var(--bo-4); padding-top: var(--bo-3); border-top: 1px dashed var(--line); }
.js-exits .d-field { margin-bottom: var(--bo-2); }
.js-exits .btn { margin-right: var(--bo-2); margin-top: var(--bo-2); min-height: 44px; }

/* --- R27: the photo alert ----------------------------------------------- */
.d-photobar {
  display: flex; align-items: center; gap: var(--bo-3); flex-wrap: wrap;
  margin-top: var(--bo-3); padding: 9px 12px; border-radius: 10px;
  background: var(--warn-bg); border: 1px solid var(--warn-line);
}
.d-photobar[hidden] { display: none; }
.d-photobar-msg {
  font-family: var(--display); font-weight: 700; font-size: .84rem; color: var(--warn-ink);
}
.d-photobar .btn { margin-left: auto; min-height: 40px; }
/* A new photo is ringed, not badged — the ring sits on the thing itself. */
.d-photos img.is-new { border-color: var(--amber); box-shadow: 0 0 0 2px rgba(242,169,0,.35); }
.jr-tag.is-newphoto { color: var(--warn-ink); border-color: var(--warn-line); background: var(--warn-bg); }

/* --- R33: board reference + search --------------------------------------- */
.jr-ref {
  font-family: var(--mono); font-size: .8rem; color: var(--ink-soft);
  white-space: nowrap; font-variant-numeric: tabular-nums;
}
.board-search {
  display: flex; align-items: center; gap: var(--bo-2);
  border: 1.5px solid var(--line); border-radius: 11px; background: #fff;
  padding: 0 12px; margin-bottom: var(--bo-3); box-shadow: var(--bo-elev-1);
}
.board-search:focus-within { border-color: var(--amber); box-shadow: 0 0 0 3px rgba(242,169,0,.16); }
.board-search-icon { flex: none; color: var(--slate); }
/* The focus ring belongs to the WHOLE search bar, not to the bare input
   inside it — two rings, one nested in the other, is what a search box looks
   like when nobody checked. Chrome also gives input[type=search] its own
   appearance, so that is cleared here too, on every state rather than only the
   resting one. */
.board-search input,
.board-search input:focus,
.board-search input:focus-visible {
  flex: 1 1 auto; min-width: 0; background: none;
  border: none; outline: none; box-shadow: none;
  appearance: none; -webkit-appearance: none;
  font: inherit; color: var(--ink-900); min-height: 44px; padding: 0;
}
.board-search input::-webkit-search-cancel-button { display: none; }
.board-search-count {
  flex: none; font-family: var(--display); font-weight: 700; font-size: .78rem;
  color: var(--slate); font-variant-numeric: tabular-nums; white-space: nowrap;
}
.board-search-clear {
  flex: none; border: none; background: none; font: inherit; cursor: pointer;
  font-family: var(--display); font-weight: 700; font-size: .78rem; color: var(--ink-900);
  padding: 10px 2px; text-decoration: underline; text-underline-offset: 3px;
}
.board-search-clear[hidden] { display: none; }
.board-search-clear:focus-visible { outline: 2px solid var(--ink-900); outline-offset: 2px; }

@media (max-width: 560px) {
  /* The stage title and its answer stop competing for one line. */
  .d-stage > summary { flex-wrap: wrap; }
  .d-stage-state { margin-left: 0; width: 100%; text-align: left; order: 3; }
  .board-search { flex-wrap: wrap; padding-bottom: 6px; }
  .board-search input { flex-basis: 100%; }
}

/* R33 — the Ref column in the phone CARD reflow. Without an explicit
   placement it lands in the same grid cell the customer name is pinned to
   (1/1) and the two print on top of each other. On a card the reference is an
   eyebrow: full width, above the name. (It carries no data-label, so it never
   picks up the generic caption the other reflowed cells get — see the cell's
   own note in staff.js renderBoard.) */
@media (max-width: 560px) {
  .job-row .jr-ref { grid-column: 1 / -1; grid-row: 1; font-size: .74rem; }
  .job-row .jr-name  { grid-row: 2; }
  .job-row .jr-price { grid-row: 2; }
}

/* A DISABLED BUTTON MUST LOOK DISABLED.
   styles.css skins only `.btn-amber:disabled`, so every ghost/ink control in
   this console rendered at full strength while refusing to act — the locked
   Schedule button, and now the price-down's apply button, which is disabled
   whenever the chosen load size would not produce a refund. A control that
   looks live and does nothing reads as a broken button, not a locked one. */
.btn:disabled { opacity: .45; cursor: not-allowed; box-shadow: none; transform: none; }
