@import url("https://fonts.googleapis.com/css2?family=Barlow:wght@400;500;600;700&family=Barlow+Condensed:wght@600;700&family=JetBrains+Mono:wght@500;700&display=swap");
/* shell/design/tokens.css */
/* =====================================================================
   OBDecode shell — DESIGN TOKENS
   =====================================================================
   Single source of truth for colour, type, space, radius, elevation and
   motion. Everything in app/shell, app/scanner, app/diag, app/perf reads
   these — no component may hard-code a colour literal or a px font size.

   WHY these constraints exist (see bugs/bug-journal.md 2026-09-17 entries):
   - Every token used anywhere is DEFINED here, in every theme block.
     `var(--x, fallback)` hides a missing token behind a plausible number;
     the contrast checker (contrast-check.mjs) fails the build if a pair
     references an undefined token.
   - Colours are plain hex (no color-mix(), no rgba()) so the checker can
     compute real WCAG ratios for each theme variant offline.
   - Nothing here shares a value with the old app (#38bdf8 / #f59e0b /
     #020409, Rajdhani / Share Tech Mono / Exo 2). This is a new product
     look: graphite instrument housing + high-visibility amber.

   THEME AXES (all attributes live on <html>):
     data-theme     = "night" (default) | "day"
     data-contrast  = "high"   (optional overlay on either theme)
     data-mode      = "drive"  (optional: bigger targets + type, fewer words)
   ===================================================================== */

:root {
  color-scheme: dark;

  /* ---------- Typography ---------- */
  /* Barlow: a wide industrial grotesk that reads like machined lettering
     on a tool housing. JetBrains Mono for values so digits align in columns. */
  --font-ui:   "Barlow", "Segoe UI Variable", "Segoe UI", system-ui, sans-serif;
  --font-cond: "Barlow Condensed", "Barlow", "Segoe UI", system-ui, sans-serif;
  --font-mono: "JetBrains Mono", "Cascadia Mono", "Roboto Mono", Consolas, monospace;

  /* Ian's rule: body 18, labels 16 caps, values 32–56. NOTHING below 16px. */
  --fs-caption: 16px;       /* smallest text on any screen */
  --fs-label:   16px;       /* uppercase, tracked labels */
  --fs-body:    18px;
  --fs-body-lg: 20px;
  --fs-h3:      22px;
  --fs-h2:      26px;
  --fs-h1:      32px;
  --fs-value-sm: 32px;      /* list-row values, chips */
  --fs-value:    40px;      /* tile / card values */
  --fs-value-lg: 56px;      /* hero values: orb %, gauge readout */
  --fs-value-xl: 72px;      /* in-dash / TV only */

  --lh-tight: 1.1;
  --lh-body:  1.4;
  --ls-label: 0.08em;       /* letter-spacing for caps labels */

  --fw-regular: 400;
  --fw-medium:  500;
  --fw-semibold: 600;
  --fw-bold:    700;

  /* ---------- Spacing (4px base) ---------- */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;
  --sp-7: 48px;
  --sp-8: 64px;
  --gutter: 16px;           /* page side gutter at phone width */

  /* ---------- Hit targets ---------- */
  --tap:      56px;         /* minimum interactive height/width */
  --tap-lg:   72px;         /* primary actions, footer hardware bar */
  --tile-min: 120px;        /* home tile minimum edge */

  /* ---------- Shape ---------- */
  /* Scan tools are squarer than consumer apps: bevels, not pills. */
  --r-1: 4px;
  --r-2: 8px;
  --r-3: 12px;
  --r-round: 999px;
  --bw: 1px;                /* hairline border */
  --bw-2: 2px;              /* emphasised border / focus ring */
  --bw-3: 3px;              /* status left-rail on rows */

  /* ---------- Layout ---------- */
  --statusbar-h: 56px;
  --footer-h:    72px;
  --content-max: 1280px;
  --sidebar-w:   0px;       /* set by the shell at ≥ 1024px */

  /* ---------- Motion ---------- */
  --dur-fast: 120ms;
  --dur-med:  220ms;
  --dur-slow: 400ms;
  --ease-out: cubic-bezier(.2,.7,.2,1);
  --ease-in-out: cubic-bezier(.4,0,.2,1);

  /* ---------- Z layers ---------- */
  --z-statusbar: 40;
  --z-footer:    40;
  --z-orb:       50;
  --z-dialog:    100;
  --z-toast:     110;

  /* ================= COLOUR — NIGHT (default) =================
     Graphite housing. Surfaces step up in lightness the closer they
     are to the user; amber is the single accent, used for the active
     state and primary action only. Semantic colours are reserved for
     vehicle status (red/amber/green/blue) and never decorate chrome. */

  /* Backgrounds / surfaces */
  --c-bg:        #101214;   /* page (deepest) */
  --c-surface-1: #181B1F;   /* panels, list backgrounds */
  --c-surface-2: #21252B;   /* tiles, cards, rows */
  --c-surface-3: #2B3038;   /* raised: chips, inputs, hovered rows */
  --c-surface-4: #353B45;   /* pressed / footer bar buttons */
  --c-line:      #3A414C;   /* hairline borders (decorative, no contrast requirement) */
  --c-line-strong: #727C8A; /* emphasised borders / icon strokes: ≥ 3:1 on bg..surface-2 */

  /* Text */
  --c-text:        #F2F4F7; /* primary   ≥ 7:1 on all surfaces */
  --c-text-2:      #C3C9D2; /* secondary ≥ 4.5:1 on all surfaces */
  --c-text-3:      #9AA3B0; /* tertiary  ≥ 4.5:1 on bg/surface-1/2 only */
  --c-text-inverse: #101214;

  /* Accent (high-vis amber) */
  --c-accent:        #FFB300; /* fills: buttons, active softkey, tile focus */
  --c-accent-strong: #FFD060; /* hover */
  --c-accent-text:   #FFC940; /* amber used AS text on dark surfaces (≥ 7:1) */
  --c-on-accent:     #1A1200; /* text on amber fills */

  /* Vehicle status — the traffic-light set + blue for "below low limit".
     On night, badge labels are DARK on the fill: a fill light enough to
     read as a 3:1 status indicator on graphite is too light for white text. */
  --c-ok:     #2FBF71;  --c-ok-text:   #5EE29A;  --c-on-ok:   #04130B;
  --c-warn:   #FFB300;  --c-warn-text: #FFC940;  --c-on-warn: #1A1200;
  --c-bad:    #F25C61;  --c-bad-text:  #FF8A8E;  --c-on-bad:  #1A0506;
  --c-low:    #5A96FF;  --c-low-text:  #8FB8FF;  --c-on-low:  #061024;
  --c-info:   #8B98AB;  --c-info-text: #B4BFCF;  --c-on-info: #0B0F14;

  /* Data-vis series (Live Data graphs) — distinct hue AND lightness */
  --c-series-1: #FFC233;
  --c-series-2: #5EE29A;
  --c-series-3: #8FB8FF;
  --c-series-4: #FF8A8E;
  --c-series-5: #E0A3FF;
  --c-series-6: #7FE7F0;

  /* Focus ring — always visible on both themes */
  --c-focus: #FFC940;

  /* Overlay / scrim behind dialogs */
  --c-scrim: rgba(0, 0, 0, 0.72);

  /* ---------- Elevation ---------- */
  --shadow-1: 0 1px 0 rgba(255,255,255,0.04) inset, 0 1px 2px rgba(0,0,0,0.5);
  --shadow-2: 0 1px 0 rgba(255,255,255,0.05) inset, 0 6px 16px rgba(0,0,0,0.55);
  --shadow-3: 0 12px 40px rgba(0,0,0,0.7);
  --glow-accent: 0 0 0 var(--bw-2) var(--c-accent);
}

/* ================= COLOUR — DAY =================
   Daylight-in-the-bay mode. Light grey housing, near-black text; the
   amber shifts darker so it still contrasts as text. Semantic fills
   stay saturated but the -text variants darken to hold 4.5:1 on white. */
:root[data-theme="day"] {
  color-scheme: light;

  --c-bg:        #E6E9EE;
  --c-surface-1: #F1F3F6;
  --c-surface-2: #FFFFFF;
  --c-surface-3: #F6F7F9;
  --c-surface-4: #DADFE6;
  --c-line:      #C2C8D1;
  --c-line-strong: #6F7986;

  --c-text:        #12161B;
  --c-text-2:      #3C4653;
  --c-text-3:      #56616F;
  --c-text-inverse: #F2F4F7;

  /* DECISION: pure high-vis amber (#FFB300) is physically incapable of 3:1
     against a light housing, so day-mode fills use a bronze amber with white
     labels. The bright amber survives only as --c-warn on a dark chip. */
  --c-accent:        #7A4E00;
  --c-accent-strong: #5E3B00;
  --c-accent-text:   #5E3B00;
  --c-on-accent:     #FFFFFF;

  --c-ok:     #157A45;  --c-ok-text:   #0E6A3A;  --c-on-ok:   #FFFFFF;
  --c-warn:   #FFB300;  --c-warn-text: #5E3B00;  --c-on-warn: #1A1200;
  --c-bad:    #C42328;  --c-bad-text:  #A81E23;  --c-on-bad:  #FFFFFF;
  --c-low:    #1F52C9;  --c-low-text:  #1D4ED8;  --c-on-low:  #FFFFFF;
  --c-info:   #5B6878;  --c-info-text: #46525F;  --c-on-info: #FFFFFF;

  --c-series-1: #8F5A00;
  --c-series-2: #0E6A3A;
  --c-series-3: #1D4ED8;
  --c-series-4: #A81E23;
  --c-series-5: #7A2AA6;
  --c-series-6: #0E7490;

  --c-focus: #1D4ED8;
  --c-scrim: rgba(16, 18, 20, 0.55);

  --shadow-1: 0 1px 2px rgba(16,18,20,0.12);
  --shadow-2: 0 6px 16px rgba(16,18,20,0.16);
  --shadow-3: 0 12px 40px rgba(16,18,20,0.28);
}

/* ================= HIGH CONTRAST overlays =================
   Pushes every text token to ≥ 7:1 and borders to full visibility.
   Applies on top of whichever theme is active. */
:root[data-contrast="high"] {
  --c-bg:        #000000;
  --c-surface-1: #0A0B0D;
  --c-surface-2: #141619;
  --c-surface-3: #1D2025;
  --c-surface-4: #2A2E35;
  --c-line:      #6B7482;
  --c-line-strong: #A5AEBB;

  --c-text:   #FFFFFF;
  --c-text-2: #E6EAF0;
  --c-text-3: #C9D0DA;

  --c-accent:      #FFC940;
  --c-accent-strong: #FFD060;
  --c-accent-text: #FFD060;
  --c-ok:        #5EE29A;
  --c-ok-text:   #7BF0AE;
  --c-warn:      #FFC940;
  --c-warn-text: #FFD060;
  --c-bad:       #FF7A7E;
  --c-bad-text:  #FFA3A6;
  --c-low:       #7FB0FF;
  --c-low-text:  #A9C8FF;
  --c-info:      #B4BFCF;
  --c-info-text: #D0D8E4;
  --c-focus: #FFFFFF;
}
:root[data-theme="day"][data-contrast="high"] {
  --c-bg:        #FFFFFF;
  --c-surface-1: #FFFFFF;
  --c-surface-2: #FFFFFF;
  --c-surface-3: #F4F5F7;
  --c-surface-4: #E1E4E9;
  --c-line:      #5B6472;
  --c-line-strong: #2C333D;

  --c-text:   #000000;
  --c-text-2: #1C2128;
  --c-text-3: #2F3741;

  --c-accent:      #5C3A00;
  --c-accent-strong: #3D2600;
  --c-accent-text: #5C3A00;
  --c-ok:        #0B5730;
  --c-ok-text:   #083F22;
  --c-warn-text: #5C3A00;
  --c-bad:       #8E181D;
  --c-bad-text:  #8E181D;
  --c-low:       #1A3FA8;
  --c-low-text:  #1A3FA8;
  --c-info:      #303A46;
  --c-info-text: #303A46;
  --c-series-1: #6B4400;
  --c-series-2: #0B5730;
  --c-series-3: #1A3FA8;
  --c-series-4: #8E181D;
  --c-series-5: #5E1F80;
  --c-series-6: #0B5566;
  --c-focus: #000000;
}

/* ================= DRIVE MODE =================
   Glanceable while moving: targets ≥ 76px, values one step larger,
   labels one step larger, tighter motion. Colours unchanged. */
:root[data-mode="drive"] {
  --tap:      76px;
  --tap-lg:   88px;
  --footer-h: 88px;
  --statusbar-h: 64px;
  --fs-label:   18px;
  --fs-body:    20px;
  --fs-body-lg: 22px;
  --fs-value-sm: 40px;
  --fs-value:    56px;
  --fs-value-lg: 72px;
  --dur-fast: 0ms;
  --dur-med:  0ms;
  --dur-slow: 0ms;
}

/* ================= LARGE DISPLAY (in-dash / TV, ≥ 1600px) =================
   Same tokens scaled; the grid does the rest. */
@media (min-width: 1600px) {
  :root {
    --fs-body:    20px;
    --fs-label:   18px;
    --fs-value:   48px;
    --fs-value-lg: 72px;
    --statusbar-h: 64px;
    --footer-h:    88px;
    --tap:         64px;
    --gutter:      32px;
  }
}

/* ================= PHONE (< 600px) =================
   The status bar wraps to two rows so vehicle AND voltage stay visible
   (voltage is a safety reading, never allowed to scroll off-screen).
   Listed with the drive-mode selector so its 64px cannot win here. */
@media (max-width: 599px) {
  :root, :root[data-mode="drive"] { --statusbar-h: 104px; }
}

/* ================= REDUCED MOTION ================= */
@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-fast: 0ms;
    --dur-med:  0ms;
    --dur-slow: 0ms;
  }
}

/* shell/design/components.css */
/* =====================================================================
   OBDecode shell — COMPONENTS
   =====================================================================
   Requires tokens.css. Rules:
   - No colour literals, no px font sizes: tokens only (contrast-check.mjs
     verifies the tokens; this file inherits that guarantee only if it
     never bypasses them).
   - Every interactive element is ≥ var(--tap) tall AND wide.
   - Focus is always visible (2px ring in --c-focus, offset 2px).
   - Status is never colour-alone: every red/amber/green carries a label
     or an icon as well (WCAG 1.4.1).
   - No inline event handlers anywhere; scripts use event delegation on
     data-action attributes (see prototypes/proto.js).
   ===================================================================== */

