/* =============================================================================
   ProDispatch Modern — COMPONENT LAYER
   Charlotte Metro Towing & Roadside Assistance
   Requires: towing-tokens.css (loaded first).

   All classes are prefixed `tw-`. This layer EXTENDS the base engine's admin
   vocabulary (.app .sidebar .content .topbar .card .grid-2 .stats .stat
   .actions .btn .alert .badge .grid .hint .stepper .wizard-wrap) — it never
   redefines those selectors. Engine re-skinning lives in towing-admin.css and
   is opt-in behind `.tw-theme`.

   Contents
     00  Base / reset / page shell
     01  Type roles
     02  Icons (Material Symbols)
     03  Global focus ring          <- doc 07 §10.4
     04  Motion safety              <- doc 07 §10.6
     05  Scrollbars + scroll shells <- doc 07 §1.4 / §5 overflow rules
     06  Utilities
     C01 Button — primary            C19 Stepper / wizard progress
     C02 Button — secondary/outline  C20 Map panel
     C03 Button — emergency          C21 Chat / activity panel
     C04 Text input                  C22 Slide-over / context panel
     C05 Field + overline label      C23 Bottom sheet (driver)
     C06 Select                      C24 Filter chips
     C07 Toggle switch               C25 KPI stat tile
     C08 Radio card                  C26 File upload / dropzone
     C09 Service capability card     C27 Empty state
     C10 Status chip                 C28 Toast / inline banner
     C11 Job card                    C29 Modal dialog
     C12 List row                    C30 Avatar
     C13 Data table                  C31 Badge / micro-tag
     C14 Tabs                        C32 Progress bar
     C15 Segmented control           C33 Pagination
     C16 Sidebar navigation          C34 Breadcrumbs
     C17 Top bar                     C35 Bento card
     C18 Bottom navigation           C36 Footer
   ============================================================================= */




/* =============================================================================
   00. BASE / RESET / PAGE SHELL
   Doc 07 §10.1 A7/A8: the mockups' white fields and white pages came from
   theme() inside a <style> tag. Every background here is explicit so no user-
   agent default can ever punch a white hole through the dark UI.
   ============================================================================= */

*, *::before, *::after { box-sizing: border-box; }

html {
  /* Requirement 5: zero horizontal overflow at 320/375/768/1024/1440.
     `clip` (not `hidden`) keeps position:sticky working inside the page. */
  max-width: 100%;
  overflow-x: clip;
  -webkit-text-size-adjust: 100%;
}

body.tw-page {
  margin: 0;
  max-width: 100%;
  overflow-x: clip;
  background: var(--background);
  color: var(--on-surface);
  font: var(--body-md-w) var(--body-md-size)/var(--body-md-lh) var(--font-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  touch-action: manipulation;           /* kills the 300ms tap delay          */
  -webkit-tap-highlight-color: transparent; /* replaced by :focus-visible     */
}

/* App shells that must not scroll the body — only the panes scroll. */
body.tw-page--fixed {
  height: 100dvh;
  overflow: hidden;
}

/* Any page carrying the fixed mobile bottom nav needs this on its scroller. */
.tw-page--has-bottomnav { padding-bottom: calc(var(--bottomnav-h) + var(--safe-bottom)); }
@media (min-width: 768px) { .tw-page--has-bottomnav { padding-bottom: 0; } }

.tw-main { display: block; min-width: 0; }
.tw-container { width: 100%; max-width: var(--content-max); margin-inline: auto; padding-inline: var(--page-inset); }
.tw-container--form   { max-width: var(--form-max); }
.tw-container--panel  { max-width: var(--panel-max); }
.tw-container--mobile { max-width: var(--mobile-flow-max); }
/* 448px is right for a phone and wrong for a desk. The driver and customer
   portals are phone-first, but a driver checking their profile or a dispatcher
   looking over their shoulder on a 1500px screen should not read a 448px
   ribbon down the middle of an empty page — that is what made these screens
   look broken.
   
   The request WIZARD keeps the narrow column deliberately: a form you answer
   one question at a time reads better in a single column, and it carries
   .tw-wiz to say so. */
@media (min-width: 62rem) {
  .tw-container--mobile          { max-width: 60rem; }
  .tw-container--mobile.tw-wiz   { max-width: var(--mobile-flow-max); }
}

img, svg, video, canvas { max-width: 100%; height: auto; display: block; }

a.tw-link { color: var(--primary); text-decoration: none; transition: color var(--dur-base) ease; }
@media (hover: hover) and (pointer: fine) {
  a.tw-link:hover { color: var(--secondary); text-decoration: underline; }
}

hr.tw-hr { border: 0; border-top: 1px solid var(--outline-variant); opacity: .3; margin: var(--stack-lg) 0; }

/* Skip link — absent from all 12 mockups (doc 07 §10.3). */
.tw-skip-link {
  position: absolute; left: var(--stack-md); top: -100px; z-index: var(--z-skiplink);
  background: var(--secondary); color: var(--on-secondary);
  font: var(--label-caps-w) var(--label-caps-size)/var(--label-caps-lh) var(--font-body);
  letter-spacing: var(--label-caps-ls); text-transform: uppercase; text-decoration: none;
  padding: 0 var(--stack-md); min-height: var(--touch-target-min);
  display: inline-flex; align-items: center; border-radius: var(--radius-lg);
}
.tw-skip-link:focus { top: var(--stack-md); }


/* =============================================================================
   01. TYPE ROLES — the whole system is these eight (plus label-btn)
   ============================================================================= */
.tw-t-display-lg  { font: var(--display-lg-w) var(--display-lg-size)/var(--display-lg-lh) var(--font-display); letter-spacing: var(--display-lg-ls); margin: 0; }
.tw-t-headline-lg { font: var(--headline-lg-w) var(--headline-lg-size)/var(--headline-lg-lh) var(--font-display); letter-spacing: var(--headline-lg-ls); margin: 0; }
.tw-t-headline-md { font: var(--headline-md-w) var(--headline-md-size)/var(--headline-md-lh) var(--font-display); letter-spacing: var(--headline-md-ls); margin: 0; }
.tw-t-body-lg     { font: var(--body-lg-w) var(--body-lg-size)/var(--body-lg-lh) var(--font-body); margin: 0; }
.tw-t-body-md     { font: var(--body-md-w) var(--body-md-size)/var(--body-md-lh) var(--font-body); margin: 0; }
.tw-t-body-sm     { font: var(--body-sm-w) var(--body-sm-size)/var(--body-sm-lh) var(--font-body); margin: 0; }
.tw-t-label-btn   { font: var(--label-btn-w) var(--label-btn-size)/var(--label-btn-lh) var(--font-display); }
.tw-t-data-mono {
  font: var(--data-mono-w) var(--data-mono-size)/var(--data-mono-lh) var(--font-mono);
  letter-spacing: var(--data-mono-ls);
  /* The mockups omit this: without tabular figures an SLA countdown jitters
     one pixel every second for eight hours. */
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}
.tw-t-label-caps {
  font: var(--label-caps-w) var(--label-caps-size)/var(--label-caps-lh) var(--font-body);
  letter-spacing: var(--label-caps-ls); text-transform: uppercase;
}

/* Colour roles */
.tw-fg          { color: var(--on-surface); }
.tw-fg-variant  { color: var(--on-surface-variant); }
.tw-fg-muted    { color: var(--outline); }
.tw-fg-primary  { color: var(--primary); }
.tw-fg-amber    { color: var(--secondary); }
.tw-fg-success  { color: var(--status-success-text); }
.tw-fg-warning  { color: var(--status-warning); }
.tw-fg-critical { color: var(--status-critical-text); }

/* Never render display-lg on a phone (DESIGN.md rule, doc 07 §1.3). */
@media (max-width: 599.98px) {
  .tw-t-display-lg,
  .tw-t-headline-lg {
    font: var(--headline-lg-mobile-w) var(--headline-lg-mobile-size)/var(--headline-lg-mobile-lh) var(--font-display);
    letter-spacing: 0;
  }
}


/* =============================================================================
   02. ICONS — Material Symbols Outlined
   One fill convention only. The mockups use five (.filled, .fill, .filled-icon,
   [data-weight="fill"], inline style) — all four extras are dead here.
   Semantic rule: FILLED = current/active/live. Outline = resting.
   ============================================================================= */
.tw-icon {
  font-family: var(--font-icon);
  font-weight: normal; font-style: normal;
  font-size: 24px; line-height: 1;
  display: inline-block; flex: 0 0 auto;
  letter-spacing: normal; text-transform: none; white-space: nowrap;
  word-wrap: normal; direction: ltr;
  -webkit-font-feature-settings: 'liga'; font-feature-settings: 'liga';
  -webkit-font-smoothing: antialiased;
  font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
  user-select: none;
  /* A real glyph is 1em wide, so this is a no-op in production. It matters
     during the font-display:block window and if the subset ever misses a
     glyph: the ligature would fall back to the literal name ("local_shipping")
     and shove its container past the viewport edge. */
  max-width: 100%; overflow: hidden;
}
.tw-icon.is-filled { font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24; }
.tw-icon--14 { font-size: 14px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20; }
.tw-icon--16 { font-size: 16px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20; }
.tw-icon--18 { font-size: 18px; }
.tw-icon--20 { font-size: 20px; }
.tw-icon--32 { font-size: 32px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 40; }
.tw-icon--40 { font-size: 40px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 40; }
.tw-icon--48 { font-size: 48px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 48; }
.tw-icon--60 { font-size: 60px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 48; }
.tw-icon.is-filled.tw-icon--14, .tw-icon.is-filled.tw-icon--16 { font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 20; }
.tw-icon.is-filled.tw-icon--32, .tw-icon.is-filled.tw-icon--40 { font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 40; }
.tw-icon.is-filled.tw-icon--48, .tw-icon.is-filled.tw-icon--60 { font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 48; }


/* =============================================================================
   03. GLOBAL FOCUS RING
   doc 07 §10.4: not one of the 12 mockup files defines a single :focus-visible
   style, while every input carries outline-none. This block is the fix and it
   is deliberately at :where() specificity so components can override the
   OFFSET (inset vs outset) without ever losing the ring itself.
   ============================================================================= */
:where(a[href], area, button, input, select, textarea, summary,
       [tabindex]:not([tabindex="-1"]), [contenteditable="true"]):focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
  border-radius: inherit;
}
/* Controls whose own surface IS the amber — an amber ring on amber is invisible. */
:where(.tw-btn--primary, .tw-chip--filter[aria-pressed="true"],
       .tw-nav-item[aria-current], .tw-bottomnav__item[aria-current]):focus-visible {
  outline-color: var(--focus-ring-on-amber);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--secondary) 45%, transparent);
}
/* Never suppress the ring. If a component needs it tucked inside its own box,
   it sets --focus-ring-offset locally rather than outline:none. */
.tw-focus-inset { --focus-ring-offset: var(--focus-ring-inset-offset); }

/* Focus-within ring for label-wrapped inputs (upload buttons, service cards). */
.tw-focus-within:focus-within {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}


/* =============================================================================
   04. MOTION SAFETY
   doc 07 §8 / §10.6: a pulsing map marker watched for an eight-hour dispatch
   shift is a real vestibular problem. Zero reduced-motion handling exists in
   the mockups.
   ============================================================================= */
@keyframes tw-pulse   { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
@keyframes tw-slide-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
/* Entrances start at 0.95, never 0 — a zero-scale element pops out of nowhere
   and is a sub-pixel click target for the whole first half of its animation. */
@keyframes tw-pop      { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
@keyframes tw-spin    { to { transform: rotate(360deg); } }

.tw-pulse { animation: tw-pulse var(--dur-pulse) var(--ease-in-out) infinite; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  /* Pulsing markers and live dots must settle fully opaque, not mid-fade. */
  .tw-pulse,
  .tw-map-marker--pickup > .tw-map-marker__pin,
  .tw-dot--live { animation: none !important; opacity: 1 !important; }
  /* Press-scale is a transform; disable rather than snap. */
  .tw-btn:active, .tw-nav-item:active, .tw-nav-link:active, .tw-tab:active,
  .tw-chip--filter:active, .tw-segmented__btn:active, .tw-pagination__item:active,
  .tw-bottomnav__item:active, .tw-map__ctrl:active, .tw-chat__send:active,
  .tw-upload-btn:active, .tw-dropzone__browse:active,
  .tw-service-card:hover, .tw-job-card:hover { transform: none !important; }
  /* Entrances still need to END visible, so replace them rather than kill them. */
  .tw-modal, .tw-toast, .tw-wizard-step.is-active { animation: none !important; opacity: 1 !important; transform: none !important; }
}


/* =============================================================================
   05. SCROLLBARS + SCROLL SHELLS
   Requirement 5: wide tables and the dispatch board scroll inside their own
   container. The page body never scrolls sideways.
   ============================================================================= */
* { scrollbar-color: var(--surface-variant) var(--surface-container); scrollbar-width: thin; }
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: var(--surface-container); }
::-webkit-scrollbar-thumb { background: var(--surface-variant); border-radius: var(--radius-full); }
@media (hover: hover) and (pointer: fine) {
  ::-webkit-scrollbar-thumb:hover { background: var(--outline); }
}

/* Horizontal scroll shell — the only element allowed to scroll sideways. */
.tw-scroll-x {
  max-width: 100%; overflow-x: auto; overflow-y: hidden;
  -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain;
}
.tw-scroll-y { overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior-y: contain; min-height: 0; }
.tw-no-scrollbar { scrollbar-width: none; -ms-overflow-style: none; }
.tw-no-scrollbar::-webkit-scrollbar { display: none; }

/* Flex/grid children default to min-width:auto and blow out their track — this
   is the actual cause of most "mystery" horizontal overflow. */
.tw-min-w-0 { min-width: 0; }


/* =============================================================================
   06. UTILITIES
   ============================================================================= */
.tw-sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}
/* Component skins are declared later; combined specificity keeps enhanced
   inputs visually hidden without letting their 100% width escape the page. */
.tw-sr-only.tw-input,
.tw-sr-only.tw-textarea,
.tw-sr-only.tw-select {
  width: 1px; height: 1px; min-height: 0; padding: 0; margin: -1px; border: 0;
}
.tw-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.tw-nowrap { white-space: nowrap; }

/* Glassmorphism — slide-overs, context panels, wizard cards (5 of 12 screens). */
.tw-glass {
  background: var(--glass-bg);
  -webkit-backdrop-filter: blur(var(--glass-blur));
          backdrop-filter: blur(var(--glass-blur));
  border: 1px solid var(--hairline);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .tw-glass { background: var(--glass-bg-fallback); }
}

.tw-card {
  background: var(--surface-card);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-xl);
  padding: var(--gutter);
}
.tw-card--flat { border-radius: var(--radius-lg); box-shadow: none; }
.tw-hairline { border-color: var(--hairline); }
.tw-divider { border: 0; border-top: 1px solid var(--outline-variant); opacity: .3; margin: 0; }

