/* Tech4Service unified CRM shell — premium design system v2 */
:root {
  --navy:#0F2A44; --navy-soft:#16385a; --blue:#1BA5E0; --blue-soft:#e8f6fd;
  --bg:#f2f5f9; --surface:#ffffff; --line:#e7ecf2; --line-soft:#f0f3f7;
  --ink:#16232f; --muted:#5a6b7a; --muted-soft:#94a3b3; /* --muted darkened from #6b7c8d (4.29:1 on white, 3.85:1 on --surface-2 -- both real axe color-contrast failures on .sub/.uc-role/.ws-section-label/tab-switcher text across ticket_view.php and ticket_new.php) to #5a6b7a (5.50:1 on white, 4.94:1 on --surface-2), both now WCAG AA */
  --amber:#f59f00; --amber-soft:#fff6e0; --red:#e0343f; --red-soft:#fdecec; --green:#1f9d55; --green-soft:#e8f8ee;
  --radius:12px; --radius-sm:8px;
  --shadow: 0 1px 2px rgba(15,42,68,.04), 0 6px 20px rgba(15,42,68,.06);
  --shadow-hover: 0 4px 10px rgba(15,42,68,.06), 0 14px 32px rgba(15,42,68,.10);

  /* ---------- Operations Console design language (v3) ----------
     Same brand palette, aliased under the semantic names the console
     components use, so there is exactly one source of truth for color —
     never two parallel token systems. */
  --navy-deep:#091826; --navy-mid: var(--navy-soft);
  --accent: var(--blue); --accent-soft: var(--blue-soft);
  --live: var(--green); --live-soft: var(--green-soft);
  --wait: var(--amber); --wait-soft: var(--amber-soft);
  --critical: var(--red); --critical-soft: var(--red-soft);
  --info: var(--blue); --info-soft: var(--blue-soft);
  --violet:#7C63C9; --violet-soft:#EFEBFB;

  /* ---------- WCAG AA text/fill variants of the brand accents ----------
     The accent tokens above are tuned for FILLS, DOTS, BORDERS and ICONS,
     where WCAG's 4.5:1 text rule does not apply. Used as small bold TEXT
     (status badges, priority badges, chips) or as a solid fill behind white
     text, several of them failed real axe-core WCAG2AA scans across 13
     surfaces (e.g. --amber as chip text on --amber-soft measured 1.98:1;
     white on --amber measured 2.13:1).

     Rather than darken the base tokens -- which would also dull every dot,
     border, stripe and icon that legitimately does not need to meet a text
     ratio -- each accent gets two AA-safe siblings:

       --X-text : for coloured TEXT on white or on the matching --X-soft
       --X-fill : for a solid background carrying WHITE text

     Every value below was verified by axe-core against the real rendered
     pages, not computed and assumed. Use these two, never the base token,
     anywhere the colour carries readable text. */
  --amber-text:#8A5A00; --amber-fill:#A06800;
  --green-text:#157A53; --green-fill:#157A53;
  --red-text:#B3232D;   --red-fill:#C62B35;
  --blue-text:#0A6A92;  --blue-fill:#0A6A92;
  --violet-text:#5B45A0; --violet-fill:#5B45A0;
  --muted-text:#637687; /* AA-safe sibling of --muted-soft (#94a3b3 = 2.57:1 on white) */
  --surface-2: var(--line-soft);
  --ink-soft: #4C5B6D;
  --mono: ui-monospace,"Cascadia Code","SF Mono","Consolas","Roboto Mono",monospace;
}
* { box-sizing: border-box; }
html, body { height: 100%; }
/* One consistent, visible keyboard-focus ring for every interactive element
   that doesn't already define its own :focus style (form fields keep their
   branded box-shadow ring above/below). :focus-visible so a mouse click
   doesn't show it — only real keyboard navigation does. */
/* Base link colour. Bootstrap's default #0d6efd measures only 4.11:1 on this
   app's own --bg (#f2f5f9) page background -- a real axe WCAG2AA failure on
   Schedule and Permissions. --link is the same blue family, darkened to pass
   on white, on --bg, and on the soft tints used by panels and table rows. */