/* ---------- 0. Base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { height: 100%; -webkit-text-size-adjust: 100%; }
body {
  margin: 0; min-height: 100%;
  background: var(--c-bg); color: var(--c-text);
  font-family: var(--font-ui); font-size: var(--fs-body); line-height: var(--lh-body);
  font-weight: var(--fw-regular);
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
  overscroll-behavior: none;
  overflow-x: hidden;
}
h1, h2, h3, p { margin: 0; }
h1 { font: var(--fw-bold) var(--fs-h1)/var(--lh-tight) var(--font-cond); text-transform: uppercase; letter-spacing: 0.02em; }
h2 { font: var(--fw-semibold) var(--fs-h2)/var(--lh-tight) var(--font-cond); text-transform: uppercase; letter-spacing: 0.02em; }
h3 { font: var(--fw-semibold) var(--fs-h3)/var(--lh-tight) var(--font-ui); }
a { color: var(--c-accent-text); text-decoration-thickness: var(--bw-2); text-underline-offset: 3px; }
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }
svg { display: block; }
:focus-visible { outline: var(--bw-2) solid var(--c-focus); outline-offset: 2px; }
::selection { background: var(--c-accent); color: var(--c-on-accent); }
.sr-only { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* Typographic utilities */
.label { font-size: var(--fs-label); font-weight: var(--fw-semibold); text-transform: uppercase; letter-spacing: var(--ls-label); color: var(--c-text-2); line-height: var(--lh-tight); }
.label--strong { color: var(--c-text); }
.caption { font-size: var(--fs-caption); color: var(--c-text-3); }
.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.value { font: var(--fw-bold) var(--fs-value)/1 var(--font-mono); font-variant-numeric: tabular-nums; color: var(--c-text); letter-spacing: -0.02em; }
.value--sm { font-size: var(--fs-value-sm); }
.value--lg { font-size: var(--fs-value-lg); }
.value--xl { font-size: var(--fs-value-xl); }
.unit { font: var(--fw-medium) var(--fs-label)/1 var(--font-ui); color: var(--c-text-2); text-transform: uppercase; letter-spacing: var(--ls-label); margin-left: var(--sp-1); }
.t-ok   { color: var(--c-ok-text); }
.t-warn { color: var(--c-warn-text); }
.t-bad  { color: var(--c-bad-text); }
.t-low  { color: var(--c-low-text); }
.t-info { color: var(--c-info-text); }
.t-accent { color: var(--c-accent-text); }

/* ---------- 1. App frame ---------- */
/* statusbar / scrolling content / footer hardware bar. Both bars are
   position:fixed against the VIEWPORT (not sticky inside a scrollport —
   see bug journal 2026-09-17 on sticky offsets). */
.app { min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; }
.app__content {
  flex: 1; width: 100%; max-width: var(--content-max); margin: 0 auto;
  padding: calc(var(--statusbar-h) + var(--sp-4)) var(--gutter) calc(var(--footer-h) + var(--sp-5));
}
.app__content--flush { padding-left: 0; padding-right: 0; max-width: none; }

/* ---------- 2. Status bar ---------- */
.statusbar {
  position: fixed; inset: 0 0 auto 0; z-index: var(--z-statusbar);
  height: var(--statusbar-h);
  background: var(--c-surface-1); border-bottom: var(--bw) solid var(--c-line);
  display: flex; align-items: stretch; gap: var(--sp-2);
  padding: 0 var(--sp-3);
  overflow-x: auto; scrollbar-width: none;
}
.statusbar::-webkit-scrollbar { display: none; }
/* Base (>=600px) rules for the three bar regions. These MUST come before the <600px block below:
   they have the same specificity, so a base rule written after the media query silently wins over it
   (that is what made the base "flex: 0 0 auto" of .statusbar__chips cancel its phone "flex: 1 0 100%"
   wrap and collapse the vehicle name — INTEGRATION-TODO D4, fixed here at the source). */
.statusbar__brand {
  display: flex; align-items: center; gap: var(--sp-2); flex: 0 0 auto;
  font: var(--fw-bold) var(--fs-h3)/1 var(--font-cond); letter-spacing: 0.04em;
  padding-right: var(--sp-3); border-right: var(--bw) solid var(--c-line); margin-right: var(--sp-1);
}
.statusbar__brand b { color: var(--c-accent-text); }
.statusbar__vehicle { display: flex; align-items: center; min-width: 0; flex: 1 1 0; gap: var(--sp-2); } /* basis 0: shrink + ellipsis, never wrap to its own row */
.statusbar__vehicle .label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--c-text); }
.statusbar__chips { display: flex; align-items: center; gap: var(--sp-2); flex: 0 0 auto; margin-left: auto; }

/* Phones: row 1 = brand + vehicle (+ any orb controls the host adds), row 2 = the chips.
   The bar is "height: auto" with the token as its MINIMUM, so a host that adds controls to row 1 (the
   scanner's orb dock and switch) grows the bar instead of clipping the last row. Anything that must
   sit below a grown bar reads --statusbar-real, which a host may set from the measured height
   (falls back to the token, i.e. the un-grown size). Row 2 shows adapter, PROTOCOL and battery — the
   three a mechanic needs to see at a glance. The plan-tier chip is dropped on phones to make the room
   (it is shown from 600px up and in Settings); the protocol was hidden here before and simply vanished
   on a phone. */
@media (max-width: 599px) {
  .statusbar { flex-wrap: wrap; align-content: flex-start; overflow: visible; padding: 0 var(--sp-3); height: auto; min-height: var(--statusbar-h); }
  .statusbar__brand { border-right: 0; height: 48px; }
  .statusbar__vehicle { height: 48px; justify-content: flex-end; }
  .statusbar__chips { flex: 1 0 100%; max-width: 100%; min-width: 0; height: auto; min-height: 56px; margin-left: 0; justify-content: space-between; padding-bottom: var(--sp-2); }
  .statusbar__chips .chip { flex: 1 1 auto; justify-content: center; padding: 0 var(--sp-2); gap: var(--sp-1); min-width: 0; }
  .statusbar__chips .chip > span:not(.icon) { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .statusbar__chips .chip:has(> .mono) { flex: 0 0 auto; } /* a numeric reading (battery volts) is never the one that gets ellipsised */
  .statusbar__chips .chip--tier { display: none; }
  .statusbar__chips .chip--protocol svg { display: none; } /* text alone says it; the icon was the room a full "CAN 11/500" needed */
  .app__content { padding-top: calc(var(--statusbar-real, var(--statusbar-h)) + var(--sp-4)); }
}

/* Status chip: icon + value; the colour rail says state, the text says it too */
.chip {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  min-height: 40px; padding: 0 var(--sp-3);
  background: var(--c-surface-3); border: var(--bw) solid var(--c-line); border-radius: var(--r-1);
  font-size: var(--fs-label); font-weight: var(--fw-semibold); letter-spacing: 0.02em; white-space: nowrap;
  color: var(--c-text);
}
.chip svg { width: 20px; height: 20px; flex: 0 0 auto; }
.chip .mono { font-weight: var(--fw-bold); }
.chip--ok   { border-color: var(--c-ok);   color: var(--c-ok-text); }
.chip--warn { border-color: var(--c-warn); color: var(--c-warn-text); }
.chip--bad  { border-color: var(--c-bad);  color: var(--c-bad-text); }
.chip--low  { border-color: var(--c-low);  color: var(--c-low-text); }
.chip--tier { background: var(--c-text); color: var(--c-text-inverse); border-color: var(--c-text); }
.chip--btn { cursor: pointer; min-height: var(--tap); min-width: var(--tap); justify-content: center; }
.chip--btn:hover { background: var(--c-surface-4); }

/* ---------- 3. Footer hardware-button bar ---------- */
/* Fixed row of equal "hard keys": BACK · HOME · SNAP · REC (+ context slot).
   Bevelled like a housing key, big label under an icon. */
.hwbar {
  position: fixed; inset: auto 0 0 0; z-index: var(--z-footer);
  height: var(--footer-h);
  background: var(--c-surface-1); border-top: var(--bw) solid var(--c-line);
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  gap: var(--sp-2); padding: var(--sp-2) var(--sp-3);
  padding-bottom: calc(var(--sp-2) + env(safe-area-inset-bottom, 0px));
}
.hwkey {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  min-height: calc(var(--footer-h) - var(--sp-2) * 2); min-width: var(--tap);
  background: var(--c-surface-3); border: var(--bw) solid var(--c-line); border-bottom-width: var(--bw-3); border-radius: var(--r-2);
  color: var(--c-text); box-shadow: var(--shadow-1); text-decoration: none;
  font: var(--fw-bold) var(--fs-label)/1 var(--font-cond); letter-spacing: var(--ls-label); text-transform: uppercase;
  transition: background var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.hwkey svg { width: 26px; height: 26px; }
.hwkey:hover { background: var(--c-surface-4); }
.hwkey:active { transform: translateY(1px); border-bottom-width: var(--bw); }
.hwkey[aria-disabled="true"] { opacity: .45; pointer-events: none; }
.hwkey--primary { background: var(--c-accent); color: var(--c-on-accent); border-color: var(--c-accent-strong); }
.hwkey--primary:hover { background: var(--c-accent-strong); }
.hwkey--rec[aria-pressed="true"] { background: var(--c-bad); color: var(--c-on-bad); border-color: var(--c-bad); }
.hwkey--rec[aria-pressed="true"] .rec-dot { animation: blink 1s steps(2) infinite; }
@keyframes blink { to { opacity: 0.25; } }

/* ---------- 4. Home tile grid ---------- */
.tile-grid {
  display: grid; gap: var(--sp-3);
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (min-width: 600px)  { .tile-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 900px)  { .tile-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-4); } .tile { aspect-ratio: auto; min-height: 168px; } .tile__icon { width: 52px; height: 52px; } }
@media (min-width: 1200px) { .tile-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); } }
.tile {
  position: relative; display: flex; flex-direction: column; justify-content: space-between;
  min-height: var(--tile-min); aspect-ratio: 1 / 0.85; padding: var(--sp-3);
  background: var(--c-surface-2); border: var(--bw) solid var(--c-line); border-radius: var(--r-3);
  color: var(--c-text); text-align: left; text-decoration: none; box-shadow: var(--shadow-1);
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.tile:hover { background: var(--c-surface-3); border-color: var(--c-line-strong); }
.tile:active { background: var(--c-surface-4); }
.tile__icon { width: 44px; height: 44px; color: var(--c-accent-text); }
.tile__icon svg { width: 100%; height: 100%; }
.tile__label { display: block; font: var(--fw-bold) var(--fs-h3)/1.05 var(--font-cond); text-transform: uppercase; letter-spacing: 0.04em; }
.tile__meta { display: block; font-size: var(--fs-caption); color: var(--c-text-2); margin-top: var(--sp-1); }
.tile__badge { position: absolute; top: var(--sp-3); right: var(--sp-3); }
.tile--primary { background: var(--c-accent); border-color: var(--c-accent-strong); color: var(--c-on-accent); }
.tile--primary .tile__icon, .tile--primary .tile__meta { color: var(--c-on-accent); }
.tile--primary:hover { background: var(--c-accent-strong); }
.tile--wide { grid-column: span 2; aspect-ratio: auto; min-height: var(--tile-min); }
.tile[aria-disabled="true"] { opacity: .5; pointer-events: none; }

/* ---------- 5. Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  min-height: var(--tap); min-width: var(--tap); padding: 0 var(--sp-5);
  border-radius: var(--r-2); border: var(--bw) solid var(--c-line-strong);
  background: var(--c-surface-3); color: var(--c-text);
  font: var(--fw-bold) var(--fs-label)/1 var(--font-cond); text-transform: uppercase; letter-spacing: var(--ls-label);
  text-decoration: none; white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out);
}
.btn svg { width: 22px; height: 22px; }
.btn:hover { background: var(--c-surface-4); }
.btn--primary { background: var(--c-accent); color: var(--c-on-accent); border-color: var(--c-accent); }
.btn--primary:hover { background: var(--c-accent-strong); border-color: var(--c-accent-strong); }
.btn--danger { background: var(--c-bad); color: var(--c-on-bad); border-color: var(--c-bad); }
.btn--ghost { background: transparent; border-color: var(--c-line-strong); }
.btn--ghost:hover { background: var(--c-surface-3); }
.btn--lg { min-height: var(--tap-lg); font-size: var(--fs-h3); padding: 0 var(--sp-6); }
.btn--block { width: 100%; }
.btn--icon { padding: 0; width: var(--tap); }
.btn[disabled], .btn[aria-disabled="true"] { opacity: .45; cursor: not-allowed; }
.btn-row { display: flex; gap: var(--sp-3); flex-wrap: wrap; }
.btn-row > .btn { flex: 1 1 auto; }

/* ---------- 6. Badges (status pills) ---------- */
.badge {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  min-height: 28px; padding: 0 var(--sp-2); border-radius: var(--r-1);
  font: var(--fw-bold) var(--fs-caption)/1 var(--font-cond); text-transform: uppercase; letter-spacing: 0.06em;
  background: var(--c-info); color: var(--c-on-info);
}
.badge--ok   { background: var(--c-ok);   color: var(--c-on-ok); }
.badge--warn { background: var(--c-warn); color: var(--c-on-warn); }
.badge--bad  { background: var(--c-bad);  color: var(--c-on-bad); }
.badge--low  { background: var(--c-low);  color: var(--c-on-low); }
.badge--accent { background: var(--c-accent); color: var(--c-on-accent); }
.badge--outline { background: transparent; border: var(--bw) solid var(--c-line-strong); color: var(--c-text-2); }
.badge--count { min-width: 36px; justify-content: center; font-family: var(--font-mono); font-size: var(--fs-label); }
/* Source badge for the identity card: ECU / NHTSA / USER */
.badge--src { background: var(--c-surface-4); color: var(--c-text-2); border: var(--bw) solid var(--c-line); }
.badge--src-ecu { color: var(--c-ok-text); border-color: var(--c-ok); }
.badge--src-nhtsa { color: var(--c-low-text); border-color: var(--c-low); }
.badge--src-user { color: var(--c-accent-text); border-color: var(--c-accent); }

/* ---------- 7. Cards & panels ---------- */
.card {
  min-width: 0;
  background: var(--c-surface-2); border: var(--bw) solid var(--c-line); border-radius: var(--r-3);
  padding: var(--sp-4); box-shadow: var(--shadow-1);
}
.card + .card, .stack > * + * { margin-top: var(--sp-4); }
.card__head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--sp-3); margin-bottom: var(--sp-3); }
.card__head h2, .card__head h3 { flex: 1 1 auto; min-width: 0; }
.card--status { border-left: var(--bw-3) solid var(--c-info); }
.card--ok   { border-left-color: var(--c-ok); }
.card--warn { border-left-color: var(--c-warn); }
.card--bad  { border-left-color: var(--c-bad); }
.card--accent { border-color: var(--c-accent); }
.card--flat { box-shadow: none; background: var(--c-surface-1); }
.grid-2 { display: grid; gap: var(--sp-4); grid-template-columns: minmax(0, 1fr); }
.grid-3 { display: grid; gap: var(--sp-4); grid-template-columns: minmax(0, 1fr); }
@media (min-width: 760px) { .grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } .grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

/* Collapsible panel (SCAN DATA on the diag workspace) */
.panel { background: var(--c-surface-1); border: var(--bw) solid var(--c-line); border-radius: var(--r-3); overflow: hidden; }
.panel__head {
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  min-height: var(--tap); padding: var(--sp-2) var(--sp-4);
  background: var(--c-surface-2);
}
.panel__title { display: flex; align-items: center; gap: var(--sp-2); font: var(--fw-bold) var(--fs-label)/1 var(--font-cond); letter-spacing: var(--ls-label); text-transform: uppercase; }
.panel__summary { display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: center; flex: 1 1 200px; color: var(--c-text-2); font-size: var(--fs-caption); }
.panel__summary .dot { width: 6px; height: 6px; border-radius: var(--r-round); background: var(--c-line-strong); }
.panel__actions { display: flex; gap: var(--sp-2); margin-left: auto; }
.panel__body { padding: var(--sp-4); border-top: var(--bw) solid var(--c-line); }
.panel[data-collapsed="true"] .panel__body { display: none; }