/* Grid helpers used by the layouts in doc 07 §4. */
.tw-stack   { display: flex; flex-direction: column; gap: var(--stack-md); min-width: 0; }
.tw-stack-lg{ display: flex; flex-direction: column; gap: var(--stack-lg); min-width: 0; }
.tw-row-x   { display: flex; align-items: center; gap: var(--stack-sm); min-width: 0; flex-wrap: wrap; }
.tw-grid    { display: grid; gap: var(--gutter); min-width: 0; }
.tw-grid--2 { grid-template-columns: 1fr; }
.tw-grid--3 { grid-template-columns: 1fr; }
.tw-grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 768px) {
  .tw-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .tw-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .tw-grid--4 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 1024px) { .tw-grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* Hide/show by band — 600 / 1024 per doc 07 §6, plus the 768 sidebar hinge. */
.tw-hide-mobile { display: none; }
@media (min-width: 768px) { .tw-hide-mobile { display: revert; } .tw-only-mobile { display: none; } }
@media (max-width: 1023.98px) { .tw-hide-below-lg { display: none !important; } }
@media (max-width: 1279.98px) { .tw-hide-below-xl { display: none !important; } }


/* =============================================================================
   C01. BUTTON — PRIMARY (Safety Amber)  · doc 07 §3.1
   Every state defined: default / hover / focus-visible / active / disabled.
   ============================================================================= */
.tw-btn {
  --_press: 0.97;                        /* one press scale, all surfaces */
  display: inline-flex; align-items: center; justify-content: center; gap: var(--stack-sm);
  min-height: var(--touch-target-min);   /* real 48px — `h-[touch-target-min]`
                                            in the mockups is invalid Tailwind
                                            and resolves to auto height        */
  min-width: var(--touch-target-min);
  padding: 0 var(--gutter);
  border: 1px solid transparent;
  border-radius: var(--radius-lg);
  font: 700 var(--body-md-size)/1 var(--font-body);
  text-align: center; text-decoration: none; cursor: pointer;
  background: transparent; color: var(--on-surface);
  transition: background-color var(--dur-base) ease,
              border-color var(--dur-base) ease,
              color var(--dur-base) ease,
              box-shadow var(--dur-base) ease,
              transform var(--dur-press) var(--ease-out);
}
.tw-btn--pill  { border-radius: var(--radius-full); padding-inline: var(--stack-lg); }
.tw-btn--block { width: 100%; }
.tw-btn--tall  { min-height: var(--form-control-h); }  /* customer wizard 56px */
.tw-btn--caps  { font: var(--label-caps-w) var(--label-caps-size)/1 var(--font-body); letter-spacing: var(--label-caps-ls); text-transform: uppercase; }
.tw-btn--label { font: var(--label-btn-w) var(--label-btn-size)/1 var(--font-display); }
.tw-btn:active { transform: scale(var(--_press)); }
.tw-btn--press-hard { --_press: var(--press-scale); }   /* driver controls, 0.95 */

.tw-btn--primary {
  background: var(--secondary);
  color: var(--on-secondary);            /* 9.92:1. NEVER text-on-primary —
                                            the pricing + vendor mockups use
                                            the wrong token (doc 07 §3.1)     */
  box-shadow: var(--shadow-amber);
}
@media (hover: hover) and (pointer: fine) {
  .tw-btn--primary:hover { background: var(--secondary-fixed); }
}
.tw-btn--primary:active { box-shadow: var(--shadow-amber-soft); }

/* CONTRAST FIX A4 — mockup disabled state was bg-secondary/50 + text/50 at
   3.40:1 and read as a still-enabled muddy tan. */
.tw-btn:disabled,
.tw-btn[aria-disabled="true"],
.tw-btn.is-disabled {
  background: var(--disabled-bg) !important;
  color: var(--disabled-fg) !important;
  border-color: var(--disabled-border) !important;
  box-shadow: none !important;
  cursor: not-allowed;
  transform: none !important;
  pointer-events: auto;                  /* keep it focusable so AT can read
                                            aria-disabled + the reason        */
}

/* Loading — not in the mockups. Width is locked so the row never reflows. */
.tw-btn.is-loading { position: relative; color: transparent !important; pointer-events: none; }
.tw-btn.is-loading::after {
  content: ""; position: absolute; width: 18px; height: 18px;
  border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%;
  color: var(--on-secondary); animation: tw-spin 700ms linear infinite;
}
.tw-btn--outline.is-loading::after,
.tw-btn--ghost.is-loading::after { color: var(--secondary); }


/* =============================================================================
   C02. BUTTON — SECONDARY / OUTLINE  · doc 07 §3.2
   ============================================================================= */
.tw-btn--outline {
  background: color-mix(in srgb, var(--surface) 50%, transparent);
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  border-color: var(--secondary);
  color: var(--secondary);
}
@media (hover: hover) and (pointer: fine) {
  .tw-btn--outline:hover  { background: color-mix(in srgb, var(--secondary) 10%, transparent); }
}
.tw-btn--outline:active { background: color-mix(in srgb, var(--secondary) 16%, transparent); }

.tw-btn--neutral {
  background: transparent;
  border-color: var(--outline-variant);
  color: var(--on-surface);
  font: var(--label-caps-w) var(--label-caps-size)/1 var(--font-body);
  letter-spacing: var(--label-caps-ls); text-transform: uppercase;
  padding-inline: var(--stack-md);
}
@media (hover: hover) and (pointer: fine) {
  .tw-btn--neutral:hover  { background: color-mix(in srgb, var(--surface-bright) 30%, transparent); border-color: var(--outline); }
}
.tw-btn--neutral:active { background: var(--surface-variant); }

/* Filled-tonal (navy). CONTRAST FIX A6: the mockups pair --primary-container
   with --on-primary-container (#8293b5, 4.58:1) which reads as disabled next
   to an amber CTA. Text is --primary-fixed #d7e2ff = 10.82:1. */
.tw-btn--tonal {
  background: var(--primary-container);
  color: var(--on-primary-container-text);
  border-color: transparent;
}
@media (hover: hover) and (pointer: fine) {
  .tw-btn--tonal:hover  { background: color-mix(in srgb, var(--primary-container) 88%, var(--primary)); }
}
.tw-btn--tonal:active { background: color-mix(in srgb, var(--primary-container) 85%, var(--surface-dim)); }

/* Ghost / text button — table "Load More", panel Cancel, wizard Back. */
.tw-btn--ghost { background: transparent; color: var(--on-surface-variant); padding-inline: var(--stack-md); font-weight: 500; }
@media (hover: hover) and (pointer: fine) {
  .tw-btn--ghost:hover  { color: var(--primary); background: var(--hairline-faint); }
}
.tw-btn--ghost:active { background: var(--hairline); }

/* Icon-only button — mockups ship these at 32–40px (doc 07 §10.2). 48px here.
   Requires an aria-label; none of the mockup icon buttons has one. */
.tw-btn--icon {
  width: var(--touch-target-min); height: var(--touch-target-min);
  padding: 0; border-radius: var(--radius-full);
  color: var(--on-surface-variant); background: transparent;
}
@media (hover: hover) and (pointer: fine) {
  .tw-btn--icon:hover  { background: var(--hairline-faint); color: var(--on-surface); }
}
.tw-btn--icon:active { background: var(--hairline); }
.tw-btn--icon-square { border-radius: var(--radius-lg); }

/* Small visual chip / control that must keep a 48px HIT area (doc 07 §10.2:
   "keep the visual box and expand the hit area"). */
.tw-hit-48 { position: relative; }
.tw-hit-48::after {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: max(100%, var(--touch-target-min)); height: max(100%, var(--touch-target-min));
  transform: translate(-50%, -50%);
}


/* =============================================================================
   C03. BUTTON — EMERGENCY / CRITICAL  · doc 07 §3.3
   CONTRAST FIX A1. Solid only: doc 07 rejects the ghost variant (3.85:1) for
   the panic button. An SOS button is never disabled — it degrades to tel:.
   ============================================================================= */
.tw-btn--critical {
  background: var(--status-critical);
  color: var(--on-status-critical);       /* #ffffff = 4.83:1. The mockups use
                                             --on-error (2.71:1) and
                                             --on-primary (2.73:1)            */
  border-color: transparent;
  box-shadow: var(--shadow-critical);
  font: var(--label-caps-w) var(--label-caps-size)/1 var(--font-body);
  letter-spacing: var(--label-caps-ls); text-transform: uppercase;
}
@media (hover: hover) and (pointer: fine) {
  .tw-btn--critical:hover  { background: var(--status-critical-hover); }
}
.tw-btn--critical:active { box-shadow: var(--shadow-critical-soft); }
.tw-btn--critical:focus-visible { outline-color: var(--on-status-critical); }
/* Explicitly refuse the disabled skin on the panic button. */
.tw-btn--critical:disabled, .tw-btn--critical[aria-disabled="true"] {
  background: var(--status-critical) !important;
  color: var(--on-status-critical) !important;
  cursor: pointer;
}


/* =============================================================================
   C04. TEXT INPUT  · doc 07 §3.4
   One input skin, not three. Background is set explicitly here — the vendor
   wizard's white fields (A7) were a dropped theme() call, not a design choice.
   ============================================================================= */
.tw-input,
.tw-textarea,
.tw-select {
  display: block; width: 100%; max-width: 100%;
  min-height: var(--touch-target-min);
  padding: 10px var(--stack-md);
  background: var(--input-bg);
  color: var(--on-surface);
  border: 1px solid var(--outline-variant);
  border-radius: var(--radius-lg);       /* 8px everywhere; the vendor wizard's
                                            6px is normalised (doc 07 §4.9)   */
  font: var(--body-md-w) var(--body-md-size)/var(--body-md-lh) var(--font-body);
  transition: border-color var(--dur-base) ease, box-shadow var(--dur-base) ease, background-color var(--dur-base) ease;
  appearance: none; -webkit-appearance: none;
}
.tw-range { width: 100%; min-height: 44px; accent-color: var(--secondary); cursor: pointer; }
.tw-textarea { min-height: 120px; resize: vertical; line-height: var(--body-md-lh); }
/* The Mac-app prompt. One line, page width, quiet — it is an offer, not an
   alert, so it must not borrow the colour language of a real warning. */
.tw-appnote {
  display: flex; align-items: center; gap: var(--stack-sm); flex-wrap: wrap;
  margin: var(--stack-sm) auto; max-width: var(--container-max, 72rem);
  padding: var(--stack-sm) var(--gutter, 1rem);
  border: 1px solid var(--hairline); border-radius: var(--radius-lg, 12px);
  background: var(--surface-container-low, var(--surface-container));
  color: var(--on-surface-variant); font-size: 0.9375rem;
}
.tw-appnote__text { flex: 1 1 18rem; min-width: 0; }
.tw-appnote strong { color: var(--on-surface); }

/* A row of buttons inside a card. NOT .tw-actionrail — that one is the sticky
   bottom save bar and would pin these to the viewport. */
.tw-btnrow { display: flex; gap: var(--stack-sm); align-items: center; flex-wrap: wrap; }

/* Numbered instructions (the Gatekeeper walkthrough). A bare <ol> inherits the
   browser default, which is the same defect as the undefined tw-t-title-md
   class: markup that looks styled and is not. */
.tw-steps { margin: 0; padding-left: 1.35rem; display: grid; gap: var(--stack-sm); }
.tw-steps li { color: var(--on-surface-variant); line-height: 1.6; }
.tw-steps li strong { color: var(--on-surface); }

/* A read-only value next to its Copy button — used by /app for the server
   address. The input must be allowed to shrink (min-width:0) or a long URL
   pushes the button off the edge on a phone. */
.tw-copyrow { display: flex; gap: var(--stack-sm); align-items: center; flex-wrap: wrap; }
.tw-copyrow > .tw-input { flex: 1 1 16rem; min-width: 0; }
.tw-copyrow > .tw-btn { flex: 0 0 auto; }

.tw-input--mono { font: var(--data-mono-w) var(--data-mono-size)/var(--data-mono-lh) var(--font-mono); letter-spacing: var(--data-mono-ls); font-variant-numeric: tabular-nums; }

.tw-input::placeholder, .tw-textarea::placeholder { color: var(--on-surface-variant); opacity: .5; }
@media (hover: hover) and (pointer: fine) {
  .tw-input:hover:not(:disabled):not([readonly]),
  .tw-textarea:hover:not(:disabled):not([readonly]),
  .tw-select:hover:not(:disabled) { border-color: var(--outline); }
}

/* 2px effective Safety Amber focus ring = 1px border + 1px inset ring.
   Matches DESIGN.md "Focus State: 2px Safety Amber border". outline is
   suppressed HERE ONLY because the ring below is the same 2px amber. */
.tw-input:focus, .tw-textarea:focus, .tw-select:focus {
  outline: none;
  border-color: var(--secondary);
  box-shadow: 0 0 0 1px var(--secondary);
}
.tw-input:focus-visible, .tw-textarea:focus-visible, .tw-select:focus-visible {
  outline: none;
  box-shadow: 0 0 0 1px var(--secondary), 0 0 0 4px color-mix(in srgb, var(--secondary) 25%, transparent);
}

.tw-input:disabled, .tw-textarea:disabled, .tw-select:disabled {
  background: var(--surface-container); color: var(--outline);
  border-color: var(--outline-variant); cursor: not-allowed;
}
.tw-input[readonly], .tw-textarea[readonly] {
  background: var(--surface-container); border-color: var(--outline-variant);
  box-shadow: none; cursor: default;
}
.tw-input[readonly]:focus, .tw-textarea[readonly]:focus { border-color: var(--outline-variant); box-shadow: none; }

.tw-input[aria-invalid="true"], .tw-textarea[aria-invalid="true"], .tw-select[aria-invalid="true"],
.tw-input.is-error, .tw-textarea.is-error, .tw-select.is-error {
  border-color: var(--status-critical);
  box-shadow: 0 0 0 1px var(--status-critical);
}
.tw-input[aria-invalid="true"]:focus, .tw-select[aria-invalid="true"]:focus {
  box-shadow: 0 0 0 1px var(--status-critical), 0 0 0 4px color-mix(in srgb, var(--status-critical) 25%, transparent);
}

/* Affix wrapper — "$" prefix, search icon, select chevron. */
.tw-input-wrap { position: relative; display: block; min-width: 0; }
.tw-input-wrap__affix {
  position: absolute; top: 50%; transform: translateY(-50%);
  color: var(--on-surface-variant); pointer-events: none; display: inline-flex; align-items: center;
}
.tw-input-wrap__affix--start { left: var(--space-3); }
.tw-input-wrap__affix--end   { right: var(--space-3); }
.tw-input--pad-start { padding-left: 34px; }
.tw-input--pad-icon  { padding-left: 42px; }
.tw-input--pad-end   { padding-right: 42px; }
.tw-input-wrap:focus-within .tw-input-wrap__affix { color: var(--secondary); }

/* Underline variant — only inside a card that IS the field container. */
.tw-input--underline {
  background: transparent; border: 0; border-bottom: 1px solid var(--outline-variant);
  border-radius: 0; padding-inline: 0; padding-bottom: 4px;
  font: var(--body-lg-w) var(--body-lg-size)/var(--body-lg-lh) var(--font-body);
}
.tw-input--underline:focus { box-shadow: none; border-bottom-color: var(--secondary); border-bottom-width: 2px; padding-bottom: 3px; }

/* Search variant */
.tw-search { position: relative; display: block; width: 100%; max-width: 288px; }
.tw-search .tw-input { padding-left: 42px; }
@media (max-width: 1023.98px) { .tw-search { max-width: 100%; } }


/* =============================================================================
   C05. FIELD + OVERLINE LABEL  · doc 07 §3.5
   Static overline label, not a floating label: more legible on dark and it
   avoids the empty/filled state bug. The amber accent supplies the affordance.
   ============================================================================= */
.tw-field { display: flex; flex-direction: column; gap: var(--stack-xs); min-width: 0; }
.tw-label {
  display: block;
  font: var(--label-caps-w) var(--label-caps-size)/var(--label-caps-lh) var(--font-body);
  letter-spacing: var(--label-caps-ls); text-transform: uppercase;
  color: var(--on-surface-variant);
  transition: color var(--dur-base) ease;
}
.tw-label__req { color: var(--status-critical-text); margin-left: 2px; }
.tw-field:focus-within .tw-label { color: var(--secondary); }
.tw-field.is-error .tw-label { color: var(--status-critical-text); }
.tw-hint { font: 400 12px/16px var(--font-body); color: var(--on-surface-variant); margin: 0; }
.tw-error-text {
  display: flex; align-items: center; gap: 6px;
  font: 500 12px/16px var(--font-body);
  color: var(--error);                    /* 10.94:1 on the canvas */
  margin: 0;
}
/* Label sitting inside the field container (dispatch Customer/Vehicle tiles). */
.tw-field-tile {
  background: color-mix(in srgb, var(--surface) 50%, transparent); border: 1px solid var(--outline-variant);
  border-radius: var(--radius-lg); padding: var(--space-3); min-width: 0;
}
.tw-field-tile .tw-label { margin-bottom: 2px; }


/* =============================================================================
   C06. SELECT  · doc 07 §3.6
   ============================================================================= */
.tw-select { padding-right: 42px; cursor: pointer; }
.tw-select-wrap { position: relative; display: block; min-width: 0; }
.tw-select-wrap::after {
  content: "expand_more"; font-family: var(--font-icon); font-size: 20px; line-height: 1;
  font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20;
  position: absolute; right: var(--space-3); top: 50%; transform: translateY(-50%);
  color: var(--on-surface-variant); pointer-events: none;
}
/* Without this, Windows/Chrome punches a white popup through the dark UI. */
.tw-select option { background: var(--surface-container); color: var(--on-surface); }
.tw-select option:disabled, .tw-select:invalid { color: var(--on-surface-variant); }


/* =============================================================================
   C07. TOGGLE SWITCH  · doc 07 §3.7
   One implementation, built on a real focusable checkbox. The mockups ship
   four, one of which drives visuals from JS classes and desyncs from the form.
   Track 44x24, knob 20x20, 2px inset.
   ============================================================================= */
/* `position: relative` is load-bearing, not cosmetic. .tw-switch__input is
   absolutely positioned; with no positioned ancestor its containing block was
   the INITIAL containing block, and an abspos box escapes the `overflow:hidden`
   of any ancestor that is itself static — which .tw-shell is. So on a long form
   the hidden checkboxes sat at document coordinates far below the 100dvh shell
   and stretched <html> to match: /admin/notifications reported a document
   scrollHeight of 3393px against a 900px viewport, i.e. 2493px the user could
   scroll into that contained nothing at all. Containing the input inside its
   own label puts it back under the shell's clip. */
.tw-switch { position: relative; display: inline-flex; align-items: center; gap: var(--space-3); cursor: pointer; min-height: var(--touch-target-min); }
.tw-switch__input { position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; }
.tw-switch__track {
  position: relative; flex: 0 0 auto; width: 44px; height: 24px;
  background: var(--surface-variant); border-radius: var(--radius-full);
  transition: background-color var(--dur-base) ease, box-shadow var(--dur-base) ease;
}
.tw-switch__track::after {
  content: ""; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px;
  background: var(--on-status-critical); border: 1px solid rgba(0,0,0,.15); border-radius: 50%;
  transition: transform var(--dur-press) var(--ease-out);
}
.tw-switch__input:checked + .tw-switch__track { background: var(--secondary); }
.tw-switch__input:checked + .tw-switch__track::after { transform: translateX(20px); }
@media (hover: hover) and (pointer: fine) {
  .tw-switch:hover .tw-switch__track { background: var(--surface-container-high); }
  .tw-switch:hover .tw-switch__input:checked + .tw-switch__track { background: var(--secondary-fixed); }
}
/* Focus ring absent in all four mockup variants. */
.tw-switch__input:focus-visible + .tw-switch__track {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}
.tw-switch__input:active + .tw-switch__track::after { width: 24px; }
.tw-switch__input:disabled + .tw-switch__track { background: var(--disabled-bg); opacity: .5; }
.tw-switch__input:disabled ~ .tw-switch__label { color: var(--outline); }
.tw-switch:has(.tw-switch__input:disabled) { cursor: not-allowed; }
.tw-switch__label { font: var(--body-md-w) var(--body-md-size)/var(--body-md-lh) var(--font-body); color: var(--on-surface); min-width: 0; }
/* A switch row inside a settings card. */
.tw-switch-row { display: flex; align-items: center; justify-content: space-between; gap: var(--stack-md); min-height: var(--touch-target-min); min-width: 0; }


/* =============================================================================
   C08. RADIO CARD  · doc 07 §3.8
   The mockup swaps 1px -> 2px border on selection, causing a 1px layout shift.
   Fixed here with a constant 1px border plus an inset box-shadow ring.
   ============================================================================= */
.tw-radio-card {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3); min-height: var(--touch-target-min);
  border: 1px solid color-mix(in srgb, var(--outline-variant) 50%, transparent); border-radius: var(--radius-lg);
  background: transparent; color: var(--on-surface-variant);
  cursor: pointer; min-width: 0;
  transition: background-color var(--dur-base) ease, border-color var(--dur-base) ease,
              color var(--dur-base) ease, box-shadow var(--dur-base) ease;
}
@media (hover: hover) and (pointer: fine) {
  .tw-radio-card:hover { background: color-mix(in srgb, var(--surface-bright) 20%, transparent); }
}
.tw-radio-card__input { flex: 0 0 auto; width: 18px; height: 18px; margin: 0; accent-color: var(--secondary); cursor: pointer; }
.tw-radio-card:has(.tw-radio-card__input:checked) {
  border-color: var(--secondary);
  box-shadow: inset 0 0 0 1px var(--secondary);   /* the "2nd px", no reflow */
  background: color-mix(in srgb, var(--secondary) 5%, transparent);
  color: var(--on-surface);
}
.tw-radio-card:focus-within { outline: var(--focus-ring-width) solid var(--focus-ring-color); outline-offset: var(--focus-ring-offset); }
.tw-radio-card:has(.tw-radio-card__input:disabled) { opacity: .5; cursor: not-allowed; }
@media (hover: hover) and (pointer: fine) {
  .tw-radio-card:has(.tw-radio-card__input:disabled):hover { background: transparent; }
}