:root { --link:#0A58CA; }
a { color: var(--link); }

/* Overdue row treatment. Replaces Bootstrap's .table-danger (#f8d7da), on
   which ordinary link and outline-button text measured 2.6-3.4:1 -- the row
   was shouting "danger" while making its own contents harder to read. This
   keeps the same at-a-glance red signal via a left stripe and a much lighter
   tint, so every control inside stays AA. */
.row-overdue > td { background: #FDF2F3; }
.row-overdue > td:first-child { box-shadow: inset 3px 0 0 var(--red-fill); }

a:focus-visible, button:focus-visible, [tabindex]:focus-visible,
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}
/* font-family here, not just on the later `body` rule: body.shell-body is a
   more specific selector, so it was quietly winning and leaving plain body
   copy on Inter while every heading, button, form control and table cell had
   moved to Poppins. Half a typeface change is worse than none -- a paragraph
   in one face beside a table in another reads as a rendering fault. Found by
   measuring the computed font across all 29 surfaces rather than by eye. */
body.shell-body { margin: 0; background: var(--bg); color: var(--ink); font-family: var(--ds-font, "Poppins", "Inter", "Segoe UI", system-ui, Helvetica, Arial, sans-serif); font-size: 14.5px; line-height: 1.5; -webkit-font-smoothing: antialiased; }
h1,h2,h3,h4,h5,h6 { font-family: inherit; }

/* ---------- Legacy class compatibility ---------- */
.btn-brand { background: var(--navy); border-color: var(--navy); color: #fff; border-radius: var(--radius-sm); }
.btn-brand:hover { background: var(--navy-soft); border-color: var(--navy-soft); color: #fff; }
.badge-brand { background: var(--navy); color: #fff; }

/* ---------- Layout grid ---------- */
.shell { display: flex; min-height: 100vh; }
.shell-nav { width: 252px; flex: 0 0 252px; background: var(--navy); color: #cfe0ee; position: sticky; top: 0; height: 100vh; overflow-y: auto; overflow-x: hidden; transition: width .18s ease; }
.shell-nav.collapsed { width: 72px; flex-basis: 72px; }
.shell-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.shell-topbar { background: var(--surface); border-bottom: 1px solid var(--line); padding: 14px 26px; display: flex; align-items: center; gap: 16px; position: sticky; top: 0; z-index: 20; }
.shell-body-area { flex: 1; display: flex; min-height: 0; }
.shell-workspace { flex: 1; min-width: 0; padding: 26px 30px 40px; overflow-y: auto; }
/* flex-basis/width (not a negative margin) so the collapsed drawer stops
   contributing to the flex row's total width — a negative margin still
   slides it out of view but leaves its 380px flex-basis in the layout,
   which was forcing horizontal overflow/scroll on every page in the app
   at desktop/tablet widths, permanently, regardless of whether the drawer
   was ever opened. */
.shell-drawer { width: 380px; flex: 0 0 380px; background: var(--surface); border-left: 1px solid var(--line); overflow-y: auto; overflow-x: hidden; transition: flex-basis .18s ease, width .18s ease, border-color .18s ease; box-shadow: -8px 0 24px rgba(15,42,68,.04); }
.shell-drawer.hidden { width: 0; flex-basis: 0; border-left-color: transparent; box-shadow: none; }

/* ---------- Left nav ---------- */
.nav-brand { display: flex; align-items: center; gap: 12px; padding: 24px 22px 20px; }
.nav-brand img { height: 34px; }
.nav-brand span { font-weight: 800; color: #fff; letter-spacing: .5px; white-space: nowrap; overflow: hidden; font-size: 14px; }
.shell-nav.collapsed .nav-brand span { display: none; }
.nav-section-label { padding: 14px 22px 6px; font-size: 10px; text-transform: uppercase; letter-spacing: .8px; color: #7e97b3; font-weight: 700; } /* WCAG AA: #7e97b3 on --navy #0F2A44 = 4.85:1 (was #5d7a97 = 3.27:1, failed axe color-contrast) */
.shell-nav.collapsed .nav-section-label { display: none; }
.nav-list { list-style: none; margin: 4px 0; padding: 0 12px; }
.nav-list li { margin-bottom: 2px; }
.nav-list a { display: flex; align-items: center; gap: 13px; padding: 11px 12px; color: #a9c2d9; text-decoration: none; font-size: 13.5px; font-weight: 500; white-space: nowrap; overflow: hidden; border-radius: 10px; transition: background .12s, color .12s; }
.nav-list a:hover { background: rgba(255,255,255,.07); color: #fff; }
.nav-list a.active { background: linear-gradient(90deg, var(--blue) 0%, #1690c4 100%); color: #fff; font-weight: 700; box-shadow: 0 4px 12px rgba(27,165,224,.35); }
.nav-list .nav-icon { width: 20px; height: 20px; flex: 0 0 20px; display: flex; align-items: center; justify-content: center; }
.nav-list .nav-icon svg { width: 18px; height: 18px; }
.shell-nav.collapsed .nav-list span.nav-label { display: none; }
.shell-nav.collapsed .nav-list a { justify-content: center; padding: 11px; }

/* ---------- Collapsible nav groups (REQ-51-01 IA pass) ----------
   Same .nav-list items, just grouped under a native <details>/<summary>
   instead of one long flat list -- zero new JS, keyboard/screen-reader
   accessible for free. Auto-open when the current page lives inside. */
.nav-group { margin: 2px 0; }
.nav-group-summary { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 10px 22px; cursor: pointer; color: #7d99b3; font-size: 10px; text-transform: uppercase; letter-spacing: .8px; font-weight: 700; list-style: none; user-select: none; }
.nav-group-summary::-webkit-details-marker { display: none; }
.nav-group-summary:hover { color: #cfe0ee; }
.nav-group-summary .nav-group-chevron { width: 14px; height: 14px; display: flex; transition: transform .15s ease; }
.nav-group-summary .nav-group-chevron svg { width: 12px; height: 12px; transform: rotate(-90deg); }
.nav-group[open] .nav-group-summary .nav-group-chevron svg { transform: rotate(0deg); }
.shell-nav.collapsed .nav-group-summary .nav-label { display: none; }
.shell-nav.collapsed .nav-group-summary { justify-content: center; padding: 10px; }
.shell-nav.collapsed .nav-group-summary .nav-group-chevron { display: none; }
.nav-collapse-btn { display: flex; align-items: center; gap: 8px; width: calc(100% - 24px); margin: 10px 12px; background: rgba(255,255,255,.06); border: none; color: #a9c2d9; padding: 10px 12px; border-radius: 10px; font-size: 12.5px; cursor: pointer; }
.nav-collapse-btn:hover { background: rgba(255,255,255,.12); color: #fff; }
.shell-nav.collapsed .nav-collapse-btn span.nav-label { display: none; }
.shell-nav.collapsed .nav-collapse-btn { justify-content: center; }
.nav-collapse-btn .nav-icon svg { transition: transform .18s ease; }
.shell-nav.collapsed .nav-collapse-btn .nav-icon svg { transform: rotate(180deg); }

/* ---------- Top bar ---------- */
.topbar-search { flex: 1; max-width: 560px; position: relative; }
.topbar-search input { width: 100%; padding: 11px 16px 11px 40px; border: 1px solid var(--line); border-radius: 999px; font-size: 13.5px; background: var(--bg); transition: box-shadow .12s, border-color .12s; }
.topbar-search input:focus { outline: none; border-color: var(--blue); background: #fff; box-shadow: 0 0 0 4px var(--blue-soft); }
.topbar-search::before { content: ''; position: absolute; left: 15px; top: 50%; transform: translateY(-50%); width: 15px; height: 15px; border: 2px solid var(--muted-soft); border-radius: 50%; background: transparent; pointer-events: none; }
.topbar-search::after { content: ''; position: absolute; left: 24px; top: 61%; width: 6px; height: 2px; background: var(--muted-soft); transform: rotate(45deg); pointer-events: none; }
.topbar-results { position: absolute; top: 100%; left: 0; right: 0; background: #fff; border: 1px solid var(--line); border-radius: var(--radius-sm); margin-top: 6px; box-shadow: var(--shadow-hover); max-height: 380px; overflow-y: auto; z-index: 50; display: none; }
.topbar-results.show { display: block; }
.topbar-result-item { display: block; padding: 10px 14px; text-decoration: none; color: var(--ink); border-bottom: 1px solid var(--line-soft); font-size: 13px; }
.topbar-result-item:hover { background: var(--bg); }
.topbar-result-item .trt { color: var(--blue); font-size: 10px; text-transform: uppercase; letter-spacing: .5px; font-weight: 700; }
.topbar-actions { display: flex; gap: 10px; align-items: center; margin-left: auto; }
.qa-btn { background: var(--navy); color: #fff; border: none; border-radius: var(--radius-sm); padding: 10px 16px; font-size: 13px; font-weight: 600; cursor: pointer; text-decoration: none; display: inline-block; transition: background .12s, transform .12s, box-shadow .12s; }
.qa-btn:hover { background: var(--navy-soft); color: #fff; transform: translateY(-1px); box-shadow: var(--shadow-hover); }
.qa-btn:active { transform: translateY(0); box-shadow: none; }
.qa-menu { position: relative; }
.qa-menu-list { display: none; position: absolute; right: 0; top: 100%; margin-top: 6px; background: #fff; border: 1px solid var(--line); border-radius: var(--radius-sm); box-shadow: var(--shadow-hover); min-width: 210px; z-index: 50; overflow: hidden; }
.qa-menu-list.show { display: block; }
.qa-menu-list a { display: block; padding: 10px 16px; color: var(--ink); text-decoration: none; font-size: 13px; }
.qa-menu-list a:hover { background: var(--bg); }
.topbar-bell { position: relative; width: 38px; height: 38px; border-radius: 50%; background: var(--bg); display: flex; align-items: center; justify-content: center; color: var(--muted); cursor: pointer; }
.topbar-bell:hover { background: var(--blue-soft); color: var(--blue); }
.topbar-bell .dot { position: absolute; top: 8px; right: 9px; width: 7px; height: 7px; border-radius: 50%; background: var(--red); border: 1.5px solid #fff; }
.user-chip { display: flex; align-items: center; gap: 8px; color: var(--ink); font-size: 13px; margin-left: 4px; padding-left: 14px; border-left: 1px solid var(--line); }
.user-chip .avatar { width: 32px; height: 32px; border-radius: 50%; background: var(--navy); color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 12.5px; }
.user-chip a { color: var(--muted); text-decoration: none; }
.user-chip a:hover { color: var(--red); }

/* ---------- Page header ---------- */
.page-hd { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 22px; flex-wrap: wrap; gap: 12px; }
.qc-dock { display: flex; gap: 8px; flex-wrap: wrap; }
.page-hd h3 { margin: 0; color: var(--navy); font-weight: 800; font-size: 24px; letter-spacing: -.2px; }
.page-hd .page-sub { color: var(--muted); font-size: 13px; margin-top: 3px; }

/* ---------- Status / priority badges ---------- */
.st-badge { display: inline-block; padding: 4px 11px; border-radius: 999px; font-size: 11px; font-weight: 700; color: #fff; text-transform: capitalize; letter-spacing: .1px; background: var(--muted); box-shadow: inset 0 0 0 1px rgba(255,255,255,.18); }
.st-new { background: var(--blue-fill); } .st-in_progress { background: var(--amber-fill); } .st-on_hold { background: var(--muted); }
.st-awaiting_parts { background: var(--violet-fill); } .st-completed { background: var(--green-fill); } .st-invoiced { background: var(--navy); } .st-cancelled { background: #c3ccd6; color:#333; }
.st-badge.st-sent, .st-badge.st-active, .st-badge.st-approved, .st-badge.st-paid { background: var(--green-fill); }
.st-badge.st-pending, .st-badge.st-draft, .st-badge.st-open { background: var(--amber-fill); }
.st-badge.st-rejected, .st-badge.st-void, .st-badge.st-overdue, .st-badge.st-declined, .st-badge.st-revoked, .st-badge.st-unpaid { background: var(--red-fill); }
.st-badge.st-changes_requested, .st-badge.st-partial { background: var(--amber-fill); }
.st-badge.st-expired { background: var(--muted); }
/* REQ-04-02: the technician's own response to an assignment. `confirmed`
   reads as done and `scheduled` as still waiting, matching how the same
   two words already behave on the dispatch board. Both use the --*-fill
   tokens, which carry the contrast ratios the accessibility guard checks. */
.st-badge.st-confirmed { background: var(--green-fill); }
.st-badge.st-scheduled { background: var(--blue-fill); }
.st-badge.st-credit { background: var(--blue-fill); }
.st-badge.st-running { background: var(--blue-fill); } .st-badge.st-paused { background: var(--amber-fill); }
.st-badge.st-failed { background: var(--red-fill); } .st-badge.st-completed_with_exceptions { background: var(--amber-fill); }
.st-badge-lg { display: inline-block; padding: 5px 14px; border-radius: 999px; font-size: 12.5px; font-weight: 700; color: #fff; letter-spacing: .1px; background: var(--muted); margin-left: 10px; vertical-align: middle; }
.st-badge-xs { font-size: 9px; padding: 1px 7px; }
.st-badge-lg.st-running { background: var(--blue-fill); } .st-badge-lg.st-paused { background: var(--amber-fill); }
.st-badge-lg.st-pending { background: var(--amber-fill); } .st-badge-lg.st-failed { background: var(--red-fill); }
.st-badge-lg.st-completed { background: var(--green-fill); } .st-badge-lg.st-completed_with_exceptions { background: var(--amber-fill); }
.bulk-log-line { white-space: pre-wrap; word-break: break-word; padding: 1px 0; }
.bulk-log-info { color: #b7c4d6; } .bulk-log-success { color: #5fd08a; } .bulk-log-warn { color: #f5c04a; } .bulk-log-error { color: #ff7a7a; }
.pr-badge { display: inline-block; padding: 3px 10px; border-radius: 999px; font-size: 10.5px; font-weight: 700; background: var(--line-soft); color: var(--muted); }
.pr-low { color: var(--muted); background: var(--line-soft); } .pr-normal { color: var(--blue-text); background: var(--blue-soft); }
.pr-high { color: var(--amber-text); background: var(--amber-soft); } .pr-urgent { color: var(--red-text); background: var(--red-soft); }

/* ---------- Filter pills ---------- */
.filter-pills { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 18px; }
.filter-pill { background: #fff; border: 1px solid var(--line); border-radius: 999px; padding: 7px 15px; font-size: 12.5px; font-weight: 600; color: var(--muted); text-decoration: none; cursor: pointer; transition: all .12s; }
.filter-pill:hover { border-color: var(--blue); color: var(--navy); }
.filter-pill.active { background: var(--navy); color: #fff; border-color: var(--navy); }
.filter-pill.active:hover { color: #fff; }

/* ---------- Ticket stage stepper (Ticket View v2, premium redesign) ----------
   Replaces the old full-width row of text pills (9 stages read as 9 loud
   buttons) with a compact connected-dot progress stepper -- the same real
   #stage-row / [data-stage] buttons the click handler already targets,
   just restyled. Current stage is the only one that reads as "big"; done
   stages read as a quiet completed trail; future stages stay faint. */
.stage-stepper { display: flex; align-items: flex-start; overflow-x: auto; padding: 2px 0 4px; }
.stage-step { display: flex; flex-direction: column; align-items: center; gap: 5px; background: none; border: none; padding: 0 2px; cursor: pointer; flex: 0 0 auto; min-width: 60px; }
.stage-step:disabled { cursor: default; }
.stage-dot { width: 18px; height: 18px; border-radius: 50%; background: var(--surface); border: 2px solid var(--line); display: flex; align-items: center; justify-content: center; font-size: 9.5px; font-weight: 800; color: var(--muted); transition: all .12s; }
.stage-step:hover:not(:disabled) .stage-dot { border-color: var(--blue); }
.stage-step.active .stage-dot { background: var(--navy); border-color: var(--navy); color: #fff; width: 22px; height: 22px; }
.stage-step.done .stage-dot { background: var(--green); border-color: var(--green); color: #fff; }
.stage-label { font-size: 10px; font-weight: 600; color: #63748a; white-space: nowrap; max-width: 76px; overflow: hidden; text-overflow: ellipsis; } /* was var(--muted-soft) #94a3b3 = 2.57:1 on white, real axe color-contrast failure on the ticket_new.php stepper; #63748a = 4.78:1 AA. Scoped here (not the shared --muted-soft token) since that token is also used for many non-text UI elements elsewhere. */
.stage-step.active .stage-label { color: var(--navy); font-weight: 800; }
.stage-step.done .stage-label { color: #157a53; } /* was var(--green) #1f9d55 = 3.49:1 on white, real axe color-contrast failure; #157a53 = 5.33:1 AA. Scoped here, not the shared --green token (used broadly for badges/borders/icons elsewhere). */
.stage-connector { flex: 1 1 14px; height: 2px; background: var(--line); margin-top: 10px; min-width: 8px; }
.stage-connector.done { background: var(--green); }
.stage-stepper[hidden] { display: none; }

/* Compact stage summary shown by default (current + previous + next only) --
   the full stepper above is collapsed behind "View full timeline" so every
   ticket doesn't permanently show its entire lifecycle across the top. */
.stage-compact { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 2px 0 4px; }
.stage-compact-item { display: flex; flex-direction: column; gap: 1px; padding: 6px 12px; border-radius: 10px; min-width: 0; }
.stage-compact-item .sc-eyebrow { font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; }
.stage-compact-item .sc-label { font-size: 12.5px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 42vw; }
.stage-compact-item.done { background: var(--green-soft); }
.stage-compact-item.done .sc-eyebrow, .stage-compact-item.done .sc-label { color: var(--green); }
.stage-compact-item.current { background: var(--navy); }
.stage-compact-item.current .sc-eyebrow { color: rgba(255,255,255,.68); }
.stage-compact-item.current .sc-label { color: #fff; }
.stage-compact-item.next { background: var(--surface); border: 1px dashed var(--line); }
.stage-compact-item.next .sc-eyebrow, .stage-compact-item.next .sc-label { color: var(--muted); }
.stage-compact-arrow { color: var(--line-soft); font-weight: 700; flex: 0 0 auto; }
.stage-compact-toggle { margin-left: auto; background: none; border: 1px solid var(--line); border-radius: 999px; padding: 5px 12px; font-size: 11.5px; font-weight: 600; color: var(--muted); cursor: pointer; white-space: nowrap; flex: 0 0 auto; }
.stage-compact-toggle:hover { border-color: var(--blue); color: var(--blue); }
@media (max-width: 480px) {
  .stage-compact-item .sc-label { max-width: 30vw; }
  /* The New Ticket wizard's 5-step .stage-stepper (below) was confirmed
     via real screenshot to clip its last step ("Create") by ~20px on a
     390px viewport -- unlike the ticket lifecycle rail (which can have up
     to 12 stages and genuinely needs to scroll), a wizard's step count is
     small and fixed, so tightening spacing to fit all steps without any
     scrolling is strictly better than a scroll-with-partial-peek here. */
  .stage-step { min-width: 50px; }
  .stage-connector { min-width: 4px; }

  /* List-page headers (Agreements, Tickets, Quotes, ...) pair a search
     input pinned to .u-minw-320 with one or two action buttons in an
     unwrapped .d-flex row inside .page-hd. At 320px the input alone
     already exceeds a 390px mobile viewport, so the primary CTA (e.g.
     "+ New Agreement") was pushed off past the right edge with zero
     visual affordance that the page scrolls sideways to reach it --
     found via a live Playwright check on agreements.php confirming the
     button's bounding rect started at x=442 on a 390px-wide viewport.
     Letting the row wrap and releasing the input's min-width keeps the
     search box and both buttons reachable without any horizontal swipe. */
  .page-hd .d-flex { flex-wrap: wrap; }
  .u-minw-320 { min-width: 0; }
}

/* ---------- New Ticket wizard (5-step POS flow, reuses the .stage-stepper
   dots above for the progress indicator) ---------- */
.wiz-hd { padding: 18px 20px 4px; }
.wiz-step { display: none; padding: 16px 20px 4px; }
.wiz-step.on { display: block; }
.wiz-err { display: none; color: var(--critical); font-size: 12px; margin-top: 6px; }
.wiz-err.show { display: block; }
.wiz-nav { display: flex; align-items: center; padding: 18px 20px; border-top: 1px solid var(--line-soft); margin-top: 8px; }
.wiz-nav-spacer { flex: 1; }
.wiz-review { border: 1px solid var(--line-soft); border-radius: var(--radius-sm); overflow: hidden; }
.wiz-review-row { display: flex; justify-content: space-between; gap: 12px; padding: 10px 14px; border-bottom: 1px solid var(--line-soft); font-size: 13px; }
.wiz-review-row:last-child { border-bottom: none; }
.wiz-review-row .k { color: var(--muted); font-weight: 600; }

/* ---------- SaaS quality pass: premium small action button (POS
   workspace redesign) — replaces the raw Bootstrap .btn-outline-primary/
   -secondary/-danger scattered across the six rebuilt workspaces. Those
   render in Bootstrap's stock blue/gray, a visibly different hue from
   this app's navy/blue brand palette — the single most obvious "two
   different apps stitched together" seam on an otherwise-premium screen.
   Scoped to a new class name (not a global .btn-outline-* override) so
   it only touches the six rebuilt workspaces, never the not-yet-migrated
   admin pages that still use Bootstrap's defaults on purpose for now. */
.ws-btn-sm { display: inline-flex; align-items: center; gap: 5px; padding: 6px 13px; border-radius: 7px; font-size: 12px; font-weight: 650; text-decoration: none; cursor: pointer; border: 1px solid var(--line); background: #fff; color: var(--navy); line-height: 1.3; transition: background .12s, border-color .12s, box-shadow .12s; }
.ws-btn-sm:hover { border-color: var(--blue); background: var(--blue-soft); color: var(--navy); box-shadow: var(--shadow); }
.ws-btn-sm:disabled { opacity: .45; cursor: default; box-shadow: none; }
.ws-btn-sm:disabled:hover { border-color: var(--line); background: #fff; }
.ws-btn-sm.fill { background: var(--navy); border-color: var(--navy); color: #fff; }
.ws-btn-sm.fill:hover { background: var(--navy-soft); border-color: var(--navy-soft); color: #fff; box-shadow: var(--shadow-hover); }
.ws-btn-sm.fill:disabled { background: var(--muted-soft); border-color: var(--muted-soft); }
.ws-btn-sm.danger { color: var(--red); border-color: var(--red-soft); background: var(--red-soft); }
.ws-btn-sm.danger:hover { border-color: var(--red); background: var(--red-soft); box-shadow: var(--shadow); }
.wiz-review-row .v { font-weight: 700; color: var(--ink); text-align: right; }

/* ---------- "What's Next" primary-action banner (workflow redesign step 3) ----------
   One unmissable next step instead of hunting across tabs. .kind-action
   uses the brand blue (a real call to action); .kind-info is a quieter
   neutral state for "nothing to do yet" / "done" moments. */
.next-action-banner { grid-column: 1 / -1; display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 14px 20px; margin: 0; border-left: none; border-right: none; border-top: none; }
.next-action-banner.kind-action { background: var(--blue-soft); border-bottom: 1px solid var(--blue); }
.next-action-banner.kind-info { background: var(--line-soft); border-bottom: 1px solid var(--line); }
.next-action-label { font-weight: 700; color: var(--navy); font-size: 14.5px; }
.next-action-hint { color: var(--muted); font-size: 12.5px; margin-top: 2px; }

/* ---------- Scannable key/value rows (workflow redesign, layout pass) ----------
   Replaces stacked "Label: value" plain-text lines with a light two-column
   layout so status blocks (Approval, Invoice) can be scanned, not read
   line by line. Reuses existing .small/.text-muted tokens -- no new colors. */
.kv-row { display: flex; gap: 8px; padding: 3px 0; font-size: 12.5px; }
.kv-row .kv-label { color: var(--muted); flex: 0 0 130px; }
.kv-row .kv-value { color: var(--ink); flex: 1; min-width: 0; }
/* A status chip carrying a real invoice number and amount ("#T4S2026-000454 --
   Paid in full") is longer than this rail is wide. Letting it wrap stopped it
   being clipped, which was the right call, but it was only half the fix: the
   label's fixed 130px basis leaves the chip 73px in a 211px rail, so the chip
   became a three-line lozenge of wrapped text -- legible, and clearly broken.

   A row whose value IS a chip stacks instead: label on its own line, chip
   underneath at its natural width. Rows with ordinary values (the payment
   drawer's Subtotal / Tax / Total) keep the two-column form, which is what
   makes them scannable. :has() picks the right ones without touching a single
   piece of markup. */
.kv-row .kv-value.chip { white-space: normal; text-align: left; line-height: 1.35; }
.kv-row:has(> .kv-value.chip) {
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  padding: 5px 0;
}
.kv-row:has(> .kv-value.chip) .kv-label { flex: 0 0 auto; }
.kv-row:has(> .kv-value.chip) .kv-value.chip { flex: 0 0 auto; max-width: 100%; }

/* ---------- More UX Modernization utilities (Operations Console pass) ---------- */
.u-mt-6 { margin-top: 6px; }
.u-mt-4 { margin-top: 4px; }
.u-minw-420 { min-width: 420px; }
.u-minw-320 { min-width: 320px; }
.u-mb-14 { margin-bottom: 14px; }
.u-legend-row { padding: 0 18px 14px; display: flex; gap: 16px; font-size: 11px; color: var(--ink-soft); }
.u-chip-pad { padding: 2px 7px; }
.u-empty-note { padding: 16px 18px; color: var(--muted); font-size: 12.5px; }
.u-pad-v-8 { padding: 8px 0; }
.u-row-border { border-bottom: 1px solid var(--line-soft); }
.u-progress-track { background: var(--line-soft); border-radius: 999px; height: 6px; margin-bottom: 12px; overflow: hidden; }
.u-progress-fill { background: var(--blue); height: 100%; }

/* ---------- Schedule page utilities ---------- */
.u-max-w-180 { max-width: 180px; }
.u-max-w-170 { max-width: 170px; }
.u-max-w-160 { max-width: 160px; }
.u-max-w-150 { max-width: 150px; }
.u-max-w-130 { max-width: 130px; }
.u-fs-34 { font-size: 34px; }
.u-rowlist-grid { grid-template-columns: 90px 2fr 1fr 100px; }

/* ---------- System Reset ("Fresh Start") page utilities ---------- */
.u-icon-critical-gradient { background: linear-gradient(135deg, var(--critical), #a52a2f); }
.u-border-l-critical { border-left: 4px solid var(--critical); }
.u-max-w-320 { max-width: 320px; }
.u-list-plain { list-style: none; padding-left: 0; }
.u-mb-16 { margin-bottom: 16px; }

/* ---------- Import Batch Workspace utilities ---------- */
.qa-btn-secondary { background: #fff; color: var(--navy); border: 1px solid var(--line); }
.u-max-w-900 { max-width: 900px; }
.u-max-w-800 { max-width: 800px; }
.u-max-w-720 { max-width: 720px; }
.u-pad-20 { padding: 20px; }
.u-pad-16 { padding: 16px; }
.u-pad-14 { padding: 14px; }
.code-block { white-space: pre-wrap; background: #f8f9fa; padding: 10px; border-radius: 6px; }
.pre-scroll-200 { white-space: pre-wrap; max-height: 200px; overflow-y: auto; }
.u-pad-18 { padding: 18px; }
.u-h-18 { height: 18px; }
.u-h-22 { height: 22px; }
.u-activity-log { max-height: 320px; overflow-y: auto; padding: 10px; font-family: Consolas, Menlo, monospace; font-size: 12.5px; background: #0b1020; }

/* ---------- Import Templates table-column-width utilities ---------- */
.u-w-220 { width: 220px; }
.u-w-50 { width: 50px; }
.u-w-160 { width: 160px; }
.u-w-150 { width: 150px; }
.u-w-90 { width: 90px; }
.u-w-80 { width: 80px; }
.u-w-100 { width: 100px; }
.u-w-70 { width: 70px; }
.u-w-40 { width: 40px; }
.u-w-65 { width: 65px; }
.u-w-60 { width: 60px; }
.u-w-200 { width: 200px; }
.u-w-180 { width: 180px; }
.u-w-170 { width: 170px; }
.u-w-140 { width: 140px; }
.u-w-120 { width: 120px; }
.u-w-110 { width: 110px; }

/* ---------- Invoice Extras Edit utilities ---------- */
.u-flex-1 { flex: 1; }
.u-mt-12 { margin-top: 12px; }
.u-mt-14 { margin-top: 14px; }

/* ---------- Import Review utilities ---------- */
.u-pdf-embed { width: 100%; height: 480px; border: 1px solid var(--line-soft); }
.u-raw-text-preview { white-space: pre-wrap; max-height: 200px; overflow-y: auto; }
.u-scroll-panel-480 { max-height: 480px; overflow: auto; border: 1px solid var(--line-soft); }

/* ---------- List rows (two-line hierarchy) ---------- */
.rowlist { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); }
.rowlist-row { display: grid; grid-template-columns: 64px 2fr 1fr 120px 100px 40px; gap: 14px; align-items: center; padding: 16px 20px; border-bottom: 1px solid var(--line-soft); cursor: pointer; text-decoration: none; color: var(--ink); transition: background .1s; }
.rowlist-row:last-child { border-bottom: none; }
.rowlist-row:hover, .rowlist-row.selected { background: var(--blue-soft); }
.rowlist-row .rl-title { font-weight: 700; font-size: 14px; color: var(--ink); }
.rowlist-row .rl-sub { color: var(--muted); font-size: 12px; margin-top: 2px; }
.rowlist-row .rl-id { color: var(--muted-text); font-size: 12px; font-weight: 600; }
.rowlist-hd { display: grid; grid-template-columns: 64px 2fr 1fr 120px 100px 40px; gap: 14px; padding: 12px 20px; background: var(--bg); font-size: 10.5px; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); font-weight: 700; border-bottom: 1px solid var(--line); }
.rl-overflow { text-align: right; position: relative; }
.rl-overflow-btn { border: none; background: none; color: var(--muted); font-size: 18px; cursor: pointer; padding: 4px 8px; border-radius: 6px; }
.rl-overflow-btn:hover { background: var(--line-soft); }
.rl-overflow-menu { display: none; position: absolute; right: 0; top: 100%; background: #fff; border: 1px solid var(--line); border-radius: var(--radius-sm); box-shadow: var(--shadow-hover); min-width: 210px; z-index: 30; text-align: left; overflow: hidden; }
.rl-overflow-menu.show { display: block; }
.rl-overflow-menu a, .rl-overflow-menu button { display: block; width: 100%; text-align: left; padding: 9px 14px; color: var(--ink); text-decoration: none; background: none; border: none; font-size: 13px; cursor: pointer; }
.rl-overflow-menu a:hover, .rl-overflow-menu button:hover { background: var(--bg); }

/* ---------- Drawer content ---------- */
.drawer-hd { padding: 20px; border-bottom: 1px solid var(--line); background: var(--navy); color: #fff; }
.drawer-hd h5 { margin: 0; color: #fff; font-weight: 700; }
.drawer-hd .text-muted { color: #a9c2d9 !important; }
.drawer-close { float: right; background: rgba(255,255,255,.12); border: none; font-size: 16px; color: #fff; cursor: pointer; width: 28px; height: 28px; border-radius: 50%; }
.drawer-section { padding: 16px 20px; border-bottom: 1px solid var(--line-soft); }
.drawer-section h6 { margin: 0 0 10px; font-size: 10.5px; text-transform: uppercase; letter-spacing: .6px; color: var(--muted); font-weight: 700; }
.drawer-kv { display: flex; justify-content: space-between; padding: 5px 0; font-size: 13px; }
.drawer-kv .k { color: var(--muted); }
.drawer-actions { display: flex; flex-direction: column; gap: 8px; padding: 16px 20px; }
.drawer-actions button, .drawer-actions a { display: block; text-align: left; background: #fff; border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 10px 14px; font-size: 13px; font-weight: 600; color: var(--ink); text-decoration: none; cursor: pointer; }
.drawer-actions button:hover, .drawer-actions a:hover { border-color: var(--blue); color: var(--navy); background: var(--blue-soft); }
.drawer-actions .primary { background: var(--navy); color: #fff; border-color: var(--navy); }
.drawer-actions .primary:hover { background: var(--navy-soft); color: #fff; }

/* ---------- Workspace cards / sections ---------- */
.ws-card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); margin-bottom: 18px; box-shadow: var(--shadow); transition: box-shadow .15s ease; }
.ws-card-hd { padding: 16px 20px; border-bottom: 1px solid var(--line-soft); display: flex; justify-content: space-between; align-items: center; cursor: pointer; }
.ws-card-hd h6 { margin: 0; color: var(--navy); font-size: 14.5px; font-weight: 700; letter-spacing: .1px; }
.ws-card-body { padding: 20px; }
.ws-card.collapsed .ws-card-body { display: none; }
.ws-toggle { color: var(--muted-soft); font-size: 12px; }

/* ---------- Stat cards (dashboard) ---------- */
.stat-card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 18px 20px; box-shadow: var(--shadow); position: relative; overflow: hidden; text-decoration: none; color: var(--ink); display: block; transition: box-shadow .15s, transform .15s; }
.stat-card:hover { box-shadow: var(--shadow-hover); transform: translateY(-2px); color: var(--ink); }
.stat-card::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; background: var(--accent, var(--blue)); }
.stat-card .stat-icon { width: 36px; height: 36px; border-radius: 10px; background: var(--accent-soft, var(--blue-soft)); color: var(--accent, var(--blue)); display: flex; align-items: center; justify-content: center; margin-bottom: 12px; font-size: 16px; }
.stat-card .stat-num { font-size: 30px; font-weight: 800; color: var(--navy); line-height: 1; }
.stat-card .stat-label { color: var(--muted); font-size: 12.5px; font-weight: 600; margin-top: 6px; }
.stat-card .stat-trend { font-size: 11.5px; color: var(--muted-text); margin-top: 4px; }

/* ---------- Needs Attention panel ---------- */
.attn-panel { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; }
.attn-hd { padding: 16px 20px; border-bottom: 1px solid var(--line-soft); display: flex; align-items: center; gap: 8px; }
.attn-hd h6 { margin: 0; color: var(--navy); font-size: 14.5px; font-weight: 700; }
.attn-item { display: flex; align-items: flex-start; gap: 12px; padding: 13px 20px; border-bottom: 1px solid var(--line-soft); text-decoration: none; color: var(--ink); transition: background .1s; }
.attn-item:last-child { border-bottom: none; }
.attn-item:hover { background: var(--bg); color: var(--ink); }
.attn-dot { width: 8px; height: 8px; border-radius: 50%; margin-top: 6px; flex: 0 0 8px; background: var(--amber); }
.attn-dot.urgent { background: var(--red); }
.attn-dot.info { background: var(--blue); }
.attn-text { font-size: 13px; }
.attn-empty { padding: 24px 20px; text-align: center; color: var(--muted); font-size: 13px; }

/* ---------- Timeline ---------- */
.timeline { position: relative; padding-left: 26px; }
.timeline::before { content: ''; position: absolute; left: 7px; top: 4px; bottom: 4px; width: 2px; background: var(--line); }
.tl-item { position: relative; padding-bottom: 18px; }
.tl-item::before { content: ''; position: absolute; left: -26px; top: 3px; width: 12px; height: 12px; border-radius: 50%; background: var(--blue); border: 3px solid #fff; box-shadow: 0 0 0 1px var(--line); }
.tl-time { color: var(--muted); font-size: 11.5px; font-weight: 600; }
.tl-body { font-size: 13.5px; margin-top: 3px; }

/* ---------- Empty state ---------- */
.empty-state { text-align: center; padding: 48px 20px; color: var(--muted); }
.empty-state .es-icon { font-size: 30px; margin-bottom: 10px; opacity: .5; }

/* ---------- Bootstrap harmonization (tables/forms already in use on
   not-yet-migrated pages benefit too, without a second design system) ---------- */
.table { font-size: 13.5px; }
.table thead th { font-size: 11px; text-transform: uppercase; letter-spacing: .4px; color: var(--th-text); font-weight: 700; border-bottom-width: 1px; background: var(--line-soft); }
.table td, .table th { padding: 12px 14px; vertical-align: middle; }
.table-hover tbody tr { transition: background-color .1s ease; }
.table-hover tbody tr:hover { background-color: var(--blue-soft); }
.card { border-radius: var(--radius); border-color: var(--line); box-shadow: var(--shadow); }
.form-control, .form-select { border-radius: var(--radius-sm); border-color: var(--line); font-size: 13.5px; padding: 9px 13px; transition: border-color .12s ease, box-shadow .12s ease; }
/* DEFECT-SOA-04 fix: the uniform 13px right padding above left no room for
   the native <select> dropdown arrow, so the last 1-2 characters of a
   tight-width (.u-w-auto) select's selected option -- e.g. the ticket
   header's priority/technician pills -- rendered clipped behind the arrow
   icon. Bootstrap's own default is 2.25rem; restore enough clearance on
   selects specifically without touching text-input padding. */
.form-select { padding-right: 2.25rem; }
.form-control:focus, .form-select:focus { border-color: var(--blue); box-shadow: 0 0 0 4px var(--blue-soft); }
.form-control.is-invalid, .form-select.is-invalid { border-color: var(--red); }
.form-control.is-invalid:focus, .form-select.is-invalid:focus { box-shadow: 0 0 0 4px var(--red-soft); }
.form-label { font-weight: 600; font-size: 12.5px; color: var(--ink); }
.form-label .req-mark { color: var(--red); margin-left: 2px; }

/* ---------- Professional button system (REQ-51-01 visual pass) ----------
   Every Bootstrap .btn-* variant gets a real hover elevation (shadow lift,
   not just a color swap), a pressed/active state, and a focus ring for
   keyboard/touch accessibility. Semantic variants map to the real actions
   this app has: primary, secondary, warning, danger/delete, success/
   approval. .btn-ic is an optional icon-plus-label layout any button can
   opt into without changing its semantic class. */
.btn { border-radius: var(--radius-sm); font-weight: 600; font-size: 13px; padding: 8px 16px; border-width: 1.5px; transition: background-color .12s ease, border-color .12s ease, box-shadow .12s ease, transform .08s ease; }
.btn:hover { box-shadow: var(--shadow-hover); transform: translateY(-1px); }
.btn:active { transform: translateY(0); box-shadow: none; }
.btn:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
.btn:disabled, .btn.disabled { transform: none; box-shadow: none; opacity: .55; }
.btn-sm { padding: 6px 12px; font-size: 12.5px; }
.btn-lg, .btn-touch { padding: 13px 22px; font-size: 15px; min-height: 48px; }
/* Premium hero CTA -- overrides .btn-sm's padding/font-size (later in the
   cascade, same specificity) so the one primary next-step button on a
   workspace page reads as unmissable, not just another small button. */
.next-action-btn { white-space: nowrap; padding: 10px 22px; font-size: 14px; font-weight: 700; }

.btn-primary, .btn-brand { background: var(--navy); border-color: var(--navy); box-shadow: 0 1px 2px rgba(15,42,68,.15), 0 3px 8px rgba(15,42,68,.12); }
.btn-primary:hover, .btn-brand:hover { background: var(--navy-soft); border-color: var(--navy-soft); color: #fff; box-shadow: 0 2px 5px rgba(15,42,68,.18), 0 8px 18px rgba(15,42,68,.18); }
.btn-outline-primary { color: var(--navy); border-color: var(--navy); background: transparent; }
.btn-outline-primary:hover { background: var(--navy); border-color: var(--navy); color: #fff; }

.btn-secondary, .btn-outline-secondary { color: var(--navy); border-color: var(--line); background: #fff; }
.btn-outline-secondary:hover { background: var(--line-soft); border-color: var(--muted-soft); color: var(--navy); }

/* --green-fill (#157A53), not --green (#1f9d55): white on --green measures
   3.49:1 and fails AA. --green-fill is the token the earlier contrast pass
   already produced for exactly this; this button was simply never audited. */
.btn-success { background: var(--green-fill); border-color: var(--green-fill); color: #fff; }
.btn-success:hover { background: #197f45; border-color: #197f45; color: #fff; }
.btn-outline-success { color: var(--green-text); border-color: var(--green); background: transparent; }
.btn-outline-success:hover { background: var(--green-fill); border-color: var(--green-fill); color: #fff; }

.btn-warning { background: var(--amber); border-color: var(--amber); color: #fff; }
.btn-warning:hover { background: #d98c00; border-color: #d98c00; color: #fff; }
.btn-outline-warning { color: var(--amber-text); border-color: var(--amber); background: transparent; }
.btn-outline-warning:hover { background: var(--amber-fill); border-color: var(--amber-fill); color: #fff; }

.btn-danger { background: var(--red); border-color: var(--red); color: #fff; }
.btn-danger:hover { background: #b82732; border-color: #b82732; color: #fff; }
.btn-outline-danger { color: var(--red-text); border-color: var(--red); background: transparent; }
.btn-outline-danger:hover { background: var(--red-fill); border-color: var(--red-fill); color: #fff; }

.btn-ic { display: inline-flex; align-items: center; justify-content: center; gap: 7px; }
.btn-ic svg { flex: 0 0 auto; width: 15px; height: 15px; }

/* ---------- Workspace identity header (customer/company/invoice pages) ---------- */
.ws-identity { display: flex; align-items: center; gap: 18px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); padding: 20px 24px; margin-bottom: 18px; flex-wrap: wrap; }
.ws-identity .ws-avatar { width: 52px; height: 52px; border-radius: 14px; background: var(--navy); color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 20px; flex: 0 0 auto; }
.ws-identity .ws-id-main { flex: 1; min-width: 200px; }
.ws-identity h3 { margin: 0; color: var(--navy); font-weight: 800; font-size: 21px; }
.ws-identity .ws-id-sub { color: var(--muted); font-size: 13px; margin-top: 2px; }
.ws-chips { display: flex; gap: 10px; flex-wrap: wrap; }
.ws-chip { background: var(--bg); border-radius: var(--radius-sm); padding: 8px 14px; text-align: left; min-width: 92px; }
.ws-chip .wc-label { font-size: 10px; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); font-weight: 700; }
.ws-chip .wc-value { font-size: 16px; font-weight: 800; color: var(--navy); margin-top: 2px; }
.ws-chip.warn .wc-value { color: var(--amber); } .ws-chip.danger .wc-value { color: var(--red); } .ws-chip.good .wc-value { color: var(--green); }
.ws-id-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* ---------- Workspace pill tabs (replaces Bootstrap nav-tabs visually; still
   wires to data-bs-toggle="tab" / data-bs-target so existing Bootstrap tab
   JS keeps working unchanged) ---------- */
.ws-tabs { display: flex; gap: 4px; flex-wrap: wrap; background: var(--surface); border: 1px solid var(--line); border-radius: 999px; padding: 5px; margin-bottom: 18px; box-shadow: var(--shadow); }
.ws-tabs .nav-link, .ws-tabs .ws-tab { border: none; background: none; color: var(--muted); font-size: 13px; font-weight: 600; padding: 9px 16px; border-radius: 999px; cursor: pointer; transition: all .12s; white-space: nowrap; }
.ws-tabs .nav-link:hover, .ws-tabs .ws-tab:hover { color: var(--navy); background: var(--bg); }
.ws-tabs .nav-link.active, .ws-tabs .ws-tab.active { background: var(--navy); color: #fff; box-shadow: 0 2px 8px rgba(15,42,68,.25); }

/* ---------- Two-column workspace layout (main + right rail) ---------- */
.ws-layout { display: grid; grid-template-columns: 1fr 300px; gap: 18px; align-items: start; }
.ws-rail .ws-card { margin-bottom: 14px; }
@media (max-width: 992px) { .ws-layout { grid-template-columns: 1fr; } }

/* ---------- Breadcrumbs ---------- */
.ws-breadcrumb { font-size: 12.5px; color: var(--muted); margin-bottom: 6px; }
.ws-breadcrumb a { color: var(--muted); text-decoration: none; } .ws-breadcrumb a:hover { color: var(--blue); }
.ws-breadcrumb .sep { margin: 0 6px; color: var(--muted-soft); }

/* ---------- Responsive ---------- */
@media (max-width: 992px) {
  .shell-nav { position: fixed; z-index: 40; left: -252px; }
  .shell-nav.mobile-open { left: 0; }
  .shell-drawer { position: fixed; right: 0; top: 0; bottom: 0; z-index: 40; }
  .shell-workspace { padding: 18px; }
  .rowlist-row, .rowlist-hd { grid-template-columns: 48px 1fr 90px 32px; }
  .rowlist-row .rl-col-customer, .rowlist-row .rl-col-updated, .rowlist-hd .rl-col-customer, .rowlist-hd .rl-col-updated { display: none; }
}

/* =====================================================================
   OPERATIONS CONSOLE COMPONENTS (v3) — Console, Board, Work Approval,
   Import Centre, and the generalized entity-workspace template. Mono
   type for anything that's a code/timestamp/dollar figure; semantic
   status colors kept separate from --accent throughout.
   ===================================================================== */
.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* ---------- status chip ---------- */
.chip { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; font-weight: 700; padding: 4px 10px; border-radius: 999px; letter-spacing: .2px; white-space: nowrap; }
.chip .sdot { width: 6px; height: 6px; border-radius: 50%; flex: 0 0 auto; }
.chip.live { background: var(--live-soft); color: var(--green-text); } .chip.live .sdot { background: var(--live); animation: t4pulse2 2s infinite; }
.chip.wait { background: var(--wait-soft); color: var(--amber-text); } .chip.wait .sdot { background: var(--wait); }
.chip.crit { background: var(--critical-soft); color: var(--red-text); } .chip.crit .sdot { background: var(--critical); }
.chip.info { background: var(--info-soft); color: var(--blue-text); } .chip.info .sdot { background: var(--info); }
.chip.violet { background: var(--violet-soft); color: var(--violet-text); } .chip.violet .sdot { background: var(--violet); }
.chip.neutral { background: var(--line-soft); color: var(--muted); } .chip.neutral .sdot { background: var(--muted); }
@keyframes t4pulse2 { 0%,100%{box-shadow:0 0 0 0 rgba(31,157,85,.45)} 70%{box-shadow:0 0 0 5px rgba(31,157,85,0)} }

/* ---------- Now strip (live technician status) ---------- */
.now-strip { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 6px; margin-bottom: 16px; }
.now-card { flex: 0 0 auto; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 10px 13px; min-width: 184px; box-shadow: var(--shadow); }
.now-card .nm { display: flex; align-items: center; gap: 8px; font-weight: 700; font-size: 12.5px; }
.now-card .av { width: 22px; height: 22px; border-radius: 50%; background: var(--navy); color: #fff; font-size: 9.5px; font-weight: 700; display: flex; align-items: center; justify-content: center; }
.now-card .job { color: var(--muted); font-size: 11px; margin-top: 6px; }
.now-card .elapsed { font-family: var(--mono); font-size: 10.5px; color: var(--ink-soft); margin-top: 4px; }

/* ---------- Today ticker (revenue/completed/utilization) ---------- */
.ticker { display: flex; background: var(--navy); border-radius: var(--radius-sm); margin-bottom: 16px; overflow-x: auto; }
.tick { flex: 1; padding: 12px 18px; border-right: 1px solid rgba(255,255,255,.1); min-width: 128px; }
.tick:last-child { border-right: none; }
.tick .l { font-size: 10px; text-transform: uppercase; letter-spacing: .5px; color: #8fb3cc; font-weight: 700; }
.tick .v { font-family: var(--mono); font-size: 19px; font-weight: 700; color: #fff; margin-top: 3px; }
.tick .v span { font-size: 12px; color: #8fb3cc; font-weight: 600; }

/* ---------- Needs Attention list (severity-striped rows) ---------- */
.panel { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; }
.panel-hd { display: flex; justify-content: space-between; align-items: center; padding: 12px 18px; border-bottom: 1px solid var(--line-soft); }
.panel-hd h3, .panel-hd h6 { font-size: 13px; font-weight: 700; margin: 0; }
.panel-hd .cnt { font-family: var(--mono); color: var(--muted); font-size: 11.5px; }

/* ---------- Action-first dashboard (REQ-51-01 IA pass) ----------
   Needs Attention is the thing a person actually opens the dashboard to
   act on -- it now leads the page and reads with real visual weight.
   The old full-width dark .ticker (Revenue/Jobs/Utilization/etc.) is
   demoted to .ticker-compact: reference numbers, not the headline. */
.panel-primary { box-shadow: var(--shadow-hover); border-top: 3px solid var(--blue); }
.ticker-compact { display: flex; flex-wrap: wrap; gap: 0; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm); margin-bottom: 14px; overflow: hidden; }
.tickc { flex: 1; min-width: 84px; padding: 8px 12px; border-right: 1px solid var(--line-soft); overflow: hidden; }
.tickc:last-child { border-right: none; }
.tickc .l { font-size: 9px; text-transform: uppercase; letter-spacing: .4px; color: var(--muted); font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tickc .v { font-family: var(--mono); font-size: 14px; font-weight: 700; color: var(--navy); margin-top: 1px; }
.tickc .v span { font-size: 10.5px; color: var(--muted); font-weight: 600; }
.attn-row { display: flex; align-items: center; gap: 12px; padding: 11px 18px; border-bottom: 1px solid var(--line-soft); text-decoration: none; color: var(--ink); }
.attn-row:last-child { border-bottom: none; }
.attn-row:hover { background: var(--surface-2); color: var(--ink); }
.stripe { width: 3px; align-self: stretch; border-radius: 2px; flex: 0 0 3px; }
.stripe.crit { background: var(--critical); } .stripe.wait { background: var(--wait); } .stripe.info { background: var(--info); } .stripe.violet { background: var(--violet); }
.attn-txt { flex: 1; font-size: 13px; }
.attn-txt b { font-weight: 700; }
.attn-meta { color: var(--muted); font-size: 11.5px; }
.attn-go { color: var(--muted); font-size: 16px; }

/* ---------- Today axis (drop-off/pickup time strip) ---------- */
.today-axis { position: relative; height: 60px; background: var(--surface-2); border-radius: var(--radius-sm); margin: 0 18px 12px; border: 1px solid var(--line-soft); }
.today-axis .hr { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--line); }
.today-axis .hrl { position: absolute; top: 4px; font-size: 10px; color: var(--muted); font-family: var(--mono); transform: translateX(-50%); }
.today-axis .now-line { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--critical); }
.today-axis .now-line::after { content: 'NOW'; position: absolute; top: -2px; left: 5px; font-size: 9px; font-weight: 800; color: var(--critical); letter-spacing: .5px; white-space: nowrap; }
.today-axis .now-line-end::after { left: auto; right: 5px; }
.today-mark { position: absolute; bottom: 8px; width: 9px; height: 9px; border-radius: 50%; transform: translateX(-50%); border: 2px solid var(--surface); }
.today-mark.dropoff { background: var(--wait); } .today-mark.pickup { background: var(--info); } .today-mark.appointment { background: var(--violet); }

/* ---------- Kanban board (Ticket Board, Inventory) — "k" prefix avoids
   colliding with Bootstrap's .card/.col grid classes already loaded ---------- */
.kboard { display: flex; gap: 12px; overflow-x: auto; padding-bottom: 10px; align-items: flex-start; }
.kcol { flex: 0 0 228px; background: var(--surface-2); border: 1px solid var(--line-soft); border-radius: var(--radius-sm); display: flex; flex-direction: column; max-height: calc(100vh - 300px); }
.kcol-hd { padding: 9px 11px; border-bottom: 1px solid var(--line-soft); display: flex; justify-content: space-between; align-items: center; font-size: 11px; font-weight: 750; text-transform: uppercase; letter-spacing: .3px; color: var(--ink-soft); }
.kcol-hd .n { font-family: var(--mono); color: var(--muted); background: var(--surface); border-radius: 6px; padding: 1px 7px; }
.kcol-body { padding: 8px; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; flex: 1; min-height: 40px; }
.kcard { background: var(--surface); border: 1px solid var(--line); border-radius: 9px; padding: 9px 10px; box-shadow: var(--shadow); cursor: grab; text-decoration: none; color: var(--ink); display: block; }
.kcard:hover { color: var(--ink); box-shadow: var(--shadow-hover); }
.kcard:active { cursor: grabbing; }
.kcard.drag { opacity: .4; }
.kcard .top { display: flex; justify-content: space-between; align-items: flex-start; gap: 6px; }
.kcard .wo { font-family: var(--mono); font-size: 10px; color: var(--muted); }
.kcard .pr { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 7px; margin-top: 3px; }
.kcard .pr.low { background: #9AA7B5; } .kcard .pr.normal { background: var(--info); } .kcard .pr.high { background: var(--wait); } .kcard .pr.urgent { background: var(--critical); }
.kcard .ttl { font-size: 12px; font-weight: 650; margin-top: 3px; }
.kcard .cust { font-size: 11px; color: var(--muted); margin-top: 2px; }
.kcard .foot { display: flex; justify-content: space-between; align-items: center; margin-top: 8px; }
.kcard .tech { display: flex; align-items: center; gap: 5px; font-size: 10.5px; color: var(--ink-soft); }
.kcard .tech .av { width: 17px; height: 17px; border-radius: 50%; background: var(--navy); color: #fff; font-size: 8px; font-weight: 700; display: flex; align-items: center; justify-content: center; }
.kcol.dragover { outline: 2px dashed var(--accent); outline-offset: -2px; }

/* ---------- segmented control (service-type switcher) ---------- */
.seg { display: inline-flex; background: var(--surface-2); border: 1px solid var(--line); border-radius: 999px; padding: 3px; margin-bottom: 14px; max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.seg button { border: none; background: none; padding: 7px 15px; border-radius: 999px; font-size: 12.5px; font-weight: 650; color: var(--muted); cursor: pointer; white-space: nowrap; flex: 0 0 auto; }
.seg button.on { background: var(--navy); color: #fff; }

/* ---------- Generalized entity workspace: identity + left section-nav +
   center + quick-action rail, all inside one bordered box ---------- */
.wsx-wrap { display: grid; grid-template-columns: 180px 1fr 240px; gap: 0; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; min-height: 540px; }
.wsx-id { grid-column: 1 / -1; padding: 16px 20px; border-bottom: 1px solid var(--line); display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.wsx-id .av { width: 42px; height: 42px; border-radius: 11px; background: var(--navy); color: #fff; font-weight: 800; font-size: 16px; display: flex; align-items: center; justify-content: center; flex: 0 0 auto; }
.wsx-id h3 { font-size: 16.5px; margin: 0; } .wsx-id .sub { color: var(--muted); font-size: 12px; margin-top: 2px; }
.wsx-chips { display: flex; gap: 8px; margin-left: auto; flex-wrap: wrap; }
/* Groups a page's quick-edit selects (status/priority/assignee/etc.) into
   one bordered cluster instead of loose floating dropdowns -- reads as one
   "ticket properties" module, not four separate controls competing for
   attention next to the title. */
.meta-cluster { display: flex; gap: 4px; align-items: center; background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 4px 6px; flex-wrap: wrap; }
.meta-cluster .form-select { border: 1px solid transparent; background-color: transparent; box-shadow: none; }
.meta-cluster .form-select:hover { border-color: var(--line); background-color: var(--surface); }
.meta-cluster .form-select:focus { border-color: var(--blue); background-color: var(--surface); box-shadow: 0 0 0 3px var(--blue-soft); }
.wsx-chip { background: var(--surface-2); border-radius: 9px; padding: 6px 12px; text-align: center; min-width: 74px; }
.wsx-chip .l { font-size: 9px; text-transform: uppercase; letter-spacing: .4px; color: var(--muted); font-weight: 700; }
.wsx-chip .v { font-family: var(--mono); font-weight: 750; font-size: 13.5px; margin-top: 2px; }
.wsx-nav { border-right: 1px solid var(--line-soft); padding: 10px 7px; background: var(--surface-2); overflow-y: auto; }
.wsx-nav button { display: block; width: 100%; text-align: left; border: none; background: none; padding: 7px 10px; border-radius: 8px; font-size: 12px; font-weight: 600; color: var(--ink-soft); margin-bottom: 1px; cursor: pointer; }
.wsx-nav button:hover { background: var(--surface); }
.wsx-nav button.on { background: var(--navy); color: #fff; }
.wsx-nav-group { margin-bottom: 6px; }
.wsx-nav-group-label { font-size: 9.5px; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); font-weight: 700; padding: 9px 10px 3px; }
.wsx-center { padding: 18px 22px; overflow-y: auto; }
.wsx-center-full { grid-column: 1 / -1; padding: 22px 24px; }
.wsx-section { display: none; }
.wsx-section.on { display: block; }
.wsx-rail { border-left: 1px solid var(--line-soft); padding: 14px; background: var(--surface-2); overflow-y: auto; }
.wsx-rail h4 { font-size: 10px; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); margin-bottom: 8px; }
.wsx-actions { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
.wsx-actions a, .wsx-actions button { text-align: left; background: var(--surface); border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px; font-size: 12px; font-weight: 600; color: var(--ink); text-decoration: none; display: block; cursor: pointer; }
.wsx-actions a:hover, .wsx-actions button:hover { border-color: var(--accent); color: var(--accent); }
.wsx-actions .pri { background: var(--navy); color: #fff; border-color: var(--navy); }
.wsx-actions .pri:hover { color: #fff; background: var(--navy-mid); }
.wsx-note { font-size: 12px; color: var(--ink-soft); line-height: 1.8; }
@media (max-width: 900px) {
  .wsx-wrap { grid-template-columns: 1fr; }
  .wsx-nav { display: flex; overflow-x: auto; border-right: none; border-bottom: 1px solid var(--line-soft); padding: 8px; }
  .wsx-nav button { width: auto; white-space: nowrap; }
  .wsx-nav-group { display: flex; align-items: center; margin-bottom: 0; }
  .wsx-nav-group-label { display: none; }
  .wsx-rail { border-left: none; border-top: 1px solid var(--line-soft); }
}
/* DEFECT-SOA-05's mobile .ws-switch-bar override used to live in the
   @media block above, but the BASE (non-media) .ws-switch-bar/.ws-switch/
   .ws-switch-tools rules are declared further down this file (POS
   Workspace v1 section) -- with equal selector specificity, the base
   rules' later source position silently won at every viewport, including
   mobile, completely defeating the fix (found by a fresh UI audit:
   .ws-switch-bar's justify-content:center, never overridden, trapped
   left-side overflow content -- e.g. customer_view.php's "History" tab --
   permanently unreachable, since centering an overflowing flex row makes
   only the right side scrollable). Real CSS/cascade rule: a later
   same-specificity selector always wins regardless of which one is inside
   a media query, so this must be declared AFTER the base rules, not
   before them. See the real mobile override below, after the POS
   Workspace v1 base rules it needs to override. */

/* ---------- POS Workspace v1 (Ticket/Customer workspace redesign) ----------
   Reuses .wsx-wrap's 3-column grid and .wsx-id header exactly as-is.
   Column 1 (was .wsx-nav, a tab list) becomes a read-only context rail.
   Column 3 (was .wsx-rail, a flat link list) becomes a real action dock
   with one computed primary action plus progressive (enabled/disabled)
   secondary actions. The center column's old "tabs" become a 2-screen
   segmented switcher -- click swaps the whole panel instantly, nothing is
   hidden behind a browser-tab-style row. */
/* DEFECT-SOA-05 root cause: .ws-col-c (the grid's center column) had no
   rule at all -- as a CSS grid item its default min-width is `auto`, so it
   expanded to fit its widest child (the tab row) instead of respecting the
   grid's 1fr track, which is what actually caused .ws-switch-bar's
   overflow-x:auto below to have nothing to scroll (its own box had already
   grown to fit the content). min-width:0 makes the column actually honor
   the grid track width on every viewport. */
.ws-col-c { min-width: 0; }
.ws-context { border-right: 1px solid var(--line-soft); padding: 20px 18px; background: var(--surface); overflow-y: auto; }
.ws-context .av { width: 36px; height: 36px; border-radius: 10px; background: var(--navy); color: #fff; font-weight: 800; font-size: 13px; display: flex; align-items: center; justify-content: center; margin-bottom: 10px; }
.ws-fact { margin-bottom: 12px; }
.ws-fact .l { font-size: 9.5px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); font-weight: 800; margin-bottom: 2px; }
.ws-fact .v { font-size: 14.5px; font-weight: 800; color: var(--ink); }
.ws-section-label { font-size: 9.5px; text-transform: uppercase; letter-spacing: .07em; color: var(--muted); font-weight: 800; margin-bottom: 8px; }
.ws-hairline { height: 1px; background: var(--line-soft); border: none; margin: 14px 0; }
.ws-hairline-tight { margin: 8px 0; }
.ws-device-row { display: flex; align-items: center; gap: 8px; padding: 7px 0; font-size: 12px; }
.ws-device-row.current { background: var(--blue-soft); margin: 0 -8px; padding: 7px 8px; border-radius: 8px; }
.ws-device-row .t { font-weight: 700; } .ws-device-row .s { font-size: 10.5px; color: var(--muted); }
.ws-device-access { font-size: 10.5px; font-weight: 700; color: var(--critical); margin-top: 2px; }
.ws-rep-row { padding: 7px 0; border-bottom: 1px solid var(--line-soft); font-size: 12px; }
.ws-rep-row:last-child { border-bottom: none; }
.ws-rep-row .t { font-weight: 700; } .ws-rep-row .meta { font-size: 10.5px; color: var(--muted); margin-top: 1px; display: flex; justify-content: space-between; }

/* DEFECT-SOA-05/09 fix: .ws-switch-bar's combination of
   justify-content:center plus .ws-switch-tools' margin-left:auto only
   ever worked when the tab group and the tools button both happened to
   fit -- on a real customer with enough tabs (7 on Customer Workspace, 8
   on Ticket Workspace) the tools button (e.g. "Follow-Ups") got pushed
   past the center column's actual width and visually clipped/overlapped
   the last tab, confirmed via real screenshot at a plain 1440px desktop
   viewport, not just narrow mobile -- this was never really a "mobile"
   bug, it's a "content wider than the center column" bug. An earlier fix
   attempt made the whole bar horizontally scrollable, which technically
   removed the overlap but was worse in practice: the tools button just
   scrolled silently out of the visible area with no on-screen affordance
   that it existed at all. Instead, let the bar wrap: the tab group keeps
   shrinking naturally the way it already did (that part was never
   broken -- tab labels fit fine at every width tried), but the tools
   button now drops to its own fully-visible second line when it doesn't
   fit, rather than ever being clipped or scrolled off. Narrow mobile,
   where even the tab labels alone can't fit one line, keeps its own
   dedicated scroll treatment below (unchanged from the original
   DEFECT-SOA-05 fix). */
.ws-switch-bar { padding: 14px 16px; border-bottom: 1px solid var(--line-soft); display: flex; flex-wrap: wrap; align-items: center; gap: 10px; background: var(--surface); }
/* When the eight screen tabs and the two tools do not fit on one line, the
   tools wrapped to a second row and landed under the tab pills looking like two
   more tabs that someone had forgotten to style. Pushing them to the far right
   and giving them a divider says what they are -- tools that act on the ticket,
   not places to go -- whether they wrap or not. */
.ws-switch-tools { margin-left: auto; padding-left: 12px; border-left: 1px solid var(--line); }
@media (max-width: 640px) { .ws-switch-tools { margin-left: 0; padding-left: 0; border-left: none; } }
.ws-switch-tools button {
  display: inline-flex; align-items: center; gap: 5px;
  border: 1px solid var(--line); background: var(--surface);
}
.ws-switch-tools button:hover { border-color: #c3d3e2; }
/* The count beside a tab label. Quiet by default, and it inverts on the
   active tab so it stays legible on the dark pill. */
.ws-switch button .ws-switch-n {
  display: inline-block; margin-left: 6px; padding: 1px 6px;
  border-radius: 999px; background: var(--line-soft); color: var(--th-text);
  font-size: 10.5px; font-weight: 700; line-height: 1.5;
}
.ws-switch button.on .ws-switch-n { background: rgba(255,255,255,.22); color: #fff; }
.ws-switch { display: inline-flex; background: var(--surface-2); border-radius: 10px; padding: 3px; gap: 2px; }
.ws-switch button { border: none; background: none; padding: 7px 14px; border-radius: 8px; font-size: 12px; font-weight: 700; color: var(--muted); cursor: pointer; white-space: nowrap; }
.ws-switch button.on { background: var(--surface); color: var(--navy); box-shadow: 0 1px 3px rgba(15,42,68,.12); }
.ws-switch-tools { display: flex; gap: 4px; flex-shrink: 0; }
.ws-switch-tools button { border: none; background: none; color: var(--muted); font-size: 11.5px; font-weight: 650; padding: 6px 10px; border-radius: 7px; cursor: pointer; white-space: nowrap; }
.ws-switch-tools button:hover { background: var(--surface-2); color: var(--navy); }
@media (max-width: 900px) {
  .ws-switch { max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; flex-shrink: 0; }
  .ws-switch::-webkit-scrollbar { height: 4px; }
  .ws-switch button { flex-shrink: 0; }
}
.ws-screen { padding: 22px 24px 28px; display: none; flex-direction: column; gap: 20px; }
.ws-screen.on { display: flex; }
.parts-status-select { min-width: 112px; } /* fits "Backordered", the longest option -- was clipping to "Installec" when a shorter previous width carried over */
.ws-blk-hd { display: flex; justify-content: space-between; align-items: center; margin-bottom: 9px; }
.ws-blk-hd h4 { margin: 0; font-size: 13px; font-weight: 800; }
.ws-blk-hd-gap { margin-top: 16px; }
.ws-context-name { font-weight: 800; font-size: 13px; }
.ws-context-sub { font-size: 11px; color: var(--muted); margin-bottom: 14px; }
.ws-context-contact { font-size: 12px; line-height: 1.8; margin-bottom: 4px; overflow-wrap: anywhere; }
.ws-context-block { font-size: 12px; line-height: 1.6; }
.ws-section-note { text-transform: none; letter-spacing: normal; font-size: 11px; }
.ws-subtitle-note { margin-top: -8px; }

.ws-dock { border-left: 1px solid var(--line-soft); padding: 18px 14px; background: var(--surface); overflow-y: auto; display: flex; flex-direction: column; gap: 8px; }
.dock-primary { background: var(--navy); color: #fff; border-radius: 12px; padding: 14px 15px; box-shadow: 0 3px 10px rgba(15,42,68,.24); margin-bottom: 6px; border: none; text-align: left; width: 100%; cursor: pointer; }
.dock-primary .lbl { font-size: 9.5px; text-transform: uppercase; letter-spacing: .07em; opacity: .7; }
.dock-primary .act { font-size: 14px; font-weight: 800; margin-top: 2px; }
.dock-primary .hint { font-size: 10.5px; opacity: .78; margin-top: 3px; }
.dock-primary:disabled { cursor: default; opacity: .55; box-shadow: none; }
.dock-btn { display: flex; align-items: center; gap: 9px; padding: 9px 7px; font-size: 12.5px; font-weight: 650; color: var(--navy); background: none; border: none; text-align: left; width: 100%; cursor: pointer; border-radius: 7px; text-decoration: none; }
.dock-btn:hover:not(:disabled) { background: var(--surface-2); }
.dock-btn:disabled { color: var(--muted-soft); cursor: default; }
.dock-btn .dock-ico { flex: 0 0 auto; width: 16px; height: 16px; color: var(--muted); }
.dock-btn .dock-ico svg { width: 100%; height: 100%; }
.dock-btn:hover:not(:disabled) .dock-ico { color: var(--blue); }
.dock-btn:disabled .dock-ico { color: var(--muted-soft); }
.dock-btn-sm { padding: 6px 0; font-size: 11px; }

/* embedded Invoice/Payment drawer -- opens over the current workspace,
   never a page navigation. Reuses the .t4-dialog-overlay show/hide
   convention (toggle the "open" class) so it gets the same ESC/backdrop
   handling shell.js already wires up for every .t4-dialog on the page. */
.pay-drawer-overlay { position: fixed; inset: 0; background: rgba(9,20,32,.44); display: none; align-items: stretch; justify-content: flex-end; z-index: 1200; }
.pay-drawer-overlay.open { display: flex; }
.pay-drawer { width: min(360px, 100%); background: var(--navy-deep); color: #fff; height: 100%; padding: 24px 22px; display: flex; flex-direction: column; box-shadow: -10px 0 32px rgba(0,0,0,.28); overflow-y: auto; }
.pay-drawer-hd { display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px; }
.pay-drawer-hd b { font-size: 14px; }
.pay-drawer-hd .x { color: #9fb6cc; font-size: 15px; cursor: pointer; background: none; border: none; }
.pay-drawer-sub { font-size: 11px; color: #9fb6cc; margin-bottom: 18px; }
.pay-line-grp { font-size: 9.5px; text-transform: uppercase; letter-spacing: .06em; color: #7d99b3; font-weight: 800; margin: 14px 0 4px; }
.pay-line { display: flex; justify-content: space-between; font-size: 11.5px; padding: 6px 0; color: #cfe0ee; }
.pay-line.total { border-top: 1px solid rgba(255,255,255,.15); margin-top: 6px; padding-top: 12px; color: #fff; font-weight: 800; font-size: 13px; }
.pay-total-label { font-size: 10.5px; color: #9fb6cc; text-transform: uppercase; letter-spacing: .07em; }
.pay-total { font-family: var(--mono); font-size: 32px; font-weight: 800; margin: 4px 0 16px; }
.pay-method { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 16px; }
.pay-method label { background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.1); border-radius: 9px; padding: 11px; text-align: center; font-size: 11.5px; font-weight: 700; cursor: pointer; display: block; }
.pay-method input { position: absolute; opacity: 0; pointer-events: none; }
.pay-method input:checked + label, .pay-method label.on { background: var(--blue); border-color: var(--blue); }
.pay-amount-input { width: 100%; background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.18); border-radius: 9px; padding: 10px 12px; color: #fff; font-family: var(--mono); font-size: 16px; font-weight: 700; margin-bottom: 16px; }
.pay-btn { text-align: center; padding: 14px; border-radius: 10px; font-weight: 800; font-size: 12.5px; border: none; cursor: pointer; }
.pay-btn-primary { background: var(--blue); color: #06222e; margin-top: auto; }
.pay-btn-secondary { margin-top: 8px; background: transparent; border: 1.5px solid rgba(255,255,255,.2); color: #fff; }
.pay-err { color: #ffb3b3; font-size: 11.5px; margin-top: 8px; }
@media (max-width: 900px) {
  .ws-context { border-right: none; border-bottom: 1px solid var(--line-soft); }
  .ws-dock { border-left: none; border-top: 1px solid var(--line-soft); }
}

/* ---------- unified timeline: type-colored dots on the existing .tl-item ---------- */
.tl-item.t-ticket::before { background: var(--info); }
.tl-item.t-invoice::before { background: var(--wait); }
.tl-item.t-payment::before { background: var(--live); }
.tl-item.t-doc::before { background: var(--muted); }
.tl-item.t-claim::before { background: var(--critical); }
.tl-item.t-comm::before { background: var(--violet); }
.tl-item.t-service::before { background: var(--navy); }
.tl-tag { display: inline-block; font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .3px; color: var(--muted); margin-right: 6px; }

/* ---------- Operational icon rail (v3) ---------- */
.oprail { width: 58px; flex: 0 0 58px; background: var(--navy-deep); display: flex; flex-direction: column; align-items: center; padding: 14px 0; gap: 2px; overflow-y: auto; }
.oprail .logo { width: 32px; height: 32px; border-radius: 9px; background: linear-gradient(135deg, var(--accent), #0f6f9c); display: flex; align-items: center; justify-content: center; color: #fff; font-weight: 800; font-size: 13px; margin-bottom: 14px; letter-spacing: -.5px; flex: 0 0 auto; }
.oprail-btn { width: 38px; height: 38px; flex: 0 0 38px; border-radius: 10px; border: none; background: transparent; color: #7590a8; display: flex; align-items: center; justify-content: center; position: relative; transition: background .12s, color .12s; text-decoration: none; }
.oprail-btn svg { width: 18px; height: 18px; }
.oprail-btn:hover { background: rgba(255,255,255,.06); color: #cfe0ee; }
.oprail-btn.active { background: var(--accent); color: #fff; }
.oprail-btn .dot { position: absolute; top: 4px; right: 4px; width: 6px; height: 6px; border-radius: 50%; background: var(--critical); border: 1.5px solid var(--navy-deep); }
.oprail-div { width: 26px; height: 1px; background: rgba(255,255,255,.14); margin: 6px 0; }
.oprail-spacer { flex: 1; }
.oprail-avatar { width: 30px; height: 30px; flex: 0 0 30px; border-radius: 50%; background: var(--navy-mid); color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 11px; margin-top: 6px; text-decoration: none; }

/* ---------- Command bar (v6) — a real two-row command center, not a
   single sparse strip. Row 1 is a thin, always-live context strip
   (breadcrumb + real today's-numbers ticker); row 2 is the dense command
   row (search, AI entry point, technician cluster, actions, profile).
   Replaces the old single 54px .opcmdbar, which left a wide dead gap
   between a small search box and the right-aligned actions. ---------- */
.cmdbar { flex: 0 0 auto; background: var(--surface); border-bottom: 1px solid var(--line); }
.cmdbar-top { height: 34px; display: flex; align-items: center; gap: 16px; padding: 0 22px; background: var(--surface-2); border-bottom: 1px solid var(--line-soft); font-size: 12px; }
.cmdbar-breadcrumb { color: var(--muted); display: flex; align-items: center; gap: 8px; white-space: nowrap; }
.cmdbar-breadcrumb a { color: var(--muted); text-decoration: none; font-weight: 600; } /* covered by the --muted token fix above (now 5.50:1 on white) */
.cmdbar-breadcrumb a:hover { color: var(--navy); }
.cmdbar-breadcrumb .sep { color: var(--muted-soft); }
.cmdbar-breadcrumb span:last-child { color: var(--ink); font-weight: 700; }
.cmdbar-ticker { margin-left: auto; display: flex; align-items: center; gap: 6px; overflow-x: auto; }
.ct-pill { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 999px; background: var(--surface); border: 1px solid var(--line); color: var(--ink-soft); font-size: 11.5px; font-weight: 650; text-decoration: none; white-space: nowrap; font-family: inherit; cursor: pointer; }
.ct-pill:hover { border-color: var(--accent); color: var(--navy); }
.ct-pill-btn { background: var(--surface); }
.ct-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--muted-soft); flex: 0 0 auto; }
.ct-dot.good { background: var(--live); } .ct-dot.info { background: var(--info); } .ct-dot.warn { background: var(--wait); }

.cmdbar-main { min-height: 66px; display: flex; align-items: center; gap: 14px; padding: 12px 22px; flex-wrap: wrap; }
.oppalette-trigger { flex: 1 1 320px; max-width: 440px; display: flex; align-items: center; gap: 10px; background: var(--surface-2); border: 1px solid var(--line); border-radius: 10px; padding: 11px 14px; color: var(--muted); font-size: 13.5px; text-align: left; transition: border-color .12s, box-shadow .12s; } /* covered by the --muted token fix above (now 4.94:1 on --surface-2) */
.oppalette-trigger svg { flex: 0 0 auto; color: var(--muted-soft); }
.oppalette-trigger span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.oppalette-trigger:hover { border-color: var(--accent); }
.oppalette-trigger:focus-visible { border-color: var(--accent); box-shadow: 0 0 0 4px var(--blue-soft); outline: none; }
.oppalette-trigger kbd { margin-left: auto; background: var(--surface); border: 1px solid var(--line); border-radius: 5px; padding: 2px 7px; font-size: 11px; color: var(--muted); font-family: var(--mono); flex: 0 0 auto; }

.cmdbar-ai-btn { display: inline-flex; align-items: center; gap: 7px; padding: 10px 15px; border-radius: 10px; border: 1px solid transparent; background: linear-gradient(135deg, var(--violet-soft), var(--blue-soft)); color: #5b3fae; font-size: 13px; font-weight: 700; cursor: pointer; white-space: nowrap; transition: box-shadow .12s, transform .12s; }
.cmdbar-ai-btn:hover { box-shadow: 0 4px 14px rgba(124,99,201,.25); transform: translateY(-1px); }
.cmdbar-ai-btn svg { color: var(--violet); }

.cmdbar-techs { display: flex; align-items: center; margin-left: -6px; }
.ct-avatar { width: 30px; height: 30px; border-radius: 50%; background: var(--surface-2); color: var(--ink-soft); border: 2px solid var(--surface); margin-left: -6px; display: flex; align-items: center; justify-content: center; font-size: 10.5px; font-weight: 700; position: relative; }
.ct-avatar.on { background: var(--navy); color: #fff; }
.ct-avatar.on::after { content: ''; position: absolute; bottom: -1px; right: -1px; width: 9px; height: 9px; border-radius: 50%; background: var(--live); border: 2px solid var(--surface); }

.cmdbar-main .topbar-actions { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.user-chip-btn { background: none; border: none; cursor: pointer; }
.user-chip .uc-name { display: flex; flex-direction: column; align-items: flex-start; line-height: 1.25; }
.user-chip .uc-role { font-size: 10.5px; color: var(--muted); font-weight: 500; }

@media (max-width: 1200px) { .cmdbar-ticker .ct-pill:nth-child(3) { display: none; } }
@media (max-width: 992px) {
  .cmdbar-top { padding: 0 14px; }
  .cmdbar-ticker { display: none; }
  .cmdbar-main { padding: 10px 14px; gap: 10px; }
  .cmdbar-ai-btn span, .cmdbar-techs { display: none; }
}
@media (max-width: 640px) {
  .oppalette-trigger span { display: none; }
  .oppalette-trigger kbd { display: none; }
  .oppalette-trigger { flex: 0 0 auto; max-width: none; padding: 11px; }
}

/* ---------- Command palette overlay ---------- */
.oppalette-overlay { position: fixed; inset: 0; background: rgba(9,20,32,.55); display: none; align-items: flex-start; justify-content: center; padding-top: 11vh; z-index: 1000; backdrop-filter: blur(2px); }
.oppalette-overlay.open { display: flex; }
.oppalette { width: min(600px, 92vw); background: var(--surface); border-radius: 14px; box-shadow: var(--shadow-hover); overflow: hidden; border: 1px solid var(--line); }
.oppalette input { width: 100%; border: none; padding: 16px 18px; font-size: 15px; background: var(--surface); color: var(--ink); outline: none; border-bottom: 1px solid var(--line); }
.oppalette input:focus-visible { outline: none; box-shadow: inset 0 -3px 0 var(--blue); }
.oppalette-list { max-height: 380px; overflow-y: auto; padding: 6px; }
.oppalette-item { display: flex; align-items: center; gap: 10px; padding: 9px 12px; border-radius: 8px; font-size: 13px; text-decoration: none; color: var(--ink); cursor: pointer; }
.oppalette-item .grp { font-size: 9.5px; color: var(--muted); text-transform: uppercase; letter-spacing: .4px; width: 58px; flex: 0 0 58px; }
.oppalette-item .meta { margin-left: auto; font-size: 11px; color: var(--muted); font-family: var(--mono); }
.oppalette-item:hover, .oppalette-item.sel { background: var(--info-soft); color: var(--info); }
.oppalette-hint { padding: 9px 16px; border-top: 1px solid var(--line-soft); font-size: 11px; color: var(--muted); display: flex; gap: 14px; }
.oppalette-hint kbd { font-family: var(--mono); background: var(--surface-2); border: 1px solid var(--line); border-radius: 4px; padding: 0 5px; }

@media (max-width: 900px) {
  .oprail { width: 100%; flex: 0 0 auto; flex-direction: row; position: fixed; bottom: 0; left: 0; right: 0; z-index: 50; padding: 6px 8px; overflow-x: auto; }
  .oprail .logo, .oprail-avatar, .oprail-div { display: none; }
  .oprail-spacer { display: none; }
}

/* ---------- Import review: per-field extraction confidence ----------
   Applied to the wrapping .import-field div so the input border/tint and
   the small badge next to the label move together as JS updates scores. */
.import-field label { display: flex; align-items: center; gap: 6px; }
.import-field .conf-badge { font-size: 10px; font-weight: 700; padding: 1px 6px; border-radius: 9px; line-height: 1.5; }
.import-field.conf-high input, .import-field.conf-high select { border-color: var(--live); background: var(--live-soft); }
.import-field.conf-med input, .import-field.conf-med select { border-color: var(--wait); background: var(--wait-soft); }
.import-field.conf-low input, .import-field.conf-low select, .import-field.conf-missing input, .import-field.conf-missing select { border-color: var(--critical); background: var(--critical-soft); }
.import-field .conf-badge.high { background: var(--live-soft); color: var(--live); }
.import-field .conf-badge.med { background: var(--wait-soft); color: var(--wait); }
.import-field .conf-badge.low, .import-field .conf-badge.missing { background: var(--critical-soft); color: var(--critical); }
.conf-panel-row { display: flex; align-items: center; justify-content: space-between; padding: 4px 0; font-size: 12.5px; border-bottom: 1px solid var(--line-soft); }
.conf-panel-row:last-child { border-bottom: none; }
.conf-panel-row .icon { width: 16px; display: inline-block; text-align: center; font-weight: 700; }
.conf-panel-row.high .icon { color: var(--live); }
.conf-panel-row.med .icon { color: var(--wait); }
.conf-panel-row.low .icon, .conf-panel-row.missing .icon { color: var(--critical); }
.conf-panel-row .pct { font-family: var(--mono); font-variant-numeric: tabular-nums; color: var(--muted); }
.docx-preview { max-height: 480px; overflow-y: auto; border: 1px solid var(--line-soft); border-radius: var(--radius-sm); padding: 16px; background: var(--surface); font-size: 13.5px; line-height: 1.6; }
.docx-preview p { margin: 0 0 .6em; }
.docx-table-preview { font-size: 12.5px; }
.conf-panel-row.has-preview { cursor: pointer; }
.conf-panel-row.has-preview:hover { background: var(--surface-hover, rgba(0,0,0,.03)); }
.xlsx-preview-grid { font-size: 11.5px; white-space: nowrap; }
.xlsx-preview-grid td { padding: 3px 6px; }
.preview-cell-flash { animation: previewCellFlash 2.5s ease-out; outline: 2px solid var(--brand, #2563eb); outline-offset: -2px; }
@keyframes previewCellFlash { 0%, 40% { background: var(--wait-soft, #fef3c7); } 100% { background: transparent; } }
.docx-table-preview td { padding: 4px 8px; vertical-align: top; }
.import-summary-bar { display: flex; flex-wrap: wrap; gap: 6px 16px; align-items: center; }
.import-summary-bar .chip { display: inline-flex; align-items: center; gap: 5px; font-weight: 600; font-size: 13px; }
.import-summary-bar .chip.ok { color: var(--live, #1a9e5c); }
.import-summary-bar .chip.warn { color: var(--wait, #c98a1f); }
.import-summary-bar .chip.missing { color: var(--critical, #d24444); }

/* =====================================================================
   ENTERPRISE PAGE FRAMEWORK (v4) — shared across every module: page
   header (icon + title + subtitle + actions), KPI row, action bar,
   richer empty states, and a real confirmation dialog to replace
   native browser prompt()/confirm() calls. Built out fully once here
   (Work Approval) so every other module reuses the same classes.
   ===================================================================== */

/* ---------- Page header: icon badge + title + subtitle + actions ---------- */
.page-header { display: flex; align-items: flex-start; gap: 16px; margin-bottom: 22px; flex-wrap: wrap; }
.page-header .ph-icon { width: 44px; height: 44px; flex: 0 0 44px; border-radius: 12px; background: linear-gradient(135deg, var(--navy), var(--navy-soft)); color: #fff; display: flex; align-items: center; justify-content: center; box-shadow: var(--shadow); }
.page-header .ph-icon svg { width: 22px; height: 22px; }
.page-header .ph-text { flex: 1; min-width: 240px; }
.page-header h1 { margin: 0; color: var(--navy); font-weight: 800; font-size: 24px; letter-spacing: -.2px; line-height: 1.25; }
.page-header .ph-sub { color: var(--muted); font-size: 13.5px; margin-top: 5px; max-width: 640px; line-height: 1.55; }
.page-header .ph-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-left: auto; padding-top: 2px; }

/* ---------- KPI row: reuses .stat-card, adds the responsive grid ---------- */
.kpi-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 14px; margin-bottom: 20px; }
.kpi-row .stat-card { cursor: default; }
.kpi-row .stat-card.is-clickable { cursor: pointer; }
.stat-card.tone-critical::before { background: var(--critical); }
.stat-card.tone-critical .stat-icon { background: var(--critical-soft); color: var(--critical); }
.stat-card.tone-wait::before { background: var(--wait); }
.stat-card.tone-wait .stat-icon { background: var(--wait-soft); color: var(--wait); }
.stat-card.tone-live::before { background: var(--live); }
.stat-card.tone-live .stat-icon { background: var(--live-soft); color: var(--live); }

/* ---------- Action bar: filters on the left, buttons on the right ---------- */
.action-bar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }
.action-bar .filter-pills { margin-bottom: 0; }
.action-bar .ab-right { display: flex; gap: 8px; align-items: center; margin-left: auto; }

/* ---------- Richer empty state (heading + body + optional action) ---------- */
.empty-state { text-align: center; padding: 52px 24px; color: var(--muted); }
.empty-state .es-icon { width: 46px; height: 46px; border-radius: 50%; background: var(--surface-2); color: var(--muted-soft); display: flex; align-items: center; justify-content: center; margin: 0 auto 14px; font-size: 20px; }
.empty-state .es-icon svg { width: 22px; height: 22px; }
.empty-state h4 { font-size: 14.5px; font-weight: 700; color: var(--ink); margin: 0 0 6px; }
.empty-state p { font-size: 12.5px; max-width: 360px; margin: 0 auto; line-height: 1.6; }
.empty-state .es-action { margin-top: 14px; }

/* ---------- Queue rows (approval queue, and any future review queue) ---------- */
.q-row { display: flex; gap: 12px; align-items: flex-start; padding: 13px 16px; border-bottom: 1px solid var(--line-soft); cursor: pointer; transition: background .1s; }
.q-row:last-child { border-bottom: none; }
.q-row:hover { background: var(--surface-2); }
.q-row.selected { background: var(--blue-soft); }
.q-row .q-av { width: 32px; height: 32px; flex: 0 0 32px; border-radius: 9px; background: var(--navy); color: #fff; font-weight: 700; font-size: 12px; display: flex; align-items: center; justify-content: center; }
.q-row .q-av.q-av-sm { width: 30px; height: 30px; flex: 0 0 30px; font-size: 11px; }
.q-row .q-main { flex: 1; min-width: 0; }
.q-row .q-title { font-weight: 700; font-size: 13.5px; color: var(--ink); display: flex; align-items: center; gap: 8px; justify-content: space-between; }
.q-row .q-sub { color: var(--muted); font-size: 12px; margin-top: 3px; }
.q-row .q-fact { color: var(--ink-soft); font-size: 12px; margin-top: 5px; font-family: var(--mono); }

/* ---------- Confirmation / prompt dialog (replaces window.prompt/confirm) ---------- */
.t4-dialog-overlay { position: fixed; inset: 0; background: rgba(9,20,32,.5); display: none; align-items: center; justify-content: center; z-index: 1100; padding: 20px; backdrop-filter: blur(1px); }
.t4-dialog-overlay.open { display: flex; }
.t4-dialog { width: min(420px, 100%); background: var(--surface); border-radius: 14px; box-shadow: var(--shadow-hover); overflow: hidden; border: 1px solid var(--line); }
.t4-dialog-hd { padding: 18px 22px 4px; }
.t4-dialog-hd h5 { margin: 0; font-size: 15.5px; font-weight: 800; color: var(--navy); }
.t4-dialog-body { padding: 12px 22px 4px; font-size: 13px; color: var(--ink-soft); line-height: 1.55; }
.t4-dialog-body textarea, .t4-dialog-body input:not([type="checkbox"]):not([type="radio"]) { width: 100%; margin-top: 8px; border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 9px 11px; font-size: 13px; font-family: inherit; }
.t4-dialog-body input[type="checkbox"], .t4-dialog-body input[type="radio"] { width: 1em; height: 1em; margin-top: 0; vertical-align: middle; }
.t4-dialog-body textarea:focus, .t4-dialog-body input:focus { outline: none; border-color: var(--blue); box-shadow: 0 0 0 4px var(--blue-soft); }
.t4-dialog-err { color: var(--critical); font-size: 12px; margin-top: 6px; min-height: 16px; }
.t4-dialog-ft { display: flex; justify-content: flex-end; gap: 8px; padding: 16px 22px 22px; }
.t4-dialog-ft button { border-radius: var(--radius-sm); padding: 9px 16px; font-size: 13px; font-weight: 650; border: 1px solid var(--line); background: #fff; color: var(--ink); cursor: pointer; }
.t4-dialog-ft button:hover { background: var(--surface-2); }
.t4-dialog-ft button.danger { background: var(--critical); border-color: var(--critical); color: #fff; }
.t4-dialog-ft button.danger:hover { background: #c22e38; }
.t4-dialog-ft button.primary { background: var(--navy); border-color: var(--navy); color: #fff; }
.t4-dialog-ft button.primary:hover { background: var(--navy-soft); }

/* =====================================================================
   DISPATCH BOARD (v5) — Schedule/Dispatch's day-grid + technician-lane
   board, week strip, month density calendar. Built as reusable, page-
   agnostic components (prefix `disp-`) so a future page (e.g. a
   technician-facing "my day" view) can reuse the same board without
   duplicating this CSS.
   ===================================================================== */

/* ---------- Segmented view switcher (extends .seg with an icon+label pair) ---------- */
.seg button.disp-view-btn { display: inline-flex; align-items: center; gap: 6px; }
.seg button.disp-view-btn svg { width: 14px; height: 14px; }

/* ---------- Date nav ---------- */
.disp-datenav { display: flex; align-items: center; gap: 8px; }
.disp-datenav .dn-btn { border: 1px solid var(--line); background: #fff; border-radius: var(--radius-sm); padding: 8px 12px; font-size: 12.5px; font-weight: 650; color: var(--ink); cursor: pointer; text-decoration: none; display: inline-flex; align-items: center; gap: 4px; }
.disp-datenav .dn-btn:hover { border-color: var(--blue); color: var(--navy); }
.disp-datenav .dn-label { font-weight: 800; color: var(--navy); font-size: 14.5px; margin-left: 4px; white-space: nowrap; }

/* ---------- Day grid + technician lanes ---------- */
.disp-board { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; }
.disp-board-scroll { overflow-x: auto; }
.disp-board-inner { display: grid; grid-template-columns: 56px 1fr; min-width: 640px; }
.disp-hourcol { border-right: 1px solid var(--line-soft); background: var(--surface-2); }
.disp-hourcol .dh-hd { height: 44px; border-bottom: 1px solid var(--line); }
.disp-hourcol .dh-slot { height: 64px; padding: 4px 8px 0 0; text-align: right; font-size: 10.5px; color: var(--muted); font-family: var(--mono); border-bottom: 1px solid var(--line-soft); }
.disp-lanes { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(168px, 1fr); position: relative; }
.disp-lane { border-right: 1px solid var(--line-soft); position: relative; }
.disp-lane:last-child { border-right: none; }
.disp-lane-hd { height: 44px; display: flex; align-items: center; gap: 8px; padding: 0 10px; border-bottom: 1px solid var(--line); background: var(--surface); position: sticky; top: 0; z-index: 3; }
.disp-lane-hd .av { width: 24px; height: 24px; border-radius: 50%; background: var(--navy); color: #fff; font-size: 10px; font-weight: 700; display: flex; align-items: center; justify-content: center; flex: 0 0 auto; }
.disp-lane-hd .av.unassigned { background: var(--muted-soft); }
.disp-lane-hd .nm { font-size: 12px; font-weight: 700; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.disp-lane-hd .ct { margin-left: auto; font-family: var(--mono); font-size: 10.5px; color: var(--muted); }
.disp-lane-body { position: relative; background-image: repeating-linear-gradient(to bottom, transparent, transparent 63px, var(--line-soft) 63px, var(--line-soft) 64px); }
.disp-lane-body.dragover { background-color: var(--blue-soft); }
.disp-now-line { position: absolute; left: 0; right: 0; height: 2px; background: var(--critical); z-index: 4; pointer-events: none; }
.disp-now-line::before { content: 'NOW'; position: absolute; left: -2px; top: -8px; background: var(--critical); color: #fff; font-size: 8.5px; font-weight: 800; padding: 1px 4px; border-radius: 3px; letter-spacing: .3px; }

/* ---------- Appointment card ---------- */
.disp-card { position: absolute; left: 3px; right: 3px; border-radius: 7px; padding: 5px 8px; font-size: 11px; overflow: hidden; cursor: grab; border: 1px solid; box-shadow: var(--shadow); transition: box-shadow .1s, transform .1s; }
.disp-card:hover { box-shadow: var(--shadow-hover); z-index: 5; }
.disp-card.dragging { opacity: .45; cursor: grabbing; }
.disp-card .dc-top { display: flex; align-items: center; gap: 5px; font-weight: 750; }
.disp-card .dc-pr { width: 6px; height: 6px; border-radius: 50%; flex: 0 0 6px; }
.disp-card .dc-pr.low { background: #9AA7B5; } .disp-card .dc-pr.normal { background: var(--info); } .disp-card .dc-pr.high { background: var(--wait); } .disp-card .dc-pr.urgent { background: var(--critical); }
.disp-card .dc-cust { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.disp-card .dc-meta { color: var(--ink-soft); font-size: 10px; margin-top: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.disp-card .dc-time { font-family: var(--mono); font-size: 9.5px; color: var(--muted); margin-top: 1px; }
.disp-card .dc-sla { display: inline-block; font-size: 8.5px; font-weight: 800; padding: 0 4px; border-radius: 4px; margin-top: 2px; letter-spacing: .2px; }
.disp-card.st-scheduled { background: var(--blue-soft); border-color: #b9e2f5; }
.disp-card.st-confirmed { background: #eaf6ee; border-color: #bfe6cb; }
.disp-card.st-in_progress { background: var(--amber-soft); border-color: #f5deA0; }
.disp-card.st-completed { background: var(--line-soft); border-color: var(--line); opacity: .75; }
.disp-card.st-cancelled, .disp-card.st-no_show { background: var(--red-soft); border-color: #f3c6c9; opacity: .7; text-decoration: line-through; }
.disp-card .dc-sla.at_risk { background: var(--wait-soft); color: #a3730a; }
.disp-card .dc-sla.breached { background: var(--red-soft); color: var(--red); }

/* ---------- Week strip ---------- */
.disp-week { display: grid; grid-template-columns: repeat(7, 1fr); gap: 10px; }
.disp-week-day { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm); box-shadow: var(--shadow); min-height: 160px; display: flex; flex-direction: column; }
.disp-week-day.is-today { border-color: var(--blue); box-shadow: 0 0 0 2px var(--blue-soft); }
.disp-week-day-hd { padding: 8px 10px; border-bottom: 1px solid var(--line-soft); cursor: pointer; }
.disp-week-day-hd .wd-dow { font-size: 10px; text-transform: uppercase; letter-spacing: .4px; color: var(--muted); font-weight: 700; }
.disp-week-day-hd .wd-date { font-size: 15px; font-weight: 800; color: var(--navy); }
.disp-week-day-body { padding: 6px; display: flex; flex-direction: column; gap: 4px; overflow-y: auto; max-height: 220px; }
.disp-week-chip { border-radius: 6px; padding: 4px 6px; font-size: 10.5px; cursor: pointer; border: 1px solid; }
.disp-week-empty { padding: 14px 10px; color: var(--muted-soft); font-size: 11px; text-align: center; }

/* ---------- Month density calendar ---------- */
.disp-month { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }
.disp-month-dow { font-size: 10px; text-transform: uppercase; letter-spacing: .4px; color: var(--muted); font-weight: 700; text-align: center; padding-bottom: 4px; }
.disp-month-cell { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm); min-height: 74px; padding: 6px 8px; cursor: pointer; transition: box-shadow .1s; }
.disp-month-cell:hover { box-shadow: var(--shadow-hover); }
.disp-month-cell.is-today { border-color: var(--blue); box-shadow: 0 0 0 2px var(--blue-soft); }
.disp-month-cell.is-other-month { opacity: .35; }
.disp-month-cell .mc-date { font-size: 12px; font-weight: 700; color: var(--ink); }
.disp-month-cell .mc-count { margin-top: 8px; font-size: 10.5px; color: var(--accent); font-weight: 700; }

/* ---------- Technician grouped list view ---------- */
.disp-techgroup { margin-bottom: 16px; }
.disp-techgroup-hd { display: flex; align-items: center; gap: 10px; padding: 10px 4px; }
.disp-techgroup-hd .av { width: 30px; height: 30px; border-radius: 50%; background: var(--navy); color: #fff; font-weight: 700; font-size: 12px; display: flex; align-items: center; justify-content: center; }
.disp-techgroup-hd .nm { font-weight: 800; color: var(--navy); font-size: 14px; }
.disp-techgroup-hd .cnt { color: var(--muted); font-size: 12px; }

/* Route suggestion panel (lib/routing.php's real nearest-neighbor OSRM
   route order) -- deliberately plain/compact, a supporting detail under
   a technician's appointment list, never competing visually with it. */
.route-suggestion { margin-top: 8px; }
.rs-panel { background: var(--bg); border: 1px solid var(--line-soft); border-radius: var(--radius-sm, 10px); padding: 12px 16px; }
.rs-hd { font-size: 12px; font-weight: 700; color: var(--navy); margin-bottom: 8px; }
.rs-hd .text-muted { font-weight: 500; }
.rs-step { display: flex; align-items: center; gap: 10px; font-size: 13px; padding: 3px 0; }
.rs-num { width: 20px; height: 20px; border-radius: 50%; background: var(--navy); color: #fff; font-size: 10.5px; font-weight: 700; display: flex; align-items: center; justify-content: center; flex: 0 0 auto; }
.rs-name { font-weight: 600; color: var(--ink); flex: 1 1 auto; }
.rs-travel { color: var(--muted); font-size: 11.5px; }
.rs-arrow { color: var(--muted-soft); font-size: 11px; margin-left: 9px; }
.rs-total { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line-soft); font-size: 12px; font-weight: 700; color: var(--navy); }

/* ---------- Map placeholder (honest "not yet built" state, not a fake map) ---------- */
.disp-map-placeholder { text-align: center; padding: 60px 24px; color: var(--muted); background: var(--surface-2); border-radius: var(--radius); border: 1px dashed var(--line); }
.disp-map-placeholder h4 { color: var(--ink); font-size: 15px; margin: 12px 0 6px; }
.disp-map-placeholder p { max-width: 460px; margin: 0 auto; font-size: 12.5px; line-height: 1.6; }
.disp-map-legend { display: flex; flex-wrap: wrap; gap: 14px; align-items: center; padding: 6px 8px 10px; font-size: 11.5px; color: var(--muted); }
.disp-map-legend-swatch { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 4px; vertical-align: -1px; border: 1px solid rgba(0,0,0,.15); }
.disp-map-legend-job { background: #3388ff; } /* Leaflet's default marker blue -- matches the actual job-pin icon */
.disp-map-legend-tech { background: var(--green); }
.disp-map-legend-tech-stale { background: var(--muted); }
.disp-tech-marker { border-radius: 50%; border: 2px solid #fff; box-shadow: 0 1px 4px rgba(0,0,0,.35); display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 800; color: #fff; }
.disp-tech-marker.is-fresh { background: var(--green); }
.disp-tech-marker.is-stale { background: var(--muted); }

@media (max-width: 900px) {
  .disp-week { grid-template-columns: 1fr; }
  .disp-month { grid-template-columns: repeat(7, minmax(36px, 1fr)); font-size: 11px; }
  .disp-month-cell { min-height: 50px; padding: 4px; }
}

/* ---------- UX Modernization (REQ-51-01): shared utility classes ----------
   Named replacements for one-off inline styles found across the app, added
   as pages are migrated off inline styles onto the shared design system.
   Each value matches the inline style it replaces exactly — this section
   is a naming pass, not a visual change. */
.u-w-auto { width: auto; }
.u-w-fit { width: fit-content; }
.u-cursor-pointer { cursor: pointer; }
.u-cursor-default { cursor: default; }
.u-fs-sm { font-size: .75rem; }
.u-fs-xs { font-size: 11.5px; }
.u-fs-icon { font-size: 1.8rem; text-decoration: none; }
.u-fs-105 { font-size: 10.5px; }
.u-fs-13 { font-size: 13px; }
.u-mt-16 { margin-top: 16px; }
.u-max-w-640 { max-width: 640px; }
.u-max-w-600 { max-width: 600px; }
.u-max-w-520 { max-width: 520px; }
.table-tight input, .table-tight select { height: 32px; padding: .25rem .5rem; }
.u-max-w-480 { max-width: 480px; }
.u-wrap-anywhere { overflow-wrap: anywhere; }
.u-max-w-420 { max-width: 420px; }
.u-scroll-400 { max-height: 400px; overflow-y: auto; }
.scroll-box-560 { max-height: 560px; overflow: auto; border: 1px solid var(--line-soft); }
.scroll-box-300 { max-height: 300px; overflow: auto; border: 1px solid var(--line-soft); margin-bottom: 12px; }
.embed-preview-480 { width: 100%; height: 480px; border: 1px solid var(--line-soft); }
.scroll-box-480 { max-height: 480px; overflow: auto; border: 1px solid var(--line-soft); }
.u-scroll-60vh { max-height: 60vh; overflow-y: auto; }
.u-scroll-70vh { max-height: 70vh; overflow-y: auto; }
.u-scroll-75vh { max-height: 75vh; overflow-y: auto; }
.u-max-w-260 { max-width: 260px; }
.u-max-w-240 { max-width: 240px; }
.u-max-w-220 { max-width: 220px; }
.u-max-w-360 { max-width: 360px; }
.u-max-w-280 { max-width: 280px; }
.u-text-wait { color: var(--wait); }
.u-text-critical { color: var(--critical); }
/* --green-text, not --live. --live is #1f9d55, which measures 3.13:1 on
   --surface-2 and fails AA. This class shows a customer's PAID-IN-FULL
   balance, a subcontractor's margin and a background-check result -- small
   bold numbers somebody reads at a glance, which is the worst place for
   low contrast. Found only once ticket_view/customer_view/invoice were
   added to the accessibility audit; the 28-surface list had never covered
   the three screens staff use most. */
.u-text-live { color: var(--green-text); }
.u-text-muted-token { color: var(--muted); }
.u-pad-24 { padding: 24px; }
.u-dialog-w-640 { width: min(640px, 100%); }
.u-dialog-w-560 { width: min(560px, 100%); }
.u-dialog-w-520 { width: min(520px, 100%); }
.u-thumb-60 { width: 100%; height: 60px; object-fit: cover; border-radius: 4px; }
.u-minh-100 { min-height: 100px; }
.u-flex-basis-100 { flex-basis: 100%; }
.u-fs-2xs { font-size: .7rem; }

/* Live incoming call/SMS popup (assets/inbound_comms.js) -- a stacked,
   fixed-position, non-blocking overlay: never a modal, never a page
   navigation. Whatever the employee was doing underneath is untouched. */
.icp-stack { position: fixed; bottom: 20px; right: 20px; z-index: 1200; display: flex; flex-direction: column; gap: 10px; max-width: 340px; pointer-events: none; }
.icp-card { pointer-events: auto; background: #fff; border: 1px solid var(--line); border-radius: var(--radius-sm); box-shadow: var(--shadow-hover); overflow: hidden; animation: icp-in .18s ease; }
.icp-card.icp-minimized .icp-body { display: none; }
@keyframes icp-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
.icp-hd { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 10px 12px; background: var(--navy-soft); color: #fff; }
.icp-hd .icp-title { font-size: 12.5px; font-weight: 700; display: flex; align-items: center; gap: 6px; }
.icp-hd .icp-actions { display: flex; gap: 4px; }
.icp-hd button { background: transparent; border: none; color: #fff; opacity: .85; cursor: pointer; font-size: 13px; line-height: 1; padding: 2px 6px; }
.icp-hd button:hover { opacity: 1; }
.icp-body { padding: 12px; font-size: 13px; }
.icp-name { font-weight: 700; font-size: 14.5px; color: var(--ink); margin-bottom: 2px; }
.icp-sub { color: var(--muted); font-size: 12px; margin-bottom: 8px; }
.icp-row { display: flex; justify-content: space-between; gap: 8px; padding: 3px 0; font-size: 12.5px; color: var(--ink); }
.icp-row .l { color: var(--muted); }
.icp-badge-unknown { display: inline-block; background: var(--wait); color: #fff; font-size: 10.5px; font-weight: 700; padding: 2px 8px; border-radius: 999px; margin-bottom: 6px; }
.icp-actions-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.icp-actions-row a, .icp-actions-row button { font-size: 12px; padding: 5px 10px; border-radius: 6px; border: 1px solid var(--line); background: var(--bg); color: var(--ink); text-decoration: none; cursor: pointer; }
.icp-actions-row a:hover, .icp-actions-row button:hover { border-color: var(--blue); background: var(--blue-soft); color: var(--navy); }
.icp-sms-body { background: var(--bg); border-radius: 6px; padding: 8px; font-size: 12.5px; color: var(--ink); margin: 6px 0; }

/* REQ-11-02 — the shared destructive-action confirmation dialog
   (assets/destructive_rail.js). Deliberately not a native confirm(): a
   dialog people have dismissed a hundred times stops being read, and this one
   has to be, because it is the only place the real consequences appear. */
.dr-overlay { position: fixed; inset: 0; background: rgba(15,42,68,.45); display: flex; align-items: center; justify-content: center; z-index: 1080; padding: 16px; }
.dr-box { background: var(--surface); border: 1px solid var(--line); border-radius: 14px; box-shadow: var(--shadow); max-width: 480px; width: 100%; padding: 20px 22px; }
.dr-title { font-size: 16px; font-weight: 700; color: var(--ink); margin: 0 0 10px; }
.dr-lead { font-size: 13px; color: var(--muted); margin: 0 0 6px; }
.dr-list { list-style: none; padding: 0; margin: 0 0 12px; }
.dr-list li { font-size: 13px; padding: 7px 10px; border-radius: 8px; margin-bottom: 5px; }
.dr-high { color: var(--red-text); background: var(--red-soft); font-weight: 600; }
.dr-medium { color: var(--amber-text); background: var(--amber-soft); }
.dr-low { color: var(--muted); background: var(--bg); }
.dr-undo { font-size: 12px; color: var(--muted); margin: 0 0 12px; }
.dr-ack { display: flex; align-items: flex-start; gap: 8px; font-size: 13px; color: var(--ink); margin-bottom: 14px; cursor: pointer; }
.dr-ack input { margin-top: 3px; min-width: 16px; min-height: 16px; }
.dr-actions { display: flex; gap: 8px; justify-content: flex-end; }
.dr-actions .btn { min-height: 38px; }

/* WCAG 2.2 (2.5.8 Target Size, Minimum) — a checkbox must be at least 24x24.
   Bootstrap ships them at 1em, which measures 15px here: found by a real
   rendered measurement on the Ticket Workspace at a 390px viewport, where
   the liquid-damage and confidential toggles were 15px targets.

   Applied globally rather than page by page, because the next form to add a
   checkbox would otherwise inherit the same 15px, and because a checkbox is
   inherently the hardest control to hit accurately -- there is no version of
   this app where a 15px one is the right size. Sized rather than padded: a
   padded 15px box still has a 15px hit area in most browsers, which is what
   makes padding-only fixes look correct and measure wrong. */
.form-check-input { min-width: 24px; min-height: 24px; }
/* Bootstrap positions its own label against a 1em box, so restore the
   vertical relationship the larger control changes. */
.form-check-label { line-height: 24px; }

/* ================================================================
   Row density + badge grouping (UI target pass)
   ----------------------------------------------------------------
   A ticket row was ~92px tall, so a shop scanning the list all day saw
   about eight at a time. Three of those pixels-worth were a stack of three
   badges on separate lines and a fourteen-digit timestamp.

   Same columns, same data, same actions -- the three badges now sit on one
   line and the padding is tuned for scanning rather than for touch on a
   desktop-width screen. Touch targets are restored at the tablet/phone
   breakpoints below, where they actually matter.
   ================================================================ */
.rowlist-row { padding: 11px 20px; }
.rl-badges { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.rl-badges .st-badge, .rl-badges .pr-badge { margin: 0; }
.rowlist-row .rl-title { line-height: 1.35; }
.rowlist-row .rl-sub { margin-top: 1px; line-height: 1.35; }

/* Density is a desktop affordance. On tablet and phone the same rows need
   room for a fingertip, so the padding goes back up rather than down. */
@media (max-width: 992px) {
  .rowlist-row { padding: 14px 16px; }
  .rl-badges { gap: 5px; }
}

/* The status column was 120px, so the three badges wrapped to a second line
   and put the row height straight back. Widened just enough for status,
   priority and invoice state to sit on one line at desktop width; the
   narrower breakpoints below already drop this column entirely. */
.rowlist-row, .rowlist-hd { grid-template-columns: 64px 2fr 1fr 208px 92px 40px; }
@media (max-width: 1280px) {
  .rowlist-row, .rowlist-hd { grid-template-columns: 60px 2fr 1fr 150px 88px 36px; }
}

/* ================================================================
   Phone: a ticket row is a stacked row, not a squeezed table row
   ----------------------------------------------------------------
   At 390px the six-column grid gave the title about 90px, so
   "Laptop / Windows - No power" wrapped to four lines, the three badges
   stacked to three more, and one row stood 146px tall. Shrinking the desktop
   layout is not a mobile layout.

   Same data, same actions, nothing hidden: id and title read as a heading,
   the customer and technician lines follow, the badges get their own full
   width row, and the Open button keeps a 44px touch target.
   ================================================================ */
@media (max-width: 640px) {
  .rowlist-hd { display: none; }              /* column headings mean nothing once the columns are gone */

  .rowlist-row {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "title  id"
      "sub    sub"
      "meta   meta"
      "badges badges"
      "action overflow";
    gap: 6px 10px;
    padding: 14px 16px;
    align-items: start;
  }
  .rowlist-row > *:nth-child(2) { grid-area: title; min-width: 0; }
  .rowlist-row .rl-id { grid-area: id; align-self: start; white-space: nowrap; }
  .rowlist-row .rl-col-customer { grid-area: meta; display: block; }
  .rowlist-row .rl-badges { grid-area: badges; }
  .rowlist-row > *:nth-child(5) { grid-area: action; }
  .rowlist-row .rl-overflow { grid-area: overflow; }

  .rowlist-row .rl-title { font-size: 15px; line-height: 1.3; }
  .rowlist-row .rl-col-customer .rl-sub { display: inline; }
  .rowlist-row .rl-col-customer .rl-sub + .rl-sub::before { content: ' · '; color: var(--muted-soft); }

  /* A finger, not a cursor. */
  .rowlist-row .btn { min-height: 44px; display: inline-flex; align-items: center; padding: 8px 20px; }
  .rowlist-row .rl-overflow-btn { min-width: 44px; min-height: 44px; }
}

/* ================================================================
   Phone: the command bar is one row, not two
   ----------------------------------------------------------------
   At 390px the bar wrapped: hamburger, search and AI on one line, then
   "+ Quick Action", the bell and the avatar pushed onto a second. Two rows of
   chrome above every page is a lot of a phone screen spent on furniture.

   The full "Quick Action" wording stays in the DOM for screen readers; only
   the visible label shortens. The avatar keeps its 44px target and drops the
   name and role text, which are already on the account page.
   ================================================================ */
.qa-btn-short { display: none; }

@media (max-width: 640px) {
  .cmdbar-main { flex-wrap: nowrap; gap: 8px; padding: 8px 12px; min-height: 58px; }
  .qa-btn-full { display: none; }
  .qa-btn-short { display: inline; }
  .cmdbar-main .topbar-actions { gap: 6px; }
  /* The real class is .uc-name (with .uc-role nested inside it); the name and
     role are already on the account page and cost ~90px here. */
  .user-chip .uc-name { display: none; }
  .user-chip { padding: 0; gap: 0; }
  .qa-btn { white-space: nowrap; }

  /* Fingertips, not cursors. */
  .cmdbar-main .qa-btn,
  .cmdbar-main .topbar-bell,
  .cmdbar-main .cmdbar-ai-btn { min-height: 44px; }
}

/* ================================================================
   KPI card grid (dashboard)
   ----------------------------------------------------------------
   Five cards across on a wide screen, folding to two and then one rather
   than shrinking each card until its number stops being the biggest thing
   on it. The whole point of a KPI card is that the figure reads at a glance
   from across a counter.
   ================================================================ */
.kpi-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 14px; }
.kpi-grid .stat-card { padding: 16px 18px; }
.kpi-grid .stat-card .stat-num { font-size: 26px; }

@media (max-width: 1280px) { .kpi-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 860px)  { .kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px)  { .kpi-grid { grid-template-columns: 1fr; gap: 10px; } }

/* Monospace is for things you compare character by character -- a hash, a
   serial. A technician's elapsed time and a work-order reference are read, not
   diffed, and setting them in mono makes an operations screen look like a
   terminal. */
.now-card .elapsed { font-family: inherit; font-size: 11.5px; }
.kcard .wo { font-family: inherit; font-size: 10.5px; }
.panel-hd .cnt { font-family: inherit; font-size: 12px; }

/* ================================================================
   Money columns
   ----------------------------------------------------------------
   Financial figures are the one place tabular digits genuinely earn their
   keep: a column of amounts should line up on the decimal so the eye can
   compare magnitudes down the column without reading each one. That is a
   different job from the monospace that was being used for labels and
   timestamps elsewhere, which only made those look technical.

   An outstanding balance is emphasised because it is the number somebody is
   on this screen to act on.
   ================================================================ */
.money-col { font-variant-numeric: tabular-nums; white-space: nowrap; }
.money-owing { color: var(--red-text); font-weight: 700; }
.u-w-150 { width: 150px; }
.u-w-110 { width: 110px; }
.u-w-160 { width: 160px; }

/* The ticket reference is context for the invoice, not the subject of the row.
   Left to wrap it took four lines and set the height of every row around it.
   Truncated with the full text on hover, so nothing is lost. */
.inv-ticket-cell { max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inv-basis { font-size: 11px; line-height: 1.25; white-space: nowrap; }

/* Ten columns is a lot for the width left beside the rail, and the two that
   suffered were the ones that must never wrap: the invoice number is the row's
   identity, and stacking Open above Print put the row height straight back
   after the ticket cell was tamed. */
.u-w-150 { width: 168px; white-space: nowrap; }
.u-w-160 { width: 150px; white-space: nowrap; }
.table td.text-end .btn + .btn { margin-left: 4px; }
.inv-ticket-cell { max-width: 220px; }

/* Classes on a <th> set the column width but do not reach the cells, so the
   two that must never wrap kept wrapping: the invoice number is the row's
   identity, and Open stacking above Print put back the height the ticket cell
   had just given up. Ten columns is genuinely tight beside the rail, so this
   table also runs a little denser than the default. */
.inv-number-cell { white-space: nowrap; font-weight: 600; }
.inv-actions-cell { white-space: nowrap; }
.inv-customer-cell { max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#invoicesTable td, #invoicesTable th { padding: 9px 10px; }

/* 1186px of table in a 1125px container meant Print sat just past the edge and
   had to be scrolled to. Trimming the two widest context columns brings the
   whole row inside the frame at desktop width; the container still scrolls at
   narrower sizes, which is correct for a table this dense and keeps the page
   body from ever scrolling sideways. */
.inv-ticket-cell { max-width: 168px; }
.inv-customer-cell { max-width: 128px; }

/* The workspace header chips carry money. Tabular figures line the digits up;
   monospace made them look like a debug readout, same as the dashboard strip. */
.wsx-chip .v { font-family: inherit; font-variant-numeric: tabular-nums; font-size: 14px; }

/* ================================================================
   One active-tab convention
   ----------------------------------------------------------------
   Three tab systems had grown up: .seg (list filters), .ws-tabs (Bootstrap
   tab panes) and .ws-switch (the customer / device / ticket workspace screen
   switcher). The first two fill the selected tab navy with white text. The
   third did the opposite -- white surface, navy text -- so on those three
   workspaces the selected tab looked like the UNSELECTED state everywhere
   else in the product.

   Aligned to the majority convention rather than the other way round: navy
   fill is the stronger signal, and it is what the filters, the segments and
   the nav rail's active item all already use.
   ================================================================ */
.ws-switch button.on { background: var(--navy); color: #fff; box-shadow: 0 2px 6px rgba(15,42,68,.22); }
.ws-switch button:hover:not(.on) { background: var(--surface); color: var(--navy); }

/* ================================================================
   Schedule / Dispatch polish
   ----------------------------------------------------------------
   Three things a person notices immediately on this page.

   The six KPI cards used auto-fit, which fitted five across at 1440px and
   orphaned the sixth on a row of its own. Six is a factor of both 3 and 2, so
   it lays out evenly at every width instead.

   The date buttons wrapped "Prev" and "Next" under their arrows, which turns
   a 32px control into a 48px one and makes the row look broken.

   The technician filter clipped its own text -- "All techniciar" -- because
   the select was narrower than its longest option. A control that cannot show
   its own current value is a defect, not a tight fit.
   ================================================================ */
/* Fixed at three columns, this laid four KPI cards out as 3 + 1 with a hole
   beside the last one (Work Approval has four). Auto-fit lets the row take
   whatever count the page actually has -- four fit on one line at desktop
   width, six still fit at 1500px -- instead of every page having to have a
   multiple of three. */
.kpi-row { grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); }
@media (max-width: 900px)  { .kpi-row { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px)  { .kpi-row { grid-template-columns: 1fr; } }

.disp-datenav .dn-btn { white-space: nowrap; }
#sch-filter-tech, #sch-filter-status, #sch-filter-priority { flex: 0 0 auto; max-width: none; }
#sch-filter-tech { width: 172px; }
#sch-filter-status { width: 152px; }
#sch-filter-priority { width: 148px; }
.ab-right { flex-wrap: wrap; }
@media (max-width: 520px) {
  #sch-filter-tech, #sch-filter-status, #sch-filter-priority { width: 100%; }
}

/* ================================================================================
   RepairCRM design system v4 - the approved SaaS reference
   ================================================================================
   One shared layer, not per-page patches. Everything below is presentation:
   no page loses a control, a filter, a permission or a data point because of it.

   The reference's character comes from four things, in order of how much they
   change the feel of a screen:

     1. Poppins, a geometric face, in place of Inter's grotesque.
     2. Space. Cards breathe at 22-24px rather than 16-20px, and the gap
        between blocks is consistent everywhere instead of per-page.
     3. Soft, large-radius surfaces with a hairline border and a shadow you can
        barely see, rather than boxes with visible edges.
     4. Status as a soft-filled pill, not a saturated block.

   Colour is deliberately NOT taken raw from the reference. Its greens and reds
   fail WCAG AA as text, and this product's palette was already corrected for
   that against real pages with axe-core. The brand hues match; the text-bearing
   variants stay the audited ones.
   ================================================================================ */

:root {
  --ds-font: "Poppins", "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;

  /* A spacing scale, so gaps stop being decided page by page. */
  --sp-1: 4px;  --sp-2: 8px;   --sp-3: 12px; --sp-4: 16px;
  --sp-5: 20px; --sp-6: 24px;  --sp-7: 32px; --sp-8: 40px;

  --radius-lg: 16px;
  --radius: 14px;
  --radius-sm: 10px;
  --radius-pill: 999px;

  /* Barely-there elevation: the reference separates surfaces with space and a
     hairline, not with drop shadows. */
  --shadow: 0 1px 2px rgba(15, 42, 68, .04), 0 1px 3px rgba(15, 42, 68, .03);
  --shadow-hover: 0 4px 12px rgba(15, 42, 68, .07), 0 12px 28px rgba(15, 42, 68, .06);
  --shadow-card: 0 1px 2px rgba(15, 42, 68, .05);

  --line: #e8edf3;
  --line-soft: #f1f5f9;
  /* Table headers sit on --line-soft, not on white, so they need their own
     value: --muted-text (#637687) is 4.70:1 on white but only 4.28:1 here,
     which failed axe on 45 surface/viewport scans. 5.31:1 on --line-soft. */
  --th-text: #566779;
  --bg: #f6f8fb;
}

body, .shell-body, .btn, .form-control, .form-select, .table, input, select, textarea, button {
  font-family: var(--ds-font);
}

/* Headings carry the geometry; -0.02em keeps Poppins from feeling loose at size. */
h1, h2, h3, h4, h5, h6, .page-hd h3, .ws-identity h3 {
  font-family: var(--ds-font);
  letter-spacing: -.02em;
  font-weight: 700;
}

/* ---------- Page header ----------------------------------------------------
   The reference gives a page a real title block: large, confident, with the
   primary action opposite it. Ours was 21px and sat tight against the content. */
.page-hd {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--sp-4); flex-wrap: wrap; margin-bottom: var(--sp-6);
}
.page-hd h3 { font-size: 26px; font-weight: 700; color: var(--navy); margin: 0; line-height: 1.2; }
.page-hd .page-sub { color: var(--muted); font-size: 13.5px; margin-top: 5px; font-weight: 400; }
.page-hd-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: center; }

/* ---------- Cards ----------------------------------------------------------
   Larger radius, hairline border, almost no shadow, and real breathing room. */
.ws-card, .card, .panel, .attn-panel {
  border-radius: var(--radius-lg);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-card);
  background: var(--surface);
}
.ws-card-hd, .card-header, .panel-hd, .attn-hd {
  padding: var(--sp-5) var(--sp-6);
  border-bottom: 1px solid var(--line-soft);
  background: transparent;
}
.ws-card-hd h6, .panel-hd h6, .attn-hd h6, .card-header {
  font-size: 15px; font-weight: 650; color: var(--navy); letter-spacing: -.01em; margin: 0;
}
.ws-card-body, .card-body { padding: var(--sp-6); }

/* ---------- KPI cards ------------------------------------------------------
   The reference's signature element: a soft-tinted icon square, the figure
   large and first, the label beneath it, and a trend chip carrying context.
   Ours had the accent as a 4px bar down the left edge, which reads as an alert
   rather than as a metric. */
.stat-card {
  position: relative; overflow: hidden;
  border-radius: var(--radius-lg);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-card);
  padding: var(--sp-5) var(--sp-6);
  background: var(--surface);
  transition: box-shadow .16s ease, transform .16s ease, border-color .16s ease;
}
.stat-card::before { display: none; }
.stat-card:hover { box-shadow: var(--shadow-hover); transform: translateY(-2px); border-color: #dbe4ee; }

.stat-card .stat-icon {
  /* 36px at top:16 ends at y=52. The figure below starts at 20 + 33 + 6 = 59,
     so the label now clears the icon on its own and does not need reserved
     width to do it -- see .stat-label. */
  position: absolute; top: 16px; right: var(--sp-5);
  width: 36px; height: 36px; border-radius: 10px; margin: 0;
  display: flex; align-items: center; justify-content: center; font-size: 17px;
  background: var(--accent-soft, var(--blue-soft));
  color: var(--accent, var(--blue));
}
.stat-card .stat-icon svg { width: 18px; height: 18px; }
.stat-card .stat-num {
  font-size: 30px; font-weight: 700; color: var(--navy);
  line-height: 1.1; letter-spacing: -.03em;
  font-variant-numeric: tabular-nums;
  /* The figure IS level with the icon, so this one really does need clearance. */
  padding-right: 46px;
}
.stat-card .stat-label {
  color: var(--muted); font-size: 13px; font-weight: 500; margin-top: 6px; letter-spacing: -.005em;
  /* Deliberately NOT padded away from the icon. Reserving 52px here fixed the
     overlap on the dashboard's five cards and immediately broke Schedule's six,
     where it left ~128px of label and wrapped "Response-Target Risk" onto three
     lines. The icon was shortened instead so the label clears it by geometry,
     which costs no width on any card at any count. */
  padding-right: 0;
}
.stat-card .stat-trend {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11.5px; font-weight: 500; color: var(--muted-text);
  margin-top: 10px; line-height: 1.35;
}

/* ---------- Tables ---------------------------------------------------------
   A quieter header, real row height, and a hairline that separates rather than
   draws. The reference's tables read as calm; ours read as a grid. */
.table { font-size: 13.5px; border-color: var(--line); }
.table thead th {
  background: var(--line-soft);
  color: var(--th-text);
  font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .05em;
  padding: 13px var(--sp-4);
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.table td { padding: 14px var(--sp-4); vertical-align: middle; border-color: var(--line-soft); }
.table-hover tbody tr:hover { background: #f7fafd; }
.table > :not(:first-child) { border-top: none; }

/* ---------- Status pills ---------------------------------------------------
   Soft tint plus an AA-safe text colour, rather than a saturated block. */
.st-badge, .pr-badge, .badge {
  display: inline-flex; align-items: center; gap: 5px;
  border-radius: var(--radius-pill);
  padding: 4px 11px;
  font-size: 11.5px; font-weight: 600; letter-spacing: .01em;
  line-height: 1.5; white-space: nowrap; border: none;
}

/* ---------- Buttons --------------------------------------------------------
   Slightly softer corners and a calmer resting state; the lift on hover stays. */
.btn { border-radius: var(--radius-sm); font-weight: 600; font-size: 13px; padding: 9px 18px; letter-spacing: -.005em; }
.btn-sm { padding: 7px 14px; font-size: 12.5px; border-radius: 9px; }

/* ---------- Forms ----------------------------------------------------------- */
.form-control, .form-select { border-radius: var(--radius-sm); border-color: var(--line); font-size: 13.5px; padding: 10px 14px; }
.form-select { padding-right: 2.25rem; }
.form-label { font-weight: 550; font-size: 12.5px; color: var(--ink-soft); margin-bottom: 6px; }

/* ---------- Empty states ---------------------------------------------------- */
.empty-state { padding: var(--sp-8) var(--sp-5); }
.empty-state .es-icon { font-size: 34px; opacity: .35; margin-bottom: var(--sp-3); }

/* ---------- Workspace shell space ------------------------------------------- */
.shell-workspace { padding: var(--sp-6) var(--sp-7); }
@media (max-width: 992px) { .shell-workspace { padding: var(--sp-4); } }

/* ---------- Dashboard charts ------------------------------------------------
   The two the approved reference leads with, drawn from real rows: a
   seven-day created/completed trend and a ticket-status donut.

   Inline SVG and CSS rather than a charting library -- no new dependency, no
   external script, and both render identically in the print path. Colours come
   from the same audited status tokens the badges use, so a colour means the
   same thing in a chart as it does in a table. */
.dash-charts { display: grid; grid-template-columns: 1.45fr 1fr; gap: var(--sp-4); }
@media (max-width: 1100px) { .dash-charts { grid-template-columns: 1fr; } }

/* ---------- Schedule: the empty day -----------------------------------------
   A strip above the board rather than a block replacing it, so an empty day
   keeps its hours and its technician lanes. */
.sch-empty-strip {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 12px var(--sp-5);
  margin-bottom: var(--sp-4);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-card);
}
.sch-empty-strip .ses-icon {
  flex: 0 0 34px; width: 34px; height: 34px; border-radius: 9px;
  background: var(--blue-soft); color: #0d6c93;
  display: flex; align-items: center; justify-content: center;
}
.sch-empty-strip .ses-icon svg { width: 18px; height: 18px; }
.sch-empty-strip .ses-text { flex: 1 1 260px; min-width: 0; font-size: 12.5px; color: var(--th-text); line-height: 1.45; }
.sch-empty-strip .ses-text b { display: block; color: var(--navy); font-size: 13.5px; font-weight: 650; }
.sch-empty-strip .ses-actions { flex: 0 0 auto; display: flex; gap: 8px; flex-wrap: wrap; }

/* ---------- Back to the list ------------------------------------------------
   The reference opens every detail view with one. Ours had none, so the only
   way out of a record was the rail -- which does not take you back to the list
   you were reading, it takes you to whatever that rail item points at. */
.ws-back {
  display: inline-flex; align-items: center; gap: 5px;
  margin-bottom: 12px;
  color: var(--th-text); font-size: 13px; font-weight: 550;
  text-decoration: none;
}
.ws-back:hover { color: var(--navy); text-decoration: none; }

/* ---------- Dashboard bands -------------------------------------------------
   The reference lays the dashboard out as three bands -- figures, then a wide
   chart beside a narrow list, then three equal columns -- and that is most of
   why it reads as a dashboard rather than as a report. Ours was nine
   full-width slabs stacked down a scroll roughly twice as tall as it needed to
   be, where almost nothing was visible at the same time as anything else.

   align-items:start matters: without it a short card in a band stretches to
   match the tallest one beside it and ends in a lake of white. */
.dash-band { display: grid; gap: var(--sp-4); margin-bottom: var(--sp-4); align-items: start; }
.dash-band-2 { grid-template-columns: 1.7fr 1fr; }
.dash-band-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.dash-band > .panel,
.dash-band > .ws-card { margin-bottom: 0; }
/* Three columns need roughly 1200px of workspace to stay readable; below that
   they go to two, and below tablet everything stacks. Nothing is ever hidden
   at any width -- the band only changes how many columns it has. */
@media (max-width: 1240px) { .dash-band-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 1100px) { .dash-band-2 { grid-template-columns: 1fr; } }
@media (max-width: 820px)  { .dash-band-3 { grid-template-columns: 1fr; } }

/* ---------- Sparklines ------------------------------------------------------
   A fortnight's shape under a headline figure, as the reference draws it. It
   sits along the bottom of the card and is decorative in the accessibility
   sense -- the number and the delta above it carry the meaning, and the SVG is
   aria-hidden -- so it must never be the only place a value appears. */
.stat-card .spark { display: block; width: 100%; height: 26px; margin-top: 10px; overflow: visible; }
.stat-card .stat-trend + .spark { margin-top: 8px; }

/* ---------- Sortable headers and the pager ----------------------------------
   The reference's list pages carry both and ours carried neither: every list
   was ORDER BY id DESC behind a hard LIMIT. That is a missing control and a
   quiet correctness problem at once -- past the limit the list is truncated
   with nothing saying so. */
.dt th.dt-sort { padding: 0; }
.dt th.dt-sort > a {
  display: flex; align-items: center; gap: 5px;
  padding: 11px var(--sp-4);
  color: inherit; text-decoration: none;
  font: inherit; letter-spacing: inherit; text-transform: inherit;
  white-space: nowrap;
}
.dt th.dt-sort > a:hover { color: var(--navy); background: #eef3f8; text-decoration: none; }
.dt th.dt-sort.on > a { color: var(--navy); }
.dt .dt-caret { font-size: 8px; line-height: 1; }
/* The idle caret is a hint that the column CAN sort, not a claim that it is
   sorted -- so it sits well back until the header is hovered. */
.dt .dt-caret-idle { opacity: .28; }
.dt th.dt-sort > a:hover .dt-caret-idle { opacity: .6; }

.dt-pager {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4);
  flex-wrap: wrap;
  padding: 12px var(--sp-5);
  border-top: 1px solid var(--line-soft);
}
.dtp-count { color: var(--th-text); font-size: 12.5px; }
.dtp-nav { display: flex; align-items: center; gap: 4px; }
.dtp-btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 32px; height: 32px; padding: 0 9px;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--surface); color: var(--ink-soft);
  font-size: 12.5px; font-weight: 600; text-decoration: none;
}
.dtp-btn:hover { border-color: #c3d3e2; color: var(--navy); background: var(--surface-2); text-decoration: none; }
.dtp-btn.is-current { background: #137AA6; border-color: #137AA6; color: #fff; }
.dtp-btn.is-disabled { opacity: .4; }
.dtp-gap { color: var(--muted); padding: 0 2px; }

/* ---------- Feed list -------------------------------------------------------
   The narrow column beside a wide chart. The reference puts Recent Tickets
   there and draws it as a list -- avatar, reference, title, customer, and a
   badge on the right -- not as a table.

   That distinction is not cosmetic. A five-column table in a 360px column wraps
   every title onto four lines, makes the card a thousand pixels tall, and drags
   the whole band down with it. The same rows as a list are one line each and
   the band closes up. Every field the table showed is still here. */
.feed { display: flex; flex-direction: column; }
.feed-row {
  display: flex; align-items: center; gap: 11px;
  padding: 11px var(--sp-5);
  border-top: 1px solid var(--line-soft);
  text-decoration: none; color: inherit;
  transition: background .12s ease;
}
.feed-row:first-child { border-top: none; }
.feed-row:hover { background: var(--surface-2); text-decoration: none; color: inherit; }
.feed-av {
  flex: 0 0 32px; width: 32px; height: 32px; border-radius: 50%;
  background: var(--blue-soft); color: #0d6c93;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700;
}
/* display:block is load-bearing, not tidiness. These are <span>s inside an
   <a>, and an inline box ignores width, overflow and text-overflow entirely --
   so the title ran straight under the badges instead of truncating before
   them. */
.feed-body { flex: 1 1 auto; min-width: 0; display: block; }
.feed-title {
  display: block; max-width: 100%;
  font-size: 13px; font-weight: 600; color: var(--navy); line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.feed-sub {
  display: block; max-width: 100%;
  font-size: 11.5px; color: var(--th-text); margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.feed-end { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; }
/* The card padding lives on the rows so a hover highlights edge to edge. */
.ws-card-body.feed-wrap { padding: var(--sp-2) 0; }
/* Same failure mode on the feed: avatar + title + two badges does not fit in a
   phone's width, and the badges were the part that fell off the edge. They move
   to their own line under the title, indented to clear the avatar, rather than
   being dropped -- a ticket's status and priority are the reason to look at
   this list. */
@media (max-width: 560px) {
  .feed-row { flex-wrap: wrap; row-gap: 7px; }
  .feed-body { flex: 1 1 100%; }
  .feed-end { flex: 0 0 100%; padding-left: 43px; }
}

.dash-charts .ws-card-hd { display: flex; align-items: center; justify-content: space-between; }
.dash-charts .cnt { color: var(--muted); font-size: 12px; font-weight: 500; }

.chart-empty { color: var(--muted); font-size: 13px; text-align: center; padding: 28px 10px; }

/* --- seven-day trend --- */
.trend-chart { display: flex; align-items: flex-end; gap: 10px; height: 170px; padding-top: 6px; }
.tc-day { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 8px; height: 100%; }
.tc-bars { flex: 1; width: 100%; display: flex; align-items: flex-end; justify-content: center; gap: 4px; }
.tc-bar { width: 46%; max-width: 22px; border-radius: 6px 6px 3px 3px; transition: opacity .12s; }
.tc-bar:hover { opacity: .82; }
.tc-created { background: var(--blue); }
.tc-completed { background: var(--green); }
.tc-label { font-size: 11px; color: var(--muted); font-weight: 500; }

.chart-legend { display: flex; gap: 18px; justify-content: center; margin-top: 14px; font-size: 12px; color: var(--muted); }
.chart-legend span { display: inline-flex; align-items: center; gap: 6px; }
.lg-dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; flex: 0 0 auto; }

/* --- status donut --- */
.donut-wrap { display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap; }
.donut { width: 140px; height: 140px; flex: 0 0 auto; }
.donut-num { font-size: 26px; font-weight: 700; fill: var(--navy); font-family: var(--ds-font); }
.donut-cap { font-size: 10px; fill: var(--muted); font-family: var(--ds-font); text-transform: uppercase; letter-spacing: .06em; }

.donut-legend { list-style: none; margin: 0; padding: 0; flex: 1; min-width: 165px; }
.donut-legend li { display: flex; align-items: center; gap: 8px; padding: 5px 0; font-size: 12.5px; }
.donut-legend .ll-name { color: var(--ink-soft); text-transform: capitalize; flex: 1; }
.donut-legend .ll-val { font-weight: 650; color: var(--navy); font-variant-numeric: tabular-nums; }
.donut-legend .ll-pct { color: var(--muted); font-size: 11.5px; min-width: 34px; text-align: right; font-variant-numeric: tabular-nums; }
/* On a phone the legend sat beside the 140px donut and its counts and
   percentages ran off past the right edge of the card, which clips them: the
   owner saw "New / Invoiced / In Progress" with no numbers at all. flex-wrap
   plus min-width was supposed to prevent that and did not, so the stack is
   stated outright rather than left to negotiation. */
@media (max-width: 620px) {
  .donut-wrap { flex-direction: column; align-items: stretch; gap: var(--sp-4); }
  .donut { align-self: center; }
  .donut-legend { min-width: 0; width: 100%; flex: 0 0 auto; }
}

@media (max-width: 520px) {
  .donut-wrap { justify-content: center; }
  .trend-chart { height: 140px; gap: 6px; }
}

/* ================================================================================
   Data table (.dt) — the shared list surface
   ================================================================================
   Tickets, Customers, Invoices, Quotes, Payments, Inventory and the rest are all
   the same object: a filter bar, a dense table, a row that opens something. They
   were each built separately, so they each looked slightly different and each
   behaved differently on a phone. This is that surface, once.

   THE MOBILE RULE. Below 860px the table becomes cards, and every cell keeps its
   label via data-label. That matters more than it sounds: the usual answer to a
   wide table on a phone is to hide columns, which quietly removes information the
   person on site actually needed. Nothing is hidden here -- it is restacked.

   Rows stay compact on purpose. The old ticket list ran 70px per row because a
   status, a priority and an invoice state wrapped onto a second line; a screen
   showed fourteen jobs. Same three facts in one line shows twice as many, and a
   dispatcher scanning for the next job is reading a screen, not a page. */

.dt-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}

/* ---------- toolbar: search, filters, count ---------- */
.dt-toolbar {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--line-soft);
  flex-wrap: wrap;
}
.dt-toolbar .dt-search { position: relative; flex: 1 1 260px; min-width: 200px; max-width: 380px; }
.dt-toolbar .dt-search input { padding-left: 34px; }
.dt-toolbar .dt-search .dt-search-icon {
  position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
  color: var(--muted-soft); pointer-events: none; display: flex;
}
.dt-toolbar .form-select, .dt-toolbar .form-control { font-size: 13px; }
/* A select must be able to show its own longest option. Capped-width selects in a
   flex row are what produced "All techniciar" on Schedule. */
.dt-toolbar .dt-filter { flex: 0 0 auto; width: auto; min-width: 150px; }
.dt-count { margin-left: auto; color: var(--muted-text); font-size: 12.5px; font-weight: 500; white-space: nowrap; }

/* ---------- filter pills ---------- */
/* One click to a view beats two clicks through a dropdown, so the pills stay --
   they are the faster control, and the reference's dropdowns would be a
   downgrade for the person using this fifty times a day. */
.dt-pills { display: flex; gap: 6px; padding: var(--sp-3) var(--sp-4); flex-wrap: wrap; border-bottom: 1px solid var(--line-soft); }
.dt-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 13px; border-radius: var(--radius-pill);
  font-size: 12.5px; font-weight: 550; text-decoration: none;
  color: var(--ink-soft); background: var(--line-soft);
  border: 1px solid transparent; white-space: nowrap;
  transition: background .12s, color .12s, border-color .12s;
}
.dt-pill:hover { background: #e6ecf3; color: var(--navy); text-decoration: none; }
.dt-pill.active { background: var(--navy); color: #fff; border-color: var(--navy); }
.dt-pill .dt-pill-n {
  font-size: 11px; font-weight: 600; padding: 0 5px; border-radius: var(--radius-pill);
  /* The chip sits on a tinted pill, not on white, so --muted-text (4.29:1 on
     white) drops to 3.70:1 here and fails AA -- caught by axe on the ticket
     list. 5.41:1 on the chip fill, with margin to spare because that fill is
     a composited rgba value rather than a fixed colour. */
  background: rgba(15,42,68,.08); color: #4c5c6d; min-width: 18px; text-align: center;
}
.dt-pill.active .dt-pill-n { background: rgba(255,255,255,.22); color: #fff; }

/* ---------- the table ---------- */
.dt { width: 100%; border-collapse: collapse; font-size: 13.5px; margin: 0; }
.dt thead th {
  background: var(--line-soft); color: var(--th-text);
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em;
  padding: 11px var(--sp-4); text-align: left; white-space: nowrap;
  border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 2;
}
.dt tbody td { padding: 11px var(--sp-4); border-top: 1px solid var(--line-soft); vertical-align: middle; }
.dt tbody tr { transition: background .1s; }
.dt tbody tr:hover { background: #f7fafd; }
.dt tbody tr.dt-clickable { cursor: pointer; }

.dt .dt-primary { font-weight: 600; color: var(--navy); line-height: 1.35; }
.dt .dt-primary a { color: inherit; text-decoration: none; }
.dt .dt-primary a:hover { text-decoration: underline; }
.dt .dt-sub { color: var(--muted-text); font-size: 12px; line-height: 1.35; margin-top: 1px; }
.dt .dt-id { color: var(--muted-text); font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
.dt .dt-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.dt .dt-nowrap { white-space: nowrap; }

/* The action column: a real button plus the overflow, right-aligned, never wrapping. */
.dt .dt-act { text-align: right; white-space: nowrap; width: 1%; }
.dt .dt-act .btn { vertical-align: middle; }

/* ---------- empty state ---------- */
.dt-empty { padding: var(--sp-8) var(--sp-5); text-align: center; }
.dt-empty .dte-icon { font-size: 30px; opacity: .3; margin-bottom: var(--sp-3); }
.dt-empty .dte-title { font-weight: 600; color: var(--navy); margin-bottom: 4px; }
.dt-empty .dte-sub { color: var(--muted); font-size: 13px; }

/* ================================================================================
   Phone: the table becomes cards, and no column is dropped.
   ================================================================================ */
@media (max-width: 860px) {
  .dt-toolbar { padding: var(--sp-3); }
  .dt-toolbar .dt-search { max-width: none; flex: 1 1 100%; }
  .dt-toolbar .dt-filter { flex: 1 1 45%; min-width: 0; }
  .dt-count { margin-left: 0; width: 100%; }

  .dt, .dt tbody, .dt tr, .dt td { display: block; width: 100%; }
  .dt thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

  .dt tbody tr {
    border-top: 1px solid var(--line-soft);
    padding: var(--sp-3) var(--sp-4);
  }
  .dt tbody tr:first-child { border-top: none; }

  .dt tbody td {
    border: none; padding: 3px 0;
    display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-4);
  }
  /* The label the header used to carry, so a stacked cell still says what it is. */
  .dt tbody td::before {
    content: attr(data-label);
    color: var(--muted-text); font-size: 11px; font-weight: 600;
    text-transform: uppercase; letter-spacing: .04em;
    flex: 0 0 auto;
  }
  /* A cell with two lines (a name and a phone) must read as one value on the
     right, not spread its two halves across the row. The first child is
     pushed against the label; anything after it takes its own line. */
  .dt tbody td:not(.dt-lead):not(.dt-act) > * { text-align: right; }
  .dt tbody td:not(.dt-lead):not(.dt-act) > *:first-child { margin-left: auto; }
  .dt tbody td:not(.dt-lead):not(.dt-act) > *:not(:first-child) { flex-basis: 100%; }
  .dt tbody td:not(.dt-lead):not(.dt-act) { flex-wrap: wrap; }
  .dt tbody td:empty { display: none; }
  /* The title cell leads the card: no label, full width, larger. */
  .dt tbody td.dt-lead { display: block; padding-bottom: 6px; }
  .dt tbody td.dt-lead::before { content: none; }
  .dt tbody td.dt-lead .dt-primary { font-size: 14.5px; }

  .dt .dt-num, .dt .dt-act { text-align: left; width: auto; }
  .dt tbody td.dt-act { justify-content: flex-start; gap: var(--sp-2); padding-top: var(--sp-3); }
  .dt tbody td.dt-act::before { content: none; }
  .dt tbody td.dt-act .btn { flex: 1 1 auto; }
}

/* ---------- badge cluster: one line, never wrapping the row open ---------- */
.dt-badges { display: flex; align-items: center; gap: 5px; flex-wrap: nowrap; }
@media (max-width: 860px) { .dt-badges { flex-wrap: wrap; justify-content: flex-end; } }

/* ---------- row overflow menu ---------- */
.dt-overflow { position: relative; display: inline-block; }
.dt-overflow-btn {
  background: transparent; border: 1px solid transparent; border-radius: var(--radius-sm);
  color: var(--muted); width: 30px; height: 30px; line-height: 1;
  cursor: pointer; font-size: 16px;
}
.dt-overflow-btn:hover { background: var(--line-soft); color: var(--navy); border-color: var(--line); }
.dt-overflow-menu {
  display: none; position: absolute; right: 0; top: calc(100% + 4px); z-index: 30;
  min-width: 210px; padding: 6px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-hover);
  text-align: left;
}
.dt-overflow.open .dt-overflow-menu { display: block; }
.dt-overflow-menu a,
.dt-overflow-menu button {
  display: block; width: 100%; text-align: left;
  padding: 8px 10px; border-radius: var(--radius-sm);
  font-size: 13px; color: var(--ink-soft); background: transparent; border: none;
  text-decoration: none; cursor: pointer;
}
.dt-overflow-menu a:hover,
.dt-overflow-menu button:hover { background: var(--line-soft); color: var(--navy); text-decoration: none; }
.dt-overflow-menu form { margin: 0; }

/* ---------- .filter-pill inherits the v4 pill ------------------------------
   Eight pages still use the older .filter-pill markup, and several of them --
   Work Approval most of all -- drive it from JavaScript that queries
   `.filter-pill` by class and toggles `.active` on it. Renaming the class in
   those templates would mean editing each page's script to match, for no gain
   beyond a different class name.

   Styling the old class as the new one upgrades all eight at once and cannot
   break a selector, because no selector changes. .dt-pill stays the name new
   markup uses; this is the same control wearing its original label. */
.filter-pills {
  display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: var(--sp-4);
}
.filter-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 13px; border-radius: var(--radius-pill);
  font-size: 12.5px; font-weight: 550; text-decoration: none;
  color: var(--ink-soft); background: var(--line-soft);
  border: 1px solid transparent; white-space: nowrap; cursor: pointer;
  transition: background .12s, color .12s, border-color .12s;
}
.filter-pill:hover { background: #e6ecf3; color: var(--navy); border-color: transparent; text-decoration: none; }
.filter-pill.active { background: var(--navy); color: #fff; border-color: var(--navy); }
.filter-pill.active:hover { background: var(--navy); color: #fff; }

/* ---------- .page-header aligned with .page-hd ------------------------------
   Six pages (Work Approval, Payroll and friends) use a richer header component:
   an icon tile, a title, a subtitle and actions. It is a better header than the
   plain one, not a worse one, so it is kept and brought into line rather than
   replaced -- rewriting six working headers to lose an icon would be change for
   its own sake.

   What was inconsistent was scale: 24px/800 here against 26px/700 on every
   other page, a heavier icon tile, and a different bottom margin, so moving
   between pages the title visibly jumped. */
.page-header { gap: var(--sp-4); margin-bottom: var(--sp-6); }
.page-header h1 {
  font-family: var(--ds-font);
  font-size: 26px; font-weight: 700;
  letter-spacing: -.02em; line-height: 1.2;
  color: var(--navy);
}
.page-header .ph-sub {
  color: var(--muted); font-size: 13.5px; margin-top: 5px;
  max-width: 720px; line-height: 1.55; font-weight: 400;
}
.page-header .ph-icon {
  width: 42px; height: 42px; flex: 0 0 42px;
  border-radius: 12px;
  /* The flat tint the v4 stat cards use, rather than a gradient with a drop
     shadow -- the header should introduce the page, not compete with it. */
  background: var(--blue-soft);
  color: var(--blue);
  box-shadow: none;
}
.page-header .ph-icon svg { width: 21px; height: 21px; }
.page-header .ph-actions { gap: var(--sp-2); }

/* ---------- Reports exports -------------------------------------------------
   Eight identical-looking links in a flat list gave no clue what any of them
   produced or which one you wanted. Grouped by the question they answer, with
   the output format stated on the row. */
.rep-exports { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--sp-5); }
.rep-export-group { min-width: 0; }
.rep-export-title {
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em;
  color: var(--th-text); margin-bottom: var(--sp-2);
}
.rep-export {
  display: block; padding: 10px 12px; margin-bottom: 6px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); text-decoration: none;
  transition: border-color .12s, background .12s;
}
.rep-export:hover { border-color: var(--blue); background: #f7fafd; text-decoration: none; }
.rep-export-name { display: block; font-size: 13.5px; font-weight: 600; color: var(--navy); }
.rep-export-desc { display: block; font-size: 12px; color: var(--muted); margin-top: 1px; }

/* ---------- Checkboxes and radios -------------------------------------------
   Bootstrap lays these out with `float: left` on the input and a negative
   margin into the label's padding. When the row is not tall enough to contain
   the float, successive floats stop clearing and stack: on Settings, ten
   service-type checkboxes marched diagonally across the card, each a couple of
   pixels further right than the one above it. Measured, not guessed -- the
   inputs sat at x = 285, 287, 290, 292.

   Flex removes the failure mode rather than tuning around it: the input and its
   label are two items in a row, so there is no float to escape and no height to
   get wrong. */
.form-check {
  display: flex; align-items: center; gap: 9px;
  padding-left: 0; min-height: 0; margin-bottom: 7px;
}
.form-check .form-check-input {
  float: none; margin-left: 0; margin-top: 0;
  flex: 0 0 auto;
}
.form-check .form-check-label { margin-bottom: 0; cursor: pointer; }

/* Inline groups keep sitting side by side. */
.form-check-inline { display: inline-flex; margin-right: var(--sp-4); }

/* A switch is wider than a checkbox and must not be squashed by the flex row. */
.form-switch .form-check-input { width: 2em; }

/* ---------- .ws-card used without a header/body wrapper ---------------------
   v4 moved padding off .ws-card and onto .ws-card-hd / .ws-card-body, which is
   right for the cards that use those. Plenty do not -- Settings puts its
   content straight into the card -- and with no padding those cards had their
   first heading's top margin COLLAPSE out through the top edge, so the heading
   rendered above the card it belongs to, looking like a stray label floating on
   the page background.

   Padding is restored only for cards that do not use the header/body pattern,
   so the two styles can coexist rather than one being converted to the other
   across dozens of files. */
.ws-card:not(:has(> .ws-card-hd)):not(:has(> .ws-card-body)):not(:has(> .table-responsive)) {
  padding: var(--sp-5) var(--sp-6);
}
/* Belt and braces for any browser without :has() -- stops the collapse itself,
   even if the padding rule above never applies. */
.ws-card > h5:first-child,
.ws-card > h6:first-child,
.ws-card > h4:first-child { margin-top: 0; }

/* ---------- Key/value detail tables (.kv) -----------------------------------
   A <tr><th>Label</th><td>value</td></tr> table is a description list wearing a
   table's markup. It is NOT a data table, and pushing it through the .dt
   treatment would be wrong: there are no columns to label, and stacking it into
   cards would turn six facts about one contract into six cards.

   What it needs instead is to stop looking like a grid -- no header fill, no
   heavy rules -- and to stack label above value once the row is too narrow to
   hold both. */
.kv { width: 100%; border-collapse: collapse; font-size: 13.5px; margin: 0 0 var(--sp-4); }
.kv tr { border-bottom: 1px solid var(--line-soft); }
.kv tr:last-child { border-bottom: none; }
.kv th {
  text-align: left; font-weight: 550; color: var(--muted-text);
  padding: 9px var(--sp-4) 9px 0; white-space: nowrap; width: 1%;
  vertical-align: top; background: transparent;
}
.kv td { padding: 9px 0; color: var(--ink); vertical-align: top; }

@media (max-width: 640px) {
  .kv, .kv tbody, .kv tr, .kv th, .kv td { display: block; width: 100%; }
  .kv tr { padding: var(--sp-2) 0; }
  .kv th { padding: 0 0 2px; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }
  .kv td { padding: 0; }
}

/* ---------- Selection list (.pick) ------------------------------------------
   The contract picker is a list you choose from, not a table of data. It was
   marked up as a table with no header at all, which is why the table converter
   refused it -- correctly. Styled as what it actually is. */
.pick { display: block; }
.pick-row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  padding: 11px var(--sp-4); border-top: 1px solid var(--line-soft);
  text-decoration: none; color: inherit;
}
.pick-row:first-child { border-top: none; }
.pick-row:hover { background: #f7fafd; text-decoration: none; }
.pick-row.on { background: var(--blue-soft); }
.pick-row.on .pick-name { color: var(--navy); }
.pick-name { font-weight: 600; color: var(--navy); font-size: 13.5px; }
.pick-sub { color: var(--muted-text); font-size: 12px; margin-top: 1px; }

/* ================================================================================
   The navigation rail — rebuilt against the reference, not against our own rules
   ================================================================================
   Two corrections in one block, and the first is mine to own.

   Last session I read the approved sheet as having a LIGHT rail and inverted
   ours to white. It does not. Every panel on that sheet -- dashboard, ticket
   list, ticket detail, invoices, and the mobile pair -- shows a DARK NAVY rail
   with white ink and a solid blue pill on the active item. Inverting it moved us
   away from the target and cost 32 contrast failures on the way there. The rail
   goes back to navy, which is where the reference and Tech4Service's own brand
   already agreed.

   The second correction is the structural one the owner actually objected to.
   Ours hid twenty destinations behind six <details> accordions, so the rail read
   as "four links and six shut drawers" while the reference reads as one flat,
   scannable list you take in at a glance. The grouping stays -- thirty flat rows
   is not legible either -- but it no longer HIDES: no chevrons, no closed state,
   the group name demoted to a quiet section heading, every page one click away
   and visible while you are looking for it.

   The rail also ends the way the reference's does: with the signed-in person,
   pinned to the bottom rather than scrolling away with the links.
   ================================================================================ */
.shell-nav {
  background: var(--navy);
  color: #cfe0ee;
  border-right: none;
  /* The rail itself no longer scrolls; .nav-scroll does. That is what lets the
     person card stay anchored at the bottom. */
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.nav-scroll { flex: 1 1 auto; overflow-y: auto; overflow-x: hidden; padding-bottom: 6px; }
/* A thin rail-coloured scrollbar; the default light one cut a bright stripe
   down the navy. */
.nav-scroll::-webkit-scrollbar { width: 6px; }
.nav-scroll::-webkit-scrollbar-thumb { background: rgba(255,255,255,.16); border-radius: 3px; }
.nav-scroll::-webkit-scrollbar-track { background: transparent; }

/* ---------- brand ---------- */
.nav-brand {
  padding: 18px 18px 16px;
  border-bottom: 1px solid rgba(255,255,255,.08);
  flex: 0 0 auto;
}
.nav-brand img { height: 30px; border-radius: 7px; background: rgba(255,255,255,.08); padding: 4px 7px; }
.nav-brand span { color: #fff; font-weight: 700; font-size: 15px; letter-spacing: -.01em; }

/* ---------- section headings ---------- */
.nav-section-label {
  color: #8fa9c2;   /* 5.02:1 on --navy */
  font-size: 10px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  padding: 15px 20px 5px;
}
.nav-group { margin: 0; }
.nav-group .nav-section-label { padding-top: 16px; }

/* ---------- items ---------- */
.nav-list { padding: 0 10px; }
.nav-list a {
  color: #b9cfe3;   /* 8.4:1 on --navy */
  font-weight: 500; font-size: 13px;
  border-radius: 9px;
  padding: 9px 12px;
  gap: 11px;
}
.nav-list a:hover { background: rgba(255,255,255,.08); color: #fff; }

/* The active item: one solid blue pill, as the reference draws it -- not a
   gradient with a glow.

   Not the raw brand --blue (#1BA5E0) though. I asserted "4.9:1" above from the
   pill-against-navy figure, which is the wrong pair entirely: what has to pass
   is the LABEL against the PILL, and white on #1BA5E0 is 2.79:1. axe found it
   on all 23 scanned surfaces, which is what an assumed number is worth.
   #137AA6 is the same hue two steps down -- 4.81:1 with white, and still 3.04:1
   against the navy behind it, so the pill reads as clearly as it did. */
.nav-list a.active,
.nav-list a.active:hover {
  background: #137AA6;
  color: #fff;
  font-weight: 650;
  box-shadow: none;
}
.nav-list a .nav-icon { color: #7f9cb8; }
.nav-list a:hover .nav-icon,
.nav-list a.active .nav-icon { color: #fff; }
.nav-list .nav-icon svg { width: 17px; height: 17px; }

/* ---------- the person, pinned to the bottom (as in the reference) ---------- */
.nav-foot {
  flex: 0 0 auto;
  border-top: 1px solid rgba(255,255,255,.08);
  padding: 10px;
}
.nav-user {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; border-radius: 10px;
  text-decoration: none; color: inherit;
}
.nav-user:hover { background: rgba(255,255,255,.08); text-decoration: none; }
.nav-user-avatar {
  flex: 0 0 34px; width: 34px; height: 34px; border-radius: 50%;
  /* Same pair, same reason as the active pill above: the initials are white. */
  background: #137AA6; color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-size: 12.5px; font-weight: 700; letter-spacing: .02em;
}
.nav-user-text { min-width: 0; display: flex; flex-direction: column; line-height: 1.25; }
.nav-user-name { color: #fff; font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nav-user-role { color: #8fa9c2; font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.shell-nav.collapsed .nav-user-text { display: none; }
.shell-nav.collapsed .nav-user { justify-content: center; padding: 8px 0; }

/* ---------- collapse control ---------- */
.nav-collapse-btn {
  background: rgba(255,255,255,.06);
  color: #b9cfe3;
  border: none;
  border-radius: 9px;
  font-weight: 550;
  margin: 6px 0 0;
  width: 100%;
}
.nav-collapse-btn:hover { background: rgba(255,255,255,.12); color: #fff; }

/* The mobile drawer inherits all of the above; it only needs its own shadow so
   it reads as sitting above the page rather than beside it. */
@media (max-width: 992px) {
  .shell-nav { box-shadow: 0 12px 40px rgba(15, 42, 68, .35); }
}

/* ---------- KPI trend delta -------------------------------------------------
   The reference pairs every headline figure with a change against the previous
   period, and that pairing is most of what makes the row read as a dashboard
   rather than a list of numbers. "$0 revenue today" means something completely
   different on a quiet Sunday than in a bad month, and the number alone cannot
   say which.

   Colour here is direction, not judgement, so it uses the AA-verified text
   variants rather than the raw brand green/red. */
.stat-delta {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11.5px; font-weight: 650;
  padding: 2px 7px; border-radius: var(--radius-pill);
  margin-right: 6px;
}
.stat-delta.up   { background: var(--green-soft); color: var(--green-text); }
.stat-delta.down { background: var(--red-soft);   color: var(--red-text); }
.stat-delta.flat { background: var(--line-soft);  color: var(--th-text); }
.stat-delta svg { width: 11px; height: 11px; }
.stat-card .stat-trend { display: flex; align-items: center; flex-wrap: wrap; gap: 2px; }