/* Key/value strip */
.kv { display: grid; grid-template-columns: max-content 1fr; gap: var(--sp-2) var(--sp-4); align-items: baseline; }
.kv dt { font-size: var(--fs-label); text-transform: uppercase; letter-spacing: var(--ls-label); color: var(--c-text-2); font-weight: var(--fw-semibold); }
.kv dd { margin: 0; font-weight: var(--fw-medium); min-width: 0; overflow-wrap: anywhere; }

/* Stat tile: label over value (dashboard key stats) */
.stat { background: var(--c-surface-2); border: var(--bw) solid var(--c-line); border-radius: var(--r-2); padding: var(--sp-3) var(--sp-4); min-height: 104px; display: flex; flex-direction: column; justify-content: space-between; gap: var(--sp-1); }
.stat__label { font-size: var(--fs-label); text-transform: uppercase; letter-spacing: var(--ls-label); color: var(--c-text-2); font-weight: var(--fw-semibold); display: flex; justify-content: space-between; gap: var(--sp-2); }
.stat__value { display: flex; align-items: baseline; }
.stat--ok .stat__value   { color: var(--c-ok-text); }
.stat--warn .stat__value { color: var(--c-warn-text); }
.stat--bad .stat__value  { color: var(--c-bad-text); }
.stat--low .stat__value  { color: var(--c-low-text); }
.stat__value .value { color: inherit; }
.stat-grid { display: grid; gap: var(--sp-3); grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 900px) { .stat-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* ---------- 8. List rows ---------- */
/* Rows carry a left status rail + a text status; the whole row is a
   ≥ tap-height button when actionable. */
.list { list-style: none; margin: 0; padding: 0; background: var(--c-surface-1); border: var(--bw) solid var(--c-line); border-radius: var(--r-3); overflow: hidden; }
.row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: var(--sp-3);
  min-height: var(--tap-lg); padding: var(--sp-3) var(--sp-4) var(--sp-3) var(--sp-3);
  border-left: var(--bw-3) solid var(--c-info); border-bottom: var(--bw) solid var(--c-line);
  background: var(--c-surface-2); color: var(--c-text); text-align: left; width: 100%;
}
.row:last-child { border-bottom: 0; }
.row[role="button"], .row.is-link { cursor: pointer; }
.row[role="button"]:hover, .row.is-link:hover { background: var(--c-surface-3); }
.row[aria-selected="true"] { background: var(--c-surface-3); box-shadow: inset 0 0 0 var(--bw-2) var(--c-accent); }
.row--ok   { border-left-color: var(--c-ok); }
.row--warn { border-left-color: var(--c-warn); }
.row--bad  { border-left-color: var(--c-bad); }
.row--low  { border-left-color: var(--c-low); }
.row__lead { display: flex; align-items: center; justify-content: center; min-width: 44px; color: var(--c-text-2); }
.row__lead svg { width: 28px; height: 28px; }
.row__code { font: var(--fw-bold) var(--fs-value-sm)/1 var(--font-mono); letter-spacing: -0.01em; min-width: 5.2ch; }
.row__main { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.row__title { font-weight: var(--fw-semibold); font-size: var(--fs-body); overflow: hidden; text-overflow: ellipsis; }
.row__sub { font-size: var(--fs-caption); color: var(--c-text-2); display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: center; }
.row__trail { display: flex; align-items: center; gap: var(--sp-2); justify-self: end; }
.row__trail svg { width: 24px; height: 24px; color: var(--c-text-3); }
/* Live-data list row: name · value · unit · sparkline */
.row--pid { grid-template-columns: minmax(0, 1fr) auto; }
.row--pid .row__value { display: flex; align-items: baseline; gap: var(--sp-1); }
.row--pid .row__range { font-size: var(--fs-caption); color: var(--c-text-3); font-family: var(--font-mono); }

@media (max-width: 599px) {
  .row--pid { grid-template-columns: 1fr; }
  .row--pid .row__trail { justify-self: stretch; justify-content: space-between; }
  .row--pid .sparkline { display: none; }
}

/* ---------- 9. Softkeys (tabs styled as scan-tool function keys) ---------- */
.softkeys { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(max-content, 1fr); gap: var(--sp-2); overflow-x: auto; scrollbar-width: thin; max-width: 100%; background: var(--c-surface-1); padding: var(--sp-2); border: var(--bw) solid var(--c-line); border-radius: var(--r-2); }
.softkey {
  text-decoration: none;
  min-height: var(--tap); padding: 0 var(--sp-3);
  background: var(--c-surface-3); border: var(--bw) solid var(--c-line); border-radius: var(--r-1);
  color: var(--c-text-2); font: var(--fw-bold) var(--fs-label)/1 var(--font-cond); text-transform: uppercase; letter-spacing: var(--ls-label);
  display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
}
.softkey svg { width: 22px; height: 22px; }
.softkey:hover { background: var(--c-surface-4); color: var(--c-text); }
.softkey[aria-selected="true"] { background: var(--c-surface-2); color: var(--c-accent-text); border-color: var(--c-accent); box-shadow: inset 0 var(--bw-3) 0 var(--c-accent); }
.softkey .badge { margin-left: var(--sp-1); }

/* Segmented control (LIST / GRAPH / GAUGE) */
.seg { display: inline-grid; grid-auto-flow: column; grid-auto-columns: 1fr; border: var(--bw) solid var(--c-line-strong); border-radius: var(--r-2); overflow: hidden; }
.seg__btn { min-height: var(--tap); min-width: var(--tap-lg); padding: 0 var(--sp-3); background: var(--c-surface-2); color: var(--c-text-2); border: 0; border-right: var(--bw) solid var(--c-line-strong); font: var(--fw-bold) var(--fs-label)/1 var(--font-cond); text-transform: uppercase; letter-spacing: var(--ls-label); }
.seg__btn:last-child { border-right: 0; }
.seg__btn[aria-pressed="true"] { background: var(--c-accent); color: var(--c-on-accent); }

/* ---------- 10. Inputs ---------- */
.field { display: flex; flex-direction: column; gap: var(--sp-2); }
.field__label { font-size: var(--fs-label); font-weight: var(--fw-semibold); text-transform: uppercase; letter-spacing: var(--ls-label); color: var(--c-text-2); display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); }
.input, .select, .textarea {
  width: 100%; min-height: var(--tap); padding: var(--sp-2) var(--sp-3);
  background: var(--c-surface-3); color: var(--c-text);
  border: var(--bw) solid var(--c-line-strong); border-radius: var(--r-2);
  font-size: var(--fs-body);
}
.input:hover, .select:hover, .textarea:hover { border-color: var(--c-text-3); }
.input:focus-visible, .select:focus-visible, .textarea:focus-visible { border-color: var(--c-focus); outline-offset: 0; }
.input::placeholder, .textarea::placeholder { color: var(--c-text-3); }
.input.mono { font-family: var(--font-mono); text-transform: uppercase; letter-spacing: 0.04em; }
.textarea { min-height: calc(var(--tap) * 2.2); resize: vertical; line-height: var(--lh-body); }
.select { appearance: none; padding-right: var(--sp-7); background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%); background-position: calc(100% - 22px) 50%, calc(100% - 14px) 50%; background-size: 8px 8px; background-repeat: no-repeat; }
.input[aria-invalid="true"] { border-color: var(--c-bad); }
.field__hint { font-size: var(--fs-caption); color: var(--c-text-3); }
.field__hint--bad { color: var(--c-bad-text); }
.input-row { display: flex; gap: var(--sp-2); }
.input-row .input { flex: 1 1 auto; min-width: 0; }
/* Toggle switch */
.switch { display: inline-flex; align-items: center; gap: var(--sp-3); min-height: var(--tap); cursor: pointer; }
.switch__track { width: 64px; height: 36px; border-radius: var(--r-round); background: var(--c-surface-4); border: var(--bw) solid var(--c-line-strong); position: relative; transition: background var(--dur-fast); flex: 0 0 auto; }
.switch__track::after { content: ""; position: absolute; top: 3px; left: 3px; width: 28px; height: 28px; border-radius: var(--r-round); background: var(--c-text-2); transition: transform var(--dur-fast) var(--ease-out), background var(--dur-fast); }
.switch input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.switch input:checked + .switch__track { background: var(--c-accent); border-color: var(--c-accent); }
.switch input:checked + .switch__track::after { transform: translateX(28px); background: var(--c-on-accent); }
.switch input:focus-visible + .switch__track { outline: var(--bw-2) solid var(--c-focus); outline-offset: 2px; }

/* ---------- 11. Tables ---------- */
.table-wrap { overflow-x: auto; border: var(--bw) solid var(--c-line); border-radius: var(--r-2); background: var(--c-surface-1); }
.table { width: 100%; border-collapse: collapse; font-size: var(--fs-body); }
.table th, .table td { padding: var(--sp-3) var(--sp-4); text-align: left; border-bottom: var(--bw) solid var(--c-line); vertical-align: middle; min-height: var(--tap); }
.table th { font-size: var(--fs-label); text-transform: uppercase; letter-spacing: var(--ls-label); color: var(--c-text-2); background: var(--c-surface-2); font-weight: var(--fw-semibold); position: sticky; top: 0; }
.table tr:last-child td { border-bottom: 0; }
.table td.num, .table th.num { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.table td .input { min-height: 48px; }
.table tr[data-quality="suspect"] td { color: var(--c-warn-text); }
.table tr[data-quality="invalid"] td { color: var(--c-bad-text); text-decoration: line-through; }

/* ---------- 12. Gauge (SVG arc) & graph ---------- */
.gauge { display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); padding: var(--sp-3); background: var(--c-surface-2); border: var(--bw) solid var(--c-line); border-radius: var(--r-3); }
.gauge__svg { width: 100%; max-width: 280px; height: auto; }
.gauge__track { fill: none; stroke: var(--c-surface-4); stroke-width: 14; stroke-linecap: butt; }
.gauge__ticks { stroke: var(--c-line-strong); stroke-width: 2; }
.gauge__arc { fill: none; stroke: var(--c-ok); stroke-width: 14; stroke-linecap: butt; transition: stroke-dashoffset var(--dur-med) var(--ease-out), stroke var(--dur-med); }
.gauge__arc--warn { stroke: var(--c-warn); }
.gauge__arc--bad  { stroke: var(--c-bad); }
.gauge__arc--low  { stroke: var(--c-low); }
.gauge__needle { stroke: var(--c-text); stroke-width: 3; stroke-linecap: round; transition: transform var(--dur-med) var(--ease-out); transform-origin: 100px 100px; }
.gauge__readout { fill: var(--c-text); font: var(--fw-bold) 44px var(--font-mono); text-anchor: middle; }
.gauge__unit { fill: var(--c-text-2); font: var(--fw-semibold) 22px var(--font-cond); text-anchor: middle; letter-spacing: 0.08em; text-transform: uppercase; }
.gauge__bounds { fill: var(--c-text-2); font: var(--fw-medium) 22px var(--font-mono); }
.gauge__name { font-size: var(--fs-label); text-transform: uppercase; letter-spacing: var(--ls-label); color: var(--c-text-2); font-weight: var(--fw-semibold); text-align: center; }
.gauge-grid { display: grid; gap: var(--sp-3); grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 900px) { .gauge-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.graph { background: var(--c-surface-2); border: var(--bw) solid var(--c-line); border-radius: var(--r-3); padding: var(--sp-3); }
.graph__svg { width: 100%; height: auto; display: block; }
.graph__grid { stroke: var(--c-line); stroke-width: 1; }
.graph__axis { fill: var(--c-text-3); font: var(--fw-medium) 16px var(--font-mono); }
.graph__line { fill: none; stroke-width: 3; stroke-linejoin: round; stroke-linecap: round; }
.graph__line--1 { stroke: var(--c-series-1); } .graph__line--2 { stroke: var(--c-series-2); }
.graph__line--3 { stroke: var(--c-series-3); } .graph__line--4 { stroke: var(--c-series-4); }
.graph__legend { display: flex; gap: var(--sp-4); flex-wrap: wrap; margin-top: var(--sp-2); font-size: var(--fs-caption); }
.graph__legend li { display: flex; align-items: center; gap: var(--sp-2); list-style: none; }
.graph__legend .swatch { width: 18px; height: 4px; border-radius: 2px; }
.swatch--1 { background: var(--c-series-1); } .swatch--2 { background: var(--c-series-2); }
.swatch--3 { background: var(--c-series-3); } .swatch--4 { background: var(--c-series-4); }
.graph__legend .mono { color: var(--c-text); font-weight: var(--fw-bold); }
.sparkline { width: 84px; height: 32px; }
.sparkline path { fill: none; stroke: var(--c-line-strong); stroke-width: 2; }

/* ---------- 13. Health Orb ---------- */
/* The orb is a canvas/SVG circle whose fill colour is driven by the
   health engine. It carries the % as text INSIDE so colour is never the
   only channel. `.orb--flash` runs only when the engine says max-alert;
   reduced-motion removes the animation and keeps the steady colour. */
.orb-wrap { display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); }
.orb { position: relative; width: var(--orb-size, 200px); height: var(--orb-size, 200px); border-radius: var(--r-round); display: grid; place-items: center; }
.orb__ring { position: absolute; inset: 0; border-radius: var(--r-round); border: var(--bw-3) solid var(--c-line); }
.orb__core { position: absolute; inset: 10px; border-radius: var(--r-round); background: var(--orb-color, var(--c-ok)); box-shadow: 0 0 0 6px var(--c-surface-1), 0 0 48px var(--orb-color, var(--c-ok)); transition: background var(--dur-slow) var(--ease-in-out), box-shadow var(--dur-slow) var(--ease-in-out); }
.orb__pct { position: relative; font: var(--fw-bold) var(--fs-value-lg)/1 var(--font-mono); letter-spacing: -0.03em; color: var(--orb-text, var(--c-on-ok)); }
.orb__pct small { font: var(--fw-bold) var(--fs-h3)/1 var(--font-cond); margin-left: 2px; }
.orb__label { position: relative; font: var(--fw-bold) var(--fs-label)/1 var(--font-cond); letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--orb-text, var(--c-on-ok)); margin-top: var(--sp-1); }
.orb__inner { position: relative; display: flex; flex-direction: column; align-items: center; }
.orb--flash .orb__core { animation: orbflash 0.6s steps(2) infinite; }
@keyframes orbflash { to { filter: brightness(1.6); } }
@media (prefers-reduced-motion: reduce) { .orb--flash .orb__core { animation: none; } }
.orb--sm { --orb-size: 120px; } .orb--sm .orb__pct { font-size: var(--fs-value-sm); }
.orb--lg { --orb-size: 260px; }
.orb__status { font-size: var(--fs-body-lg); font-weight: var(--fw-semibold); text-align: center; }
.orb__contrib { display: flex; gap: var(--sp-2); flex-wrap: wrap; justify-content: center; }

/* ---------- 14. Dialog ---------- */
.dialog { border: var(--bw) solid var(--c-line-strong); border-radius: var(--r-3); background: var(--c-surface-2); color: var(--c-text); padding: 0; max-width: min(92vw, 560px); width: 100%; box-shadow: var(--shadow-3); }
.dialog::backdrop { background: var(--c-scrim); }
.dialog__head { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-4) var(--sp-5); border-bottom: var(--bw) solid var(--c-line); }
.dialog__head svg { width: 32px; height: 32px; }
.dialog--danger .dialog__head { color: var(--c-bad-text); }
.dialog__body { padding: var(--sp-5); font-size: var(--fs-body-lg); }
.dialog__foot { display: flex; gap: var(--sp-3); padding: var(--sp-4) var(--sp-5); border-top: var(--bw) solid var(--c-line); }
.dialog__foot .btn { flex: 1 1 0; }