/* =============================================================================
   C09. SERVICE CAPABILITY CARD (checkbox tile)  · doc 07 §3.9
   The mockup is a <div onclick> with no input: no form value, no keyboard, no
   AT state. This is a real checkbox driven by :has(:checked), no JS.
   ============================================================================= */
.tw-service-card {
  position: relative; display: flex; flex-direction: column;
  align-items: center; justify-content: center; text-align: center; gap: var(--stack-sm);
  min-height: 128px; padding: var(--stack-md);
  border: 1px solid var(--outline-variant); border-radius: var(--radius-lg);
  background: transparent; color: var(--on-surface); cursor: pointer; min-width: 0;
  transition: transform var(--dur-base) var(--ease-in-out),
              background-color var(--dur-base) var(--ease-in-out),
              border-color var(--dur-base) var(--ease-in-out),
              color var(--dur-base) var(--ease-in-out);
}
.tw-service-card__input { position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; }
.tw-service-card__icon { color: var(--on-surface-variant); transition: color var(--dur-base) ease; }
.tw-service-card__label { font: var(--label-caps-w) var(--label-caps-size)/var(--label-caps-lh) var(--font-body); letter-spacing: var(--label-caps-ls); text-transform: uppercase; }
@media (hover: hover) and (pointer: fine) {
  .tw-service-card:hover { transform: translateY(-2px); background: color-mix(in srgb, var(--surface-bright) 80%, transparent); }
}
.tw-service-card:active { transform: translateY(0); }
.tw-service-card:has(:checked) { border-color: var(--secondary); background: color-mix(in srgb, var(--secondary) 10%, transparent); }
.tw-service-card:has(:checked) .tw-service-card__icon { color: var(--secondary); }
/* Selection must not be colour-only. */
.tw-service-card:has(:checked)::after {
  content: "check"; font-family: var(--font-icon); font-size: 16px; line-height: 1;
  font-variation-settings: 'FILL' 1, 'wght' 500, 'GRAD' 0, 'opsz' 20;
  position: absolute; top: 8px; right: 8px;
  color: var(--on-secondary); background: var(--secondary);
  border-radius: var(--radius-full); padding: 2px;
}
.tw-service-card:focus-within { outline: var(--focus-ring-width) solid var(--focus-ring-color); outline-offset: var(--focus-ring-offset); }
.tw-service-card:has(:disabled) { opacity: .5; cursor: not-allowed; transform: none; }
.tw-service-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--stack-md); }
@media (min-width: 768px)  { .tw-service-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .tw-service-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }


/* =============================================================================
   C10. STATUS CHIP  · doc 07 §3.10
   Fill = 15% tint of the status colour. Text = the -text token, never the
   fill token. Status is never conveyed by colour alone: the label carries it.
   ============================================================================= */
.tw-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 8px; border-radius: var(--radius);
  font: var(--label-caps-w) var(--label-caps-size)/var(--label-caps-lh) var(--font-body);
  letter-spacing: var(--label-caps-ls); text-transform: uppercase;
  white-space: nowrap; border: 1px solid transparent;
  background: var(--surface-variant); color: var(--on-surface);
}
.tw-chip--pill { border-radius: var(--radius-full); padding: 4px 10px; }
.tw-chip--xs   { font-size: 10px; }

.tw-chip--warning  { background: color-mix(in srgb, var(--status-warning) 15%, transparent); color: var(--status-warning); border-color: color-mix(in srgb, var(--status-warning) 30%, transparent); }
/* CONTRAST FIX A2 — #2D6A4F as text was 2.58:1 on card / 2.6:1 on its own
   tint. #4ade80 is 8.49:1 on the tint, 9.56:1 on the card. */
.tw-chip--success  { background: color-mix(in srgb, var(--status-success) 15%, transparent);  color: var(--status-success-text); border-color: color-mix(in srgb, var(--status-success) 30%, transparent); }
/* CONTRAST FIX A3 — #DC2626 as text on its own 15% tint was 3.10:1.
   --error #ffb4ab is 8.83:1 on the same tint. Fill and border stay #DC2626. */
.tw-chip--critical { background: color-mix(in srgb, var(--status-critical) 15%, transparent);  color: var(--status-critical-text); border-color: color-mix(in srgb, var(--status-critical) 30%, transparent); }
.tw-chip--info     { background: color-mix(in srgb, var(--primary) 15%, transparent); color: var(--primary); border-color: color-mix(in srgb, var(--primary) 30%, transparent); }
.tw-chip--amber    { background: color-mix(in srgb, var(--secondary) 15%, transparent); color: var(--secondary); border-color: color-mix(in srgb, var(--secondary) 30%, transparent); }
.tw-chip--unassigned { background: color-mix(in srgb, var(--error-container) 20%, transparent); color: var(--error); border-color: color-mix(in srgb, var(--error-container) 45%, transparent); }
.tw-chip--neutral  { background: var(--surface-variant); color: var(--on-surface); }

/* Leading dot for the pricing-table pill. Shape + label carry the meaning. */
.tw-chip__dot { width: 6px; height: 6px; border-radius: var(--radius-full); background: currentColor; flex: 0 0 auto; }
.tw-chip--success .tw-chip__dot  { background: var(--status-success); }
.tw-chip--warning .tw-chip__dot  { background: var(--status-warning); }
.tw-chip--critical .tw-chip__dot { background: var(--status-critical); }
.tw-dot--live { animation: tw-pulse var(--dur-pulse) var(--ease-in-out) infinite; }


/* =============================================================================
   C11. JOB CARD  · doc 07 §3.11 — the signature component of the product.
   Rendered as a <button>/<a>: the mockup is a cursor-pointer <div>, i.e.
   unreachable by keyboard.
   ============================================================================= */
.tw-job-card {
  position: relative; display: block; width: 100%; text-align: left;
  background: var(--surface-card); color: inherit;
  border: 1px solid color-mix(in srgb, var(--outline-variant) 20%, transparent); border-radius: var(--radius-lg);
  padding: var(--stack-md); overflow: hidden; cursor: pointer; min-width: 0;
  font: inherit;
  transition: border-color var(--dur-base) ease, background-color var(--dur-base) ease, box-shadow var(--dur-base) ease;
}
@media (hover: hover) and (pointer: fine) {
  .tw-job-card:hover { border-color: var(--outline); }
  .tw-job-card:hover .tw-job-card__id { color: var(--primary-fixed); }
}
.tw-job-card:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring-color); outline-offset: var(--focus-ring-offset); }
.tw-job-card__head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--stack-sm); margin-bottom: var(--stack-sm); }
.tw-job-card__id { font: var(--data-mono-w) var(--data-mono-size)/var(--data-mono-lh) var(--font-mono); letter-spacing: var(--data-mono-ls); font-variant-numeric: tabular-nums; color: var(--primary); transition: color var(--dur-base) ease; }
.tw-job-card__title { font: 600 var(--body-lg-size)/var(--body-lg-lh) var(--font-body); color: var(--on-surface); margin: 0 0 4px; }
.tw-job-card__loc { display: flex; align-items: center; gap: var(--stack-sm); color: var(--on-surface-variant); font-size: var(--body-sm-size); line-height: var(--body-sm-lh); margin-bottom: var(--space-3); min-width: 0; }
.tw-job-card__loc > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.tw-job-card__foot { display: flex; justify-content: space-between; align-items: center; gap: var(--stack-sm); padding-top: var(--stack-sm); border-top: 1px solid color-mix(in srgb, var(--outline-variant) 20%, transparent); }
.tw-job-card__sla { font: var(--label-caps-w) var(--label-caps-size)/var(--label-caps-lh) var(--font-body); letter-spacing: var(--label-caps-ls); text-transform: uppercase; color: var(--status-critical-text); font-variant-numeric: tabular-nums; }
.tw-job-card__eta { font: var(--label-caps-w) var(--label-caps-size)/var(--label-caps-lh) var(--font-body); letter-spacing: var(--label-caps-ls); text-transform: uppercase; color: var(--on-surface-variant); }

/* Selected — 4px amber right rail. Constant 1px border, ring supplies the 2nd px. */
.tw-job-card[aria-current="true"], .tw-job-card.is-selected {
  border-color: var(--secondary);
  box-shadow: inset 0 0 0 1px var(--secondary), var(--shadow-lg);
}
.tw-job-card[aria-current="true"]::after, .tw-job-card.is-selected::after {
  content: ""; position: absolute; top: 0; right: 0; width: 4px; height: 100%;
  background: var(--secondary);
}
/* SLA breach outranks selection — 2px solid critical on the whole card. */
.tw-job-card.is-breached {
  border-color: var(--status-critical);
  box-shadow: inset 0 0 0 1px var(--status-critical);
}
.tw-job-card.is-unassigned .tw-job-card__eta { color: var(--outline); }
.tw-job-card:disabled, .tw-job-card[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }


/* =============================================================================
   C12. LIST ROW  · doc 07 §3.12
   48px icon tile · title + subtitle · trailing status + action.
   ============================================================================= */
.tw-row {
  display: flex; flex-direction: column; gap: var(--stack-md);
  align-items: flex-start; justify-content: space-between;
  background: var(--surface-container);
  border: 1px solid var(--outline-variant); border-radius: var(--radius-lg);
  padding: var(--stack-md); min-width: 0;
  transition: background-color var(--dur-base) ease, border-color var(--dur-base) ease, transform var(--dur-base) ease;
}
@media (min-width: 640px) { .tw-row { flex-direction: row; align-items: center; } }
@media (hover: hover) and (pointer: fine) {
  .tw-row:hover { background: var(--surface-container-high); }
}
.tw-row--quiet { border-color: var(--hairline); }
@media (hover: hover) and (pointer: fine) {
  .tw-row--quiet:hover { background: var(--surface-container); }
  .tw-row--lift:hover { transform: translateY(-2px); }
}
.tw-row__lead { display: flex; align-items: flex-start; gap: var(--stack-md); min-width: 0; }
.tw-row__tile {
  flex: 0 0 auto; width: 40px; height: 40px; border-radius: var(--radius);
  background: var(--surface-variant); color: var(--on-surface-variant);
  display: inline-flex; align-items: center; justify-content: center;
}
.tw-row__tile--success { background: color-mix(in srgb, var(--status-success) 20%, transparent); color: var(--status-success-text); }
.tw-row__tile--critical { background: color-mix(in srgb, var(--status-critical) 15%, transparent); color: var(--status-critical-text); }
.tw-row__title { font: 600 var(--body-md-size)/var(--body-md-lh) var(--font-body); color: var(--on-surface); margin: 0; }
.tw-row__title--mono { font: var(--data-mono-w) var(--data-mono-size)/var(--data-mono-lh) var(--font-mono); letter-spacing: var(--data-mono-ls); }
.tw-row__sub { font: 400 var(--body-sm-size)/var(--body-sm-lh) var(--font-body); color: var(--on-surface-variant); margin: 4px 0 0; }
.tw-row__trail { display: flex; align-items: center; gap: var(--stack-md); width: 100%; justify-content: space-between; }
@media (min-width: 640px) { .tw-row__trail { width: auto; justify-content: flex-end; } }
.tw-row.is-disabled { opacity: .5; pointer-events: none; }
/* Row action that fades in on hover must stay reachable by keyboard and touch
   (doc 07 §4.8: the mileage delete button is opacity-0 until :hover). */
.tw-row__action-reveal { opacity: 1; transition: opacity var(--dur-base) ease; }
@media (hover: hover) and (min-width: 768px) {
  .tw-row__action-reveal { opacity: 0; }
  .tw-row:hover .tw-row__action-reveal,
  .tw-row:focus-within .tw-row__action-reveal { opacity: 1; }
}


/* =============================================================================
   C13. DATA TABLE  · doc 07 §3.13
   The table scrolls inside .tw-table__scroll. The page never scrolls sideways.
   ============================================================================= */
.tw-table-card {
  background: var(--surface-card);
  border: 1px solid color-mix(in srgb, var(--outline-variant) 20%, transparent);
  border-radius: var(--radius-xl);
  overflow: hidden; min-width: 0; max-width: 100%;
}
.tw-table-card__bar {
  display: flex; flex-wrap: wrap; gap: var(--stack-sm);
  justify-content: space-between; align-items: center;
  padding: var(--stack-md); background: var(--surface-container-high);
  border-bottom: 1px solid color-mix(in srgb, var(--outline-variant) 20%, transparent);
}
.tw-table__scroll { max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain; }
.tw-table { width: 100%; border-collapse: collapse; text-align: left; }
.tw-table caption { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
.tw-table thead tr { background: var(--surface); border-bottom: 1px solid var(--hairline); }
.tw-table th {
  padding: var(--stack-md) var(--gutter);
  font: var(--label-caps-w) var(--label-caps-size)/var(--label-caps-lh) var(--font-body);
  letter-spacing: var(--label-caps-ls); text-transform: uppercase;
  color: var(--on-surface-variant); white-space: nowrap;
}
.tw-table--sticky thead th { position: sticky; top: 0; z-index: var(--z-sticky); background: var(--surface-container-high); }
.tw-table td { padding: var(--stack-md) var(--gutter); border-top: 1px solid var(--hairline-faint); color: var(--on-surface); vertical-align: middle; }
.tw-table tbody tr:first-child td { border-top: 0; }
.tw-table__num, .tw-table th.tw-table__num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.tw-table__center, .tw-table th.tw-table__center { text-align: center; }
.tw-table__mono { font: var(--data-mono-w) var(--data-mono-size)/var(--data-mono-lh) var(--font-mono); letter-spacing: var(--data-mono-ls); font-variant-numeric: tabular-nums; white-space: nowrap; }
.tw-table__when { white-space: nowrap; }
/* Let the name column flex and keep the data columns from wrapping to three
   lines (doc 07 §4.6 PNG note). */
.tw-table__flex-col { min-width: 180px; }

.tw-table tbody tr { transition: background-color var(--dur-base) ease; }
@media (hover: hover) and (pointer: fine) {   /* one hover convention */
  .tw-table tbody tr:hover { background: color-mix(in srgb, var(--surface-bright) 30%, transparent); }
}
.tw-table tbody tr[aria-selected="true"], .tw-table tbody tr.is-selected {
  background: color-mix(in srgb, var(--surface-bright) 80%, transparent);
  box-shadow: inset 3px 0 0 0 var(--secondary);
}
.tw-table tbody tr[tabindex]:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring-color); outline-offset: var(--focus-ring-inset-offset); }
.tw-table th[aria-sort] { cursor: pointer; }
.tw-table th[aria-sort="ascending"]::after  { content: " ↑"; color: var(--secondary); }
.tw-table th[aria-sort="descending"]::after { content: " ↓"; color: var(--secondary); }
.tw-table-card__foot {
  display: flex; justify-content: center; padding: var(--stack-md);
  border-top: 1px solid var(--hairline); background: var(--surface-container-lowest);
}

/* Responsive: below md a data table becomes the stacked card layout of C12.
   The mockups never do this — the vendor queue's min-w-[600px] guarantees a
   horizontal scrollbar on every phone. Requires data-label on each <td>. */
@media (max-width: 767.98px) {
  .tw-table--stack thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .tw-table--stack, .tw-table--stack tbody, .tw-table--stack tr, .tw-table--stack td { display: block; width: 100%; }
  .tw-table--stack tr {
    border: 1px solid var(--outline-variant); border-radius: var(--radius-lg);
    background: var(--surface-container); margin-bottom: var(--stack-sm); padding: var(--stack-sm) 0;
  }
  .tw-table--stack tr[aria-selected="true"], .tw-table--stack tr.is-selected { box-shadow: inset 3px 0 0 0 var(--secondary); }
  .tw-table--stack td { border: 0; padding: 6px var(--stack-md); text-align: left !important; }
  .tw-table--stack td::before {
    content: attr(data-label); display: block;
    font: var(--label-caps-w) var(--label-caps-size)/var(--label-caps-lh) var(--font-body);
    letter-spacing: var(--label-caps-ls); text-transform: uppercase; color: var(--on-surface-variant);
  }
  .tw-table--stack td:empty { display: none; }
}


/* =============================================================================
   C14. TABS  · doc 07 §3.14
   Inactive tabs carry a transparent 2px border so activation never jumps 2px.
   ============================================================================= */
.tw-tabs {
  border-bottom: 1px solid color-mix(in srgb, var(--outline-variant) 30%, transparent);
  background: color-mix(in srgb, var(--surface) 50%, transparent);
  padding-inline: var(--page-inset);
  max-width: 100%;
}
.tw-tabs__list {
  display: flex; gap: var(--stack-lg);
  max-width: 100%; overflow-x: auto;
  scrollbar-width: none; -ms-overflow-style: none;
}
.tw-tabs__list::-webkit-scrollbar { display: none; }
/* admin/pages/email-templates.php emits <nav class="tw-tabs"> with the tabs as
   DIRECT children, skipping .tw-tabs__list — so nothing carried the overflow and
   its 765px of tabs pushed the whole page sideways at 375px. Same scroller as
   the wrapper gets, applied only when the tabs really are direct children. */
.tw-tabs:has(> .tw-tab) {
  display: flex; gap: var(--stack-lg);
  overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none;
}
.tw-tabs:has(> .tw-tab)::-webkit-scrollbar { display: none; }
.tw-tab {
  flex: 0 0 auto; min-height: var(--touch-target-min);
  padding: var(--stack-md) 0 var(--space-3);
  background: transparent; border: 0; border-bottom: 2px solid transparent;
  color: var(--on-surface-variant); cursor: pointer; white-space: nowrap;
  /* Admin renders tabs as <a>, so without this the UA underline sits under
     every inactive tab and competes with the border-bottom that marks the
     active one. */
  text-decoration: none;
  font: var(--label-btn-w) var(--label-btn-size)/var(--label-btn-lh) var(--font-display);
  transition: color var(--dur-menu) ease, border-color var(--dur-menu) ease,
              transform var(--dur-press) var(--ease-out);
}
.tw-tab:active { transform: scale(0.97); }
@media (hover: hover) and (pointer: fine) {
  .tw-tab:hover { color: var(--on-surface); }
}
.tw-tab[aria-selected="true"] { color: var(--secondary); border-bottom-color: var(--secondary); font-weight: 700; }
.tw-tab:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring-color); outline-offset: var(--focus-ring-inset-offset); }
.tw-tab:disabled, .tw-tab[aria-disabled="true"] { color: var(--outline); cursor: not-allowed; }
.tw-tabpanel:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring-color); outline-offset: var(--focus-ring-inset-offset); }
.tw-tabpanel[hidden] { display: none; }


/* =============================================================================
   C15. SEGMENTED CONTROL  · doc 07 §3.15
   ============================================================================= */
.tw-segmented {
  display: flex; gap: 0; padding: 4px;
  background: var(--surface-container);
  border: 1px solid color-mix(in srgb, var(--outline-variant) 30%, transparent);
  border-radius: var(--radius-lg); min-width: 0;
}
.tw-segmented__btn {
  flex: 1 1 0; min-width: 0; min-height: var(--touch-target-min);
  padding: var(--stack-sm) var(--space-3);
  background: transparent; border: 1px solid transparent; border-radius: var(--radius-md);
  color: var(--on-surface-variant); cursor: pointer;
  font: var(--body-md-w) var(--body-sm-size)/var(--body-sm-lh) var(--font-body);
  transition: background-color var(--dur-menu) ease, color var(--dur-menu) ease, border-color var(--dur-menu) ease,
              transform var(--dur-press) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .tw-segmented__btn:hover { color: var(--on-surface); }
}
.tw-segmented__btn[aria-checked="true"], .tw-segmented__btn[aria-selected="true"] {
  background: var(--surface-bright); color: var(--on-surface);
  border-color: color-mix(in srgb, var(--outline-variant) 50%, transparent); box-shadow: var(--shadow-sm); font-weight: 600;
}
.tw-segmented__btn:active { background: var(--surface-container-high); transform: scale(0.97); }
.tw-segmented__btn:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring-color); outline-offset: var(--focus-ring-inset-offset); }
.tw-segmented__btn:disabled { color: var(--outline); cursor: not-allowed; }


/* =============================================================================
   C16. SIDEBAR NAVIGATION  · doc 07 §3.16
   Layout/width live in towing-admin.css; the skin lives here.
   ============================================================================= */
.tw-sidebar {
  display: flex; flex-direction: column;
  width: var(--sidebar-w); flex: 0 0 var(--sidebar-w);
  height: 100%; background: var(--surface-container-low);
  padding-block: var(--gutter); gap: var(--stack-md);
  z-index: var(--z-sidebar); box-shadow: var(--shadow-sm);
}
.tw-sidebar__brand { padding-inline: var(--gutter); margin-bottom: var(--stack-md); }
.tw-sidebar__brand-title { font: var(--headline-md-w) var(--headline-md-size)/var(--headline-md-lh) var(--font-display); font-weight: 700; color: var(--primary); margin: 0; }
.tw-sidebar__brand-sub { font: var(--label-caps-w) var(--label-caps-size)/var(--label-caps-lh) var(--font-body); letter-spacing: var(--label-caps-ls); text-transform: uppercase; color: var(--on-surface-variant); margin: 4px 0 0; }
.tw-sidebar__nav { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 4px; padding: var(--stack-md) var(--stack-sm); }
.tw-sidebar__section { padding-inline: var(--gutter); }
.tw-sidebar__foot {
  display: flex; flex-direction: column; gap: var(--stack-sm);
  padding: var(--stack-md) var(--gutter) var(--gutter);
  border-top: 1px solid color-mix(in srgb, var(--outline-variant) 30%, transparent);
}

.tw-nav-item {
  display: flex; align-items: center; gap: var(--space-3);
  min-height: var(--touch-target-min);
  padding: var(--space-3) var(--stack-md); margin-inline: var(--stack-sm);
  border-radius: var(--radius-lg);
  color: var(--on-surface-variant); text-decoration: none; cursor: pointer;
  font: var(--label-caps-w) var(--label-caps-size)/var(--label-caps-lh) var(--font-body);
  letter-spacing: var(--label-caps-ls); text-transform: uppercase;
  background: transparent; border: 0; width: calc(100% - (var(--stack-sm) * 2)); text-align: left;
  /* The mockups park every item at a permanent scale-95, which blurs text on
     fractional DPR and makes the focus ring geometry unpredictable. Removed;
     a background flash replaces it. */
  transition: background-color var(--dur-menu) ease, color var(--dur-menu) ease,
              transform var(--dur-press) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .tw-nav-item:hover { background: color-mix(in srgb, var(--surface-bright) 20%, transparent); color: var(--on-surface); }
}
.tw-nav-item:active { background: color-mix(in srgb, var(--surface-bright) 45%, transparent); transform: scale(0.97); }
/* CONTRAST FIX A5 — #6b4b00 on #feb700 was 4.56:1 (no margin at bold 12px).
   #271900 on #feb700 is 9.71:1. */
.tw-nav-item[aria-current="page"], .tw-nav-item.is-active {
  background: var(--nav-active-bg); color: var(--nav-active-fg); font-weight: 800;
}
.tw-nav-item:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring-color); outline-offset: var(--focus-ring-inset-offset); }
.tw-nav-item[aria-disabled="true"] { color: var(--outline); pointer-events: none; }
.tw-nav-item__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* Footer links must be 48px too (mockup py-2 = 32px). */
.tw-nav-link {
  display: flex; align-items: center; gap: var(--space-3);
  min-height: var(--touch-target-min);
  color: var(--on-surface-variant); text-decoration: none;
  font: var(--label-caps-w) var(--label-caps-size)/var(--label-caps-lh) var(--font-body);
  letter-spacing: var(--label-caps-ls); text-transform: uppercase;
  transition: color var(--dur-menu) ease, transform var(--dur-press) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .tw-nav-link:hover { color: var(--on-surface); }
}
.tw-nav-link:active { transform: scale(0.97); }

/* User chip */
.tw-user-chip { display: flex; align-items: center; gap: var(--stack-sm); min-height: var(--touch-target-min); min-width: 0; }
.tw-user-chip__name { font: 600 var(--body-sm-size)/var(--body-sm-lh) var(--font-body); color: var(--on-surface); min-width: 0; overflow: hidden; text-overflow: ellipsis; }


/* =============================================================================
   C17. TOP BAR  · doc 07 §3.17 — three variants
   ============================================================================= */
.tw-topbar {
  display: flex; align-items: center; gap: var(--stack-md);
  height: var(--topbar-h); flex: 0 0 auto;
  padding-inline: var(--page-inset);
  background: var(--surface);
  border-bottom: 1px solid var(--hairline);
  max-width: 100%; min-width: 0;
}
.tw-topbar--app   { background: var(--surface-container-high); position: sticky; top: 0; z-index: var(--z-topbar); }
.tw-topbar--admin {
  height: auto; min-height: var(--appbar-h);
  padding-block: var(--space-3);
  background: color-mix(in srgb, var(--surface) 80%, transparent);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  border-bottom-color: color-mix(in srgb, var(--outline-variant) 30%, transparent);
  align-items: center; justify-content: space-between; flex-wrap: wrap;
}
.tw-topbar__brand { font: var(--headline-md-w) var(--headline-md-size)/var(--headline-md-lh) var(--font-display); color: var(--on-surface); text-decoration: none; min-width: 0; }
.tw-topbar__brand--amber { color: var(--secondary); }
.tw-topbar__spacer { flex: 1 1 auto; min-width: 0; }
.tw-topbar__nav { display: none; align-items: center; gap: var(--stack-lg); min-width: 0; }
@media (min-width: 768px) { .tw-topbar__nav { display: flex; } }
.tw-topbar__link {
  display: inline-flex; align-items: center; min-height: var(--touch-target-min);
  color: var(--on-surface-variant); text-decoration: none;
  border-bottom: 2px solid transparent;
  font: var(--body-md-w) var(--body-md-size)/1 var(--font-body);
  transition: color var(--dur-base) ease, border-color var(--dur-base) ease;
}
@media (hover: hover) and (pointer: fine) {
  .tw-topbar__link:hover { color: var(--on-surface); }
}
.tw-topbar__link[aria-current="page"] { color: var(--secondary); font-weight: 700; border-bottom-color: var(--secondary); }
.tw-topbar__actions { display: flex; align-items: center; gap: var(--stack-sm); min-width: 0; }
.tw-topbar__title { font: var(--headline-lg-w) var(--headline-lg-size)/var(--headline-lg-lh) var(--font-display); letter-spacing: -.01em; color: var(--on-surface); margin: 0; }
.tw-topbar__subtitle { font: var(--body-md-w) var(--body-md-size)/var(--body-md-lh) var(--font-body); color: var(--on-surface-variant); margin: 4px 0 0; }
/* Three-cell balance so a centred title is truly centred (customer wizard). */
.tw-topbar--centered { display: grid; grid-template-columns: var(--touch-target-min) 1fr var(--touch-target-min); align-items: center; }
.tw-topbar--centered > :nth-child(2) { text-align: center; min-width: 0; }


/* =============================================================================
   C18. BOTTOM NAVIGATION (mobile)  · doc 07 §3.18
   The active item keeps its visible label — the mockups sr-only it, leaving
   the current tab as the only unnamed one.
   ============================================================================= */
.tw-bottomnav {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: var(--z-bottomnav);
  display: flex; justify-content: space-around; align-items: center;
  gap: var(--stack-xs);
  min-height: var(--bottomnav-h);
  padding: var(--stack-sm) var(--stack-md);
  padding-bottom: calc(var(--stack-sm) + var(--safe-bottom));
  background: var(--surface-container-highest);
  border-top-left-radius: var(--radius-xl); border-top-right-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
}
/* On a desktop the same nav renders as a static strip under the page title —
   hiding it with nothing in its place left portal users with no menu and no
   way to sign out on anything wider than a tablet. */
@media (min-width: 768px) {
  .tw-bottomnav {
    position: static;
    justify-content: flex-start;
    gap: var(--stack-md);
    min-height: 0;
    padding: var(--stack-sm) var(--gutter, 1rem);
    border-radius: 0;
    box-shadow: none;
    border-bottom: 1px solid var(--outline-variant);
  }
  .tw-page--has-bottomnav { padding-bottom: 0; }
}
.tw-bottomnav__item {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  min-width: var(--touch-target-min); min-height: var(--touch-target-min);
  padding: 4px var(--stack-sm); border-radius: var(--radius-lg);
  color: var(--on-surface-variant); text-decoration: none;
  font: var(--label-caps-w) 10px/12px var(--font-body); letter-spacing: var(--label-caps-ls); text-transform: uppercase;
  transition: background-color var(--dur-menu) ease, color var(--dur-menu) ease,
              transform var(--dur-press) var(--ease-out);
}
.tw-bottomnav__item:active { background: var(--surface-variant); transform: scale(0.97); }
.tw-bottomnav__item[aria-current="page"] {
  background: var(--secondary); color: var(--on-secondary);
  border-radius: var(--radius-full); padding-inline: var(--space-3);
}


/* =============================================================================
   C19. STEPPER / WIZARD PROGRESS  · doc 07 §3.19 — four legitimate treatments
   ============================================================================= */
/* A. Thin bar */
.tw-progressbar { width: 100%; height: 4px; background: var(--surface-container-high); border-radius: var(--radius-full); overflow: hidden; }
.tw-progressbar__fill { height: 100%; background: var(--secondary); border-radius: var(--radius-full); transition: width var(--dur-slow) ease; }
/* B. Labelled bar */
.tw-progressbar--lg { height: 8px; background: var(--surface-variant); }
.tw-progress-caption { display: flex; justify-content: space-between; gap: var(--stack-sm); margin-bottom: var(--stack-sm); }

/* C. Segmented bar */
.tw-steps-seg { display: flex; align-items: center; gap: var(--stack-sm); }
.tw-steps-seg__pill { flex: 1 1 0; height: 8px; border-radius: var(--radius-full); background: var(--surface-container-high); }
.tw-steps-seg__pill.is-done    { background: var(--status-success); }
.tw-steps-seg__pill.is-current { background: var(--secondary); }
.tw-steps-seg__labels { display: flex; gap: var(--stack-sm); margin-top: var(--stack-sm); }
.tw-steps-seg__labels > * { flex: 1 1 0; min-width: 0; font: var(--label-caps-w) var(--label-caps-size)/var(--label-caps-lh) var(--font-body); letter-spacing: var(--label-caps-ls); text-transform: uppercase; color: var(--on-surface-variant); }
.tw-steps-seg__labels > [aria-current="step"] { color: var(--secondary); }

/* D. Vertical rail. The connector is drawn per-item, so it cannot overshoot
   past the last step the way the mockup's absolute full-height line does. */
.tw-steps-rail { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--gutter); }
.tw-steps-rail__item { position: relative; display: flex; gap: var(--stack-md); min-width: 0; }
.tw-steps-rail__item:not(:last-child)::before {
  content: ""; position: absolute; left: 16px; top: 36px; bottom: calc(var(--gutter) * -1 + 4px);
  width: 1px; background: var(--outline-variant);
}
.tw-steps-rail__dot {
  flex: 0 0 auto; width: 32px; height: 32px; border-radius: var(--radius-full);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-variant); border: 1px solid var(--outline-variant);
  color: var(--on-surface-variant); font: 700 var(--body-sm-size)/1 var(--font-body);
}
.tw-steps-rail__item[aria-current="step"] .tw-steps-rail__dot { background: var(--secondary); border-color: var(--secondary); color: var(--on-secondary); }
.tw-steps-rail__item.is-done .tw-steps-rail__dot { background: var(--status-success); border-color: var(--status-success); color: var(--on-status-critical); }
/* Completed is marked with a glyph, not only colour. */
.tw-steps-rail__item.is-done .tw-steps-rail__dot::after { content: "check"; font-family: var(--font-icon); font-size: 18px; font-variation-settings: 'FILL' 1, 'wght' 500, 'GRAD' 0, 'opsz' 20; }
.tw-steps-rail__item.is-done .tw-steps-rail__num { display: none; }
.tw-steps-rail__kicker { font: var(--label-caps-w) var(--label-caps-size)/var(--label-caps-lh) var(--font-body); letter-spacing: var(--label-caps-ls); text-transform: uppercase; color: var(--on-surface-variant); }
.tw-steps-rail__item[aria-current="step"] .tw-steps-rail__kicker { color: var(--secondary); }
.tw-steps-rail__title { font: 600 var(--body-md-size)/var(--body-md-lh) var(--font-body); color: var(--on-surface-variant); margin: 2px 0 0; }
.tw-steps-rail__item[aria-current="step"] .tw-steps-rail__title,
.tw-steps-rail__item.is-done .tw-steps-rail__title { color: var(--on-surface); }
/* If a step circle NAVIGATES it must be 48px, not 32px (doc 07 §10.2). */
.tw-steps-rail__dot:is(a, button) { width: var(--touch-target-min); height: var(--touch-target-min); }
.tw-steps-rail__item:has(.tw-steps-rail__dot:is(a, button)):not(:last-child)::before { left: 24px; top: 52px; }

/* Wizard step transition */
.tw-wizard-step { display: none; }
.tw-wizard-step.is-active { display: flex; flex: 1 1 auto; flex-direction: column; animation: tw-slide-in var(--dur-panel) var(--ease-out) forwards; }
/* Actions pinned to the bottom without leaving 400px of dead space above
   (the customer-flow PNG bug, doc 07 §4.2). */
.tw-wizard-actions { margin-top: auto; padding-top: var(--stack-lg); display: flex; gap: var(--stack-md); }
.tw-wizard-actions--split > :first-child { flex: 1 1 0; }
.tw-wizard-actions--split > :last-child  { flex: 2 1 0; }


/* =============================================================================
   C20. MAP PANEL  · doc 07 §3.20 — z-index 0, everything floats over it
   ============================================================================= */
.tw-map { position: relative; flex: 1 1 auto; min-width: 0; min-height: 0; background: var(--surface-dim); overflow: hidden; z-index: var(--z-map); }
.tw-map__tiles { position: absolute; inset: 0; background-size: cover; background-position: center; opacity: .40; }
.tw-map--driver .tw-map__tiles, .tw-map--zones .tw-map__tiles { opacity: .60; }
.tw-map__overlay { position: absolute; inset: 0; pointer-events: none; }
.tw-map__controls {
  position: absolute; top: var(--stack-md); right: var(--stack-md); z-index: var(--z-map-ui);
  display: flex; flex-direction: column; gap: var(--stack-sm);
}
.tw-map__tools { position: absolute; top: var(--stack-md); left: var(--stack-md); z-index: var(--z-map-ui); display: flex; gap: var(--stack-sm); padding: var(--stack-sm); background: color-mix(in srgb, var(--surface-container) 90%, transparent); -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); border: 1px solid color-mix(in srgb, var(--outline-variant) 50%, transparent); border-radius: var(--radius-lg); }
.tw-map__ctrl {
  width: var(--touch-target-min); height: var(--touch-target-min);   /* mockup: 40px */
  display: inline-flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--surface-card) 80%, transparent); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  border: 1px solid color-mix(in srgb, var(--outline-variant) 30%, transparent); border-radius: var(--radius-lg);
  color: var(--on-surface); cursor: pointer; box-shadow: var(--shadow-md);
  transition: background-color var(--dur-menu) ease, transform var(--dur-press) var(--ease-out);
}
.tw-map__ctrl:active { transform: scale(0.97); }
@media (hover: hover) and (pointer: fine) {
  .tw-map__ctrl:hover { background: var(--surface-variant); }
}
.tw-map__legend { position: absolute; left: var(--stack-md); bottom: var(--stack-md); z-index: var(--z-map-ui); display: flex; flex-wrap: wrap; gap: var(--stack-sm); max-width: calc(100% - var(--stack-lg)); pointer-events: none; } /* reads over the map; must never intercept a click meant for a pin */
.tw-map__legend-pill {
  display: inline-flex; align-items: center; gap: var(--stack-sm);
  padding: 4px var(--space-3); border-radius: var(--radius);
  background: color-mix(in srgb, var(--surface-container) 90%, transparent); -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  border: 1px solid color-mix(in srgb, var(--outline-variant) 50%, transparent);
  font: var(--label-caps-w) var(--label-caps-size)/var(--label-caps-lh) var(--font-body);
  letter-spacing: var(--label-caps-ls); text-transform: uppercase; color: var(--on-surface);
}
.tw-map__legend-dot { width: 12px; height: 12px; border-radius: var(--radius-full); flex: 0 0 auto; }
.tw-map__legend-dot--internal { background: var(--map-driver-internal); box-shadow: var(--glow-driver-internal); }
.tw-map__legend-dot--vendor   { background: var(--map-driver-vendor);   box-shadow: var(--glow-driver-vendor); }
.tw-map__legend-dot--amber    { background: var(--secondary);           box-shadow: var(--glow-zone-amber); }

/* Markers */
.tw-map-marker { position: absolute; transform: translate(-50%, -50%); display: flex; flex-direction: column; align-items: center; z-index: var(--z-map-ui); }
.tw-map-marker__pin {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: var(--radius-full);
  border: 2px solid var(--surface); box-shadow: var(--shadow-lg); position: relative; z-index: 1;
}
.tw-map-marker--pickup   .tw-map-marker__pin { background: var(--secondary); color: var(--on-secondary); animation: tw-pulse var(--dur-pulse) var(--ease-in-out) infinite; }
.tw-map-marker--internal .tw-map-marker__pin { background: var(--map-driver-internal); color: var(--on-status-critical); box-shadow: var(--glow-driver-internal); }
.tw-map-marker--vendor   .tw-map-marker__pin { background: var(--map-driver-vendor);   color: var(--on-status-critical); box-shadow: var(--glow-driver-vendor); }
.tw-map-marker--lg .tw-map-marker__pin { width: var(--touch-target-min); height: var(--touch-target-min); }
.tw-map-marker__label {
  margin-top: 4px; padding: 2px var(--stack-sm); border-radius: var(--radius);
  background: var(--surface-card); border: 1px solid color-mix(in srgb, var(--outline-variant) 30%, transparent);
  box-shadow: var(--shadow-md); white-space: nowrap;
  font: var(--data-mono-w) 12px/16px var(--font-mono); letter-spacing: var(--data-mono-ls);
  color: var(--on-surface);
}
.tw-map-marker--pickup .tw-map-marker__label { border-color: var(--secondary); }
.tw-map-glow--internal { box-shadow: var(--glow-driver-internal); }
.tw-map-glow--vendor   { box-shadow: var(--glow-driver-vendor); }

/* Zone polygons live in an SVG overlay; these are the two committed styles. */
.tw-zone--draft     { stroke-dasharray: 4; }
.tw-zone--committed { stroke-dasharray: none; }