/* ---------- 15. Toast ---------- */
.toast-region { position: fixed; left: 50%; bottom: calc(var(--footer-h) + var(--sp-4)); transform: translateX(-50%); z-index: var(--z-toast); display: flex; flex-direction: column; gap: var(--sp-2); width: min(92vw, 520px); pointer-events: none; }
.toast { pointer-events: auto; display: flex; align-items: center; gap: var(--sp-3); min-height: var(--tap); padding: var(--sp-3) var(--sp-4); background: var(--c-surface-3); border: var(--bw) solid var(--c-line-strong); border-left: var(--bw-3) solid var(--c-info); border-radius: var(--r-2); box-shadow: var(--shadow-2); font-weight: var(--fw-medium); }
.toast svg { width: 24px; height: 24px; flex: 0 0 auto; }
.toast--ok { border-left-color: var(--c-ok); } .toast--ok svg { color: var(--c-ok-text); }
.toast--warn { border-left-color: var(--c-warn); } .toast--warn svg { color: var(--c-warn-text); }
.toast--bad { border-left-color: var(--c-bad); } .toast--bad svg { color: var(--c-bad-text); }
.toast .btn { margin-left: auto; min-height: 44px; }

/* ---------- 16. Master-detail (ECU Select) ---------- */
.master-detail { display: grid; gap: var(--sp-4); grid-template-columns: minmax(0, 1fr); }
@media (min-width: 900px) { .master-detail { grid-template-columns: minmax(300px, 2fr) minmax(0, 3fr); align-items: start; } .master-detail__master { position: sticky; top: calc(var(--statusbar-h) + var(--sp-4)); } }
.detail-actions { display: grid; gap: var(--sp-3); grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 600px) { .detail-actions { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.detail-actions .btn { min-height: var(--tap-lg); flex-direction: column; gap: var(--sp-1); white-space: normal; text-align: center; padding: var(--sp-2); }
.detail-actions .btn--write { border-color: var(--c-warn); }
.detail-actions .btn--write .caption { color: var(--c-warn-text); text-transform: none; letter-spacing: 0; font-family: var(--font-ui); font-weight: var(--fw-medium); }

/* ---------- 17. Media tray & misc ---------- */
.media-tray { display: grid; gap: var(--sp-3); grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
.media-tray__add { min-height: 112px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--sp-2); border: var(--bw-2) dashed var(--c-line-strong); border-radius: var(--r-2); background: var(--c-surface-1); color: var(--c-text-2); font: var(--fw-bold) var(--fs-label)/1 var(--font-cond); text-transform: uppercase; letter-spacing: var(--ls-label); }
.media-tray__add svg { width: 30px; height: 30px; color: var(--c-accent-text); }
.media-tray__add:hover { background: var(--c-surface-3); color: var(--c-text); }
.media-tray__item { position: relative; min-height: 112px; border-radius: var(--r-2); overflow: hidden; border: var(--bw) solid var(--c-line); background: var(--c-surface-4); display: flex; align-items: flex-end; }
.media-tray__item .badge { position: absolute; top: var(--sp-2); left: var(--sp-2); }
.media-tray__item .caption { padding: var(--sp-2); background: var(--c-surface-2); width: 100%; color: var(--c-text-2); }
.media-tray__item svg { position: absolute; inset: 0; margin: auto; width: 40px; height: 40px; color: var(--c-text-3); }

.page-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-4); }
.page-head__title { display: flex; align-items: center; gap: var(--sp-3); }
.page-head__title svg { width: 32px; height: 32px; color: var(--c-accent-text); }
.page-head__actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.divider { border: 0; border-top: var(--bw) solid var(--c-line); margin: var(--sp-4) 0; }
.pill-row { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.ranked { counter-reset: rank; list-style: none; margin: 0; padding: 0; }
.ranked > li { counter-increment: rank; display: grid; grid-template-columns: auto 1fr auto; gap: var(--sp-3); align-items: start; padding: var(--sp-3) 0; border-bottom: var(--bw) solid var(--c-line); }
.ranked > li:last-child { border-bottom: 0; }
.ranked > li::before { content: counter(rank); font: var(--fw-bold) var(--fs-value-sm)/1 var(--font-mono); color: var(--c-accent-text); min-width: 2ch; }
.meter { height: 10px; border-radius: var(--r-round); background: var(--c-surface-4); overflow: hidden; margin-top: var(--sp-2); }
.meter > span { display: block; height: 100%; background: var(--c-accent); }
.step-list { margin: 0; padding-left: 1.4em; display: flex; flex-direction: column; gap: var(--sp-2); }
.empty { padding: var(--sp-6) var(--sp-4); text-align: center; color: var(--c-text-2); }
.empty svg { width: 48px; height: 48px; margin: 0 auto var(--sp-3); color: var(--c-text-3); }

/* Two-pane page for laptop: left content, right rail (orb / stats) */
/* minmax(0,1fr) everywhere: a plain 1fr is minmax(auto,1fr) and lets one unbreakable
   value (a VIN in 20px mono) widen the whole layout past the viewport. */
.split { display: grid; gap: var(--sp-4); grid-template-columns: minmax(0, 1fr); }
.split > * { min-width: 0; }
@media (min-width: 1024px) { .split { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); align-items: start; } }
@media (max-width: 1023px) { .split__aside { order: -1; } } /* the orb leads on a phone */

/* health/health.css */
/* =====================================================================
   OBDecode Health Orb — renderer + editor styles (S8)
   =====================================================================
   Requires the shell tokens (app/shell/design/tokens.css). Every font
   size, spacing and chrome colour is a token; the ONLY literal colours
   are the per-orb disc colour and its text colour, which arrive from the
   evaluator as CSS custom properties (--horb-color / --horb-text) because
   a profile may carry any hex the user or the AI chose. Both are chosen
   for ≥ 4.5:1 by renderer.textOn().

   Status is never colour-alone: glyph + state word + ring pattern.
   ===================================================================== */