/* =============================================================================
   C21. CHAT / ACTIVITY PANEL  · doc 07 §3.21
   ============================================================================= */
.tw-chat { display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; min-height: 0; }
.tw-chat__log {
  flex: 1 1 auto; min-height: 150px; overflow-y: auto;
  padding: var(--space-3); border-radius: var(--radius);
  background: color-mix(in srgb, var(--surface) 30%, transparent); border: 1px solid color-mix(in srgb, var(--outline-variant) 20%, transparent);
  display: flex; flex-direction: column; gap: var(--stack-sm);
}
.tw-chat__event { display: flex; gap: var(--stack-sm); font-size: var(--body-sm-size); line-height: var(--body-sm-lh); min-width: 0; }
/* The text column must be allowed to shrink, or a long system event pushes the
   whole activity panel past its column edge. */
.tw-chat__event > *:not(.tw-chat__dot) { min-width: 0; overflow-wrap: anywhere; }
.tw-chat__msg, .tw-chat__log { overflow-wrap: anywhere; }
.tw-chat__dot { flex: 0 0 auto; width: 8px; height: 8px; margin-top: 6px; border-radius: var(--radius-full); background: var(--primary); }
.tw-chat__dot--action { background: var(--status-warning); }
.tw-chat__dot--alert  { background: var(--status-critical); }
.tw-chat__time { font: var(--data-mono-w) 12px/16px var(--font-mono); letter-spacing: var(--data-mono-ls); color: var(--on-surface-variant); margin-right: var(--stack-sm); font-variant-numeric: tabular-nums; }
.tw-chat__msg {
  padding: var(--stack-sm); margin-left: var(--stack-md);
  background: color-mix(in srgb, var(--surface-bright) 50%, transparent); border-radius: var(--radius);
  border-left: 2px solid var(--map-driver-internal);
  font-size: var(--body-sm-size); line-height: var(--body-sm-lh); min-width: 0;
}
.tw-chat__msg--vendor { border-left-color: var(--map-driver-vendor); }
.tw-chat__author { display: block; font: 700 12px/16px var(--font-body); color: var(--primary); margin-bottom: 4px; }
.tw-chat__composer { display: flex; gap: var(--stack-sm); align-items: stretch; min-width: 0; }
.tw-chat__composer .tw-input { flex: 1 1 auto; min-width: 0; background: var(--surface-container); font-size: var(--body-sm-size); }
.tw-chat__send {
  flex: 0 0 auto; width: var(--touch-target-min); height: var(--touch-target-min);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-variant); color: var(--on-surface);
  border: 1px solid color-mix(in srgb, var(--outline-variant) 50%, transparent); border-radius: var(--radius); cursor: pointer;
  transition: color var(--dur-menu) ease, background-color var(--dur-menu) ease,
              transform var(--dur-press) var(--ease-out);
}
.tw-chat__send:active { transform: scale(0.97); }
@media (hover: hover) and (pointer: fine) {
  .tw-chat__send:hover { color: var(--primary); background: var(--surface-container-high); }
}


/* =============================================================================
   C22. SLIDE-OVER / CONTEXT PANEL  · doc 07 §3.22
   Below the wide band it becomes a full-screen dialog — a hard 400/480px
   column below ~1100px is what causes the mockups' horizontal overflow.
   ============================================================================= */
.tw-slideover {
  display: flex; flex-direction: column;
  width: var(--context-panel-w); flex: 0 0 var(--context-panel-w);
  height: 100%; min-height: 0; z-index: var(--z-slideover);
  border-left: 1px solid var(--hairline);
  background: var(--glass-bg);
  -webkit-backdrop-filter: blur(var(--glass-blur)); backdrop-filter: blur(var(--glass-blur));
  transform: translateX(0);
  transform-origin: var(--transform-origin, center);
  transition: transform var(--dur-panel) var(--ease-drawer);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .tw-slideover { background: var(--glass-bg-fallback); }
}
.tw-slideover[hidden], .tw-slideover.is-closed { display: none; }
.tw-slideover__head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--stack-md); padding: var(--gutter); border-bottom: 1px solid color-mix(in srgb, var(--outline-variant) 30%, transparent); }
.tw-slideover__title { font: var(--headline-md-w) var(--headline-md-size)/1.2 var(--font-display); color: var(--on-surface); margin: 0; }
.tw-slideover__meta { display: flex; align-items: center; gap: var(--stack-sm); margin-bottom: 4px; flex-wrap: wrap; }
.tw-slideover__body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: var(--gutter); display: flex; flex-direction: column; gap: var(--stack-lg); }
.tw-slideover__section-title {
  display: flex; align-items: center; gap: var(--stack-sm);
  font: var(--label-caps-w) var(--label-caps-size)/var(--label-caps-lh) var(--font-body);
  letter-spacing: var(--label-caps-ls); text-transform: uppercase;
  color: var(--primary-fixed-dim); margin: 0 0 var(--stack-sm);
}
.tw-slideover__foot { flex: 0 0 auto; display: flex; gap: var(--stack-md); padding: var(--gutter); border-top: 1px solid color-mix(in srgb, var(--outline-variant) 30%, transparent); background: color-mix(in srgb, var(--surface-card) 90%, transparent); }
.tw-slideover__foot > .tw-btn { flex: 1 1 0; }

/* Below 1280px the docked column is not safe — full-screen dialog instead. */
@media (max-width: 1279.98px) {
  .tw-slideover {
    position: fixed; inset: 0; width: 100%; max-width: 100%; flex-basis: auto;
    border-left: 0; z-index: var(--z-modal);
  }
  .tw-slideover.is-closed { display: none; }
}

/* Bottom-sheet variant (operator context panel) */
.tw-context-sheet {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: var(--z-sticky);
  max-height: var(--sheet-max-h); overflow-y: auto;
  padding: var(--gutter);
  padding-bottom: calc(var(--gutter) + var(--safe-bottom));
  background: color-mix(in srgb, var(--surface-card) 90%, transparent);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  border-top: 1px solid color-mix(in srgb, var(--outline-variant) 30%, transparent);
  box-shadow: var(--shadow-sheet);
  display: flex; flex-direction: column; gap: var(--gutter);
}
@media (min-width: 768px) { .tw-context-sheet { max-height: 35vh; flex-direction: row; } }
.tw-context-sheet__main { flex: 1 1 auto; min-width: 0; }
/* The border-l + padding must not survive the mobile stack (doc 07 §4.4). */
.tw-context-sheet__aside { flex: 0 0 auto; min-width: 0; border-top: 1px solid color-mix(in srgb, var(--outline-variant) 30%, transparent); padding-top: var(--stack-md); }
@media (min-width: 768px) {
  .tw-context-sheet__aside { width: 33.3333%; border-top: 0; padding-top: 0; border-left: 1px solid color-mix(in srgb, var(--outline-variant) 30%, transparent); padding-left: var(--gutter); }
}


/* =============================================================================
   C23. BOTTOM SHEET (driver)  · doc 07 §3.23
   ============================================================================= */
.tw-sheet {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: var(--z-sticky);
  display: flex; flex-direction: column; min-height: 0;
  background: var(--surface-card);
  border-top: 1px solid var(--hairline);
  border-top-left-radius: var(--radius-xl); border-top-right-radius: var(--radius-xl);
  box-shadow: var(--shadow-2xl);
  padding-bottom: var(--safe-bottom);
  transform: translateY(0);
  transform-origin: var(--transform-origin, bottom center);
  transition: transform var(--dur-panel) var(--ease-drawer), max-height var(--dur-panel) var(--ease-drawer);
}
.tw-sheet__grip { display: flex; justify-content: center; padding: var(--stack-sm) 0; flex: 0 0 auto; cursor: grab; touch-action: none; }
.tw-sheet__grip::before { content: ""; width: 48px; height: 4px; border-radius: var(--radius-full); background: var(--outline-variant); }
.tw-sheet__grip:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring-color); outline-offset: var(--focus-ring-inset-offset); }
.tw-sheet__body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 0 var(--margin-mobile) var(--margin-mobile); display: flex; flex-direction: column; gap: var(--stack-md); }
/* Snap points, driven by a class on the sheet. */
.tw-sheet.is-peek { max-height: 30vh; }
.tw-sheet.is-half { max-height: 55vh; }
.tw-sheet.is-full { max-height: 92vh; }
.tw-sheet.is-closed { transform: translateY(100%); }


/* =============================================================================
   C24. FILTER CHIPS  · doc 07 §3.24
   Real 48px hit area; the mockup's px-3 py-1 is ~28px tall.
   ============================================================================= */
.tw-chipbar {
  display: flex; gap: var(--stack-sm); align-items: center;
  padding: var(--space-3); max-width: 100%; overflow-x: auto;
  background: var(--surface); border-bottom: 1px solid color-mix(in srgb, var(--outline-variant) 30%, transparent);
  scrollbar-width: none; -ms-overflow-style: none;
}
.tw-chipbar::-webkit-scrollbar { display: none; }
.tw-chip--filter {
  flex: 0 0 auto; min-height: var(--touch-target-min);
  padding: 0 var(--space-3); border-radius: var(--radius-full);
  background: var(--surface-variant); color: var(--on-surface-variant);
  border: 1px solid transparent; cursor: pointer; white-space: nowrap;
  font: var(--label-caps-w) var(--label-caps-size)/1 var(--font-body);
  letter-spacing: var(--label-caps-ls); text-transform: uppercase;
  display: inline-flex; align-items: center; gap: 6px;
  transition: background-color var(--dur-menu) ease, color var(--dur-menu) ease,
              transform var(--dur-press) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .tw-chip--filter:hover { background: var(--surface-container-high); color: var(--on-surface); }
}
.tw-chip--filter:active { background: var(--surface-bright); transform: scale(0.97); }
.tw-chip--filter[aria-pressed="true"] { background: var(--nav-active-bg); color: var(--nav-active-fg); }  /* A5 fix applies here too */
.tw-chip--filter:disabled { background: var(--disabled-bg); color: var(--disabled-fg); cursor: not-allowed; }


/* =============================================================================
   C25. KPI STAT TILE  · doc 07 §3.25
   ============================================================================= */
.tw-kpi {
  position: relative; display: flex; flex-direction: column; justify-content: space-between; gap: var(--stack-md);
  background: var(--surface-card); border: 1px solid var(--hairline);
  border-radius: var(--radius-lg); padding: var(--stack-md);
  overflow: hidden; min-width: 0;
  /* A KPI tile is often the link itself. The whole card is the affordance, so
     the UA underline would run under the label, the figure and the caption. */
  text-decoration: none;
}
a.tw-kpi, .tw-kpi a { text-decoration: none; }
.tw-kpi__head { display: flex; align-items: center; gap: var(--stack-sm); color: var(--on-surface-variant); min-width: 0; }
.tw-kpi__label { font: var(--label-caps-w) var(--label-caps-size)/var(--label-caps-lh) var(--font-body); letter-spacing: var(--label-caps-ls); text-transform: uppercase; min-width: 0; }
.tw-kpi__value {
  /* The mockup's fixed 48px numeral collides with its caption at lg:grid-cols-3
     (visible in the PNG). Clamp it and let the caption stack below. */
  font: var(--display-lg-w) clamp(28px, 4vw, var(--display-lg-size))/1.15 var(--font-display);
  letter-spacing: var(--display-lg-ls); color: var(--on-surface);
  min-width: 0; overflow-wrap: anywhere;
}
.tw-kpi__unit { font: var(--body-lg-w) var(--body-lg-size)/var(--body-lg-lh) var(--font-body); color: var(--on-surface-variant); letter-spacing: 0; }
.tw-kpi__caption { display: block; font: 400 var(--body-sm-size)/var(--body-sm-lh) var(--font-body); color: var(--on-surface-variant); margin-top: 4px; }
/* Alert flavour — 4px left rail */
.tw-kpi--warning  { border-color: color-mix(in srgb, var(--status-warning) 30%, transparent); }
.tw-kpi--critical { border-color: color-mix(in srgb, var(--status-critical) 30%, transparent); }
.tw-kpi--warning::before, .tw-kpi--critical::before { content: ""; position: absolute; left: 0; top: 0; width: 4px; height: 100%; }
.tw-kpi--warning::before  { background: var(--status-warning); }
.tw-kpi--critical::before { background: var(--status-critical); }
.tw-kpi--warning  .tw-kpi__head, .tw-kpi--warning  .tw-kpi__caption { color: var(--status-warning); }
.tw-kpi--critical .tw-kpi__head, .tw-kpi--critical .tw-kpi__caption { color: var(--status-critical-text); }
/* Compact flavour — the admin header trio */
.tw-kpi--compact { min-width: 120px; padding: var(--stack-sm) var(--stack-md); gap: 2px; }
.tw-kpi--compact .tw-kpi__value { font: var(--headline-md-w) var(--headline-md-size)/var(--headline-md-lh) var(--font-display); letter-spacing: 0; }


/* =============================================================================
   C26. FILE UPLOAD / DROPZONE  · doc 07 §3.26
   Inputs are visually hidden with the sr-only technique, never display:none —
   display:none removes them from the tab order (the mockup bug).
   ============================================================================= */
.tw-file-input { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
.tw-upload-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--stack-sm);
  min-height: var(--touch-target-min); min-width: var(--touch-target-min);
  padding: 0 var(--stack-md); border-radius: var(--radius);
  border: 1px solid var(--outline); color: var(--on-surface); cursor: pointer; background: transparent;
  font: var(--label-caps-w) var(--label-caps-size)/1 var(--font-body); letter-spacing: var(--label-caps-ls); text-transform: uppercase;
  transition: border-color var(--dur-menu) ease, color var(--dur-menu) ease, background-color var(--dur-menu) ease,
              transform var(--dur-press) var(--ease-out);
}
.tw-upload-btn:active { transform: scale(0.97); }
@media (hover: hover) and (pointer: fine) {
  .tw-upload-btn:hover { border-color: var(--secondary); color: var(--secondary); }
}
.tw-upload-btn:focus-within { outline: var(--focus-ring-width) solid var(--focus-ring-color); outline-offset: var(--focus-ring-offset); }
.tw-upload-btn:has(.tw-file-input:disabled) { border-color: var(--disabled-border); color: var(--disabled-fg); cursor: not-allowed; }