/* ---------- the one fixed layer ---------- */
.orb-layer {
  position: fixed; inset: 0; z-index: var(--z-orb, 50);
  pointer-events: none;            /* the app underneath stays fully usable */
  contain: layout style;           /* orb repaints never invalidate page layout */
}
.orb-layer[data-interactive="true"] .horb { pointer-events: auto; cursor: grab; touch-action: none; }
.orb-layer[data-interactive="true"] .horb.horb--dragging { cursor: grabbing; }
.orb-layer__live { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* ---------- one orb ---------- */
.horb {
  --horb-size: 96px;
  --horb-ring: 4px;
  position: absolute; left: 0; top: 0;
  width: var(--horb-size); height: var(--horb-size);
  transform: translate3d(var(--horb-x, 0px), var(--horb-y, 0px), 0);
  z-index: var(--horb-z, 1);
  opacity: var(--horb-opacity, 1);
  border-radius: var(--r-round, 999px);
  display: grid; place-items: center;
  font-family: var(--font-ui, system-ui, sans-serif);
  will-change: transform;
}
.horb--hidden { display: none; }

.horb__ring {
  position: absolute; inset: 0; border-radius: inherit;
  border: var(--horb-ring) solid var(--horb-color);
  box-shadow: 0 0 0 var(--bw-2, 2px) var(--c-bg, #101214);   /* separates the ring from whatever is behind */
  transition: border-color var(--dur-fast, 120ms) var(--ease-out, ease-out);
}
.horb__core {
  position: absolute; inset: calc(var(--horb-ring) + 4px); border-radius: inherit;
  background: var(--horb-color);
  box-shadow: 0 0 calc(var(--horb-size) * 0.25) color-mix(in srgb, var(--horb-color) 55%, transparent);
  transition: background-color var(--dur-fast, 120ms) var(--ease-out, ease-out);
}
.horb__inner {
  position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center;
  color: var(--horb-text); text-align: center; line-height: 1; gap: 0;
  width: calc(var(--horb-size) * 0.82); max-width: calc(var(--horb-size) * 0.82); overflow: hidden;
  padding: calc(var(--horb-size) * 0.04) 0;
}
.horb__word, .horb__label { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.horb__glyph { font-size: max(var(--fs-label, 16px), calc(var(--horb-size) * 0.18)); font-weight: var(--fw-bold, 700); line-height: 1; }
.horb__pct {
  font-family: var(--font-mono, monospace); font-variant-numeric: tabular-nums; font-weight: var(--fw-bold, 700);
  font-size: max(var(--fs-value-sm, 32px), calc(var(--horb-size) * 0.34)); letter-spacing: -0.03em; line-height: 1;
}
.horb__pct::after { content: '%'; font-size: 0.45em; font-family: var(--font-cond, var(--font-ui)); margin-left: 0.06em; vertical-align: top; }
.horb--nopct .horb__pct::after { content: ''; }
.horb--nopct:not(.horb--nodata) .horb__pct { display: none; }       /* showPercent:false → colour + glyph + word only */
.horb--nopct:not(.horb--nodata) .horb__glyph { font-size: max(var(--fs-value-sm, 32px), calc(var(--horb-size) * 0.3)); }
.horb--nodata .horb__pct { font-size: max(var(--fs-value-sm, 32px), calc(var(--horb-size) * 0.28)); }
.horb__word, .horb__label {
  font-family: var(--font-cond, var(--font-ui)); text-transform: uppercase; letter-spacing: var(--ls-label, 0.08em);
  font-weight: var(--fw-bold, 700); font-size: max(var(--fs-label, 16px), calc(var(--horb-size) * 0.1)); line-height: 1.2;
}
.horb__label { opacity: 0.85; font-weight: var(--fw-semibold, 600); font-size: max(var(--fs-label, 16px), calc(var(--horb-size) * 0.08)); letter-spacing: 0.04em; }
.horb__label--empty { display: none; }
/* Latch mark (flash habituated → solid): inverted disc colours so it reads on any profile colour. */
.horb__latch { position: absolute; right: 4%; top: 4%; display: none; font-size: var(--fs-label, 16px); color: var(--horb-color);
  background: var(--horb-text); border: var(--bw-2, 2px) solid var(--horb-color); border-radius: var(--r-round, 999px); width: 1.7em; height: 1.7em; line-height: 1.6em; text-align: center; }
.horb--latched .horb__latch { display: block; }

/* Small orbs: drop the word, keep glyph + %. Large: everything, bigger. */
.horb--sm .horb__word, .horb--sm .horb__label { display: none; }
.horb--sm .horb__glyph { font-size: max(var(--fs-label, 16px), calc(var(--horb-size) * 0.2)); }
.horb--sm .horb__pct { font-size: max(var(--fs-value-sm, 32px), calc(var(--horb-size) * 0.36)); }
.horb--lg { --horb-ring: 6px; }

/* ---------- state patterns (the non-colour channel) ---------- */
.horb--tripped .horb__ring { border-width: calc(var(--horb-ring) * 2.5); border-style: double; }
.horb--nodata .horb__ring { border-style: dashed; }
.horb--nodata .horb__core { background: repeating-linear-gradient(135deg, var(--horb-color) 0 8px, color-mix(in srgb, var(--horb-color) 60%, var(--c-bg, #101214)) 8px 16px); box-shadow: none; }
.horb--warn .horb__ring { border-style: solid; }

/* ---------- flash (≤ 2 Hz, ring only, engine-driven — no CSS animation so the fake clock owns time) ---------- */
.horb--flash .horb__ring { border-color: var(--horb-color); }
.horb--flash-on .horb__ring { border-color: var(--horb-text); box-shadow: 0 0 0 var(--bw-2, 2px) var(--c-bg, #101214), 0 0 calc(var(--horb-size) * 0.3) var(--horb-color); }
.horb--flash-on .horb__core { filter: brightness(1.25); }
/* reduced motion: steady thick outline instead of any flashing */
.horb--alert .horb__ring { border-color: var(--horb-text); border-width: calc(var(--horb-ring) * 2.5); }

/* ---------- host variants ---------- */
.orb-layer--high-contrast .horb__ring { box-shadow: 0 0 0 var(--bw-2, 2px) #000, 0 0 0 calc(var(--bw-2, 2px) * 2) #fff; }
.orb-layer--high-contrast .horb__inner { text-shadow: none; }
.orb-layer--drive-mode .horb__pct { font-size: max(var(--fs-value, 40px), calc(var(--horb-size) * 0.4)); }
.orb-layer--drive-mode .horb__word { font-size: max(var(--fs-body, 18px), calc(var(--horb-size) * 0.13)); }
.orb-layer--reduced-motion .horb__ring, .orb-layer--reduced-motion .horb__core { transition: none; }
@media (prefers-reduced-motion: reduce) { .horb__ring, .horb__core { transition: none; } }

/* =====================================================================
   Profile editor (editor.js) — scan-tool form, tokens only
   ===================================================================== */
.horb-editor { display: grid; gap: var(--sp-4, 16px); color: var(--c-text, #F2F4F7); font-size: var(--fs-body, 18px); }
.horb-editor__head { display: flex; flex-wrap: wrap; gap: var(--sp-3, 12px); align-items: center; justify-content: space-between; }
.horb-editor__title { font: var(--fw-bold, 700) var(--fs-h2, 26px)/1.1 var(--font-cond, var(--font-ui)); text-transform: uppercase; letter-spacing: 0.02em; }
.horb-editor__actions { display: flex; flex-wrap: wrap; gap: var(--sp-2, 8px); }
.horb-editor section { background: var(--c-surface-1, #181B1F); border: var(--bw, 1px) solid var(--c-line, #3A414C); border-radius: var(--r-2, 8px); padding: var(--sp-4, 16px); display: grid; gap: var(--sp-3, 12px); }
.horb-editor h3 { font: var(--fw-semibold, 600) var(--fs-h3, 22px)/1.1 var(--font-cond, var(--font-ui)); text-transform: uppercase; letter-spacing: 0.04em; color: var(--c-text-2, #C3C9D2); margin: 0; }
.horb-editor .field { display: grid; gap: var(--sp-1, 4px); min-width: 0; }
.horb-editor .field > span { font-size: var(--fs-label, 16px); text-transform: uppercase; letter-spacing: var(--ls-label, 0.08em); color: var(--c-text-2, #C3C9D2); font-weight: var(--fw-semibold, 600); }
.horb-editor input[type="number"], .horb-editor input[type="text"], .horb-editor select, .horb-editor textarea {
  min-height: var(--tap, 56px); padding: 0 var(--sp-3, 12px); font: inherit; font-size: var(--fs-body, 18px);
  color: var(--c-text, #F2F4F7); background: var(--c-surface-3, #2B3038); border: var(--bw, 1px) solid var(--c-line-strong, #727C8A); border-radius: var(--r-1, 4px);
}
/* A native <select> reports its widest option as its min-content width, and every grid track above it
   is `auto`, so at 360 px one long option ("Coolant temperature (ECT)") stretched the whole rule card to
   405 px and the page scrolled sideways. minmax(0,1fr) tracks + a capped control width stop that. */
.horb-editor select, .horb-editor input[type="text"], .horb-editor input[type="number"], .horb-editor textarea { width: 100%; min-width: 0; max-width: 100%; box-sizing: border-box; }
.horb-editor section, .horb-editor .rule, .horb-editor .orb-row { grid-template-columns: minmax(0, 1fr); }
.horb-editor input[type="number"] { font-family: var(--font-mono, monospace); font-variant-numeric: tabular-nums; }
.horb-editor textarea { min-height: 160px; padding: var(--sp-3, 12px); font-family: var(--font-mono, monospace); font-size: var(--fs-caption, 16px); }
.horb-editor input[type="color"] { width: var(--tap, 56px); height: var(--tap, 56px); padding: 0; border: var(--bw, 1px) solid var(--c-line-strong, #727C8A); border-radius: var(--r-1, 4px); background: none; }
.horb-editor input[type="range"] { min-height: var(--tap, 56px); accent-color: var(--c-accent, #FFB300); }
.horb-editor .check { display: inline-flex; align-items: center; gap: var(--sp-2, 8px); min-height: var(--tap, 56px); padding: 0 var(--sp-2, 8px); font-size: var(--fs-body, 18px); }
.horb-editor .check input { width: 28px; height: 28px; accent-color: var(--c-accent, #FFB300); }
.horb-editor .row { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr)); gap: var(--sp-3, 12px); align-items: end; }
.horb-editor .rule, .horb-editor .orb-row { border: var(--bw, 1px) solid var(--c-line, #3A414C); border-left: var(--bw-3, 3px) solid var(--c-line-strong, #727C8A); border-radius: var(--r-1, 4px); padding: var(--sp-3, 12px); display: grid; gap: var(--sp-3, 12px); background: var(--c-surface-2, #21252B); }
.horb-editor .rule[data-band="ok"] { border-left-color: var(--c-ok, #2FBF71); }
.horb-editor .rule[data-band="low"] { border-left-color: var(--c-low, #5A96FF); }
.horb-editor .rule[data-band="high"], .horb-editor .rule[data-band="bad"] { border-left-color: var(--c-bad, #F25C61); }
.horb-editor .rule[data-band="warn"] { border-left-color: var(--c-warn, #FFB300); }
.horb-editor .rule__head, .horb-editor .orb-row__head { display: flex; flex-wrap: wrap; gap: var(--sp-2, 8px); align-items: center; justify-content: space-between; }
.horb-editor .rule__live { font-family: var(--font-mono, monospace); font-variant-numeric: tabular-nums; font-size: var(--fs-body-lg, 20px); color: var(--c-text, #F2F4F7); }
.horb-editor .warn { color: var(--c-warn-text, #FFC940); font-size: var(--fs-caption, 16px); }
.horb-editor .err { color: var(--c-bad-text, #FF8A8E); font-size: var(--fs-body, 18px); }
.horb-editor .btn { min-height: var(--tap, 56px); min-width: var(--tap, 56px); padding: 0 var(--sp-4, 16px); border-radius: var(--r-1, 4px); border: var(--bw, 1px) solid var(--c-line-strong, #727C8A);
  background: var(--c-surface-3, #2B3038); color: var(--c-text, #F2F4F7); font: var(--fw-bold, 700) var(--fs-label, 16px)/1 var(--font-cond, var(--font-ui)); text-transform: uppercase; letter-spacing: var(--ls-label, 0.08em); cursor: pointer; }
.horb-editor .btn--primary { background: var(--c-accent, #FFB300); color: var(--c-on-accent, #1A1200); border-color: transparent; }
.horb-editor .btn--danger { color: var(--c-bad-text, #FF8A8E); }
.horb-editor .btn[disabled] { opacity: 0.5; cursor: not-allowed; }
.horb-editor .diff { display: grid; gap: var(--sp-1, 4px); font-family: var(--font-mono, monospace); font-size: var(--fs-caption, 16px); }
.horb-editor .diff__line { display: grid; grid-template-columns: 1fr auto 1fr; gap: var(--sp-2, 8px); padding: var(--sp-1, 4px) var(--sp-2, 8px); background: var(--c-surface-3, #2B3038); border-radius: var(--r-1, 4px); }
.horb-editor .diff__from { color: var(--c-bad-text, #FF8A8E); text-decoration: line-through; }
.horb-editor .diff__to { color: var(--c-ok-text, #5EE29A); }
.horb-editor .status { min-height: var(--tap, 56px); display: flex; align-items: center; gap: var(--sp-2, 8px); }
.horb-editor .screens { display: flex; flex-wrap: wrap; gap: var(--sp-1, 4px); }
.horb-editor .orbprefs { display: grid; gap: var(--sp-2, 8px); }
.horb-editor .orbprefs__row { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--sp-2, 8px); padding: var(--sp-2, 8px) var(--sp-3, 12px); background: var(--c-surface-2, #21252B); border-radius: var(--r-1, 4px); border-left: var(--bw-3, 3px) solid var(--c-line-strong, #727C8A); }
.horb-editor .orbprefs__row[data-state="minimized"] { border-left-color: var(--c-warn, #FFB300); }
.horb-editor .orbprefs__row[data-state="hidden"] { border-left-color: var(--c-info, #8B98AB); opacity: 0.85; }
.horb-editor .orbprefs__name { font: var(--fw-semibold, 600) var(--fs-body, 18px)/1.2 var(--font-ui); }
.horb-editor .orbprefs__state { font-size: var(--fs-caption, 16px); color: var(--c-text-2, #C3C9D2); text-transform: uppercase; letter-spacing: var(--ls-label, 0.08em); }
.horb-editor .btn[aria-pressed="true"] { background: var(--c-accent, #FFB300); color: var(--c-on-accent, #1A1200); border-color: transparent; }

/* =====================================================================
   Minimized orb — a 48 px button pill (≥ 44 px touch floor). Colour + glyph + ring pattern
   stay, so the state is still readable without colour; flash / tripped / nodata rules above
   apply unchanged because the classes are the same. Hover / focus shows the score as a tip.
   ===================================================================== */
.horb--min { --horb-ring: 3px; pointer-events: auto; cursor: pointer; }
.horb--min .horb__pct, .horb--min .horb__word, .horb--min .horb__label, .horb--min .horb__latch { display: none; }
.horb--min .horb__inner { width: 100%; max-width: 100%; padding: 0; }
.horb--min .horb__glyph { font-size: max(var(--fs-label, 16px), calc(var(--horb-size) * 0.5)); line-height: 1; }
.horb--min .horb__core { inset: calc(var(--horb-ring) + 3px); box-shadow: 0 0 calc(var(--horb-size) * 0.35) color-mix(in srgb, var(--horb-color) 55%, transparent); }
.horb--min.horb--tripped .horb__ring { border-width: calc(var(--horb-ring) * 2); }
.horb--min:focus-visible { outline: var(--bw-2, 2px) solid var(--c-focus, #FFC940); outline-offset: 3px; }
.horb--min::after {
  content: attr(data-tip); position: absolute; right: 0; top: calc(100% + var(--sp-2, 8px));
  white-space: nowrap; padding: var(--sp-2, 8px) var(--sp-3, 12px); border-radius: var(--r-1, 4px);
  background: var(--c-surface-4, #353B45); color: var(--c-text, #F2F4F7); border: var(--bw, 1px) solid var(--c-line-strong, #727C8A);
  font: var(--fw-bold, 700) var(--fs-label, 16px)/1 var(--font-cond, var(--font-ui)); letter-spacing: var(--ls-label, 0.08em); text-transform: uppercase;
  opacity: 0; pointer-events: none; transition: opacity var(--dur-fast, 120ms);
}
.horb--min:hover::after, .horb--min:focus-visible::after { opacity: 1; }
.orb-layer--drive-mode .horb--min { --horb-size: 56px !important; }
.orb-layer--high-contrast .horb--min .horb__ring { box-shadow: 0 0 0 var(--bw-2, 2px) #000, 0 0 0 calc(var(--bw-2, 2px) * 2) #fff; }

/* ---------------------------------------------------------------- orb quick control (orbswitch.js)
 * The top-bar switch that turns orbs on, minimizes them all, or turns them OFF COMPLETELY, from
 * any screen. A real child of the status bar, like the dock: it occupies layout space, so it can
 * never cover a control. The only thing that floats is the menu it opens, and that is dismissed
 * on Escape, on a tap outside, and on choosing an item.
 *
 * `--tap` is 56 px (76 px at drive density), and the switch is never below it in either axis. */
.orbswitch-wrap { position: relative; flex: none; margin-left: var(--sp-2); }
.orbswitch {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  min-width: var(--tap); min-height: var(--tap); padding: var(--sp-1);
  border: var(--bw) solid var(--c-line); border-radius: var(--r-2);
  background: var(--c-surface-1); color: var(--c-text); cursor: pointer;
}
.orbswitch__label { font: var(--fw-bold) var(--fs-caption)/1 var(--font-cond); letter-spacing: var(--ls-label); }
.orbswitch[data-state="off"] { color: var(--c-text-2); border-style: dashed; }
.orbswitch[data-state="min"] { color: var(--c-accent-text); }
.orbswitch[aria-expanded="true"] { border-color: var(--c-accent); box-shadow: var(--glow-accent); }

/* The menu is position:FIXED under the bar, not absolute inside it: from 600px up .statusbar is an
   overflow-x:auto scroll container (its own narrow-width fallback), and an absolutely positioned child
   is clipped by / scrolls the bar — the menu showed up as a sliver at the top of the page. A fixed
   child escapes that clip. Hosts with no status bar (the Perf Lab's title row) use the inline variant. */
.orbmenu {
  position: fixed; top: calc(var(--statusbar-real, var(--statusbar-h, 56px)) + var(--sp-1)); right: var(--sp-3); z-index: 60;
  display: flex; flex-direction: column; min-width: 232px;
  background: var(--c-surface-2); color: var(--c-text);
  border: var(--bw) solid var(--c-line); border-radius: var(--r-2); box-shadow: var(--shadow-2);
}
.orbmenu[hidden] { display: none; }
.orbswitch-wrap--inline .orbmenu { position: absolute; top: calc(100% + var(--sp-1)); right: 0; }
.orbmenu__item {
  display: flex; align-items: center; gap: var(--sp-2);
  min-height: var(--tap); padding: var(--sp-1) var(--sp-3);
  border: 0; background: none; color: inherit; cursor: pointer; text-align: left;
  font: var(--fw-bold) var(--fs-label)/1.2 var(--font-cond); letter-spacing: var(--ls-label);
}
.orbmenu__item + .orbmenu__item { border-top: var(--bw) solid var(--c-line); }
.orbmenu__item[aria-checked="true"] { color: var(--c-accent-text); }
.orbmenu__item:hover, .orbmenu__item:focus-visible { background: var(--c-surface-3); }

/* scanner/scanner.css */
/* app/scanner/scanner.css — scanner-only styles.
 *
 * Rules:
 *  - tokens only. Every colour is a var(--c-*), every size a var(--sp-*)/var(--fs-*)/var(--tap).
 *    A literal here would be invisible to design/contrast-check.mjs, which only sees tokens.css.
 *  - NOTHING in design/ is edited. That directory is a byte-exact copy of D4's design system
 *    (see design/SYNCED-FROM.json); anything the scanner needs on top lives in this file, so the
 *    merge on `Test` is a comparison and not an argument.
 *
 * Font loading: D4's prototypes load the three faces from Google Fonts and INTEGRATION-TODO has
 * an open D4 item to self-host them for `font-src 'self'`. The scanner follows D4 rather than
 * inventing a second answer; when D4 self-hosts, this @import goes away with theirs.
 */


/* ---------------------------------------------------------------- status bar, phone ordering
 * The wrap / grow / protocol-chip fixes that used to live here are now in design/components.css
 * (INTEGRATION-TODO D4, fixed at the source). What stays is scanner-only: keep the docked orb on the
 * FIRST row, at the right of the vehicle name, rather than on a row of its own below the chips.
 * Ordering only — it still occupies layout space, so it still cannot cover anything. */
@media (max-width: 599px) {
  .statusbar__vehicle { order: 1; }
  .orbdock { order: 2; margin-left: auto; }
  .statusbar__chips { order: 3; }
}

/* ---------------------------------------------------------------- docked orb
 * A real child of the status bar, so it occupies layout space and cannot cover a control. The
 * floating overlay (app/health's layer) is only used for orbs the user places deliberately. */
.orbdock {
  flex: none; position: relative; display: grid; place-items: center;
  width: 52px; height: 52px; margin-left: var(--sp-2);
  border-radius: 50%; border: 0; padding: 0; cursor: pointer;
  background: var(--c-surface-1); color: var(--c-text-2);
}
.orbdock[hidden] { display: none; }
.orbdock__ring { position: absolute; inset: 0; border-radius: 50%; border: var(--bw-3) solid currentColor; }
.orbdock__face { display: grid; place-items: center; line-height: 1; }
.orbdock__pct { font: var(--fw-bold) var(--fs-label)/1 var(--font-mono); color: var(--c-text); }
.orbdock__word { font: var(--fw-bold) 10px/1 var(--font-cond); letter-spacing: var(--ls-label); }
/* The `-text` variants, not the fill ones: the ring inherits this colour, and --c-warn is
   1.79:1 on a light surface in day mode (the same trap the readiness rail fell into). */
.orbdock[data-tone="ok"] { color: var(--c-ok-text); }
.orbdock[data-tone="warn"] { color: var(--c-warn-text); }
.orbdock[data-tone="bad"] { color: var(--c-bad-text); }
.orbdock[data-tone="low"] { color: var(--c-low-text); }
.orbdock[data-tone="unknown"] { color: var(--c-text-2); }
.orbdock[data-flash="true"] { animation: orbdock-flash var(--dur-2, 500ms) steps(1, end) infinite; }
@keyframes orbdock-flash { 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .orbdock[data-flash="true"] { animation: none; } }
@media (min-width: 600px) { .orbdock { width: 44px; height: 44px; } }
/* Minimized: the dock keeps its colour, ring and score — that is the whole job of a minimized
   orb — and drops the band word, which is what was taking the room. */
.orbdock[data-min="true"] { width: 36px; height: 36px; }
.orbdock[data-min="true"] .orbdock__word { display: none; }

/* ---------------------------------------------------------------- orb quick control
 * The .orbswitch / .orbmenu styles moved to app/health/health.css (shared with Diagnose and the
 * Perf Lab). Only the scanner-specific phone ordering stays here. */
/* At 360 px the status bar wraps: the vehicle name takes row one and the chips take row two.
   The dock and the switch sit together at the right of row one, where a thumb already is. */
@media (max-width: 599px) {
  .orbswitch-wrap { order: 2; margin-left: var(--sp-1); }
  .orbdock { margin-left: auto; }
}

/* ---------------------------------------------------------------- layout */
.list--plain { list-style: none; margin: 0; padding: 0; }
.softkeys-wrap { margin-bottom: var(--sp-4); }
.card__head-extras { display: flex; gap: var(--sp-2); align-items: center; flex-wrap: wrap; }

.dash-split { display: grid; gap: var(--sp-4); grid-template-columns: 1fr; align-items: start; }
@media (min-width: 900px) {
  .dash-split { grid-template-columns: minmax(0, 1fr) minmax(300px, 380px); }
  /* Health-first on a clean car: the orb column is the narrow one and comes first. */
  .dash-split--health-first { grid-template-columns: minmax(300px, 380px) minmax(0, 1fr); }
}

.tile-grid--compact { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 760px) { .tile-grid--compact { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.tile--row {
  aspect-ratio: auto; min-height: var(--tap-lg); flex-direction: row; align-items: center;
  justify-content: flex-start; gap: var(--sp-3); text-align: left;
}
.tile--row .tile__icon { width: 34px; height: 34px; flex: none; }

/* ---------------------------------------------------------------- connect */
.connect-hero { display: grid; gap: var(--sp-4); justify-items: center; text-align: center; padding: var(--sp-5) 0; }
.connect-hero svg { color: var(--c-accent-text); }
.connect-hero .value { color: var(--c-text); font-size: var(--fs-h2); }
.adapter-grid { display: grid; gap: var(--sp-3); grid-template-columns: repeat(2, minmax(0, 1fr)); margin-bottom: var(--sp-4); }
@media (min-width: 900px) { .adapter-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.adapter {
  min-height: var(--tap-lg); display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--sp-2); padding: var(--sp-3); background: var(--c-surface-2);
  border: var(--bw) solid var(--c-line); border-radius: var(--r-2); color: var(--c-text);
  font: var(--fw-bold) var(--fs-label)/1.1 var(--font-cond); text-transform: uppercase; letter-spacing: var(--ls-label);
  text-align: center; cursor: pointer;
}
.adapter svg { width: 30px; height: 30px; color: var(--c-accent-text); }
.adapter[aria-pressed="true"] { border-color: var(--c-accent); box-shadow: inset 0 0 0 var(--bw) var(--c-accent); }
.adapter[aria-disabled="true"] { opacity: .55; }
.adapter .caption { text-transform: none; letter-spacing: 0; font-family: var(--font-ui); font-weight: var(--fw-medium); }
.steps { display: grid; gap: var(--sp-2); }
.steps .row { grid-template-columns: auto 1fr auto; min-height: var(--tap); border: var(--bw) solid var(--c-line); border-radius: var(--r-2); }

/* ---------------------------------------------------------------- dashboard */
.code-tiles { display: grid; gap: var(--sp-3); grid-template-columns: 1fr; }
@media (min-width: 760px) { .code-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.code-tile {
  display: grid; grid-template-columns: auto 1fr; gap: var(--sp-3); align-items: center; text-align: left;
  padding: var(--sp-3) var(--sp-4); min-height: var(--tap-lg); background: var(--c-surface-2);
  border: var(--bw) solid var(--c-line); border-left: var(--bw-3) solid var(--c-bad);
  border-radius: var(--r-2); color: var(--c-text); cursor: pointer;
}
.code-tile--warn { border-left-color: var(--c-warn); }
.code-tile--info { border-left-color: var(--c-info); }
.code-tile__code { font: var(--fw-bold) var(--fs-value)/1 var(--font-mono); letter-spacing: -0.02em; }
.code-tile__desc { display: block; font-size: var(--fs-body); font-weight: var(--fw-medium); }
.code-tile__meta { display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: center; margin-top: var(--sp-1); }

/* Orb read-out on the dashboard. The orb ITSELF is drawn by app/health's fixed layer; this is
   the number, the word and the "why" beside it, so the card is readable even when the user has
   scoped the live orb to another screen. */
.orb-readout {
  position: relative; display: grid; place-items: center; justify-self: center;
  width: 200px; height: 200px; border-radius: 50%;
  background: radial-gradient(circle at 50% 42%, var(--c-surface-3), var(--c-surface-1) 72%);
  border: var(--bw-3) solid var(--c-line-strong); margin: var(--sp-3) 0;
}
.orb-readout__ring { position: absolute; inset: 8px; border-radius: 50%; border: var(--bw-3) solid currentColor; opacity: .85; }
.orb-readout__pct { display: flex; align-items: baseline; gap: 2px; font: var(--fw-bold) var(--fs-value-lg)/1 var(--font-mono); color: var(--c-text); }
.orb-readout__pct small { font-size: var(--fs-h3); }
.orb-readout__word { position: absolute; bottom: 34px; font: var(--fw-bold) var(--fs-label)/1 var(--font-cond); letter-spacing: var(--ls-label); text-transform: uppercase; }
.orb-readout[data-tone="ok"] { color: var(--c-ok); }
.orb-readout[data-tone="warn"] { color: var(--c-warn); }
.orb-readout[data-tone="bad"] { color: var(--c-bad); }
.orb-readout[data-tone="low"] { color: var(--c-low); }
.orb-readout[data-tone="unknown"] { color: var(--c-text-3); }
.orb-readout[data-tone="ok"] .orb-readout__word { color: var(--c-ok-text); }
.orb-readout[data-tone="warn"] .orb-readout__word { color: var(--c-warn-text); }
.orb-readout[data-tone="bad"] .orb-readout__word { color: var(--c-bad-text); }
.orb-readout[data-tone="low"] .orb-readout__word { color: var(--c-low-text); }
.orb-readout[data-tone="unknown"] .orb-readout__word { color: var(--c-text-2); }
.orb-readout__status { text-align: center; color: var(--c-text-2); font-size: var(--fs-body); margin: 0 0 var(--sp-3); }
.orb-readout__contrib { display: flex; flex-wrap: wrap; gap: var(--sp-2); justify-content: center; margin-bottom: var(--sp-3); }
.orb-editor-host { display: block; }

/* ---------------------------------------------------------------- live data */
.live-toolbar { display: flex; gap: var(--sp-3); align-items: center; flex-wrap: wrap; margin-bottom: var(--sp-4); }
.live-view[hidden] { display: none; }
.live-list .row__value { display: flex; align-items: baseline; gap: var(--sp-1); }
.row--traced { box-shadow: inset var(--bw-3) 0 0 var(--c-accent); }
.row--focus { outline: var(--bw-3) solid var(--c-focus); outline-offset: 2px; }
.gauge-grid { display: grid; gap: var(--sp-3); grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 760px) { .gauge-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.gauge__arc--nodata { stroke: var(--c-text-3); }
.graph__legend { list-style: none; margin: var(--sp-3) 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--sp-3); }
.graph__legend li { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-caption); color: var(--c-text-2); }
.pid-picker { display: grid; gap: var(--sp-1); max-height: 46vh; overflow: auto; margin-top: var(--sp-3); }
.pid-pick {
  display: grid; grid-template-columns: auto 1fr auto auto; gap: var(--sp-3); align-items: center;
  min-height: var(--tap); padding: 0 var(--sp-3); border-radius: var(--r-1); background: var(--c-surface-2);
  font-size: var(--fs-body);
}
.pid-pick input { width: 24px; height: 24px; }

/* ---------------------------------------------------------------- readiness */
.readiness-grid { display: grid; gap: var(--sp-2); grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); margin-top: var(--sp-3); }
.readiness-cell {
  display: grid; grid-template-columns: auto 1fr auto; gap: var(--sp-2); align-items: center;
  min-height: var(--tap); padding: 0 var(--sp-3); border-radius: var(--r-1);
  background: var(--c-surface-2); border-left: var(--bw-3) solid var(--c-line);
}
.readiness-cell--ready { border-left-color: var(--c-ok); color: var(--c-ok-text); }
/* --c-warn is the FILL amber and drops to 1.79:1 on white in day mode (contrast-check caught
   it); --c-warn-text is the one tuned to be legible on a surface. */
.readiness-cell--not { border-left-color: var(--c-warn-text); color: var(--c-warn-text); }
.readiness-cell--unknown, .readiness-cell--na { border-left-color: var(--c-line-strong); color: var(--c-text-3); }
.readiness-cell__name { color: var(--c-text); font-size: var(--fs-body); }
.readiness-grid--compact .readiness-cell { min-height: var(--tap); }

/* ---------------------------------------------------------------- terminal */
.terminal-input { display: grid; grid-template-columns: 1fr auto; gap: var(--sp-2); margin-bottom: var(--sp-3); }
.terminal-out {
  background: var(--c-bg); border: var(--bw) solid var(--c-line); border-radius: var(--r-2);
  padding: var(--sp-3); min-height: 180px; max-height: 44vh; overflow: auto;
  font-family: var(--font-mono); font-size: var(--fs-body); color: var(--c-text);
}
.terminal-line { white-space: pre-wrap; word-break: break-word; padding: 2px 0; }
.terminal-line--cmd { color: var(--c-accent-text); font-weight: var(--fw-bold); }
.terminal-line--meta { color: var(--c-text-3); font-size: var(--fs-caption); }
.terminal-line--err { color: var(--c-bad-text); }
.cmd-group { margin-bottom: var(--sp-3); }
.cmd-chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-2); }
.chip--cmd { min-height: var(--tap); cursor: pointer; }

/* ---------------------------------------------------------------- adapter */
.adapter-id { margin-top: var(--sp-2); }

/* ---------------------------------------------------------------- sheet */
.sheet {
  position: fixed; left: 0; right: 0; bottom: var(--footer-h); z-index: var(--z-dialog, 60);
  max-height: 70vh; overflow: auto;
  background: var(--c-surface-2); border-top: var(--bw-3) solid var(--c-accent);
  box-shadow: var(--el-3, 0 -8px 24px rgb(0 0 0 / .45));
  padding: var(--sp-4); display: grid; gap: var(--sp-3);
}
@media (min-width: 900px) { .sheet { left: auto; right: var(--sp-4); width: min(560px, 92vw); border-radius: var(--r-2) var(--r-2) 0 0; } }
.sheet__head h2 { margin: 0; font-size: var(--fs-h2); }
.sheet__body { display: grid; gap: var(--sp-2); color: var(--c-text-2); font-size: var(--fs-body); }
.sheet__body p { margin: 0; }
.sheet__foot { display: flex; gap: var(--sp-2); flex-wrap: wrap; justify-content: flex-end; }

/* The selected module in a master/detail pair. components.css styles `[aria-selected]`, which
   is not a legal attribute on role="button" — `aria-current` carries the same meaning here. */
.master-detail__master .row[aria-current="true"] {
  border-color: var(--c-accent);
  box-shadow: inset var(--bw-3) 0 0 var(--c-accent);
}

/* ---------------------------------------------------------------- misc */
/* Tail room for a FREELY PLACED orb parked near the bottom. The default orb is docked in the
   status bar and needs none of this. */
.app__content { padding-bottom: calc(var(--footer-h) + var(--sp-6) + 48px); }
.btn--write { border-color: var(--c-warn); }
.screen { display: block; }

/* diag/diag.css */
/*
 * diag.css — layout-only styles for the Diagnostic workspace.
 *
 * Rules (app/shell/design/README.md §"Rules for screen authors"):
 *   - tokens only: no colour literal, no font-size below 16px, nothing that could need a new
 *     contrast pair (D4 owns contrast-pairs.json and D3 must not edit it);
 *   - every component look already in components.css is USED, not re-declared. What is here is
 *     the handful of grid/stack primitives the workspace needs and a few sizes on top of
 *     existing components.
 */

/* The design system's three faces. Same import the D4 prototypes use. */


.stack { display: flex; flex-direction: column; gap: var(--sp-4); }

/*
 * icons.js emits an SVG with no intrinsic size unless `size` is passed; components.css sizes the
 * ones inside its own components (.btn, .row__lead, .chip …). These are the places D3 puts an
 * icon that components.css does not cover — without this they render at the SVG default 300×150.
 */
h1 svg, h2 svg, h3 svg, .field__label svg, .panel__title svg, .card--flat > h3 svg, p > svg {
  width: 24px; height: 24px; flex: 0 0 auto; color: var(--c-accent-text);
}
.card__head h2, .card h3, .panel__title { display: flex; align-items: center; gap: var(--sp-2); }

/* Identity grid: one column on a phone, two from 560px, VIN always spans the row. */
.identity-grid {
  display: grid;
  gap: var(--sp-4);
  grid-template-columns: 1fr;
}
@media (min-width: 560px) { .identity-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .identity-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.field--wide { grid-column: 1 / -1; }

/* The field label carries the source badge and the unlock button on the same line. */
.field__label {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
}
.btn--tiny {
  min-height: var(--tap);
  padding-inline: var(--sp-2);
  font-size: var(--fs-caption);
}

/* Value cells inside the scan-data table stay readable and right-aligned. */
.input--cell {
  width: 10ch;
  text-align: right;
  text-transform: none;
}

/* Separator dot in the collapsed scan-panel summary. */
.panel__summary .dot::before { content: "\00b7"; opacity: 0.6; }
.panel__summary { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }

/* Raw CAN digest: monospace block that scrolls rather than stretching the page. */
.raw-digest {
  max-height: 16em;
  overflow: auto;
  margin: 0;
  padding: var(--sp-3);
  background: var(--c-surface-1);
  border: var(--bw) solid var(--c-line);
  border-radius: var(--r-2);
  font-size: var(--fs-caption);
  line-height: var(--lh-body);
  white-space: pre;
}

/* Media thumbnails keep a fixed box so the tray does not reflow as images load. */
.media-tray__thumb {
  width: 100%;
  max-width: 220px;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--r-2);
  border: var(--bw) solid var(--c-line);
}
.select--sm { min-height: var(--tap); font-size: var(--fs-caption); }
/* The design system's .media-tray__item is a badge + one icon + a caption pinned into a 112 px
   box (overflow hidden, everything absolutely placed). A real tray item also carries a thumbnail,
   a hint <select>, an ANALYZE button and a remove button, which that box clipped to slivers at
   360 px. Same tokens, a stacked layout that grows with its content. */
.media-tray { grid-template-columns: repeat(auto-fill, minmax(min(100%, 240px), 1fr)); }
.media-tray__item { display: grid; align-content: start; gap: var(--sp-2); padding: var(--sp-3); overflow: visible; min-width: 0; }
.media-tray__item .badge { position: static; justify-self: start; }
.media-tray__item > svg { position: static; margin: 0; }
.media-tray__item .caption { padding: 0; background: none; overflow-wrap: anywhere; }
.media-tray__item .pill-row { flex-wrap: wrap; align-items: stretch; }
.media-tray__item .btn svg { position: static; margin: 0; width: 20px; height: 20px; color: inherit; }
.media-tray__item .select { min-width: 0; flex: 1 1 140px; }

/* Verdict block. */
.verdict__summary {
  font-family: var(--font-cond);
  font-weight: var(--fw-bold);
  font-size: var(--fs-h2);
  line-height: var(--lh-tight);
}
.verdict__conf { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }

/* Ranked list score column. */
.ranked__score { text-align: right; min-width: 6ch; }

/* Result section nav sticks under the status bar. */
.results-nav {
  position: sticky;
  top: var(--statusbar-h);
  z-index: 1;
}

/* Provenance strip above the cards. */
.provenance { margin-block: var(--sp-2); }

/* Prose from the model: comfortable measure, no colour of its own. */
.prose { display: flex; flex-direction: column; gap: var(--sp-3); max-width: 72ch; }
.prose p { margin: 0; line-height: var(--lh-body); }
.prose code { font-family: var(--font-mono); }

/* Follow-up thread turns. */
.thread__turn { margin-block: var(--sp-2); }
.thread__turn--user { border-left: var(--bw-3) solid var(--c-accent); }

.disclaimer { max-width: 72ch; margin-top: var(--sp-6); }

/* Page header used by the standalone entry page. */
.page-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4); flex-wrap: wrap; }

/* perf/perf.css */
/* Perf Lab layout. Tokens and components.css only: no colour literals, no font size under 16px,
   nothing that depends on colour alone (values, labels and badges carry the meaning). */

.perf { display: flex; flex-direction: column; gap: var(--sp-4); padding: var(--gutter); max-width: var(--content-max); margin: 0 auto; }
.perf[hidden], .perf [hidden] { display: none !important; }

.perf__titlebar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--sp-3) var(--sp-5); }
.perf__titlebar h1 { margin: 0; font: var(--fw-bold) var(--fs-h1)/var(--lh-tight) var(--font-cond); text-transform: uppercase; letter-spacing: var(--ls-label); }
.perf__chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.perf__tools { align-items: stretch; }
.perf__orbswitch { display: flex; margin-left: auto; }
.perf__orbswitch:empty { display: none; }
@media (max-width: 599px) { .perf__titlebar .perf__chips { flex: 1 0 100%; } } /* title + orb switch share row 1, the sensor chips take row 2 */
.perf__notice { margin: 0; }

.perf__messages { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.perf__messages:empty { display: none; }
.perf__msg { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-3) var(--sp-4); background: var(--c-surface-2); border: var(--bw) solid var(--c-line-strong); border-left-width: var(--bw-3); border-radius: var(--r-2); font-size: var(--fs-body); }
.perf__msg svg { width: 24px; height: 24px; flex: 0 0 auto; }
.perf__msg--warn { border-color: var(--c-warn); }
.perf__msg--warn svg { color: var(--c-warn-text); }
.perf__msg--bad { border-color: var(--c-bad); }
.perf__msg--bad svg { color: var(--c-bad-text); }

.perf__grid { display: grid; gap: var(--sp-4); grid-template-columns: minmax(0, 1fr); align-items: start; }
.perf__panel[hidden] { display: none; }
@media (min-width: 900px) { .perf__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.perf__card { display: flex; flex-direction: column; gap: var(--sp-3); min-width: 0; }
.perf__side .stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.perf__side { display: flex; flex-direction: column; gap: var(--sp-3); min-width: 0; }
.perf__sub { margin: var(--sp-3) 0 0; font: var(--fw-bold) var(--fs-label)/1 var(--font-cond); letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--c-text-2); }

/* timer */
.perf__modes { align-self: stretch; overflow-x: auto; }
.perf__bigtime { display: flex; align-items: baseline; justify-content: center; gap: var(--sp-2); padding: var(--sp-4) 0 var(--sp-2); }
.perf__time { font-size: calc(var(--fs-value-xl) * 1.5); }
.perf__speedrow { display: flex; align-items: baseline; justify-content: center; flex-wrap: wrap; gap: var(--sp-2) var(--sp-3); }
.perf__status { margin: 0; padding: var(--sp-3) var(--sp-4); background: var(--c-surface-1); border: var(--bw) solid var(--c-line); border-left-width: var(--bw-3); border-radius: var(--r-2); font-size: var(--fs-body-lg); font-weight: var(--fw-semibold); }
.perf__status[data-tone="ok"] { border-left-color: var(--c-ok); }
.perf__status[data-tone="warn"] { border-left-color: var(--c-warn); }
.perf__kv dd { font-size: var(--fs-body-lg); }
.perf__runs { max-height: 320px; overflow-y: auto; }

/* g-force */
.perf__scope { align-items: center; }
.perf__scope-svg { width: 100%; max-width: 520px; height: auto; aspect-ratio: 1 / 1; display: block; margin: 0 auto; }
.perf__axis { stroke: var(--c-line-strong); stroke-width: 1; }
.perf__ring { fill: none; stroke: var(--c-line-strong); stroke-width: 1; }
.perf__ringlabel { fill: var(--c-text-2); font: var(--fw-medium) 16px var(--font-mono); }
.perf__refring { fill: none; stroke: var(--c-warn); stroke-width: 3; stroke-dasharray: 10 7; }
.perf__trail { fill: none; stroke: var(--c-series-3); stroke-width: 3; stroke-linejoin: round; stroke-linecap: round; }
.perf__dot { fill: var(--c-accent); stroke: var(--c-bg); stroke-width: 3; }
.perf__dot--est { fill: var(--c-info); }
.perf__meter { position: relative; height: 48px; background: var(--c-surface-2); border: var(--bw) solid var(--c-line-strong); border-radius: var(--r-2); overflow: hidden; }
.perf__meter-fill { position: absolute; top: 0; bottom: 0; background: var(--c-accent); }
.perf__meter-zero { position: absolute; top: 0; bottom: 0; left: 50%; width: var(--bw-2); background: var(--c-text-2); z-index: 1; }
.perf__meter-ref { position: absolute; top: 0; bottom: 0; width: var(--bw-3); background: var(--c-warn); z-index: 1; }
.perf__grip { margin: 0; font: var(--fw-bold) var(--fs-body-lg)/var(--lh-body) var(--font-mono); }
.perf__orb-slot { min-height: 160px; border: var(--bw) dashed var(--c-line); border-radius: var(--r-3); }
@media (min-width: 900px) { .perf__orb-slot { min-height: 240px; } }

/* hp */
.perf__hp-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--sp-3); }
.perf__pm { font: var(--fw-semibold) var(--fs-body-lg)/1 var(--font-mono); color: var(--c-text-2); }
.perf__assume { margin: 0; padding-left: var(--sp-5); display: flex; flex-direction: column; gap: var(--sp-2); color: var(--c-text-2); }

/* log */
.perf__instruction { margin: 0; font: var(--fw-bold) var(--fs-h2)/var(--lh-tight) var(--font-ui); }
.perf__cycle { display: flex; flex-direction: column; gap: var(--sp-3); }
.perf__cycle-table { display: flex; flex-direction: column; gap: var(--sp-2); overflow-x: auto; }

.perf__cal-msg { min-height: 1.4em; margin: 0; font-weight: var(--fw-semibold); }

/* Track-friendly drive mode: the design tokens already enlarge targets and values; the timer and
   the g-g readouts get one more step so they read at arm's length. */
:root[data-mode="drive"] .perf__time { font-size: calc(var(--fs-value-xl) * 2.2); }
:root[data-mode="drive"] .perf__notice { display: none; }
:root[data-mode="drive"] .perf .stat__value .value { font-size: var(--fs-value-lg); }
:root[data-mode="drive"] .perf__scope-svg { max-width: 640px; }
.perf .input-row > .seg { flex: 0 0 auto; }

/* garage/garage.css */
/* app/garage/garage.css — layout only. Colour, type and sizing come from the shared tokens
   (app/shell/design/tokens.css) and the shared components (components.css); this file adds no
   colour literals and no px font sizes. */

.garage { display: flex; flex-direction: column; gap: var(--sp-4); }
.garage-section { display: flex; flex-direction: column; gap: var(--sp-3); }
.garage-note { color: var(--c-text-2); }
.garage-verdict { font-size: var(--fs-body-lg); font-weight: var(--fw-semibold); margin-bottom: var(--sp-3); }
.garage-para { max-width: 72ch; }
.garage-quote { border-left: var(--bw-3) solid var(--c-accent); padding-left: var(--sp-4); white-space: pre-wrap; color: var(--c-text-2); }
.garage-ranked { margin: 0; padding-left: var(--sp-5); display: flex; flex-direction: column; gap: var(--sp-4); }
.garage-ranked > li { padding-left: var(--sp-2); }
.garage-ranked p { margin: var(--sp-1) 0 0; }
.garage-diff { display: block; font-size: var(--fs-body); color: var(--c-text); overflow-wrap: anywhere; }
.garage-affiliate { display: inline-flex; flex-direction: column; gap: var(--sp-1); }
.garage-affiliate[hidden] { display: none; }
.garage-vehicle-card .btn-row { margin-top: var(--sp-4); }
.garage .btn-row { display: flex; flex-wrap: wrap; gap: var(--sp-3); }
.garage .card ul, .garage .card ol { margin: 0; padding-left: var(--sp-5); }
.garage .list { margin: 0; }
.garage .mono { font-family: var(--font-mono); }
.garage .t-warn { color: var(--c-warn-text); }
.garage .row[role="button"] { min-height: var(--tap-lg); }
@media (max-width: 599px) {
  .garage .btn-row .btn { flex: 1 1 100%; }
}
.garage-h3 { display: flex; align-items: center; gap: var(--sp-2); }
.garage-h3 svg { flex: 0 0 auto; }

/* safety/consent.css */
/* =====================================================================
   app/safety/consent.css — ADVANCED FEATURES consent gate + action UI
   =====================================================================
   Requires app/scanner/design/tokens.css (+ components.css for .btn,
   .input, .field). Rules inherited from there: tokens only, no colour
   literals, no px font sizes below the 16px floor, every control
   ≥ var(--tap), focus always visible, status never colour-alone.

   Design intent: this is the one screen that MUST feel heavier than the
   rest of the tool. Big type, a red rail, a warning that cannot scroll
   away, an agreement pane the user has to reach the end of, and two
   equally weighted choices at the bottom (Accept / Decline). No timers,
   no pre-ticked boxes, no "skip", no shrinking Decline button.
   ===================================================================== */

/* ---------- Dialog shell ---------- */
.consent {
  border: var(--bw-2) solid var(--c-bad);
  border-radius: var(--r-3);
  background: var(--c-surface-2);
  color: var(--c-text);
  padding: 0;
  width: min(96vw, 820px);
  max-width: 96vw;
  max-height: 96vh;
  box-shadow: var(--shadow-3);
  display: flex;
  flex-direction: column;
}
.consent[open] { display: flex; }
.consent::backdrop { background: var(--c-scrim); }

/* Head: red rail + warning summary. It does NOT scroll with the agreement. */
.consent__head {
  padding: var(--sp-5);
  border-bottom: var(--bw-2) solid var(--c-bad);
  border-left: calc(var(--bw-3) * 3) solid var(--c-bad);
  background: var(--c-surface-1);
  flex: 0 0 auto;
}
.consent__eyebrow {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font: var(--fw-bold) var(--fs-label)/1 var(--font-cond);
  text-transform: uppercase; letter-spacing: var(--ls-label);
  color: var(--c-bad-text);
  margin-bottom: var(--sp-3);
}
.consent__eyebrow svg { width: 28px; height: 28px; }
.consent__title {
  font: var(--fw-bold) var(--fs-h1)/var(--lh-tight) var(--font-cond);
  text-transform: uppercase; letter-spacing: 0.02em;
  color: var(--c-text);
}
.consent__summary {
  margin-top: var(--sp-4);
  font-size: var(--fs-body-lg);
  line-height: var(--lh-body);
  color: var(--c-text);
}
.consent__summary p + p { margin-top: var(--sp-3); }
.consent__summary strong { color: var(--c-bad-text); }

/* Body scrolls as a whole on small screens; the agreement pane has its own scroll. */
.consent__body {
  padding: var(--sp-5);
  display: flex; flex-direction: column; gap: var(--sp-5);
  overflow-y: auto;
  flex: 1 1 auto;
  min-height: 0;
}

/* ---------- Agreement pane ---------- */
.consent__section-label {
  font: var(--fw-semibold) var(--fs-label)/1 var(--font-cond);
  text-transform: uppercase; letter-spacing: var(--ls-label);
  color: var(--c-text-2);
  display: flex; justify-content: space-between; align-items: center; gap: var(--sp-3);
}
.consent__progress {
  font: var(--fw-bold) var(--fs-caption)/1 var(--font-mono);
  color: var(--c-warn-text);
}
.consent__progress[data-done="true"] { color: var(--c-ok-text); }
.consent__agreement {
  height: min(38vh, 380px);
  /* `flex: 0 0 auto` is LOAD-BEARING, not tidiness. This is a flex item in the column that is
     `.consent__body`, so it shrinks by default — and that body always overflows, because the
     whole point of this dialog is that it is longer than the screen. At 360x740 the pane
     collapsed from its 34vh (252 px) to **35 px** while holding 10,554 px of agreement text, so
     the one thing the flow requires — scrolling the agreement to its end — meant dragging a
     35 px window through the whole document. Found in a browser at phone width; a jsdom test
     cannot see it, because jsdom does not lay out. (2026-09-20) */
  flex: 0 0 auto;
  overflow-y: auto;
  padding: var(--sp-4) var(--sp-5);
  background: var(--c-surface-1);
  border: var(--bw) solid var(--c-line-strong);
  border-radius: var(--r-2);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-text);
  scroll-behavior: auto; /* never smooth: a smooth scroll can "arrive" before the user has read */
}
.consent__agreement:focus-visible { border-color: var(--c-focus); }
.consent__agreement h2, .consent__agreement h3 {
  font: var(--fw-semibold) var(--fs-h3)/var(--lh-tight) var(--font-ui);
  text-transform: none; letter-spacing: 0;
  margin: var(--sp-5) 0 var(--sp-2);
  color: var(--c-text);
}
.consent__agreement h2:first-child { margin-top: 0; }
.consent__agreement p { margin: 0 0 var(--sp-3); }
.consent__agreement ul { margin: 0 0 var(--sp-3); padding-left: var(--sp-5); }
.consent__agreement li { margin-bottom: var(--sp-2); }
.consent__agreement .consent__meta { color: var(--c-text-2); font-size: var(--fs-caption); }
.consent__end {
  margin-top: var(--sp-5);
  padding: var(--sp-3);
  border-top: var(--bw-2) dashed var(--c-line-strong);
  text-align: center;
  font: var(--fw-bold) var(--fs-label)/1 var(--font-cond);
  text-transform: uppercase; letter-spacing: var(--ls-label);
  color: var(--c-text-2);
}

/* ---------- Acknowledgement list ---------- */
.consent__acks { display: flex; flex-direction: column; gap: var(--sp-3); margin: 0; padding: 0; list-style: none; }
.consent__ack {
  display: grid; grid-template-columns: var(--tap) 1fr; align-items: start; gap: var(--sp-3);
  min-height: var(--tap);
  padding: var(--sp-3);
  background: var(--c-surface-1);
  border: var(--bw) solid var(--c-line-strong);
  border-radius: var(--r-2);
  cursor: pointer;
}
.consent__ack:hover { background: var(--c-surface-3); }
.consent__ack input[type="checkbox"] {
  width: 32px; height: 32px; margin: var(--sp-2) auto 0;
  accent-color: var(--c-accent);
  cursor: pointer;
}
.consent__ack input:focus-visible { outline: var(--bw-2) solid var(--c-focus); outline-offset: 3px; }
.consent__ack-text { font-size: var(--fs-body); line-height: var(--lh-body); color: var(--c-text); }
.consent__ack[data-checked="true"] { border-color: var(--c-ok); }
.consent__ack[data-checked="true"] .consent__ack-text::before { content: "✓ "; color: var(--c-ok-text); font-weight: var(--fw-bold); }

/* ---------- Typed phrase ---------- */
.consent__phrase .input {
  font-family: var(--font-mono); font-size: var(--fs-body-lg);
  text-transform: uppercase; letter-spacing: 0.06em;
  min-height: var(--tap-lg);
}
.consent__phrase-target {
  display: inline-block;
  font-family: var(--font-mono); font-weight: var(--fw-bold);
  padding: var(--sp-1) var(--sp-2);
  background: var(--c-surface-4); border-radius: var(--r-1);
  letter-spacing: 0.06em;
  user-select: none; -webkit-user-select: none;   /* type it, don't copy it */
}

/* ---------- Missing list (why Accept is still off) ---------- */
.consent__missing {
  margin: 0; padding: var(--sp-3) var(--sp-4);
  list-style: none;
  background: var(--c-surface-1);
  border-left: var(--bw-3) solid var(--c-warn);
  border-radius: var(--r-1);
  font-size: var(--fs-caption);
  color: var(--c-text-2);
}
.consent__missing li + li { margin-top: var(--sp-1); }
.consent__missing:empty { display: none; }

/* ---------- Footer: two equal choices ---------- */
.consent__foot {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-top: var(--bw) solid var(--c-line);
  background: var(--c-surface-1);
  flex: 0 0 auto;
}
.consent__foot .btn { min-height: var(--tap-lg); font-size: var(--fs-h3); white-space: normal; }
.consent__foot .btn--accept { background: var(--c-bad); color: var(--c-on-bad); border-color: var(--c-bad); }
.consent__foot .btn--accept:hover:not([disabled]) { filter: brightness(1.08); }
.consent__foot .btn--accept[disabled] { opacity: .45; cursor: not-allowed; }
.consent__foot .btn--decline { background: var(--c-surface-3); color: var(--c-text); }
@media (max-width: 599px) {
  .consent__foot { grid-template-columns: 1fr; }
  .consent__agreement { height: 34vh; }
}

/* ---------- Persistent ADVANCED MODE banner ---------- */
.advanced-banner {
  position: fixed; left: 0; right: 0; bottom: var(--footer-h);
  z-index: var(--z-toast);
  display: flex; align-items: center; gap: var(--sp-3);
  min-height: var(--tap);
  padding: var(--sp-2) var(--gutter);
  background: var(--c-warn); color: var(--c-on-warn);
  border-top: var(--bw-2) solid var(--c-on-warn);
  font: var(--fw-bold) var(--fs-label)/1 var(--font-cond);
  text-transform: uppercase; letter-spacing: var(--ls-label);
}
.advanced-banner[hidden] { display: none; }
.advanced-banner svg { width: 26px; height: 26px; flex: 0 0 auto; }
.advanced-banner__text { flex: 1 1 auto; }
.advanced-banner__meta { font: var(--fw-medium) var(--fs-caption)/1 var(--font-mono); text-transform: none; letter-spacing: 0; opacity: .9; }
.advanced-banner .btn { min-height: var(--tap); background: var(--c-on-warn); color: var(--c-warn); border-color: var(--c-on-warn); }

/* ---------- Per-action confirmation card ---------- */
.action-confirm {
  border: var(--bw-2) solid var(--c-warn);
  border-radius: var(--r-3);
  background: var(--c-surface-2);
  padding: var(--sp-5);
  display: flex; flex-direction: column; gap: var(--sp-4);
}
.action-confirm[data-risk="high"] { border-color: var(--c-bad); }
.action-confirm__type { font: var(--fw-semibold) var(--fs-label)/1 var(--font-cond); text-transform: uppercase; letter-spacing: var(--ls-label); color: var(--c-text-2); }
.action-confirm__title { font: var(--fw-bold) var(--fs-h2)/var(--lh-tight) var(--font-cond); text-transform: uppercase; }
.action-confirm__risk { display: inline-flex; align-items: center; gap: var(--sp-1); min-height: 32px; padding: 0 var(--sp-3); border-radius: var(--r-1); font: var(--fw-bold) var(--fs-caption)/1 var(--font-cond); text-transform: uppercase; letter-spacing: 0.06em; }
.action-confirm__risk[data-risk="low"] { background: var(--c-info); color: var(--c-on-info); }
.action-confirm__risk[data-risk="medium"] { background: var(--c-warn); color: var(--c-on-warn); }
.action-confirm__risk[data-risk="high"], .action-confirm__risk[data-risk="unknown"] { background: var(--c-bad); color: var(--c-on-bad); }
.action-confirm__lines { margin: 0; padding-left: var(--sp-5); font-size: var(--fs-body); line-height: var(--lh-body); }
.action-confirm__lines li + li { margin-top: var(--sp-2); }
.action-confirm__hazards { margin: 0; padding: var(--sp-3) var(--sp-4); list-style: none; background: var(--c-surface-1); border-left: var(--bw-3) solid var(--c-bad); border-radius: var(--r-1); font-size: var(--fs-body); }
.action-confirm__hazards li { display: flex; gap: var(--sp-2); }
.action-confirm__hazards li::before { content: "!"; font-weight: var(--fw-bold); color: var(--c-bad-text); flex: 0 0 auto; }
.action-confirm__hazards li + li { margin-top: var(--sp-2); }
.action-confirm__unverified { font-size: var(--fs-caption); color: var(--c-warn-text); font-weight: var(--fw-semibold); }

/* ---------- Hold-to-run (dead-man) control ---------- */
.hold-run {
  position: relative;
  width: 100%; min-height: calc(var(--tap-lg) * 1.5);
  border-radius: var(--r-3);
  border: var(--bw-2) solid var(--c-accent);
  background: var(--c-surface-3); color: var(--c-text);
  font: var(--fw-bold) var(--fs-h2)/1 var(--font-cond);
  text-transform: uppercase; letter-spacing: 0.06em;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--sp-2);
  touch-action: none;          /* a scroll gesture must not become a half-press */
  user-select: none; -webkit-user-select: none;
  overflow: hidden;
}
.hold-run__hint { font: var(--fw-medium) var(--fs-caption)/1 var(--font-ui); text-transform: none; letter-spacing: 0; color: var(--c-text-2); }
.hold-run[data-state="armed"] { background: var(--c-accent); color: var(--c-on-accent); }
.hold-run[data-state="armed"] .hold-run__hint { color: var(--c-on-accent); }
.hold-run[data-state="pressed"] { background: var(--c-bad); color: var(--c-on-bad); border-color: var(--c-bad); }
.hold-run[data-state="pressed"] .hold-run__hint { color: var(--c-on-bad); }
.hold-run[data-state="pressed"]::after {
  /* drains left→right over the bounded duration set on --hold-ms; purely informative */
  content: ""; position: absolute; left: 0; bottom: 0; height: 6px; width: 100%;
  background: var(--c-on-bad); opacity: .6;
  transform-origin: left; animation: hold-drain var(--hold-ms, 10000ms) linear forwards;
}
@keyframes hold-drain { from { transform: scaleX(1); } to { transform: scaleX(0); } }
.hold-run[data-state="done"] { background: var(--c-surface-4); border-color: var(--c-line-strong); color: var(--c-text-2); }
.hold-run[disabled] { opacity: .45; cursor: not-allowed; }
@media (prefers-reduced-motion: reduce) { .hold-run[data-state="pressed"]::after { animation: none; } }

/* shell/shell.css */
/* =====================================================================
   app/shell/shell.css — the combined shell's own layout.
   =====================================================================
   Tokens only; no colour literals, no font-size below 16px. This file adds
   THREE things to the design system and overrides nothing in it:

     1. `.shellnav`      the persistent primary navigation
     2. `.onboard`       the landing screen's "no scanner needed" panel
     3. `data-textsize`  the shell's fourth theme axis (Settings → Text size)

   `design/` is D4's and the scanner keeps a byte-exact synced copy of it, so
   nothing here may be moved into it without re-running app/scanner/sync-design.mjs.
   ===================================================================== */

:root {
  --shellnav-h: 72px;     /* the horizontal strip below 900px */
  --shellnav-w: 104px;    /* the vertical rail at 900px and up */
}

/* ---------- 1. Primary navigation ---------- */
/* Below 900px it sits directly ABOVE the hardware-key bar. Anchoring from the
   bottom rather than the top is deliberate: under 600px the status bar is
   `height: auto` and wraps its chips onto a second row, so a fixed top offset
   lands either over the chips or below a gap. The bottom edge is a known
   height (`--footer-h`). */
.shellnav {
  position: fixed;
  inset: auto 0 var(--footer-h) 0;
  z-index: var(--z-footer);
  height: var(--shellnav-h);
  background: var(--c-surface-1);
  border-top: var(--bw) solid var(--c-line);
}
.shellnav__list {
  display: flex; align-items: stretch; gap: var(--sp-1);
  height: 100%; margin: 0; padding: var(--sp-1) var(--sp-2);
  list-style: none;
  overflow-x: auto; overflow-y: hidden;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.shellnav__list::-webkit-scrollbar { display: none; }
.shellnav__item { flex: 0 0 auto; display: flex; }
.shellnav__btn {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  min-width: var(--tap); min-height: calc(var(--shellnav-h) - var(--sp-2));
  padding: 0 var(--sp-2);
  background: transparent; border: var(--bw) solid transparent; border-radius: var(--r-2);
  color: var(--c-text-2);
  font: var(--fw-bold) var(--fs-caption)/1 var(--font-cond);
  text-transform: uppercase; letter-spacing: var(--ls-label);
  cursor: pointer;
}
.shellnav__btn:hover { background: var(--c-surface-2); color: var(--c-text); }
.shellnav__btn[data-active="true"] {
  background: var(--c-surface-3);
  border-color: var(--c-accent);
  color: var(--c-accent-text);
}
.shellnav__icon { display: block; width: 28px; height: 28px; }
.shellnav__icon svg { width: 100%; height: 100%; }
.shellnav__label { display: block; white-space: nowrap; }
.shellnav__lock { display: none; }
.shellnav__btn[data-locked="true"] { color: var(--c-text-3); }
.shellnav__btn[data-locked="true"] .shellnav__lock { display: block; width: 18px; height: 18px; }
.shellnav__btn[data-needs-link="true"] .shellnav__label { opacity: 0.75; }

@media (min-width: 900px) {
  .shellnav {
    inset: var(--statusbar-h) auto 0 0;
    width: var(--shellnav-w); height: auto;
    border-top: 0; border-right: var(--bw) solid var(--c-line);
  }
  .shellnav__list {
    flex-direction: column; gap: var(--sp-2);
    padding: var(--sp-3) var(--sp-2);
    overflow-x: hidden; overflow-y: auto;
  }
  .shellnav__item { flex: 0 0 auto; }
  .shellnav__btn { width: 100%; min-height: var(--tap-lg); }
  /* Content, the hardware-key bar and the bottom sheet all clear the rail. */
  .app__content { padding-left: calc(var(--shellnav-w) + var(--gutter)); }
  .hwbar { left: var(--shellnav-w); }
  .sheet { left: var(--shellnav-w); bottom: var(--footer-h); }
}

/* Below 900px everything above the hardware-key bar has to clear the strip too. */
@media (max-width: 899px) {
  .app__content { padding-bottom: calc(var(--footer-h) + var(--shellnav-h) + var(--sp-6)); }
  .sheet { bottom: calc(var(--footer-h) + var(--shellnav-h)); }
  .advanced-banner { bottom: calc(var(--footer-h) + var(--shellnav-h)); }
}

/* ---------- 2. The landing screen's onboarding panel ---------- */
.screen--diagnose { display: block; }
.onboard {
  display: grid; gap: var(--sp-4);
  padding: var(--sp-5) var(--sp-4);
  margin-bottom: var(--sp-4);
  background: var(--c-surface-2);
  border: var(--bw) solid var(--c-line);
  border-top: var(--bw-3) solid var(--c-accent);
  border-radius: var(--r-3);
}
.onboard__head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-3); }
.onboard__eyebrow {
  margin: 0; color: var(--c-accent-text);
  font: var(--fw-bold) var(--fs-label)/1.2 var(--font-cond);
  text-transform: uppercase; letter-spacing: var(--ls-label);
}
.onboard__title {
  margin: var(--sp-2) 0 0;
  font: var(--fw-bold) var(--fs-h1)/var(--lh-tight) var(--font-cond);
  color: var(--c-text);
}
.onboard__pitch { margin: 0; font-size: var(--fs-body-lg); line-height: var(--lh-body); color: var(--c-text); }
.onboard__pitch strong { color: var(--c-accent-text); }
.onboard__steps {
  display: grid; gap: var(--sp-3); margin: 0; padding: 0; list-style: none;
  grid-template-columns: 1fr;
}
@media (min-width: 600px)  { .onboard__steps { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1100px) { .onboard__steps { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.onboard__step {
  display: grid; gap: var(--sp-1);
  grid-template-columns: auto 1fr;
  grid-template-areas: "num icon" "title title" "body body";
  padding: var(--sp-3);
  background: var(--c-surface-3);
  border: var(--bw) solid var(--c-line);
  border-radius: var(--r-2);
}
.onboard__num {
  grid-area: num; color: var(--c-accent-text);
  font: var(--fw-bold) var(--fs-h3)/1 var(--font-mono);
}
.onboard__icon { grid-area: icon; justify-self: end; color: var(--c-line-strong); width: 26px; height: 26px; }
.onboard__icon svg { width: 100%; height: 100%; }
.onboard__step-title {
  grid-area: title; margin: var(--sp-2) 0 0;
  font: var(--fw-bold) var(--fs-h3)/var(--lh-tight) var(--font-cond);
  color: var(--c-text);
}
.onboard__step-body { grid-area: body; margin: 0; font-size: var(--fs-body); line-height: var(--lh-body); color: var(--c-text-2); }
.onboard__foot { margin: 0; font-size: var(--fs-caption); color: var(--c-text-2); }
.onboard-recall { margin-bottom: var(--sp-3); }

/* ---------- 3. Settings rows ---------- */
.setting { display: grid; gap: var(--sp-2); padding: var(--sp-3) 0; border-bottom: var(--bw) solid var(--c-line); }
.setting:last-child { border-bottom: 0; }
.setting__head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.setting__label { font: var(--fw-semibold) var(--fs-body-lg)/1.2 var(--font-ui); color: var(--c-text); }
.setting__help { margin: 0; max-width: 62ch; }

/* ---------- 4. Text size — the shell's fourth theme axis ---------- */
/* The design system's type tokens are absolute px (Ian's "nothing below 16px"
   only means something if the number is real), so scaling `html{font-size}`
   would move nothing. These redefine the tokens themselves. */
:root[data-textsize="lg"] {
  --fs-caption: 18px; --fs-label: 18px; --fs-body: 20px; --fs-body-lg: 23px;
  --fs-h3: 25px; --fs-h2: 30px; --fs-h1: 36px;
  --fs-value-sm: 36px; --fs-value: 46px; --fs-value-lg: 62px; --fs-value-xl: 80px;
  --tap: 64px; --tap-lg: 80px;
}
:root[data-textsize="xl"] {
  --fs-caption: 20px; --fs-label: 20px; --fs-body: 23px; --fs-body-lg: 26px;
  --fs-h3: 29px; --fs-h2: 34px; --fs-h1: 42px;
  --fs-value-sm: 40px; --fs-value: 52px; --fs-value-lg: 70px; --fs-value-xl: 92px;
  --tap: 72px; --tap-lg: 88px;
  --shellnav-h: 84px; --shellnav-w: 120px;
}

/* ---------- 5. Reduced motion ---------- */
/* The Settings switch is an explicit override; the media query still applies on
   its own for users who set it at the OS level. */
:root[data-motion="reduced"] *,
:root[data-motion="reduced"] *::before,
:root[data-motion="reduced"] *::after {
  animation-duration: 0.001ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.001ms !important;
}

/* ---------- 6. noscript ---------- */
.noscript {
  margin: 0; padding: var(--sp-5);
  font: var(--fw-bold) var(--fs-body-lg)/var(--lh-body) var(--font-ui);
  color: var(--c-text); background: var(--c-bg);
}

/* ---------- 7. Hosts ---------- */
.perf-host, .garage-host, .diag-onboard-host, .diag-link-host { display: block; }
.row--action { display: flex; align-items: center; gap: var(--sp-3); width: 100%; text-align: left; }