.tw-dropzone {
  display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center;
  padding: var(--stack-lg) var(--gutter);
  border: 2px dashed var(--outline-variant); border-radius: var(--radius-xl);
  background: var(--surface-container-low);
  transition: border-color var(--dur-base) ease, background-color var(--dur-base) ease;
}
@media (hover: hover) and (pointer: fine) {
  .tw-dropzone:hover { border-color: var(--outline); }
}
.tw-dropzone.is-dragover { border-color: var(--secondary); background: color-mix(in srgb, var(--secondary) 5%, transparent); }
.tw-dropzone.is-error { border-color: var(--status-critical); background: color-mix(in srgb, var(--status-critical) 6%, transparent); }
.tw-dropzone__icon {
  width: 64px; height: 64px; border-radius: var(--radius-full);
  background: var(--surface-variant); color: var(--on-surface-variant);
  display: inline-flex; align-items: center; justify-content: center; margin-bottom: var(--stack-md);
}
.tw-dropzone__title { font: var(--headline-md-w) var(--headline-md-size)/var(--headline-md-lh) var(--font-display); color: var(--on-surface); margin: 0 0 var(--stack-sm); }
.tw-dropzone__hint { font: var(--body-md-w) var(--body-md-size)/var(--body-md-lh) var(--font-body); color: var(--on-surface-variant); max-width: 42ch; margin: 0 0 var(--stack-md); }
.tw-dropzone__browse {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: var(--touch-target-min); padding: 0 var(--gutter);
  background: var(--surface-variant); color: var(--on-surface);
  border-radius: var(--radius-lg); cursor: pointer;
  font: var(--label-caps-w) var(--label-caps-size)/1 var(--font-body); letter-spacing: var(--label-caps-ls); text-transform: uppercase;
  transition: background-color var(--dur-menu) ease, transform var(--dur-press) var(--ease-out);
}
.tw-dropzone__browse:active { transform: scale(0.97); }
@media (hover: hover) and (pointer: fine) {
  .tw-dropzone__browse:hover { background: var(--surface-container-high); }
}
.tw-dropzone__browse:focus-within { outline: var(--focus-ring-width) solid var(--focus-ring-color); outline-offset: var(--focus-ring-offset); }
/* Uploading state — determinate bar, not a spinner. */
.tw-upload-progress { width: 100%; max-width: 320px; margin-top: var(--stack-md); }


/* =============================================================================
   C27. EMPTY STATE  · doc 07 §3.27 — not present in any mockup, built here
   from the dropzone's icon-circle + headline + copy + one action rhythm.
   ============================================================================= */
.tw-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center;
  padding: 64px var(--gutter); gap: var(--stack-md); min-width: 0;
}
.tw-empty__icon {
  width: 64px; height: 64px; border-radius: var(--radius-full);
  background: var(--surface-variant); color: var(--on-surface-variant);
  display: inline-flex; align-items: center; justify-content: center;
}
.tw-empty__title { font: var(--headline-md-w) var(--headline-md-size)/var(--headline-md-lh) var(--font-display); color: var(--on-surface); margin: 0; }
.tw-empty__body { font: var(--body-md-w) var(--body-md-size)/var(--body-md-lh) var(--font-body); color: var(--on-surface-variant); max-width: 42ch; margin: 0; }
.tw-empty--compact { padding: var(--stack-lg) var(--stack-md); }
.tw-empty--critical .tw-empty__icon { background: color-mix(in srgb, var(--status-critical) 15%, transparent); color: var(--status-critical-text); }


/* =============================================================================
   C28. TOAST / INLINE BANNER  · doc 07 §3.28
   No toast exists in the mockups; the inline info banner is the base skin.
   ============================================================================= */
.tw-banner {
  display: flex; align-items: flex-start; gap: var(--stack-sm);
  padding: var(--stack-md); border-radius: var(--radius-lg);
  background: var(--surface-container-high); border: 1px solid var(--outline-variant);
  color: var(--on-surface-variant);
  font: var(--body-md-w) var(--body-md-size)/var(--body-md-lh) var(--font-body);
  min-width: 0;
}
.tw-banner__icon { flex: 0 0 auto; color: var(--secondary); }
.tw-banner__body { min-width: 0; }
.tw-banner--info     { border-color: color-mix(in srgb, var(--primary) 30%, transparent); }
.tw-banner--info     .tw-banner__icon { color: var(--primary); }
.tw-banner--success  { border-color: color-mix(in srgb, var(--status-success) 45%, transparent); }
.tw-banner--success  .tw-banner__icon { color: var(--status-success-text); }
.tw-banner--warning  { border-color: color-mix(in srgb, var(--status-warning) 45%, transparent); }
.tw-banner--warning  .tw-banner__icon { color: var(--status-warning); }
.tw-banner--critical { border-color: color-mix(in srgb, var(--status-critical) 50%, transparent); }
.tw-banner--critical .tw-banner__icon { color: var(--status-critical-text); }

.tw-toast-region {
  position: fixed; z-index: var(--z-toast);
  right: var(--page-inset);
  bottom: calc(var(--bottomnav-h) + var(--safe-bottom) + var(--stack-md));
  left: var(--page-inset);
  display: flex; flex-direction: column; gap: var(--stack-sm);
  pointer-events: none;
}
@media (min-width: 768px) { .tw-toast-region { left: auto; bottom: calc(var(--stack-md) + var(--safe-bottom)); width: 400px; max-width: calc(100vw - var(--page-inset) * 2); } }
.tw-toast {
  pointer-events: auto;
  display: flex; align-items: flex-start; gap: var(--stack-sm);
  padding: var(--stack-md); padding-left: var(--space-3);
  border-radius: var(--radius-lg);
  border-left: 4px solid var(--primary);
  background: var(--glass-bg);
  -webkit-backdrop-filter: blur(var(--glass-blur)); backdrop-filter: blur(var(--glass-blur));
  box-shadow: var(--shadow-lg);
  color: var(--on-surface); min-width: 0;
  transform-origin: var(--transform-origin, bottom right);
  animation: tw-pop var(--dur-menu) var(--ease-out);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .tw-toast { background: var(--glass-bg-fallback); }
}
.tw-toast--success  { border-left-color: var(--status-success-text); }
.tw-toast--warning  { border-left-color: var(--status-warning); }
.tw-toast--critical { border-left-color: var(--status-critical); }
.tw-toast__body { flex: 1 1 auto; min-width: 0; font-size: var(--body-sm-size); line-height: var(--body-sm-lh); }
.tw-toast__title { display: block; font-weight: 700; margin-bottom: 2px; }
.tw-toast__close { flex: 0 0 auto; }
/* A critical dispatch alert never auto-dismisses. Nothing in CSS enforces
   that; the class exists so the JS timer can opt out on sight. */
.tw-toast--sticky { --tw-toast-autodismiss: none; }


/* =============================================================================
   C29. MODAL DIALOG  · doc 07 §3.29 — every mockup "overlay" is a slide-over.
   ============================================================================= */
.tw-modal-scrim {
  position: fixed; inset: 0; z-index: var(--z-scrim);
  background: var(--scrim);
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  display: flex; align-items: center; justify-content: center;
  padding: var(--page-inset);
}
.tw-modal-scrim[hidden] { display: none; }
.tw-modal {
  position: relative; z-index: var(--z-modal);
  width: 100%; max-width: var(--panel-max); max-height: calc(100dvh - var(--stack-lg));
  display: flex; flex-direction: column; min-height: 0;
  border-radius: var(--radius-xl);
  background: var(--glass-bg);
  -webkit-backdrop-filter: blur(var(--glass-blur)); backdrop-filter: blur(var(--glass-blur));
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow-2xl);
  /* Modals are the exception to the scale-from-trigger rule: a dialog that
     owns the whole screen has no trigger to grow out of. Centred, always. */
  transform-origin: center;
  animation: tw-pop var(--dur-panel) var(--ease-out);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .tw-modal { background: var(--glass-bg-fallback); }
}
.tw-modal__head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--stack-md); padding: var(--stack-lg) var(--stack-lg) var(--stack-md); }
.tw-modal__title { font: var(--headline-md-w) var(--headline-md-size)/var(--headline-md-lh) var(--font-display); color: var(--on-surface); margin: 0; }
.tw-modal__body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 0 var(--stack-lg); color: var(--on-surface-variant); }
.tw-modal__foot { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--stack-md); padding: var(--stack-md) var(--stack-lg) var(--stack-lg); }
.tw-modal--danger .tw-modal__title { color: var(--status-critical-text); }
/* Body scroll lock helper — apply to <body> while a modal is open. */
body.tw-scroll-locked { overflow: hidden; }
/* Native <dialog> works too and gets the focus trap for free. */
dialog.tw-modal { padding: 0; color: inherit; }
dialog.tw-modal::backdrop { background: var(--scrim); -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px); }


/* =============================================================================
   C30. AVATAR  · doc 07 §3.30
   ============================================================================= */
.tw-avatar {
  display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto;
  width: 40px; height: 40px; border-radius: var(--radius-full);
  background: var(--surface-variant); color: var(--on-surface);
  border: 1px solid var(--outline-variant); overflow: hidden;
  font: 700 var(--body-sm-size)/1 var(--font-body);
}
.tw-avatar img { width: 100%; height: 100%; object-fit: cover; }
.tw-avatar--sm { width: 32px; height: 32px; font-size: 12px; }
.tw-avatar--tile { border-radius: var(--radius); }        /* square, 4px */
.tw-avatar--tile.is-selected { background: var(--nav-active-bg); color: var(--nav-active-fg); border-color: var(--nav-active-bg); }
/* Driver badge — colour is the internal/vendor signal, so ALWAYS pair it with
   the adjacent text label; colour alone is not a status. */
.tw-avatar--driver { width: 24px; height: 24px; font-size: 10px; color: var(--on-status-critical); border: 0; }
.tw-avatar--internal { background: var(--map-driver-internal); }
.tw-avatar--vendor   { background: var(--map-driver-vendor); }
.tw-avatar--primary  { background: var(--primary-container); color: var(--primary-fixed); }


/* =============================================================================
   C31. BADGE / MICRO-TAG  · doc 07 §3.31 — flatter and smaller than a chip
   ============================================================================= */
.tw-tag {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px var(--stack-sm); border-radius: var(--radius-sm);
  background: var(--surface-variant); color: var(--on-surface);
  font: 500 12px/16px var(--font-body); white-space: nowrap;
}
.tw-tag--mono     { font: var(--data-mono-w) 12px/16px var(--font-mono); letter-spacing: var(--data-mono-ls); }
.tw-tag--flat     { background: color-mix(in srgb, var(--primary-container) 30%, transparent); color: var(--primary); }
.tw-tag--multiplier { background: color-mix(in srgb, var(--tertiary-container) 30%, transparent); color: var(--tertiary); }
.tw-tag--amber    { background: color-mix(in srgb, var(--secondary) 15%, transparent); color: var(--secondary); }
.tw-taglist { display: flex; flex-wrap: wrap; gap: 4px; min-width: 0; }


/* =============================================================================
   C32. PROGRESS BAR  · doc 07 §3.32
   The mono n/4 counter beside it is mandatory: colour alone is not a status.
   ============================================================================= */
.tw-progress { display: flex; align-items: center; gap: var(--stack-sm); min-width: 0; }
.tw-progress__track { flex: 1 1 auto; max-width: 60px; height: 6px; background: var(--surface-variant); border-radius: var(--radius-full); overflow: hidden; }
.tw-progress__bar { height: 100%; border-radius: var(--radius-full); background: var(--status-success); transition: width var(--dur-slow) ease; }
.tw-progress__bar--warning  { background: var(--status-warning); }
.tw-progress__bar--critical { background: var(--status-critical); }
.tw-progress__bar--amber    { background: var(--secondary); }
.tw-progress__count { font: var(--data-mono-w) 12px/16px var(--font-mono); letter-spacing: var(--data-mono-ls); color: var(--status-success-text); font-variant-numeric: tabular-nums; }
.tw-progress__count--warning  { color: var(--status-warning); }
.tw-progress__count--critical { color: var(--status-critical-text); }
.tw-progress--full .tw-progress__track { max-width: none; height: 8px; }


/* =============================================================================
   C33. PAGINATION  · doc 07 §3.33
   "Load More" for the customer invoice list; real pagination for operator
   queues — an operator with 400 open jobs cannot click Load More forty times.
   ============================================================================= */
.tw-loadmore { display: flex; justify-content: center; padding: var(--stack-md); }
.tw-pagination { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--stack-sm); padding: var(--stack-md); }
.tw-pagination__item {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: var(--touch-target-min); min-height: var(--touch-target-min);
  padding: 0 var(--space-3); border-radius: var(--radius-full);
  background: var(--surface-variant); color: var(--on-surface-variant);
  border: 1px solid transparent; text-decoration: none; cursor: pointer;
  font: var(--label-caps-w) var(--label-caps-size)/1 var(--font-body); letter-spacing: var(--label-caps-ls);
  font-variant-numeric: tabular-nums;
  transition: background-color var(--dur-menu) ease, color var(--dur-menu) ease,
              transform var(--dur-press) var(--ease-out);
}
.tw-pagination__item:active { transform: scale(0.97); }
@media (hover: hover) and (pointer: fine) {
  .tw-pagination__item:hover { background: var(--surface-container-high); color: var(--on-surface); }
}
.tw-pagination__item[aria-current="page"] { background: var(--nav-active-bg); color: var(--nav-active-fg); }
.tw-pagination__item[aria-disabled="true"], .tw-pagination__item:disabled { background: transparent; color: var(--outline); cursor: not-allowed; }
.tw-pagination__gap { color: var(--outline); padding-inline: 4px; }


/* =============================================================================
   C34. BREADCRUMBS  · doc 07 §3.34
   ============================================================================= */
.tw-breadcrumb { display: flex; flex-wrap: wrap; align-items: center; gap: var(--stack-sm); font: var(--body-md-w) var(--body-sm-size)/var(--body-sm-lh) var(--font-body); color: var(--on-surface-variant); min-width: 0; }
/* 48px floor applies to breadcrumb links too — "48px, no exceptions". */
.tw-breadcrumb a { display: inline-flex; align-items: center; min-height: var(--touch-target-min); color: inherit; text-decoration: none; transition: color var(--dur-base) ease; }
@media (hover: hover) and (pointer: fine) {
  .tw-breadcrumb a:hover { color: var(--primary); }
}
.tw-breadcrumb [aria-current="page"] { color: var(--on-surface); font-weight: 500; }
.tw-breadcrumb__sep { color: var(--outline); display: inline-flex; }


/* =============================================================================
   C35. BENTO CARD (marketing)  · doc 07 §3.35
   auto-rows-min + a min-height on the small cells, not auto-rows-[250px] —
   the fixed row height leaves a large dead gap (visible in the PNG).
   ============================================================================= */
.tw-bento { display: grid; grid-template-columns: 1fr; grid-auto-rows: min-content; gap: var(--gutter); }
@media (min-width: 768px) { .tw-bento { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.tw-bento__cell {
  position: relative; display: flex; flex-direction: column; justify-content: space-between; gap: var(--stack-md);
  min-height: 180px; padding: var(--gutter); overflow: hidden; min-width: 0;
  background: var(--surface-card); border: 1px solid var(--hairline-faint);
  border-radius: var(--radius-xl);
  transition: background-color var(--dur-base) ease;
}
@media (hover: hover) and (pointer: fine) {
  .tw-bento__cell:hover { background: var(--surface-container-high); }
}
.tw-bento__cell--hero { justify-content: flex-end; padding: var(--stack-lg); min-height: 320px; }
@media (min-width: 768px) { .tw-bento__cell--hero { grid-column: span 2; grid-row: span 2; min-height: 500px; } }
.tw-bento__media { position: absolute; inset: 0; background-size: cover; background-position: center; opacity: .30; transition: opacity var(--dur-image) ease; }
@media (hover: hover) and (pointer: fine) {
  .tw-bento__cell--hero:hover .tw-bento__media { opacity: .40; }
}
.tw-bento__scrim { position: absolute; inset: 0; background: linear-gradient(to top, var(--surface-card), color-mix(in srgb, var(--surface-card) 50%, transparent) 50%, transparent); }
.tw-bento__content { position: relative; z-index: 1; min-width: 0; }
.tw-bento__icon {
  width: 40px; height: 40px; border-radius: var(--radius-full);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-container); color: var(--primary); margin-bottom: var(--stack-md);
}
.tw-bento__cell--hero .tw-bento__icon { width: 48px; height: 48px; background: var(--primary-container); color: var(--primary-fixed); }
.tw-bento__icon--amber    { color: var(--secondary); }
.tw-bento__icon--tertiary { color: var(--tertiary); }
.tw-bento__title { font: var(--headline-md-w) var(--headline-md-size)/var(--headline-md-lh) var(--font-display); color: var(--on-surface); margin: 0 0 var(--stack-sm); }


/* =============================================================================
   C36. FOOTER  · doc 07 §3.36
   ============================================================================= */
.tw-footer {
  display: flex; flex-direction: column; align-items: center; gap: var(--stack-md);
  padding: var(--stack-lg) var(--page-inset);
  background: var(--surface-dim); border-top: 1px solid var(--outline-variant);
  color: var(--on-surface-variant); text-align: center;
}
@media (min-width: 768px) { .tw-footer { flex-direction: row; justify-content: space-between; text-align: left; } }
.tw-footer--app { background: var(--surface-container-lowest); }
.tw-footer--app .tw-footer__links { font: var(--label-caps-w) var(--label-caps-size)/var(--label-caps-lh) var(--font-body); letter-spacing: .1em; text-transform: uppercase; }
.tw-footer__brand { font: var(--headline-md-w) var(--headline-md-size)/var(--headline-md-lh) var(--font-display); color: var(--on-surface); }
.tw-footer__copy { font: var(--body-md-w) var(--body-sm-size)/var(--body-sm-lh) var(--font-body); }
.tw-footer__links { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--stack-md); }
.tw-footer__links a { display: inline-flex; align-items: center; justify-content: center; min-height: var(--touch-target-min); min-width: var(--touch-target-min); color: var(--on-surface-variant); text-decoration: none; transition: color var(--dur-base) ease; }
@media (hover: hover) and (pointer: fine) {
  .tw-footer__links a:hover { color: var(--primary); }
}
/* Pages with a fixed mobile bottom nav. */
.tw-footer--above-bottomnav { padding-bottom: calc(var(--stack-lg) + var(--bottomnav-h) + var(--safe-bottom)); }
@media (min-width: 768px) { .tw-footer--above-bottomnav { padding-bottom: var(--stack-lg); } }

/* ==========================================================================
   ALIAS LAYER
   --------------------------------------------------------------------------
   Page code written against a slightly different vocabulary than the component
   layer settled on. Aliasing here keeps one visual source of truth instead of
   two near-duplicate implementations drifting apart.

   .tw-alert   -> .tw-banner      (inline status message)
   .tw-badge   -> .tw-tag         (small static label)
   .tw-crumbs  -> .tw-breadcrumb
   .tw-actions                    (button row — no prior equivalent)
   .tw-form                       (vertical form stack — no prior equivalent)
   .tw-check                      (checkbox/radio row — no prior equivalent)

   NOTE (2026-08-11): this block was written against `--tw-`-prefixed variables
   that were never declared anywhere — the real tokens have no `tw-` prefix. So
   every rule below silently fell through to its literal fallback and rendered
   the OLD warm palette regardless of the token layer. Rewritten against the
   real token names; the fallbacks are gone, not re-pointed.
   ========================================================================== */

/* An alert is a PARAGRAPH, not a row. `display:flex` turned every child into a
   flex item, so each <b>/<strong>/<code> inside the sentence was blockified into
   its own column and the prose shattered — the /admin/zones note rendered as six
   uneven columns split exactly on its <strong> tags. No page emits an
   .tw-alert__icon / .tw-alert__body pair (0 occurrences), so nothing wanted the
   row layout. Block keeps the emphasis inline and the sentence flowing. */
.tw-alert { display: block;
  padding: var(--stack-md); border-radius: var(--radius-lg);
  border: 1px solid var(--outline-variant);
  background: var(--surface-container); color: var(--on-surface);
  font-size: .9375rem; line-height: 1.5; }
.tw-alert--ok    { border-color: var(--status-success-text);
  background: color-mix(in srgb, var(--status-success-text) 12%, transparent); }
.tw-alert--warn  { border-color: var(--status-warning);
  background: color-mix(in srgb, var(--status-warning) 12%, transparent); }
.tw-alert--error { border-color: var(--status-critical-text);
  background: color-mix(in srgb, var(--status-critical-text) 12%, transparent); }

/* line-height is NOT optional here: without it the badge inherits whatever the
   context sets, so the same badge measured 22px inside a .tw-hint (16px lh) and
   30px inside a table cell (24px lh) — the oversized green "wins an unscoped
   quote" on /admin/pricing. .tw-chip gets this right via its `font:` shorthand;
   this is the same label-caps type role, stated the long way. */
.tw-badge { display: inline-flex; align-items: center; gap: .25rem;
  padding: .125rem .5rem; border-radius: var(--radius-full);
  font-size: .75rem; font-weight: 700; line-height: var(--label-caps-lh);
  letter-spacing: var(--label-caps-ls); text-transform: uppercase; white-space: nowrap;
  background: var(--surface-container-high); color: var(--on-surface-variant);
  border: 1px solid var(--outline-variant); }
.tw-badge--ok   { color: var(--status-success-text);
  border-color: color-mix(in srgb, var(--status-success-text) 40%, transparent); }
.tw-badge--info { color: var(--primary);
  border-color: color-mix(in srgb, var(--primary) 40%, transparent); }
/* Warning badge — beacon amber, the "needs attention" signal, never an action. */
.tw-badge--warn { color: var(--status-warning);
  border-color: color-mix(in srgb, var(--status-warning) 45%, transparent);
  background: color-mix(in srgb, var(--status-warning) 12%, var(--surface-container-high)); }
.tw-badge--lock { color: var(--secondary);
  border-color: color-mix(in srgb, var(--secondary) 40%, transparent); }
.tw-notification-bell { position: relative; }
.tw-notification-count { display: inline-flex; align-items: center; justify-content: center; min-width: 1.35rem; height: 1.35rem; padding-inline: .3rem; border-radius: var(--radius-full); background: var(--status-critical); color: #fff; font: 700 .6875rem/1 var(--font-body); }

.tw-crumbs { display: flex; flex-wrap: wrap; gap: .375rem; align-items: center;
  font-size: .8125rem; color: var(--on-surface-variant); }
.tw-crumbs a { color: inherit; }

.tw-actions { display: flex; flex-wrap: wrap; gap: var(--stack-sm);
  align-items: center; margin-top: var(--stack-md); }
.tw-actions--end { justify-content: flex-end; }
.tw-table td > .tw-actions { margin-top: 0; }

.tw-form { display: flex; flex-direction: column; gap: var(--stack-md); }

.tw-check { display: flex; gap: .625rem; align-items: flex-start;
  min-height: var(--touch-target-min); padding: .5rem 0; cursor: pointer; }
.tw-check input { width: 1.25rem; height: 1.25rem; margin-top: .125rem; flex: none;
  accent-color: var(--secondary); }
.tw-check > span { line-height: 1.4; }

/* --------------------------------------------------------------------------
   Gaps the pages already depend on. These class names are emitted across
   admin/pages and admin/partials but were never defined, so the controls
   rendered at default UA size — .tw-btn--sm in particular was a full-height
   48px button wherever a compact one was intended.
   -------------------------------------------------------------------------- */
/* Compact button. It only shrinks where a precise pointer exists — on touch the
   48px floor stands (doc 07 §10.2), so the density never costs a hit target. */
.tw-btn--sm { padding-inline: var(--stack-md); font: 600 var(--body-sm-size)/1 var(--font-body); }
.tw-btn--inline { padding-inline: var(--stack-sm); font: 600 var(--body-sm-size)/1 var(--font-body); }
@media (pointer: fine) {
  .tw-btn--sm, .tw-btn--inline { min-height: 34px; min-width: 34px; }
  .tw-btn--sm.tw-btn--icon { width: 34px; height: 34px; padding: 0; }
  .tw-table td > .tw-actions { flex-wrap: nowrap; }
}

/* .tw-kpi--warn is the name the pages use for .tw-kpi--warning. */
.tw-kpi--warn { border-color: color-mix(in srgb, var(--status-warning) 30%, transparent); }
.tw-kpi--warn::before { content: ""; position: absolute; left: 0; top: 0; width: 4px; height: 100%;
  background: var(--status-warning); }
.tw-kpi--warn .tw-kpi__head, .tw-kpi--warn .tw-kpi__caption { color: var(--status-warning); }

/* Inline SVG icons carry no intrinsic size, so inside a flex row they stretch
   to the row height — the SLA chips on the dispatch board were rendering their
   warning triangle several times larger than the text beside it, which is the
   non-colour half of the signal. .tw-icon--N sets font-size, which an <svg>
   ignores, so the sizes are restated here as real box dimensions. */
svg.tw-icon        { width: 1.5rem;   height: 1.5rem;   }
svg.tw-icon--14    { width: .875rem;  height: .875rem;  }
svg.tw-icon--16    { width: 1rem;     height: 1rem;     }
svg.tw-icon--18    { width: 1.125rem; height: 1.125rem; }
svg.tw-icon--20    { width: 1.25rem;  height: 1.25rem;  }
svg.tw-icon--32    { width: 2rem;     height: 2rem;     }
svg.tw-icon--40    { width: 2.5rem;   height: 2.5rem;   }
svg.tw-icon--48    { width: 3rem;     height: 3rem;     }
svg.tw-icon--60    { width: 3.75rem;  height: 3.75rem;  }
.tw-chip svg.tw-icon, .tw-badge svg.tw-icon { flex: 0 0 auto; }

/* admin_empty() emits bare h2/p inside .tw-empty. */
.tw-empty h2 { font: var(--headline-md-w) var(--headline-md-size)/var(--headline-md-lh) var(--font-display); color: var(--on-surface); margin: 0; }
.tw-empty p  { font: var(--body-md-w) var(--body-md-size)/var(--body-md-lh) var(--font-body); color: var(--on-surface-variant); max-width: 42ch; margin: 0; }

/* ---------------------------------------------------------------------------
   Type-size utilities.
   The CSP is `style-src 'self'`, which blocks style ATTRIBUTES as well as
   <style> blocks — verified in the browser: "Applying inline style violates...
   The action has been blocked." So `style="font-size:1.25rem"` silently does
   nothing. These classes replace it.

   They live here, not in towing-site.css, because this is the only stylesheet
   both the public layout and the admin layout load. Declared last so they win
   over the `font:` shorthand on components like .tw-kpi__value at equal
   specificity.
   --------------------------------------------------------------------------- */
.tw-fs-sm   { font-size: 0.875rem; }
.tw-fs-md   { font-size: 1rem; }
.tw-fs-lg   { font-size: 1.125rem; }
.tw-fs-xl   { font-size: 1.25rem; }
.tw-fs-2xl  { font-size: 1.5rem; }
.tw-fs-3xl  { font-size: 1.875rem; }

/* Emoji/glyph badges sized without touching layout. */
.tw-glyph     { font-size: 1.25rem; line-height: 1; }
.tw-glyph--lg { font-size: 1.5rem;  line-height: 1; }

/* Impersonation banner. Deliberately unmissable and deliberately not dismissible:
   an administrator must never forget whose account they are acting in. */
.tw-impbar {
  position: sticky; top: 0; z-index: 900;
  display: flex; align-items: center; gap: .625rem; flex-wrap: wrap;
  padding: .5rem .875rem;
  background: var(--tw-warn, #FFB020); color: #1A1206;
  font-size: .875rem; font-weight: 600;
}
.tw-impbar__dot { width: .5rem; height: .5rem; border-radius: 50%; background: #1A1206; flex: 0 0 auto; }
.tw-impbar__text { flex: 1 1 auto; min-width: 0; }
.tw-impbar__exit {
  flex: 0 0 auto; padding: .25rem .625rem; border-radius: 6px;
  background: #1A1206; color: var(--tw-warn, #FFB020); text-decoration: none; font-weight: 700;
}
.tw-impbar__exit:hover { background: #000; }

/* "Use my current location" status line. Speaks after the button, never
   instead of it — the coordinate can succeed while naming the spot fails. */
.tw-geoloc__status:empty { display: none; }
.tw-geoloc__status { margin-top: .5rem; }
.tw-geoloc__status.is-ok    { color: var(--success, #34D399); }
.tw-geoloc__status.is-error { color: var(--warning, #FBBF24); }

/* ===========================================================================
   Notification bell dropdown. MOVED here from towing-admin.css: the bell
   renders in BOTH shells (public layout_header and the admin topbar), but
   its styles lived only in the admin stylesheet, so on the public site the
   dropdown items were unstyled inline spans — title, body and time ran
   together as one line ('New messageperfect4 minutes ago'). towing-ui.css
   is loaded by every layout, so the bell now looks the same everywhere.
   =========================================================================== */
.tw-bell { position: relative; }
.tw-bell > summary { list-style: none; cursor: pointer; }
.tw-bell > summary::-webkit-details-marker { display: none; }
.tw-bell > summary::marker { content: ""; }

.tw-bell__panel {
  position: absolute;
  top: calc(100% + 0.5rem);
  right: 0;
  z-index: var(--z-modal, 900);
  width: min(24rem, calc(100vw - 2rem));
  background: var(--surface-container, #131820);
  border: 1px solid var(--outline-variant, #333E4D);
  border-radius: 12px;
  box-shadow: var(--shadow-2xl, 0 18px 48px rgba(0,0,0,.55));
  overflow: hidden;
}

.tw-bell__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: .5rem; flex-wrap: wrap;
  padding: .625rem .75rem;
  border-bottom: 1px solid var(--outline-variant, #333E4D);
}
.tw-bell__tools { display: flex; gap: .25rem; flex-wrap: wrap; }

.tw-bell__list { max-height: min(24rem, 60vh); overflow-y: auto; }

.tw-bell__item {
  display: grid; gap: .125rem;
  padding: .625rem .75rem;
  border-bottom: 1px solid color-mix(in srgb, var(--outline-variant, #333E4D) 45%, transparent);
  color: inherit; text-decoration: none;
}
a.tw-bell__item:hover { background: color-mix(in srgb, var(--tw-accent, #FF6B1F) 12%, transparent); }
.tw-bell__item.is-unread { border-left: 3px solid var(--tw-accent, #FF6B1F); }
.tw-bell__item-title { font-weight: 600; font-size: .875rem; }
.tw-bell__item-body  { font-size: .8125rem; color: var(--tw-text-dim, #93A1B5); }
.tw-bell__item-when  { font-size: .6875rem; color: var(--tw-text-dim, #93A1B5); }

.tw-bell__all {
  display: block; padding: .625rem .75rem; text-align: center;
  font-size: .875rem; font-weight: 600;
  border-top: 1px solid var(--outline-variant, #333E4D);
}

@media (max-width: 599.98px) {
  /* On a phone the panel is pinned to the viewport, not to a cramped button. */
  .tw-bell__panel { position: fixed; top: auto; right: .75rem; left: .75rem; width: auto; }
}
