/* Self-hosted Inter variable font (unicode-ranges from @fontsource). */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url('/fonts/inter-latin-wght-normal.woff2') format('woff2-variations');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url('/fonts/inter-latin-ext-wght-normal.woff2') format('woff2-variations');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Self-hosted Barlow Condensed (from @fontsource) — the canvas widgets' timing
   face. Latin only, and only the two weights they draw: labels and figures,
   never driver names. Canvas text does NOT trigger a webfont fetch, so these
   are also loaded explicitly at startup (see CANVAS_FACES in overlay.ts);
   without that a canvas asking for this face draws in the fallback and says
   nothing about it. */
@font-face {
  font-family: 'Barlow Condensed';
  font-style: normal;
  font-display: swap;
  font-weight: 600;
  src: url('/fonts/barlow-condensed-latin-600-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Barlow Condensed';
  font-style: normal;
  font-display: swap;
  font-weight: 700;
  src: url('/fonts/barlow-condensed-latin-700-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* The hidden attribute is only UA-level display:none — any author display
   rule (e.g. .bnr { display: flex }) silently defeats it. Every state swap
   in the dashboard relies on hidden, so make it absolute. */
[hidden] {
  display: none !important;
}

html,
body {
  background: transparent;
  overflow: hidden;
  font-family: "Segoe UI", system-ui, sans-serif;
  user-select: none;
  width: 100%;
  height: 100%;
}

/* ---- DDU surface (/ddu) ----
   The same page as the overlay, shown on a dedicated screen instead of over
   the sim: opaque black behind the letterbox (a DDU has nothing behind it),
   no cursor, and no touch gestures — a wheel screen is watched, not used. */
body.ddu {
  background: #000;
  cursor: none;
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
}
/* The widget guide's frames are DDU pages viewed with a mouse. */
body.ddu.demo {
  cursor: auto;
}

/* The DDU dashboard ships with NO rev/saver strip: a wheel screen has its
   own rev lights, and the lift cue lives in the standalone Fuel saver lights
   widget. The Second-screen options can add either back — the shell stamps
   .ddu-strip on the body when a tick is on, which reserves the slot
   permanently (never appearing/disappearing mid-race). display:none (not the
   desktop dash's visibility trick) — the freed 20px slot flows into the
   flexible main row, and the card keeps its native 720x405, so the 16:9
   screen fit is unchanged. IDs, not classes: the standalone widget's rail
   reuses .dash-liftrail and must survive on a /ddu?widget=saverlights
   page. */
body.ddu:not(.ddu-strip) #d-leds,
body.ddu:not(.ddu-strip) #d-liftrail {
  display: none;
}

/* ---- widget frame ---- */

.widget {
  position: absolute;
  touch-action: none;
  /* User-set widget opacity (options page), fed via the custom property so
     the .peek-hidden class below still wins — an inline opacity would
     override it and break the hover fade. */
  opacity: var(--w-op, 1);
  /* "Peek away": fade to transparent on hover when out of the car, fade back
     when the cursor leaves (renderer removes .peek-hidden after a delay). */
  transition: opacity 0.35s ease;
}

/* Faded out (opacity only, so the box still catches the cursor / mouseleave —
   and the window is click-through regardless, so the sim stays clickable). */
.widget.peek-hidden {
  opacity: 0;
}

.widget .card {
  width: 100%;
  height: 100%;
}

/* Spotter canvas fills its widget rect; drawing is imperative (design 23). */
#w-spotter canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* Track map: same deal, and deliberately NO background — every mark carries
   its own contrast so the widget works over sky and over a black cockpit. */
#w-trackmap canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* Ghost probe drawing surface: spans the whole window (which spans the whole
   display while the probe is visible — overlayLayout forces it). Under every
   widget (z-index) — markers are road furniture, not chrome — and never a
   pointer target. Sized by the renderer to the window, drawn in the rAF loop. */
#gp-canvas {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: -1;
}

.editlabel {
  display: none;
  position: absolute;
  top: -18px;
  left: 6px;
  font-size: 11px;
  color: rgba(255, 255, 255, 0.85);
  background: rgba(30, 90, 200, 0.85);
  padding: 1px 8px;
  border-radius: 3px 3px 0 0;
  white-space: nowrap;
}

body.edit .widget {
  cursor: move;
  outline: 1.5px dashed rgba(80, 150, 255, 0.9);
  outline-offset: 2px;
}

body.edit .widget:hover,
body.edit .widget.dragging {
  outline-style: solid;
}

/* Orphan: a widget whose monitor is unplugged, parked on the primary window's
   edge for retrieval (edit mode only — locked mode hides it and its stored
   spot survives a re-plug). Amber marks "this is not where it lives". */
body.edit .widget.orphan {
  outline-color: rgba(255, 170, 60, 0.9);
}

body.edit .editlabel {
  display: block;
}

#grid {
  position: absolute;
  inset: 0;
  pointer-events: none;
  --gs: 16px;
  background-image:
    linear-gradient(to right, rgba(80, 150, 255, 0.16) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(80, 150, 255, 0.16) 1px, transparent 1px);
  background-size: var(--gs) var(--gs);
}

body.edit {
  background: rgba(10, 12, 18, 0.25);
}

/* ---- pit-wall widget (the /pitwall page in a frame) ---- */
/* The card is sized by zoomCard to the design 1920x1080 and zoomed to the
   rect; the frame fills it exactly. Opaque page ground behind the frame so
   the letterbox during load never flashes the sim through. */
.pwl-host {
  width: 100%;
  height: 100%;
  background: #0b0c12;
  border-radius: 8px;
  overflow: hidden;
}
.pwl-host iframe {
  width: 1920px;
  height: 1080px;
  border: 0;
  display: block;
}
/* Edit mode: the frame swallows pointer events, so a grab only landed on the
   edit label (tester tanguy, 08-25 — "only drag it via top left corner").
   Dead to the mouse while editing, the whole card drags like any widget. */
body.edit .pwl-host iframe {
  pointer-events: none;
}

/* ---- shared card chrome ---- */

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 14px;
  border-radius: 10px;
  background: rgba(12, 14, 20, 0.78);
  border: 1px solid rgba(255, 255, 255, 0.08);
}

.card.disconnected {
  opacity: 0.55;
}

.row {
  display: flex;
  gap: 8px;
}

.cell {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 0;
}

.cell.wide {
  flex: 1.6;
}

.label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: rgba(255, 255, 255, 0.55);
}

.value {
  font-variant-numeric: tabular-nums;
  font-size: 22px;
  font-weight: 600;
  line-height: 1.2;
  color: #fff;
  white-space: nowrap;
}

.value.small {
  font-size: 18px;
}

.relative {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px 8px;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.05);
}

.relrow {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-size: 14px;
  color: #fff;
}

.relrow .arrow {
  font-size: 10px;
  width: 12px;
}

.relrow.ahead .arrow {
  color: #e0483e;
}

.relrow.behind .arrow {
  color: #3e9de0;
}

.relnum {
  font-variant-numeric: tabular-nums;
  color: rgba(255, 255, 255, 0.6);
  min-width: 30px;
}

.relname {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.relname.othercls {
  color: rgba(255, 255, 255, 0.55);
}

.relname.inpit {
  font-style: italic;
  color: rgba(255, 255, 255, 0.4);
}

.relgap {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.status {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #e0483e; /* red = no data */
}

.status.live {
  background: #3ecf6a;
}

.status.mock {
  background: #e5b13e; /* amber = mock data */
}

/* ---- fuel calculator widget ---- */

/* Design 42a (docs/FUEL-CALCULATOR.md), replacing 35b. Per-skin tokens restate
   the dash values (pscard pattern) so the panel reads as the same family; the
   two basis accents (MAX SEEN violet, LAST teal) are the widget's own, kept
   against the handoff's brightness-only proposal (Marcus, 08-01) — a row has
   to be identifiable in peripheral vision without being read.

   Every chip fill, channel ground and state tint below is a color-mix off the
   skin's own ground and state inks, never a literal hex, so the light (paper)
   skin works from the same rules as the dark three. */
.fuelmatrix {
  --fm-ground: #161826; /* --bg */
  --fm-hair1: #292b31; /* --hairline (internal rules) */
  --fm-hair2: #3f424d; /* --frame (card border) */
  --fm-text: #e9e9ed; /* --text: AVERAGE row + header figures */
  --fm-dim: #75798c; /* --dim: column heads, est, chip SHORT */
  --fm-green: #21d07a; /* --green: ok lamp, chip OPEN */
  --fm-amber: #ffb020; /* --amber: close lamp, chip 2 LAPS */
  --fm-red: #ff5252; /* --red: short lamp, chip <1 LAP */
  --fm-blue: #56b8ff; /* fst-blue: chip PITS COMPLETE */
  --fm-violet: #9184d9; /* MAX SEEN */
  --fm-teal: #7fb2a6; /* LAST */
  --fm-est: #8a7550; /* warm parentheses on a race length still moving */

  /* Derived surfaces (42a). All color-mix off the tokens above, so a skin only
     ever redefines ground + state inks and these follow. */
  --fm-channel: color-mix(in srgb, var(--fm-ground) 88%, var(--fm-dim) 12%);
  --fm-chan-warn: color-mix(in srgb, var(--fm-ground) 80%, var(--fm-amber) 20%);
  /* Chip fills. Grey = pitting here would add a stop; steel = no stop left to
     add, so it is plain fuel; the green is deeper than the lamp green because
     it is a filled area, not a dot. */
  --fm-chip-grey: color-mix(in srgb, var(--fm-ground) 70%, var(--fm-dim) 30%);
  --fm-chip-steel: color-mix(in srgb, var(--fm-ground) 38%, var(--fm-dim) 62%);
  --fm-chip-green: color-mix(in srgb, var(--fm-green) 76%, var(--fm-ground) 24%);
  --fm-chip-past: color-mix(in srgb, var(--fm-ground) 84%, var(--fm-dim) 16%);
  /* The chequered chip is the panel's maximum-contrast ink. It gets its own
     token because the monochrome amber skin makes --fm-text and --fm-amber
     the SAME colour, which would leave the flag indistinguishable from the
     two-laps-of-fuel chip sitting right beside it. */
  --fm-chip-flag: var(--fm-text);
  --fm-brk-rule: color-mix(in srgb, var(--fm-green) 40%, var(--fm-ground));
  --fm-brk-lbl: color-mix(in srgb, var(--fm-green) 62%, var(--fm-ground));

  /* Height follows content (VARIABLE_HEIGHT), never the widget box — a font
     metric drifting a pixel must clip nothing. */
  height: auto;
  gap: 0;
  padding: 0;
  background: var(--fm-ground);
  border: 1px solid var(--fm-hair2);
  border-radius: 8px;
  font-variant-numeric: tabular-nums;
  overflow: hidden;
}

.fuelmatrix[data-theme='amber'] {
  --fm-ground: #0b0a07;
  --fm-hair1: #241f12;
  --fm-hair2: #2e2617;
  --fm-text: #ffb020;
  --fm-dim: #6b5a38;
  --fm-green: #21d07a;
  --fm-amber: #ffb020;
  --fm-red: #ff4438;
  --fm-est: #8a6a2c;
  /* Paler than the amber chip beside it — this skin's text ink IS the amber. */
  --fm-chip-flag: #ffe6b8;
}

.fuelmatrix[data-theme='paper'] {
  --fm-ground: #f5f5f6;
  --fm-hair1: #e0e0e4;
  --fm-hair2: #c9c9ce;
  --fm-text: #1b1d1f;
  --fm-dim: #9a9da3;
  --fm-green: #149655;
  --fm-amber: #a06800;
  --fm-red: #d92b20;
  --fm-blue: #2d7dd2;
  /* Darkened for contrast on the light ground. */
  --fm-violet: #6558b8;
  --fm-teal: #2f7d6d;
  --fm-est: #8a6a2c;
  /* On a light ground the mixes above run the wrong way — a chip has to be
     DARKER than the panel to read as filled, not lighter. Restated rather
     than derived, since "toward the ground" is the wrong direction here. */
  --fm-chip-grey: #d3d4d9;
  --fm-chip-steel: #8f939c;
  --fm-chip-green: #149655;
  --fm-chip-past: #e4e4e8;
}

.fuelmatrix[data-theme='slate'] {
  --fm-ground: #10141c;
  --fm-hair1: #1b2230;
  --fm-hair2: #232c3d;
  --fm-text: #f0f3f8;
  --fm-dim: #6b7383;
  --fm-green: #3ddc84;
  --fm-amber: #b3791a;
  --fm-red: #ff5252;
}

/* ---- 1. header: four facts that are simply true ---- */
/* Baseline-aligned flex, SET FILL right-justified to the panel edge so the row
   fills the width without a hole. */
.fm-hdr {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 14px;
  padding: 9px 14px 10px;
  border-bottom: 1px solid var(--fm-hair1);
}

.fm-cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.fm-cell.fm-right {
  align-items: flex-end;
  text-align: right;
  /* Anchors the short flag, which hangs BELOW the value out of flow — so its
     width never widens this cell and can never nudge the value or the other
     header cells (no-dynamic-reposition). */
  position: relative;
}

.fm-h {
  font-size: 11px;
  letter-spacing: 0.09em;
  color: var(--fm-dim);
}

.fm-line {
  display: flex;
  align-items: baseline;
  gap: 5px;
}

.fm-big {
  font-size: 24px;
  font-weight: 700;
  line-height: 27px;
  letter-spacing: -0.01em;
  color: var(--fm-text);
  white-space: nowrap;
}

.fm-unit {
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--fm-dim);
}

/* The raw projection, in the standings board's grammar: warm parentheses that
   vanish the moment the race distance stops moving. */
.fm-raw {
  font-size: 13px;
  font-weight: 500;
  color: var(--fm-est);
}

/* SET FILL has three inks and only one is a warning (42a): steel once no stop
   remains to spend it, amber only when exactly one does and it is short. */
.fm-big.fm-settled {
  color: var(--fm-dim);
}

.fm-big.fm-shortfill {
  color: var(--fm-amber);
}

.fm-shortflag {
  position: absolute;
  top: 100%;
  right: 0;
  /* Sits just under the value, straddling the header hairline; the ground fill
     masks the rule so it reads as a clean warning chip in any theme. */
  margin-top: -1px;
  padding: 1px 4px;
  background: var(--fm-ground);
  border-radius: 3px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.05em;
  color: var(--fm-amber);
  white-space: nowrap;
  pointer-events: none;
}

/* ---- 3. table ---- */
.fm-tbl {
  padding: 0 14px 8px;
}

.fm-trow {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr 1.15fr;
  column-gap: 12px;
  align-items: center;
}

.fm-thead {
  padding: 7px 0 4px;
}

.fm-trow:not(.fm-thead) {
  height: 30px;
  border-top: 1px solid var(--fm-hair1);
}

.fm-trow.fm-avg {
  border-top: none;
}

.fm-r {
  text-align: right;
}

.fm-name {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.09em;
}

.fm-fig {
  font-size: 20px;
  font-weight: 700;
  line-height: 23px;
}

/* Each basis owns one ink across the whole row (row = one object). */
.fm-avg .fm-fig,
.fm-avg .fm-name {
  color: var(--fm-text);
}

.fm-max .fm-fig,
.fm-max .fm-name {
  color: var(--fm-violet);
}

.fm-last .fm-fig,
.fm-last .fm-name {
  color: var(--fm-teal);
}

/* PITS sits on an inked channel that bleeds into the gutter each side and runs
   from the head row to the last row as one continuous surface. The column is
   sized and aligned like every other; the ground is the only thing marking it
   as the number that decides the race. */
.fm-chan {
  background: var(--fm-channel);
  margin: 0 -6px;
  padding: 0 6px;
  text-align: right;
}

.fm-thead .fm-chan {
  margin-top: -7px;
  padding-top: 9px;
  margin-bottom: -4px;
  padding-bottom: 4px;
}

.fm-trow:not(.fm-thead) .fm-chan {
  height: 30px;
  line-height: 30px;
}

/* A basis whose stop count disagrees with AVERAGE tints its OWN channel cell.
   No banner, no row recolour — a 3/4/3 split then lands before a 3.30/3.40 one
   does, which is the whole point of the column. */
/* Both selectors below are (0,2,0), same as the basis inks above — they win on
   source order, so no !important is needed to beat the row colour. */
.fm-chan.fm-disagree {
  background: var(--fm-chan-warn);
  color: var(--fm-amber);
}

/* A short margin overrides the basis ink: the row is still identifiable by its
   three other figures, and this is the one number worth shouting. `close` is
   left alone — the lamp carries it without costing the row its identity. */
.fm-fig.fm-short {
  color: var(--fm-red);
}

.fm-res {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  white-space: nowrap;
}

/* SAVE is a prefix, not a value: the figure keeps the table's 20px rhythm and
   the column stays one line. */
.fm-pfx {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.1em;
  color: var(--fm-red);
}

.fm-lamp {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex: none;
  background: transparent;
}

.fm-lamp.ok {
  background: var(--fm-green);
}

.fm-lamp.close {
  background: var(--fm-amber);
}

.fm-lamp.short {
  background: var(--fm-red);
}

/* ---- 2. pit range: when can I act ---- */
.fm-range {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 8px 14px 9px;
  border-bottom: 1px solid var(--fm-hair1);
}

/* The lap-chip strip is a canvas drawn on the paint loop: a continuously-
   resizing strip whose leading chip is the remaining fraction of the current
   lap, coarsening as fuel burns. Chip fills, the bracket hairline and its
   caption are all resolved from the skin tokens above (via a probe), so every
   skin works from one set of rules. Canvas rather than DOM chips because a
   smoothly-resizing strip is high-frequency (the project's rule) and it removes
   the animation-ghost class of bugs a hidden overlay/OBS source produced. */
.fm-strip {
  flex: 1;
  min-width: 0;
  height: 33px;
  display: block;
}

/* PIT chip: head-over-value grammar like the figures beside it. One state ink
   (--fm-ink); ground tint and hairline border derive from it via color-mix so
   they track every skin's ground. Fixed width so the strip's left edge never
   moves when the word inside changes. Nothing blinks. */
.fm-pit {
  --fm-ink: var(--fm-dim); /* window not yet open / no data */
  display: flex;
  align-items: stretch;
  gap: 9px;
  width: 138px;
  box-sizing: border-box;
  flex: none;
  border: 1px solid color-mix(in srgb, var(--fm-ink) 20%, transparent);
  border-radius: 3px;
  padding: 4px 9px 5px;
  background: color-mix(in srgb, var(--fm-ground) 90%, var(--fm-ink) 10%);
}

.fm-pit.open {
  --fm-ink: var(--fm-green);
}

.fm-pit.low {
  --fm-ink: var(--fm-amber);
}

.fm-pit.crit {
  --fm-ink: var(--fm-red);
}

.fm-pit.done {
  --fm-ink: var(--fm-blue);
}

.fm-pitbar {
  width: 3px;
  border-radius: 2px;
  background: var(--fm-ink);
}

.fm-pitstack {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.fm-pitlbl {
  font-size: 11px;
  letter-spacing: 0.12em;
  color: color-mix(in srgb, var(--fm-ink) 70%, transparent);
}

.fm-pitword {
  font-size: 17px;
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: 0.01em;
  color: var(--fm-ink);
  white-space: nowrap;
}

/* ---- pit-strategy traffic widget (design 28a, PIT-TRAFFIC handoff) ------ */
/* Palette mirrors the DASHBOARD tokens 1:1 per skin so the widget reads as the
   same panel family (ground=--bg, hair1=--hairline, hair2=--frame/border,
   text/muted/dim, pos=--green, amber=--amber-label). The card is a sibling of
   .dash (overlay.ts mirrors data-theme onto it), so the dash values are
   restated here, not inherited. Nocturne (default) below; other skins override.
   The two tints derive from the ground via color-mix, so they track any skin
   automatically. */
.pscard {
  --ps-ground: #161826; /* --bg */
  --ps-hair1: #292b31; /* --hairline */
  --ps-hair2: #3f424d; /* --frame (card border) */
  --ps-text: #e9e9ed; /* --text */
  --ps-muted: #9397ab; /* --muted */
  --ps-dim: #75798c; /* --dim */
  --ps-pos: #21d07a; /* --green */
  --ps-amber: #b98617; /* --amber-label */
  --ps-pos-tint: color-mix(in srgb, var(--ps-ground) 88%, var(--ps-pos) 12%);
  --ps-amber-tint: color-mix(in srgb, var(--ps-ground) 88%, var(--ps-amber) 12%);
  --ps-rowh: 20px;
  --ps-rowgap: 3px;

  width: 240px;
  box-sizing: border-box;
  background: var(--ps-ground);
  border: 1px solid var(--ps-hair2);
  border-radius: 8px;
  padding: 11px 12px 10px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  color: var(--ps-text);
  font-variant-numeric: tabular-nums;
}

/* Per-skin: restate the dash tokens for this skin (ground=--bg, hair1=--hairline,
   hair2=--frame, text/muted/dim, pos=--green, amber=--amber-label). Tints are
   inherited from the base color-mix, so they track each skin's ground. */
.pscard[data-theme='amber'] {
  --ps-ground: #0b0a07;
  --ps-hair1: #241f12;
  --ps-hair2: #2e2617;
  --ps-text: #ffb020;
  --ps-muted: #8a7245;
  --ps-dim: #6b5a38;
  --ps-pos: #21d07a;
  --ps-amber: #8a7245;
}

.pscard[data-theme='paper'] {
  --ps-ground: #f5f5f6;
  --ps-hair1: #e0e0e4;
  --ps-hair2: #c9c9ce;
  --ps-text: #1b1d1f;
  --ps-muted: #6f7379;
  --ps-dim: #9a9da3;
  --ps-pos: #149655;
  --ps-amber: #a06800;
}

.pscard[data-theme='slate'] {
  --ps-ground: #10141c;
  --ps-hair1: #1b2230;
  --ps-hair2: #232c3d;
  --ps-text: #f0f3f8;
  --ps-muted: #8a92a3;
  --ps-dim: #6b7383;
  --ps-pos: #3ddc84;
  --ps-amber: #b3791a;
}

.ps-titlerow {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  white-space: nowrap;
}

.ps-title {
  font-size: 13px;
  letter-spacing: 0.1em;
  color: var(--ps-muted);
}

.ps-scope {
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--ps-dim);
}

.ps-verdict {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-top: 2px;
  white-space: nowrap;
}

.ps-vhero {
  font-size: 22px;
  font-weight: 700;
  line-height: 1.15;
}

.ps-vsuffix {
  font-size: 14px;
  font-weight: 500;
}

.ps-verdict.best .ps-vhero,
.ps-verdict.best .ps-vsuffix {
  color: var(--ps-pos);
}

/* Fuel is deciding (even / forced onto the last lap): amber, not neutral. */
.ps-verdict.even .ps-vhero,
.ps-verdict.even .ps-vsuffix,
.ps-verdict.forced .ps-vhero {
  color: var(--ps-amber);
}

.ps-verdict.calib .ps-vhero {
  color: var(--ps-dim);
}

.ps-divider {
  height: 1px;
  background: var(--ps-hair1);
  margin: 7px 0 6px;
}

.ps-ladderclip {
  overflow: hidden;
}

.ps-ladder {
  display: flex;
  flex-direction: column;
  gap: var(--ps-rowgap);
}

.ps-row {
  display: grid;
  grid-template-columns: 36px 1fr 30px 42px;
  align-items: center;
  gap: 8px;
  height: var(--ps-rowh);
}

.ps-lap {
  font-size: 13px;
  letter-spacing: 0.06em;
  color: var(--ps-muted);
}

.ps-bar {
  position: relative;
  height: 3px;
  border-radius: 1.5px;
  background: var(--ps-hair1);
  overflow: hidden;
}

.ps-fill {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  background: var(--ps-dim);
  border-radius: 1.5px;
}

.ps-cars {
  font-size: 17px;
  font-weight: 700;
  text-align: right;
  color: var(--ps-text);
}

.ps-loss {
  font-size: 13px;
  text-align: right;
  color: var(--ps-muted);
}

.ps-row.calib .ps-cars,
.ps-row.calib .ps-loss {
  color: var(--ps-dim);
}

/* Crowned row: cell bleeds into the side padding, positive everything. */
.ps-row.crown {
  margin: 0 -7px;
  padding: 0 7px;
  background: var(--ps-pos-tint);
  box-shadow: inset 2px 0 0 var(--ps-pos);
  border-radius: 3px;
}

.ps-row.crown .ps-lap,
.ps-row.crown .ps-cars,
.ps-row.crown .ps-loss {
  color: var(--ps-pos);
}

.ps-row.crown .ps-fill {
  background: var(--ps-pos);
}

/* Deadline row: the fuel limit lap when traffic isn't choosing — same shape as
   the crown, but amber (fuel, not traffic). A row is never both at once. */
.ps-row.deadline {
  margin: 0 -7px;
  padding: 0 7px;
  background: var(--ps-amber-tint);
  box-shadow: inset 2px 0 0 var(--ps-amber);
  border-radius: 3px;
}

.ps-row.deadline .ps-lap,
.ps-row.deadline .ps-cars,
.ps-row.deadline .ps-loss {
  color: var(--ps-amber);
}

.ps-row.deadline .ps-fill {
  background: var(--ps-amber);
}

.ps-fuellimit {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 14px;
  margin-top: 2px;
}

.ps-flrule {
  flex: 1;
  height: 1px;
  background: var(--ps-hair1);
}

.ps-fllabel {
  font-size: 10px;
  letter-spacing: 0.1em;
  color: var(--ps-amber);
}

/* Fuel-save hint droplet: marks a row reachable only with a light save. Sits
   inside the fixed lap cell, so it never shifts the other columns. Amber caution
   even when the row is crowned green. */
.ps-savehint {
  width: 7px;
  height: 9px;
  margin-left: 3px;
  vertical-align: -1px;
}
.ps-savehint path {
  fill: var(--ps-amber);
}

/* ---- the only three animations (handoff: motion section) ---------------- */

.pscard.ps-enter {
  animation: psEnter 240ms ease-out;
}

@keyframes psEnter {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}

.pscard.ps-exit {
  animation: psExit 120ms ease-in forwards;
}

@keyframes psExit {
  from { opacity: 1; }
  to { opacity: 0; }
}

/* Once-a-lap shift: whole ladder slides up one slot; the prepended ghost of
   the expired NOW row rides out through the clip; the new far row fades in. */
.ps-ladder.ps-shift {
  animation: psShift 250ms ease-in-out forwards;
}

@keyframes psShift {
  from { transform: translateY(0); }
  to { transform: translateY(calc(-1 * (var(--ps-rowh) + var(--ps-rowgap)))); }
}

.ps-ladder.ps-shift .ps-row.ghost {
  animation: psGhost 250ms ease-in-out forwards;
}

@keyframes psGhost {
  from { opacity: 1; }
  to { opacity: 0; }
}

.ps-ladder.ps-shift .ps-row.fresh {
  animation: psRowIn 250ms ease-in-out;
}

@keyframes psRowIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* ---- dashboard: 520x280, design 8a, themes 9a–9d ------------------------ */
/* One DOM; a theme is a token swap on .dash[data-theme]. */

.dash {
  /* 9a Nocturne (default) */
  --bg: #161826;
  --frame: #3f424d;
  --hairline: #292b31;
  --text: #e9e9ed;
  --muted: #9397ab;
  --dim: #75798c;
  --text2: #cfd3e5;
  --led-off: #2c2f3d;
  --led-off-red: #322a3f;
  --green: #21d07a;
  --amber: #ffb020;
  --amber-label: #b98617;
  --pit-bg: #251e0e;
  --accent: #b5abfc;
  --accent-muted: #8583a8;
  --traffic-bg: #1d1f31;
  --traffic-border: #3a3560;
  --purple: #d2cefd;
  --red: #ff5252;
  --sev-yellow: #e8d44d;
  --sev-orange: #ff7a1a;
  --sev-crimson: #ff3d7f;
  --fuel-crit-bg: #241014;
  --fuel-crit-ink: #e06c62;
  /* Fuel-saver accent (lift rail countdown, echo bar). */
  --sel-blue: #56b8ff;
  /* Traffic lift (F12): free-save countdown green. Deliberately NOT the
     RPM/ok green (--green #21d07a) — mint-teal hue, unmistakable beside it
     (Marcus 08-01: "the green needs to be different to the rpm green"). */
  --tl-green: #3fe8c0;

  /* Quali mode tokens (18a/18b, Nocturne values from the handoff; the
     other themes carry derived values pending designer confirmation). */
  --q-green: #18a05f;
  --q-yellow: #8a7a35;
  --q-purple: #c05ff0;
  --q-neutral: #3a3e4c;
  --q-hair: #2e313d;
  --q-marker: #f2f3f5;
  --q-delta: #b9bcc4;
  --q-delta-warn: #d8c04a;
  --q-self: #9d90e8;
  --q-pole: #c95df0;

  /* Pitlane state tokens (12a-12d, Carbon values; overridden per theme). */
  --pit-warn-bg: #c72218;
  --pit-warn-bg-alt: #3a0f0b;
  --pit-warn-text: #ffffff;
  --pit-warn-text-alt: #ff8a80;
  --pit-center-bg: #1c0c0a;
  --pit-muted-warn: #c47a72;
  --pit-ok-bg: #0c2015;
  --pit-ok-border: #14532f;
  --pit-ok-sub: #7fd6a6;
  /* 12e green skin for the returning hero: ground + the dim green the
     secondary tower lines take while the limiter is legitimately on. */
  --pit-ok-center-bg: #0a1c13;
  --pit-ok-muted: #6f9a83;
  /* 12c exit-traffic band. Deliberately one notch DIMMER than the slow-car
     imminent tier (a solid --flag-yellow fill): the flag-yellow fill stays
     reserved for "about to be passed", this is "watch your mirrors". */
  --pit-exit-bg: #2a230c;
  --pit-exit-border: #6e5d1c;
  --pit-exit-text: #ffd21e;
  /* Bottom drain rail: the 15s exit window running out. */
  --pit-exit-rail: #8a7420;
  /* 12c exit-clear band. Its own greens rather than the 12b OK tokens: this
     band now holds the rest of the window, so it needs a rail of its own. */
  --pit-clear-bg: #0f2019;
  --pit-clear-border: #1f4433;
  --pit-clear-rail: #1c7a4c;
  /* 12d limiter-left-on: amber pit grammar, deliberately NOT 12a's red —
     12a is penalty risk in the pits, 12d is stuck slow on circuit. */
  --pit-lim-bg: #6e4600;
  --pit-lim-bg-alt: #201703;
  --pit-lim-text: #ffb020;
  --pit-lim-text-alt: #7a5a1e;
  --pit-lim-center-bg: #1c1405;
  --pit-lim-muted: #b8935a;
  --pit-lim-sub: #ffc766;
  /* Ink for the tick sitting on the amber "being serviced" tyre cell. */
  --svc-tick-ink: #1a1206;
}

.dash[data-theme='amber'] {
  --bg: #0b0a07;
  --frame: #2e2617;
  --hairline: #241f12;
  --text: #ffb020;
  --muted: #8a7245;
  --dim: #6b5a38;
  --text2: #ffcf6b;
  --led-off: #241f12;
  --led-off-red: #2b1616;
  --green: #21d07a;
  --amber: #ffb020;
  --amber-label: #8a7245;
  --pit-bg: #201409;
  --accent: #ffcf6b;
  --accent-muted: #8a7245;
  --traffic-bg: #14100a;
  --traffic-border: #2e2617;
  --purple: #ffe9c0;
  --red: #ff4438;
  --sev-yellow: #ffd94d;
  --sev-orange: #ff7a1a;
  --sev-crimson: #ff3d7f;
  --fuel-crit-bg: #200d08;
  --fuel-crit-ink: #d9705c;
  --q-green: #149655;
  --q-yellow: #7a6428;
  --q-purple: #c05ff0;
  --q-neutral: #3a301b;
  --q-hair: #2b2415;
  --q-marker: #ffe9c0;
  --q-delta: #c9ab6b;
  --q-delta-warn: #ffcf6b;
  --q-self: #b9a8f0;
  --q-pole: #c95df0;
}

.dash[data-theme='paper'] {
  --pit-warn-bg: #d92b20;
  --pit-warn-bg-alt: #f6d7d4;
  --pit-warn-text: #ffffff;
  --pit-warn-text-alt: #8f1d15;
  --pit-center-bg: #fbeeec;
  --pit-muted-warn: #a3564c;
  --pit-ok-bg: #e7f5ec;
  --pit-ok-border: #9fd4b4;
  --pit-ok-sub: #1f7a45;
  --pit-ok-center-bg: #eef8f1;
  --pit-ok-muted: #4a7a5f;
  --pit-exit-bg: #fbedc2;
  --pit-exit-border: #d9bf70;
  --pit-exit-text: #8a6d00;
  --pit-exit-rail: #b08a3c;
  --pit-clear-bg: #ddf1e4;
  --pit-clear-border: #9fd4b4;
  --pit-clear-rail: #2f9c63;
  --pit-lim-bg: #f0a92b;
  --pit-lim-bg-alt: #fdf0d6;
  --pit-lim-text: #4a3200;
  --pit-lim-text-alt: #b08a3c;
  --pit-lim-center-bg: #fdf6e6;
  --pit-lim-muted: #8a6d2a;
  --pit-lim-sub: #a06800;
  --svc-tick-ink: #2a1c00;
  --sel-blue: #2d7dd2;
  /* Traffic lift green, light theme: darker teal so it holds contrast on the
     pale rail while staying clearly apart from the ok green. */
  --tl-green: #0d9f78;
  --bg: #f5f5f6;
  --frame: #c9c9ce;
  --hairline: #e0e0e4;
  --text: #1b1d1f;
  --muted: #6f7379;
  --dim: #9a9da3;
  --text2: #43474d;
  --led-off: #dedee2;
  --led-off-red: #eadfe2;
  --green: #149655;
  --amber: #c47d00;
  --amber-label: #a06800;
  --pit-bg: #f5ead2;
  --accent: #0f6fbe;
  --accent-muted: #5c8db0;
  --traffic-bg: #eaf2f9;
  --traffic-border: #c8dcee;
  --purple: #8b3fc0;
  --red: #d92b20;
  --sev-yellow: #b59a00;
  --sev-orange: #d9600a;
  --sev-crimson: #c8184f;
  --fuel-crit-bg: #f8e3df;
  --fuel-crit-ink: #a63c31;
  --q-green: #149655;
  --q-yellow: #a8891a;
  --q-purple: #8b3fc0;
  --q-neutral: #cfcfd6;
  --q-hair: #dcdce1;
  --q-marker: #1b1d1f;
  --q-delta: #43474d;
  --q-delta-warn: #8a6d00;
  --q-self: #5b50c8;
  --q-pole: #8b3fc0;
}

.dash[data-theme='slate'] {
  --bg: #10141c;
  --frame: #232c3d;
  --hairline: #1b2230;
  --text: #f0f3f8;
  --muted: #8a92a3;
  --dim: #6b7383;
  --text2: #c3c9d4;
  --led-off: #1c2434;
  --led-off-red: #291f30;
  --green: #3ddc84;
  --amber: #f5a623;
  --amber-label: #b3791a;
  --pit-bg: #1c1710;
  --accent: #4cc3ff;
  --accent-muted: #4c8aa8;
  --traffic-bg: #14202b;
  --traffic-border: #1f3a4d;
  --purple: #c586f0;
  --red: #ff5252;
  --sev-yellow: #f0d84a;
  --sev-orange: #ff8a2a;
  --sev-crimson: #ff4d8a;
  --fuel-crit-bg: #26121a;
  --fuel-crit-ink: #e06c62;
  --q-green: #18a05f;
  --q-yellow: #8a7a35;
  --q-purple: #c586f0;
  --q-neutral: #2c3850;
  --q-hair: #223043;
  --q-marker: #f0f3f8;
  --q-delta: #b9c2d4;
  --q-delta-warn: #d8c04a;
  --q-self: #8f9ff0;
  --q-pole: #c586f0;
}

/* Design 11a "Big type": native 720x405, every size bumped ~15-20%,
   minimum 13px, taller strips. */
.dash {
  position: relative;
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--frame);
  border-radius: 8px;
  font-family: Inter, 'Segoe UI', system-ui, sans-serif;
  font-variant-numeric: tabular-nums;
}

.dash.disconnected {
  opacity: 0.55;
}

.dash .status {
  top: auto;
  bottom: 4px;
  right: 4px;
  width: 5px;
  height: 5px;
}

.dash .hairline {
  height: 1px;
  background: var(--hairline);
  flex-shrink: 0;
}

/* 1. LED RPM display */
.dash-leds {
  display: flex;
  gap: 3px;
  height: 16px;
  padding: 4px 4px 0;
  flex-shrink: 0;
}

/* 36 segments, so the radius matches the lift rail's rather than the old
   12-LED strip's chunkier 3px. */
.dash-leds .led {
  flex: 1;
  border-radius: 2px;
  background: var(--led-off);
}

.dash-leds .led.z-red {
  background: var(--led-off-red);
}

.dash-leds .led.z-green.lit { background: var(--green); }
.dash-leds .led.z-amber.lit { background: var(--amber); }
.dash-leds .led.z-red.lit   { background: var(--red); }

/* Shift lights turned off (wheel/DDU has its own rev lights). `visibility`,
   not `display`, so the 16px slot survives — the fuel-saver lift rail shares
   it, and a collapsing strip would shove the whole card up and down as cues
   come and go. */
.dash-leds.off {
  visibility: hidden;
}

.dash-leds.blink .led.lit {
  animation: led-blink 0.25s steps(1) infinite;
}

@keyframes led-blink {
  50% { background: var(--led-off); }
}

/* 1b. Fuel-saver lift rail (30e) — EXACT same box as .dash-leds (one of the
   two is hidden at any time, so swapping never moves the layout below). */
.dash-liftrail {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 16px;
  padding: 4px 4px 0;
  flex-shrink: 0;
}

.dash-liftrail .lr-side {
  display: flex;
  gap: 3px;
  flex: 1;
}

.dash-liftrail .lr-led {
  flex: 1;
  height: 12px;
  border-radius: 2px;
  background: var(--led-off);
}

.dash-liftrail .lr-led.lit { background: var(--sel-blue); }

.dash-liftrail .lr-word {
  width: 52px;
  text-align: center;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0.24em;
  line-height: 12px;
  color: var(--led-off);
}

/* At the lift point: word + LEDs go RED and flash until the throttle closes
   (clearing hides the rail entirely — no confirm state). Red + 0.3 s period
   per Marcus's 07-22 tweak (blue stays the approach/countdown colour). */
.dash-liftrail.lift .lr-word { color: var(--red); }
.dash-liftrail.lift .lr-led {
  animation: lift-led-flash 0.3s step-end infinite;
}

@keyframes lift-led-flash {
  0%, 54% { background: var(--red); }
  55%, 100% { background: color-mix(in srgb, var(--bg) 82%, var(--red) 18%); }
}

/* Traffic lift (F12, docs/TRAFFIC-LIFT-DESIGN.md): this approach's extension
   is FREE — countdown wears the traffic green instead of blue. The LIFT
   flash stays red in every mode: the action command must read identically
   whether the lift is paid or free. */
.dash-liftrail.traffic .lr-led.lit { background: var(--tl-green); }
.dash-liftrail.traffic:not(.lift) .lr-word { color: var(--tl-green); }

/* 1c. Fuel saver lights — the SAME rail as a standalone widget (users were
   hand-building this by cropping a second DDU window down to the strip). The
   inner markup reuses .dash-liftrail wholesale; this card supplies the theme
   tokens the dash normally scopes (pscard pattern: per-skin restatement, not
   inheritance) plus the visibility grammar: fully invisible until a cue arms,
   fast fade in (the countdown is ~2 s — the fade must not eat it), gentler
   fade out. --bg is TRANSPARENT here: the lift flash's dim phase color-mixes
   against it, so over the sim the flash beats red against the track instead
   of against a card ground. */
.slrail {
  --led-off: #2c2f3d;
  --red: #ff5252;
  --sel-blue: #56b8ff;
  --tl-green: #3fe8c0;
  --bg: transparent;
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.slrail.armed {
  opacity: 1;
  transition-duration: 0.15s;
}

/* Edit mode: the off-state rail shows so the widget can be found/dragged. */
body.edit .slrail {
  opacity: 1;
}

/* The dashboard rail's 4px top pad seats it under the card edge; standalone
   the rail centres in its own box instead. */
.slrail .dash-liftrail {
  padding: 0 4px;
}

.slrail[data-theme='amber'] {
  --led-off: #241f12;
  --red: #ff4438;
}

.slrail[data-theme='paper'] {
  --led-off: #dedee2;
  --red: #d92b20;
  --sel-blue: #2d7dd2;
  --tl-green: #0d9f78;
}

.slrail[data-theme='slate'] {
  --led-off: #1c2434;
  --red: #ff5252;
}

/* ---- Wind widget (design 57b, marching chevrons) ----------------------
   Perspective ground disc; chevrons march downwind inside the tilted plane,
   the kph number billboards flat above it. Colour/glow arrive as custom
   properties from widgets/wind.ts (continuous 0-30 kph scale); the steel
   defaults below are the calm end of that scale. */
.wd-scene {
  --wd-col: #7fa8d4;
  --wd-glow: rgba(127, 168, 212, 0.55);
  position: relative;
  width: 150px;
  height: 150px;
  perspective: 460px;
}

.wd-plane {
  position: absolute;
  inset: 10px; /* 130x130 disc centred in the 150 box */
  transform: rotateX(60deg);
  transform-style: preserve-3d;
}

.wd-ring {
  position: absolute;
  border-radius: 50%;
}
.wd-ring-outer {
  inset: 0;
  border: 1px solid rgba(175, 185, 205, 0.42);
}
.wd-ring-inner {
  inset: 28px;
  border: 1px solid rgba(150, 160, 180, 0.15);
}

/* Heading ticks on the outer ring; north (the car's heading) is brighter. */
.wd-tick {
  position: absolute;
  background: rgba(150, 160, 180, 0.38);
}
.wd-tick-n {
  left: calc(50% - 1px);
  top: 0;
  width: 2px;
  height: 11px;
  background: rgba(232, 236, 246, 0.72);
}
.wd-tick-s {
  left: calc(50% - 1px);
  bottom: 0;
  width: 2px;
  height: 8px;
}
.wd-tick-e {
  top: calc(50% - 1px);
  right: 0;
  width: 8px;
  height: 2px;
}
.wd-tick-w {
  top: calc(50% - 1px);
  left: 0;
  width: 8px;
  height: 2px;
}

/* Zero-size wrapper at plane centre; widgets/wind.ts rotates it to the push
   direction and lays the chevrons out along local -y (outward = downwind). */
.wd-rot {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 0;
  height: 0;
  transform-style: preserve-3d;
}

/* ---- Compass style (concept 58b, Marcus 09-16): no disc — the ground plane
   is an invisible reference. The coin lies IN the plane at the upwind point
   (it foreshortens to an ellipse like everything else, and the perspective
   sizes it near/far on its own); widgets/wind.ts sets its distance from the
   car (top, grows with strength) and the digits' counter-rotation. The
   stronger perspective is the concept's: near/far size difference obvious. */
.wd-scene.wd-compass {
  perspective: 320px;
}
.wd-scene.wd-compass .wd-ring,
.wd-scene.wd-compass .wd-tick {
  display: none;
}
.wd-tailpos {
  position: absolute;
  left: 0;
  top: 55px; /* overwritten per frame by widgets/wind.ts (coinOffset) */
  width: 0;
  height: 0;
  transform-style: preserve-3d;
}
.wd-coin {
  position: absolute;
  left: 0;
  top: 0;
  width: 38px;
  height: 38px;
  box-sizing: border-box;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  border: 2.5px solid var(--wd-col);
  background: #12141c;
  /* Ink fill, coloured ring, dark halo outside the ring, glow: the
     background-proof stack (tested dark, light and split grounds). */
  box-shadow: 0 0 0 1.5px rgba(0, 0, 0, 0.6), 0 0 12px var(--wd-glow);
  display: flex;
  align-items: center;
  justify-content: center;
}
.wd-coin span {
  font: 700 17px 'Inter';
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: #f4f5f8;
  /* Counter-rotated so it reads upright on screen, and pre-stretched like
     road-marking lettering so the 60deg tilt squashes it back to normal. */
  transform: rotateZ(var(--wd-nd, 0deg)) scaleY(1.75);
}

/* Style switch: the JS puts wd-compass / wd-classic on the scene. */
.wd-scene.wd-compass .wd-num {
  display: none;
}
.wd-scene.wd-classic .wd-tailpos {
  display: none;
}

.wd-chev {
  position: absolute;
  filter: drop-shadow(0 0 6px var(--wd-glow));
  /* The casing stroke is wider than the viewBox allowed for — don't clip. */
  overflow: visible;
}
.wd-chev path {
  stroke: var(--wd-col);
}
/* Trackmap-style own-contrast casing: dark ink under the colour core (the
   glow above is the light rim for pure-black scenes). Same ink as the map. */
.wd-chev path.wd-chev-case {
  stroke: #12141c;
}

/* Billboarded flat in the OUTER box — never foreshortens. The innermost
   chevron offsets clear a two-digit number at this size (design 57b). */
.wd-num {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 3;
  font: 700 24px 'Inter';
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: var(--wd-col);
  /* Glow first (light rim), then a TIGHT doubled ink halo — the text
     equivalent of the chevron casing, so the figure survives sky and white
     kerbs where the old soft shadow washed out. */
  text-shadow:
    0 0 10px var(--wd-glow),
    0 0 3px rgba(10, 12, 18, 0.9),
    0 0 3px rgba(10, 12, 18, 0.9),
    0 1px 3px rgba(10, 12, 18, 0.9);
}

/* 2. Traffic strip. Height 40 is the STRIP SLOT CONTRACT: every pit banner
   (12a/12b/12c) is exactly this height too, so state swaps never move the
   layout below. */
.dash-traffic {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 40px;
  padding: 0 14px;
  margin-top: 4px;
  background: var(--traffic-bg);
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--traffic-border);
  font-size: 17px;
  white-space: nowrap;
  overflow: hidden;
  flex-shrink: 0;
}

.dash-traffic-label {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--accent-muted);
}

/* Left-aligned queue with a fixed gap: soonest encounter leftmost, new
   qualifiers join at the tail, and the FLIP animator slides the queue left
   when the lead chip drops. (Replaces the design's space-between spread,
   which stretched sparse chips across the full strip.) */
.dash-traffic-chips {
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: flex-start;
  gap: 16px; /* 9 four-char chips must fit the strip's worst case */
  /* Room for the zone plate's 5/7 px reach (its negative margin) inside the
     clip: padding here, the margin pulled back by the same amount, so
     content positions are exactly where they were. */
  padding: 5px 7px;
  margin: -5px 0 -5px 13px;
  overflow: hidden;
  /* Overflowing entries fade over the last ~28px instead of shearing at
     the strip edge (2026-07-16 driver feedback). Static mask: a queue
     that ends short of the edge has nothing in the fade zone. */
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
}

.dash-traffic .chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--text);
  /* Exactly the settings-cluster value style (.s-val): the driver compared
     the two directly — chips are secondary numerics and read best in the
     same 16px/600 (two feedback rounds: 700 filled in and read mushy). */
  font-size: 16px;
  font-weight: 600;
}

/* The count lives in a <b>: without this the UA's 700 silently overrides
   the chip weight above (the reason round one "didn't take"). */
.dash-traffic .chip b {
  font-weight: inherit;
}

.dash-traffic .chip.lowconf {
  opacity: 0.45;
}

/* Traffic zone WARNING plate (docs/design/traffic-zones/warning, 09-21): the
   chip's own box filled solid #6e2118 with a 2 px #ff7b6b border all the way
   round and the laps figure re-inked #ffe3de — a lit block on the dark strip,
   findable at a glance. The border exists in EVERY state as 2 px transparent,
   with padding and a negative margin sized to absorb it, so entering or
   leaving zone changes only border-color, background and the figure's colour:
   0 px of layout. Not box-shadow / outline — both were clipped by the strip's
   overflow and read as side bars only (Marcus 09-21). The chips container
   pads by the same 5/7 px (margin-compensated) so the grown box is never
   clipped. The chip's opacity (low confidence, FCY) carries the plate. */
.dash-traffic .chip {
  padding: 3px 5px;
  border: 2px solid transparent;
  margin: -5px -7px;
  border-radius: 3px;
}
.dash-traffic .chip.zone {
  background: #6e2118;
  border-color: #ff7b6b;
}
.dash-traffic .chip.zone b {
  color: #ffe3de;
}

.dash-traffic .chip .sw {
  width: 12px;
  height: 12px;
  border-radius: 3px;
}

/* 33d loss line: number over a 2px line, the number's own width, flush left.
   The empty row is reserved so numbers share one baseline whether or not a
   line is drawn (unknown = nothing at all — never a dim placeholder). The
   inks are deliberately desaturated: saturated colour on this strip already
   means class (the dot) and state (takeovers). */
.dash-traffic .chip .chip-stack {
  display: inline-flex;
  flex-direction: column;
  gap: 2px;
}

.dash-traffic .chip .loss {
  display: block;
  height: 2px;
  border-radius: 1px;
}

.dash-traffic .chip .loss-small { background: #3f8a72; }
.dash-traffic .chip .loss-medium { background: #9d7c2c; }
.dash-traffic .chip .loss-high { background: #a8493c; }

/* Gap markers: >1 lap of empty road between traffic trains — dim dotted
   leaders around the lap count, deliberately the visual opposite of a chip
   so clusters read at a glance. A leading marker = clear road before the
   first encounter. */
.dash-traffic .chip-gap {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--dim);
  font-size: 13px;
  font-weight: 500;
}

.dash-traffic .chip-gap i {
  width: 12px;
  border-top: 1px dotted var(--dim);
}

/* 3. Main row */
.dash-main {
  display: grid;
  grid-template-columns: 205px 1fr 205px;
  grid-template-rows: 100%;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

/* 3a. Laptime panel — a deliberate vertical grid (8px system):
   four semantic groups sharing the leftover space equally with centered
   content, three identical separators each living in their own gutter
   (8px clear above and below the 1px line), one label edge, one value
   edge, unified 52px label column across every two-column row. */
/* 22a alignment contract — OPTICAL cap line, not box tops. A line box
   sits above its glyph caps by a font-dependent gap (half-leading +
   (ascender − capHeight) × size; Inter: 0.969em / 0.727em), so equal
   box insets splay the visible tops (measured ink below the hairline:
   P2 12 · speed 15 · BST 16). The shared CAP LINE is 15px below the
   main-row hairline, anchored on the 42px heroes (speed / BST). Every
   first-row inset below is derived from in-page ink measurement
   (canvas actualBoundingBoxAscent), not eyeballed:
     left panel pad-top 10  → BST cap at 15
     .cn-tl/.cn-tr top 12.4 → corner line-1 cap at 15 (20px/600 token)
     c-default pad-top 10   → speed cap at 15
     battle pad-top 11      → P1 cap at 15
   Adjust all together, never one alone (LAPTIME-PANEL.md handoff). */
.dash-laptime {
  border-right: 1px solid var(--hairline);
  padding: 10px 14px 12px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  min-width: 0;
}

.dash-laptime > .hairline {
  margin: 8px 0;
}

/* Content-sized groups on an exact vertical budget: line-heights below are
   chosen so groups + 8px gutters + padding sum to the panel height. Groups
   never stretch or shrink — gutters stay clear by construction. */
/* Corner grammar (docs/design/corner-grammar): four configurable two-line
   tokens on the centre tower's corners. Line 1 = the live number, 20px/600,
   with fused 11.5px scope marks; line 2 = its reference or name, 11.5px dim,
   ALWAYS holding its slot (even empty) so a chip appearing reflows nothing.
   Left corners align both lines left, right corners right — an edge-anchored
   stack reads to its own edge (Marcus 08-12). Absolute + fixed insets: no
   content change moves anything, including the corners themselves. */
.c-corner {
  position: absolute;
  z-index: 1;
  display: flex;
  flex-direction: column;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  /* The fade (bottom corners, toggled by corners.ts when an event owns the
     bottom-centre slot): out fast — the arriving thing is urgent; back only
     after a settle, so an echo→echo sequence can't strobe the corners. */
  transition: opacity 220ms cubic-bezier(0.2, 0.7, 0.3, 1) 400ms;
}

.c-corner.cfade {
  opacity: 0;
  transition: opacity 90ms linear;
}

.cn-tl,
.cn-bl {
  left: 12px;
  align-items: flex-start;
}

.cn-tr,
.cn-br {
  right: 12px;
  align-items: flex-end;
}

/* Insets: line 1's caps on the shared cap line (top corners), line 1's
   BASELINE on the RPM line's baseline (bottom corners), both measured
   in-page like every other 22a inset — see the cap-line note above. */
.cn-tl,
.cn-tr {
  top: 13.5px; /* measured: 20px caps level with the 42px speed cap */
}

.cn-bl,
.cn-br {
  /* Two visible rows: lifted off the floor (Marcus 08-12 — the strict
     baseline match read too tight with a second line under it). */
  bottom: 8px;
}

/* One visible row (line 2 empty — dash-only, OUTL, chip yet to appear):
   line 1's baseline back on the RPM baseline, measured in-page. The 4.5px
   swap on a token's own row-count change is deliberate (same ruling). */
.cn-bl.csingle,
.cn-br.csingle {
  bottom: 3.5px;
}

.c-corner .cr1 {
  display: flex;
  align-items: baseline;
  height: 20px;
  font-size: 20px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.005em;
}

/* Line 2 keeps its box when empty — that IS the no-reflow guarantee. */
.c-corner .cr2 {
  display: flex;
  align-items: baseline;
  height: 12.5px;
  margin-top: 1px;
  font-size: 11.5px;
  font-weight: 500;
  line-height: 1;
  color: var(--dim);
}

/* Fused scope mark (P, ST, x): stays with the number it scopes. */
.c-corner .cmp {
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.05em;
  color: var(--dim);
}

.c-corner .cr1 .cmp:first-child { margin-right: 3px; }
.c-corner .cr1 .cmp:last-child { margin-left: 1px; }

/* Word value (OUTL) — tracked so it can't read as a number. */
.c-corner .cwd {
  letter-spacing: 0.06em;
  color: var(--muted);
}

/* Item name on line 2 (iR, to go, BAT) — same defence. */
.c-corner .cnm { letter-spacing: 0.08em; }

/* Absent: each number degrades independently to a dim dash. */
.c-corner .cabs {
  color: var(--dim);
  opacity: 0.55;
}

/* Positions-gained chip: part of the position item, travels with it. */
.c-corner .cchip {
  display: inline-flex;
  align-items: baseline;
  gap: 2px;
  font-weight: 600;
}

.c-corner .cch { font-size: 8px; }
.c-corner .ccn { font-size: 11.5px; }

/* Colour only where the dash already colours that number elsewhere; line 2
   follows line 1 one step down the ramp (cref), never a cell tint. */
.c-corner .cgreen { color: var(--green); }
.c-corner .camber { color: var(--amber); }
.c-corner .cred { color: var(--red); }
.c-corner .cmut { color: var(--muted); }
.c-corner .cr2 .cref { opacity: 0.8; }

.lt-gained {
  font-size: 14px;
}

.bt-gained {
  font-size: 13px;
  flex-shrink: 0;
}

.gained-up { color: var(--green); }
.gained-down { color: var(--red); }

.lt-spacer {
  flex: 1;
}

.lt-deltas,
.lt-times,
.lt-session {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* One row template for every label/value pair: same label edge, same value
   edge, baseline-aligned — except the primary delta row, whose label is
   centered against the 36px numeral. */
.lt-drow,
.lt-trow,
.lt-srow {
  display: grid;
  align-items: baseline;
  white-space: nowrap;
}

/* Per-group label columns (22a): deltas 46 · times 37 · session 58. */
.lt-drow { grid-template-columns: 46px 1fr; }
.lt-trow { grid-template-columns: 37px 1fr; }
.lt-srow { grid-template-columns: 58px 1fr; }

.lt-drow.primary {
  align-items: center;
}

.lt-lbl {
  font-size: 16px;
  line-height: 18px;
  letter-spacing: 0.08em;
  color: var(--muted);
}

.lt-bst {
  font-size: 42px;
  font-weight: 600;
  line-height: 42px;
  text-align: right;
}

.lt-lst {
  font-size: 23px;
  line-height: 23px;
  text-align: right;
}

.delta-neg { color: var(--green); }
.delta-pos { color: var(--red); }

.lt-lbl-pb {
  font-size: 16px;
  line-height: 18px;
  color: var(--muted);
}

.lt-trow.pb .lt-lbl-pb,
.lt-pb {
  color: var(--purple);
}

.lt-pb,
.lt-lastlap {
  font-size: 20px;
  line-height: 22px;
  text-align: right;
}

/* Invalidated best: the time still stands (the panel deliberately keeps the
   lap the driver actually did), marked so the driver knows it did not count.
   PB only — no reliable prompt signal exists for the LST row (see
   DASHBOARD-SPEC). Rides as ::before on the right-aligned value, so the
   digits never move — the mark grows leftward into the empty part of the
   column. The glyph is the house warning triangle (same geometry as .bnr-tri
   / .svc-tri), as a mask so it takes the theme's amber: a data-URI SVG cannot
   read CSS variables directly. */
.lt-pb.invalid::before {
  content: '';
  display: inline-block;
  width: 13px;
  height: 13px;
  margin-right: 5px;
  background-color: var(--amber);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 3 L22 20 L2 20 Z' fill='none' stroke='%23000' stroke-width='2.5' stroke-linejoin='round'/%3E%3Cline x1='12' y1='9' x2='12' y2='14' stroke='%23000' stroke-width='2.5'/%3E%3Ccircle cx='12' cy='17' r='1.4' fill='%23000'/%3E%3C/svg%3E") no-repeat center / contain;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 3 L22 20 L2 20 Z' fill='none' stroke='%23000' stroke-width='2.5' stroke-linejoin='round'/%3E%3Cline x1='12' y1='9' x2='12' y2='14' stroke='%23000' stroke-width='2.5'/%3E%3Ccircle cx='12' cy='17' r='1.4' fill='%23000'/%3E%3C/svg%3E") no-repeat center / contain;
}

.lt-slbl {
  font-size: 15px;
  line-height: 18px;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.lt-sval {
  font-size: 20px;
  line-height: 22px;
  font-weight: 600;
  text-align: right;
}

.lt-sval .mut {
  color: var(--muted);
  font-weight: 400;
}

.lt-sval .est {
  font-size: 15px;
  color: var(--muted);
  font-weight: 400;
}

/* 3b. Contextual panel (center) */
.dash-center {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-evenly;
  padding: 8px 0;
  min-width: 0;
}

/* 22a hero tower: speed → gear → RPM top-to-bottom; position/stint on the
   corners. space-between + 10px top pad pins speed's top edge to the same
   line as the corner labels and the side panels' first rows. */
#c-default {
  position: relative;
  justify-content: space-between;
  padding: 10px 0 14px;
}

.c-rpm {
  font-size: 25px;
  color: var(--muted);
}

/* Fuel saver active: LAP SAVE takes the RPM line's slot (30c, trimmed).
   Sized to the same 25px line-height so the swap can't shift the tower. */
.c-lapsave {
  display: flex;
  align-items: baseline;
  gap: 8px;
  white-space: nowrap;
}

.c-lapsave .ls-lbl {
  font-size: 13px;
  letter-spacing: 0.1em;
  color: var(--muted);
}

/* One steady ink for the saved-fuel number (Marcus, 07-30): no green/red
   ahead/behind flip — the amber matches the fuel stripe's save skin. */
.c-lapsave .ls-val {
  font-size: 25px;
  font-weight: 700;
  color: var(--amber);
}

.c-lapsave .ls-quota {
  font-size: 15px;
  color: var(--muted);
}

/* 31c low fuel. Sizes mirror the LAP SAVE slot it replaces (the handoff's
   absolute px are from its own smaller panel; the RATIO label:number:unit is
   what carries over), so the slot's weight is unchanged when it swaps. */
.c-lowfuel {
  display: flex;
  align-items: baseline;
  gap: 8px;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  /* The WHOLE line breathes as one. Pulsing only the digits would strobe the
     thing you are trying to read. */
  animation: lowfuel-breathe 1.6s ease-in-out infinite;
}

/* Under a lap: the one escalation. Red, and a faster cycle. */
.c-lowfuel.crit {
  animation: lowfuel-breathe-hard 1s ease-in-out infinite;
}

@keyframes lowfuel-breathe {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.46; }
}

@keyframes lowfuel-breathe-hard {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.3; }
}

.c-lowfuel .lf-lbl {
  font-size: 13px;
  letter-spacing: 0.1em;
  color: var(--amber);
}

.c-lowfuel .lf-val {
  font-size: 25px;
  font-weight: 700;
  color: var(--amber);
}

/* Two decimals always: at this range the choice is one more lap or pit now,
   and 1.43 vs 1.4 changes that call. */
.c-lowfuel .lf-unit {
  font-size: 15px;
  letter-spacing: 0.08em;
  color: var(--amber-label);
}

.c-lowfuel.crit .lf-lbl,
.c-lowfuel.crit .lf-val { color: var(--red); }
.c-lowfuel.crit .lf-unit { color: var(--fuel-crit-ink); }

.c-gear {
  font-size: 120px;
  font-weight: 700;
  line-height: 0.92;
  /* Hard-fixed slot (= the gear digit's natural 110.4px box), centred, so the
     metres-to-contact readout swaps in without changing the slot height. The
     tower's height is content-driven, so a taller slot would push the RPM line
     down — this pins it (the no-reflow rule). overflow stays visible; the
     readout is shorter than the box anyway. */
  height: 110.41px;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Metres-to-contact readout in the gear slot, styled like the pit-box measure
   (Marcus 08-03): big number + small unit, yellow for slow / red for stopped,
   the two cross-fading. Smaller than the gear so four digits + unit fit. */
.c-gear.c-contact {
  font-size: 92px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  transition: color 200ms ease;
}
.c-gear.slow { color: var(--hazard-slow-ink); }
.c-gear.stopped { color: var(--hazard-stop-ink); }
/* The NUMBER is centred on the panel; the unit hangs off its right. The unit
   is zero-width and its gap margin is cancelled from the flex measure, so the
   number's centre never moves as the digit count changes (95 / 168 / 1204).
   Marcus 08-03. */
.c-contact-num {
  display: inline-flex;
  align-items: baseline;
}
.c-contact-unit {
  font-size: 30px;
  font-weight: 600;
  opacity: 0.75;
  width: 0;
  margin-left: 6px;
  margin-right: -6px;
  white-space: nowrap;
}

.c-speed {
  font-size: 42px;
  font-weight: 600;
  line-height: 1;
}

/* 3c. Battle panel — one competitor-card component rendered twice on the
   same vertical grid: equal card heights (flex: 1), identical four-row
   template so P1/P3 rows land at matching Y offsets, and the separator in
   its own 8px gutter like the left panel's. */
.dash-battle {
  border-left: 1px solid var(--hairline);
  padding: 11px 14px 12px;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

#b-battle > .hairline {
  margin: 8px 0;
}

.bt-entry {
  display: flex;
  flex-direction: column;
  /* flex-start, not center: the first row must sit on the shared 11px top
     line (22a alignment contract) — centering let the card's content bleed
     upward above the panel inset. */
  justify-content: flex-start;
  gap: 4px;
  min-width: 0;
  min-height: 0;
  flex: 1;
}

/* Card rows are HEIGHT-PINNED: driver names are the dashboard's only
   latin-ext text, and accented capitals / late-loading font subsets can
   grow a content-sized line box — glyphs may overpaint a pinned row, but
   they can never reflow the rows beneath. */
.bt-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  white-space: nowrap;
  line-height: 22px;
  height: 22px;
}

.bt-p {
  font-size: 18px;
  font-weight: 700;
  flex-shrink: 0;
}

.bt-name {
  font-size: 18px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  /* Flex items refuse to shrink below content (min-width: auto) — without
     this a long surname never ellipsizes and shoves the gained marker off
     the panel edge. */
  min-width: 0;
}

.bt-name.inpit {
  color: var(--dim);
  font-style: italic;
}

.bt-gaprow {
  display: flex;
  align-items: center;
  gap: 12px;
  height: 32px;
}

.bt-gap {
  font-size: 30px;
  font-weight: 600;
  color: var(--accent);
  line-height: 32px;
  flex-shrink: 0;
}

.bt-spark {
  display: block;
  flex: 1;
  min-width: 0;
  height: 16px;
}

/* Race control on a battle rival. Same three domains as the relative row; the
   word crossfades in over the sparkline, which is the one decorative thing in
   the block and the only fixed slot that can be borrowed without moving the
   gap number or the two data rows below it.

   The word is boxed into the SPARKLINE's own flex slot, not the whole gap row:
   anchored to the row it overlapped the gap number as soon as the word was
   long ("BLACK FLAG" over "-8.66"). The slot occupies exactly what the
   sparkline occupied, so nothing moves either way. */
.bt-sparkslot {
  position: relative;
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
}

.bt-flag {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  max-width: 100%;
  overflow: hidden;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0.14em;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 250ms ease;
}

.bt-entry[data-flag] .bt-flag {
  opacity: 1;
}

.bt-entry[data-flag] .bt-spark {
  opacity: 0;
  transition: opacity 250ms ease;
}

/* The wash and rail live on their own layer, BLEEDING OUT to the panel's own
   edges. Painted on the entry they floated in the middle of the panel, because
   .dash-battle insets every entry by its `11px 14px 12px` padding plus a 1px
   left border. The panel clips (overflow: hidden), so the bleed is safe.

   Two more things fall out of the separate layer: the furled breathe animates
   the WASH instead of the whole block, so the gap and lap times stay at full
   strength while it pulses; and the rail lands on the panel edge where it
   reads as an edge marker rather than a stray line mid-panel. */
.bt-entry {
  position: relative;
}

/* Positioned so they paint above the wash layer, which precedes them in the
   DOM. No layout effect — they are already block-level in normal flow. */
.bt-entry > * {
  position: relative;
}

.bt-entry[data-flag]::before {
  content: '';
  position: absolute;
  left: -15px; /* 14px panel padding + its 1px left border */
  right: -14px;
  pointer-events: none;
  transition: background 250ms ease, box-shadow 250ms ease;
}

/* Vertically each block owns its half: out to the panel edge on the outside,
   and up to the divider's 8px gutter between them, so the hairline still
   reads. */
#b-battle > .bt-entry:first-child[data-flag]::before {
  top: -11px;
  bottom: -8px;
}

#b-battle > .bt-entry:last-child[data-flag]::before {
  top: -8px;
  bottom: -12px;
}

.bt-entry[data-flag='furled']::before {
  background: linear-gradient(90deg, rgba(122, 61, 56, 0.42), transparent 78%);
  box-shadow: inset 3px 0 0 var(--rc-furled);
  animation: lowfuel-breathe 1.6s ease-in-out infinite;
}

.bt-entry[data-flag='black']::before,
.bt-entry[data-flag='repair']::before {
  background: linear-gradient(90deg, rgba(35, 16, 13, 0.92), transparent 78%);
}

.bt-entry[data-flag='black']::before {
  box-shadow: inset 3px 0 0 var(--rc-red-text);
}

.bt-entry[data-flag='repair']::before {
  box-shadow: inset 3px 0 0 var(--rc-orange);
}

.bt-entry[data-flag='furled'] .bt-flag,
.bt-entry[data-flag='furled'] .bt-name {
  color: var(--rc-furled-text);
}

.bt-entry[data-flag='black'] .bt-flag,
.bt-entry[data-flag='black'] .bt-name {
  color: var(--rc-red-text);
}

.bt-entry[data-flag='repair'] .bt-flag,
.bt-entry[data-flag='repair'] .bt-name {
  color: var(--rc-orange);
}

/* A flagged car is never the greyed-out "he's in the pits" italic — the flag
   is why you are looking at him. */
.bt-entry[data-flag] .bt-name.inpit {
  font-style: normal;
}

.bt-spark polyline {
  stroke: var(--accent);
}

.bt-datarow {
  display: flex;
  align-items: baseline;
  gap: 8px;
  white-space: nowrap;
  line-height: 18px;
  height: 18px;
}

.bt-lbl {
  font-size: 13px;
  line-height: 16px;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.bt-lbl.hiddenlbl {
  visibility: hidden;
}

.bt-last,
.bt-st {
  font-size: 15px;
  color: var(--text2);
}

.bt-vsdelta {
  font-size: 15px;
  font-weight: 600;
}

/* 4. Info strip */
.dash-info {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  height: 40px;
  padding: 0 14px;
  border-top: 1px solid var(--hairline);
  white-space: nowrap;
  overflow: hidden;
  flex-shrink: 0;
}

.info-session {
  font-size: 17px;
  font-weight: 700;
  justify-self: start;
}

.info-dot {
  color: var(--muted);
  font-weight: 400;
}

.info-weather {
  font-size: 14px;
  color: var(--muted);
  display: inline-flex;
  gap: 12px;
  align-items: center;
}

.w-wind {
  display: inline-flex;
  align-items: center;
  gap: 3px;
}

.w-wind svg {
  width: 13px;
  height: 13px;
}

.w-ring {
  stroke: var(--muted);
  opacity: 0.5;
}

.w-arrow {
  stroke: var(--accent);
}

.info-sub {
  font-size: 13px;
  color: var(--dim);
}

/* Wetness and rain share one severity ramp keyed on their percentage
   (kit wetSeverity): nothing · green · yellow · amber · orange · red · crimson.
   Tokens per theme beside --red. */
.w-sev-0 { color: var(--muted); }
.w-sev-1 { color: var(--green); }
.w-sev-2 { color: var(--sev-yellow); }
.w-sev-3 { color: var(--amber); }
.w-sev-4 { color: var(--sev-orange); }
.w-sev-5 { color: var(--red); }
.w-sev-6 { color: var(--sev-crimson); }

/* inline-flex like .info-weather, so the counter below can hold a fixed slot
   (min-width does nothing to a plain inline span). Centred, not baseline: the
   iR item is an empty box until a delta exists, and an empty box's baseline is
   its bottom edge — which would move the counter vertically as it fills. */
.info-right {
  font-size: 17px;
  justify-self: end;
  display: inline-flex;
  align-items: center;
}

.info-ir.gain { color: var(--green); }
.info-ir.loss { color: var(--red); }

.info-inc { color: var(--muted); }
.info-inc.warn { color: var(--amber); }
.info-inc.crit { color: var(--red); }

/* Fixed slot, same reason as the weather items: the counter gaining a digit
   (9/17x → 10/17x), or growing its ' · ' prefix the moment an iR delta
   appears, must not drag the value beside it. 80px = ' · 199/17x' at 17px
   Inter, the widest with a two-digit limit (every iRacing limit is one or two
   digits); right-aligned so the strip stays flush at the padding edge and the
   counter grows leftward into its own reserved space. The iR value needs no
   slot — it is the group's first item, so its own width only ever moves its
   own left edge, and the group's total stays inside what the old shrink-to-fit
   box already reached. */
.info-right > #d-inc { min-width: 80px; text-align: right; }

/* 5. Fuel & settings strip */
.dash-fuel {
  display: grid;
  grid-template-columns: 108px 130px 148px 148px minmax(0, 1fr);
  height: 72px;
  border-top: 1px solid var(--frame);
  flex-shrink: 0;
}

.f-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 0 12px;
  border-right: 1px solid var(--hairline);
  min-width: 0;
  overflow: hidden; /* long labels clip in-cell, never bleed across */
}

.f-cell.settings {
  flex-direction: row;
  justify-content: space-evenly;
  padding: 0 8px;
  border-right: none;
}

/* Three shared tiers across every cell of the strip, so labels, heroes and
   suffixes sit on the same baselines: label 13px / hero 24px / suffix 14px. */
.f-lbl {
  font-size: 13px;
  letter-spacing: 0.1em;
  line-height: 1.4;
  color: var(--muted);
  white-space: nowrap;
}

.f-lbl-sm {
  letter-spacing: 0.08em;
}

.f-hero {
  font-size: 24px;
  font-weight: 700;
  line-height: 1.2;
  white-space: nowrap;
}

.f-suf,
.f-pct,
.pit-suf {
  font-size: 14px;
  font-weight: 500;
  margin-left: 5px;
}

.f-suf {
  color: var(--muted);
}

/* Base color only; live values get an inline red→neutral→green mix by value
   (0% = about to lose a lap, 99% = about to bank one). */
.f-pct {
  color: var(--text);
}

/* FUEL OVERRIDE (Marcus 09-24): the figure is assumed, not read. */
.f-lbl.assumed { color: var(--amber, #ffb020); }
.f-hero.assumed { color: var(--muted); }
.lapscell.nodata .f-hero,
.lapscell.nodata .f-pct { color: var(--dim); }

.lapscell.warn .f-hero { color: var(--amber); }

.lapscell.crit { background: var(--fuel-crit-bg); }
.lapscell.crit .f-hero { color: var(--red); }
.lapscell.crit .f-lbl { color: var(--fuel-crit-ink); }

/* PIT RECOMMENDATION (21a): one contextual state at a time. */
.pit-suf {
  color: var(--muted);
}

.pitcell.nodata .pit-val,
.pitcell.nodata .pit-suf { color: var(--dim); }

.pitcell.calm .pit-val { color: var(--green); }
.pitcell.calm .pit-suf { color: var(--muted); }

.pitcell.save,
.pitcell.needed {
  background: var(--pit-bg);
}

.pitcell.save .pit-lbl {
  color: var(--amber);
  font-weight: 700;
  letter-spacing: 0.12em;
}

.pitcell.needed .pit-lbl { color: var(--amber-label); }

.pitcell.save .pit-val,
.pitcell.needed .pit-val { color: var(--amber); }

.pitcell.save .pit-suf,
.pitcell.needed .pit-suf { color: var(--amber-label); }

/* PIT-cell ±1 confidence chip (21c): directional CONFIDENT / +1 LAP EXTRA /
   −1 LAP POSS, replacing the old symmetric ±1 marker. The label row is an
   inline-flex group with a FIXED-WIDTH chip slot, so "PIT" holds its position
   as the chip appears, changes text, or blanks (never re-centers). Sized to the
   widest string ("+1 LAP EXTRA") at the reduced chip font — clips, never bleeds. */
.pit-lbl {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
}
.pit-conf {
  display: inline-block;
  width: auto;
  text-align: left;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 1.4;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
}
/* Reserve the slot only in the pit-add state (where the chip actually shows),
   so PIT holds position as the chip changes/blanks — but MARGIN/SAVE in other
   states aren't pushed out of the 148px cell. */
.pitcell.needed .pit-conf {
  width: 74px;
}
.pit-conf.extraLap {
  color: var(--amber);
}
.pit-conf.confident {
  color: var(--green);
}
.pit-conf.lapEarly {
  color: var(--muted);
}
/* GET ME HOME in the same slot (Marcus, 07-28): green while the lap quota is
   being met, amber when it isn't. Both are already the cell's vocabulary —
   green is MARGIN's colour, amber is SAVE/PIT's — so nothing new enters the
   palette. The slot needs its reserved width outside the pit-add state too,
   since GMH only ever appears alongside MARGIN or SAVE. */
.pitcell .pit-conf.gmh {
  width: 74px;
  letter-spacing: 0.08em;
}
.pit-conf.gmh.onPlan { color: var(--green); }
.pit-conf.gmh.behind { color: var(--amber); }

.s-col {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.s-col .f-lbl {
  letter-spacing: 0.08em;
}

.s-val {
  font-size: 16px;
  font-weight: 600;
}

/* ---- pitlane states (12a/12b/12c) --------------------------------------- */

/* Banner slot between the LEDs and the main row. */
.dash-banner {
  flex-shrink: 0;
  margin-top: 4px;
}

.bnr {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* STRIP SLOT CONTRACT: every banner variant is exactly the traffic
     strip's 40px so slot swaps never move the layout below. Enforced here
     on the base class — variants must not override it. */
  height: 40px;
  box-sizing: border-box;
  padding: 0 16px;
  white-space: nowrap;
  overflow: hidden;
}

.bnr-left {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

/* 12a — flashing takeover. Banner and hero share one step-end clock.
   Same 40px slot as the traffic strip (see the contract note there). */
.bnr-off {
  height: 40px;
  background: var(--pit-warn-bg);
  color: var(--pit-warn-text);
}

.dash-banner.flashing .bnr-off {
  animation: pit-warn-flash 0.7s step-end infinite;
}

@keyframes pit-warn-flash {
  0%, 100% { background: var(--pit-warn-bg); color: var(--pit-warn-text); }
  54% { background: var(--pit-warn-bg-alt); color: var(--pit-warn-text-alt); }
}

.bnr-tri {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
}

.bnr-title {
  font-size: 24px;
  font-weight: 800;
  letter-spacing: 0.04em;
}

.bnr-limit {
  font-size: 17px;
  font-weight: 700;
}

/* 12d — limiter left on out on track. Same 0.7s step-end cadence as 12a and
   in phase with its own hero, but amber: this is "you are stuck slow on
   circuit", not "you are about to take a penalty in the pits". */
.bnr-track {
  height: 40px;
  background: var(--pit-lim-bg);
  color: var(--pit-lim-text);
}

.dash-banner.flashing .bnr-track {
  animation: pit-lim-flash 0.7s step-end infinite;
}

@keyframes pit-lim-flash {
  0%, 100% { background: var(--pit-lim-bg); color: var(--pit-lim-text); }
  54% { background: var(--pit-lim-bg-alt); color: var(--pit-lim-text-alt); }
}

/* The triangle holds constant amber through the flash — only the band and
   the wordmark step. */
.bnr-track .bnr-tri {
  color: var(--pit-lim-text);
}

/* 12b — calm confirm. */
.bnr-on {
  height: 40px;
  background: var(--pit-ok-bg);
  border-bottom: 1px solid var(--pit-ok-border);
}

.bnr-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--green);
  flex-shrink: 0;
}

.bnr-title-on {
  font-size: 20px;
  font-weight: 700;
  color: var(--green);
}

.bnr-speeds {
  font-size: 20px;
  font-weight: 700;
  color: var(--text);
}

.bnr-vs {
  color: var(--pit-ok-sub);
}

/* 12c — exit traffic band. Bordered top AND bottom (32a): the band is a mode
   swap in the strip slot, not a header, so it closes on both edges. */
.bnr-exit {
  position: relative;
  height: 40px;
  background: var(--pit-exit-bg);
  border-top: 1px solid var(--pit-exit-border);
  border-bottom: 1px solid var(--pit-exit-border);
  gap: 12px;
}

.bnr-tri-sm {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  color: var(--pit-exit-text);
}

/* Same type as the settled strip's TRAFFIC label — every state name in this
   slot wears one style, so the ground colour is what changes, not the word. */
.bnr-title-exit {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--pit-exit-text);
}

/* The proximity axis. Cars are absolutely positioned inside it by the painter
   (transform only — never left/top, so movement stays off the layout path).
   x = 0 is arrival, x = width is the 5s threshold, linear between. */
.bnr-axis {
  position: relative;
  flex: 1;
  align-self: stretch;
  min-width: 0;
  margin-left: 18px;
  /* Entering cars emerge from the fade instead of popping in at the edge. */
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
}

/* The zero mark: where a car arrives on you. Without it the leftmost dot
   reads as "somewhere near the label" rather than "here, now". */
.bnr-axis-zero {
  position: absolute;
  left: 0;
  top: 50%;
  width: 1px;
  height: 16px;
  margin-top: -8px;
  background: var(--pit-exit-border);
}

.exit-car {
  position: absolute;
  top: 50%;
  left: 0;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
  /* Only opacity transitions: the x position is eased numerically by the
     painter, so a CSS transform transition would double-smooth it. */
  transition: opacity 160ms ease;
}

/* Arriving (ETA hit 0) and dropping out of the 5s threshold both fade rather
   than vanish — a car blinking out reads as a glitch. */
.exit-car.gone {
  opacity: 0;
}

.exit-car .sw {
  width: 10px;
  height: 10px;
  border-radius: 3px;
  flex-shrink: 0;
}

/* 3 letters (Marcus 07-25): the axis is a position scale, and three cars only
   fit without shoving each other off their true positions at this width. */
.exit-car .nm {
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
}

/* No sign and no green/red: everything on this axis is behind and closing,
   so direction is a constant and the ink is the band's own yellow. */
.exit-car b {
  font-size: 17px;
  font-weight: 700;
  color: var(--pit-exit-text);
}

/* Window drain, shared by both bands: 2px along the bottom edge, width
   written from the engine's countdown (never a CSS animation — a CSS clock
   desyncs from session time under replay, pause and speed changes). */
.bnr-rail {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2px;
  width: 0;
  background: var(--pit-exit-rail);
}

/* 12c — exit clear. Statement only; its own greens because it now holds the
   rest of the window and carries a rail. */
.bnr-clear {
  position: relative;
  height: 40px;
  background: var(--pit-clear-bg);
  border-top: 1px solid var(--pit-clear-border);
  border-bottom: 1px solid var(--pit-clear-border);
  gap: 12px;
}

.bnr-title-clear {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--green);
}

.bnr-clear .bnr-rail {
  background: var(--pit-clear-rail);
}

/* Faster-class traffic band (Marcus 08-03) — the exit band's proximity axis in
   a blue skin, no rail and no clear twin. The axis, chips and painter are all
   shared; only the ground, label ink and the ETA colour change. */
.bnr-faster {
  position: relative;
  height: 40px;
  background: var(--flag-blue-bg);
  border-top: 1px solid var(--flag-blue-border);
  border-bottom: 1px solid var(--flag-blue-border);
  gap: 12px;
}
.bnr-title-faster {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--flag-blue);
}
.bnr-faster .bnr-tri-sm { color: var(--flag-blue); }
.bnr-faster .bnr-axis-zero { background: var(--flag-blue-border); }
/* The chips are the same .exit-car nodes; only their ETA ink is re-skinned. */
.bnr-faster .exit-car b { color: var(--flag-blue); }

/* Center panel state variants. */
.c-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-evenly;
  width: 100%;
  height: 100%;
  padding: 8px 0;
  min-width: 0;
}

.dash-center {
  padding: 0;
  overflow: hidden;
}

.dash-battle {
  overflow: hidden;
}

.c-limoff {
  background: var(--pit-center-bg);
}

.lo-top {
  font-size: 22px;
  color: var(--pit-muted-warn);
}

.lo-top b {
  color: var(--text);
  font-weight: 700;
}

.lo-speed {
  font-size: 130px;
  font-weight: 800;
  line-height: 0.9;
  color: var(--red);
}

.c-limoff.flashing .lo-speed {
  animation: pit-hero-flash 0.7s step-end infinite;
}

@keyframes pit-hero-flash {
  0%, 100% { color: var(--red); }
  54% { color: var(--pit-warn-text); }
}

.lo-sub {
  font-size: 18px;
  font-weight: 700;
  color: var(--text);
}

.lo-kmh {
  color: var(--pit-warn-text-alt);
}

/* 12d reuses the 12a center skeleton in the amber palette: speed is the whole
   message (no over-limit delta — the renderer leaves .lo-over empty), and the
   hero always flashes because being on the limiter out here is always wrong. */
.c-limoff.ontrack {
  background: var(--pit-lim-center-bg);
}

.c-limoff.ontrack .lo-top {
  color: var(--pit-lim-muted);
}

.c-limoff.ontrack .lo-speed {
  color: var(--pit-lim-text);
}

.c-limoff.ontrack.flashing .lo-speed {
  animation: pit-lim-hero-flash 0.7s step-end infinite;
}

@keyframes pit-lim-hero-flash {
  0%, 100% { color: var(--pit-lim-text); }
  54% { color: var(--pit-lim-text-alt); }
}

.c-limoff.ontrack .lo-kmh {
  color: var(--pit-lim-sub);
}

/* 12e reuses the same skeleton in the lane's green (limiter on, stop done /
   box behind us): identical slots and sizes to 12a/12d, only the palette
   moves. Never flashes — the flash grammar stays reserved for 12a/12d. */
.c-limoff.go {
  background: var(--pit-ok-center-bg);
}

.c-limoff.go .lo-top {
  color: var(--pit-ok-muted);
}

.c-limoff.go .lo-speed {
  color: var(--green);
}

.c-limoff.go .lo-kmh {
  color: var(--pit-ok-sub);
}

.bx-lbl {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--muted);
}

.bx-dist {
  display: flex;
  align-items: baseline;
}

.bx-dist span:first-child {
  font-size: 110px;
  font-weight: 800;
  line-height: 0.9;
}

.bx-unit {
  font-size: 26px;
  color: var(--muted);
  /* Centre the number, not the number+unit group: zero-width unit with its gap
     margin cancelled from the flex measure (Marcus 08-03). Matches the hero. */
  width: 0;
  margin-left: 8px;
  margin-right: -8px;
  white-space: nowrap;
}

.bx-bar {
  position: relative;
  width: 72%;
  height: 8px;
  border-radius: 3px;
  background: var(--led-off);
}

.bx-fill {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 0;
  border-radius: 3px;
  background: var(--green);
}

.bx-tick {
  position: absolute;
  right: 0;
  top: -3px;
  bottom: -3px;
  width: 3px;
  background: var(--text);
}

.bx-tele {
  font-size: 16px;
  color: var(--muted);
}

.bx-tele b {
  font-weight: 600;
  color: var(--text2);
}

/* SERVICE panel (right slot while on pit road). */
.b-swap {
  display: flex;
  flex-direction: column;
  justify-content: space-evenly;
  height: 100%;
  min-width: 0;
}

.svc-title {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--muted);
}

/* The panel exists so the driver verifies what the crew is about to do, so
   while a stop is being staged the header nags: amber, and a pure opacity
   pulse — no color change, no movement — that draws the eye and then reads
   calmly once checked. Its own clock, deliberately out of phase with the
   12a banner flash. */
.svc-title.nag {
  color: var(--amber);
  animation: svc-nag-pulse 0.8s step-end infinite;
}

@keyframes svc-nag-pulse {
  0%, 100% { opacity: 1; }
  56% { opacity: 0.32; }
}

/* Shown only in the nag state, and driven by the header's class rather than
   the `hidden` property: `hidden` is an HTMLElement property, so assigning it
   to an SVG element silently does nothing. */
.svc-tri {
  display: none;
  width: 15px;
  height: 15px;
  flex-shrink: 0;
}

.svc-title.nag .svc-tri {
  display: block;
}

.svc-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.svc-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
}

.svc-lbl {
  font-size: 14px;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.svc-fuel {
  font-size: 26px;
  font-weight: 700;
  color: var(--amber);
}

.svc-bar {
  position: relative;
  height: 7px;
  border-radius: 3px;
  background: var(--led-off);
  overflow: hidden;
}

.svc-bar-now {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  background: var(--dim);
}

.svc-bar-add {
  position: absolute;
  top: 0;
  bottom: 0;
  background: var(--amber);
}

.svc-sub {
  display: flex;
  font-size: 13px;
  color: var(--dim);
}

.svc-tyres {
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
}

.svc-tyres-left {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.svc-compound-row {
  display: flex;
  align-items: baseline;
  gap: 5px;
}

.svc-compound {
  font-size: 22px;
  font-weight: 700;
}

/* Axle hint next to the compound ("MED · FRONTS") — only when the selection
   is a partial one the two words can name; the grid carries the detail. */
.svc-axle {
  font-size: 13px;
  font-weight: 500;
  color: var(--muted);
}

.svc-wheels {
  display: grid;
  grid-template-columns: 23px 23px;
  grid-auto-rows: 23px;
  gap: 5px;
}

/* Per-corner ticks and crosses: unambiguous at a glance, where the old
   fill-vs-outline diagram needed a legend. Both glyphs ship in the markup and
   swap by class, so a service change is a classList toggle, not DOM churn. */
.wheel {
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 4px;
  background: var(--led-off);
  border: 1px solid var(--frame);
  color: var(--dim);
}

.wheel.change {
  background: var(--amber);
  border-color: var(--amber);
  color: var(--svc-tick-ink);
}

.wh-tick {
  width: 13px;
  height: 13px;
}

.wh-cross {
  width: 11px;
  height: 11px;
}

.wheel .wh-tick,
.wheel.change .wh-cross {
  display: none;
}

.wheel.change .wh-tick {
  display: block;
}

.svc-est {
  font-size: 20px;
  font-weight: 700;
}

/* Swapped into a stop already under way: the countdown is an upper bound
   (its origin is the takeover, not the crew's start). Dimmed to read as
   "approximate", never hidden. The tilde is added by the renderer. */
.svc-est.partial {
  color: var(--muted);
}


/* Setting-adjustment takeover (14a/15a/15b). Domain tokens: dc* live-car =
   cyan, PitSv* black box = amber; paper needs darker inks for contrast. */
.dash {
  --adj-car: #2fc3f7;
  --adj-car-muted: #5a97ad;
  --adj-pit: #ffb020;
  --adj-pit-muted: #b98617;
  --adj-track: #23252a;
}

.dash[data-theme='paper'] {
  --adj-car: #0e7ec2;
  --adj-car-muted: #54809b;
  --adj-pit: #c47d00;
  --adj-pit-muted: #8a6a2e;
  --adj-track: #e0e0e4;
}

/* No gear/kph/rpm footer (Marcus 09-16): the hero takes the freed height;
   the dismiss bar's own margin plus the pad keep it off the floor. */
.c-adjust {
  justify-content: space-between;
  padding: 8px 10px;
  border-left: 3px solid var(--adj-dom);
  border-right: 3px solid var(--adj-dom);
}

.c-adjust[data-dom='car'] {
  --adj-dom: var(--adj-car);
  --adj-dom-muted: var(--adj-car-muted);
}

.c-adjust[data-dom='pit'] {
  --adj-dom: var(--adj-pit);
  --adj-dom-muted: var(--adj-pit-muted);
}

.c-adjust.in {
  animation: adj-fade-in 120ms ease-out;
}

.c-adjust.out {
  animation: adj-fade-out 180ms ease-in forwards;
}

@keyframes adj-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes adj-fade-out {
  from { opacity: 1; }
  to { opacity: 0; }
}

.adj-label {
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.18em;
  color: var(--adj-dom-muted);
}

.adj-hero {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.adj-num {
  display: flex;
  align-items: baseline;
  font-size: 92px;
  font-weight: 700;
  line-height: 0.9;
}

.c-adjust[data-dom='pit'] .adj-num {
  color: var(--adj-pit);
}

.adj-unit {
  font-size: 30px;
  font-weight: 400;
  color: var(--dim);
  margin-left: 6px;
}

.adj-word {
  font-size: 60px;
  font-weight: 700;
  line-height: 0.95;
  color: var(--adj-dom);
}

.adj-word.on { color: var(--green); }
.adj-word.off { color: var(--muted); }

/* Cells sized down from the handoff's 36×50 — at the dashboard's real center
   row height the full tyre stack (label + grid + intent + was + bar + footer)
   overflowed the panel top. */
.adj-tyregrid {
  display: grid;
  grid-template-columns: 28px 28px;
  grid-auto-rows: 38px;
  gap: 6px;
}

.adj-cell {
  border-radius: 4px;
  border: 1px solid var(--frame);
  color: var(--dim);
  font-size: 9px;
  font-weight: 700;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding-bottom: 2px;
}

.adj-cell.change {
  background: var(--adj-pit);
  border-color: var(--adj-pit);
  color: #1a1408;
}

.adj-intent {
  font-size: 22px;
  font-weight: 700;
  color: var(--adj-dom);
}

.adj-intent.none {
  color: var(--muted);
}

.c-adjust[data-kind='tyres'] .adj-hero {
  gap: 5px;
}

.c-adjust[data-kind='tyres'] .adj-sub {
  min-height: 20px;
}

.adj-sub {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 10px;
  min-height: 26px;
}

.adj-delta {
  font-size: 23px;
  font-weight: 600;
  color: var(--adj-dom);
}

.adj-was {
  font-size: 15px;
  color: var(--dim);
}

/* Dismiss bar: drains over 2.5 s, refilled by every click. The transition
   smooths the 10-20 Hz fraction steps to display rate. */
.adj-bar {
  width: 86px;
  height: 3px;
  border-radius: 2px;
  background: var(--adj-track);
  margin: 4px auto 8px;
  overflow: hidden;
}

.adj-fill {
  height: 100%;
  width: 100%;
  border-radius: 2px;
  background: var(--adj-dom);
  transition: width 140ms linear;
}

/* ---------------------------------------------------------------------------
   Flag awareness (16b-16f). Stripe tokens are Carbon/Nocturne values from the
   handoff; paper gets light-ground overrides for the dark-tinted stripes.
   --------------------------------------------------------------------------- */

.dash {
  --flag-yellow: #ffd21e;
  --flag-yellow-ink: #1a1508;
  --flag-yellow-ink2: #6b5c14;
  --flag-caution-bg: #1e1a0b;
  --flag-caution-border: #574a17;
  --flag-caution-text: #d8c04a;
  /* Stopped-car (red) counterparts of the caution set, + the hero inks. */
  --flag-danger-bg: #23100d;
  --flag-danger-border: #5a201b;
  --flag-danger-text: #e0685e;
  --flag-red: #ff4438;
  --flag-red-ink: #fff1ef;
  --hazard-slow-ink: #ffd21e;
  --hazard-stop-ink: #ff5a4d;
  --flag-green-bg: #0f1d17;
  --flag-green-border: #1d4636;
  --flag-blue-bg: #0e1626;
  --flag-blue-border: #1e3a66;
  --flag-blue: #3f8cff;
  --flag-white: #f2f3f5;
  --flag-white-ink: #14161f;
  --flag-white-ink2: #6a6e80;
  --flag-frozen: #3a3d4a;
  --rc-red: #ff4438;
  --rc-red-text: #ff5252;
  --rc-red-lbl: #c0524a;
  --rc-orange: #ff7a29;
  --rc-orange-lbl: #bd6a30;
  --rc-furled: #7a3d38;
  --rc-furled-text: #d87c74;
  --rc-dq-bg: #23100d;
  --start-amber: #ffb020;
  --start-amber-lbl: #b9a53f;
  --start-go-bg: #0d1f18;
  --start-go-lbl: #35a06c;
}

.dash[data-theme='paper'] {
  --flag-caution-bg: #fdf4dc;
  --flag-caution-border: #e3cf94;
  --flag-caution-text: #8a6d00;
  --flag-danger-bg: #fbe4e1;
  --flag-danger-border: #e7b0a8;
  --flag-danger-text: #a3271b;
  --flag-red: #d92b20;
  --flag-red-ink: #fff2f0;
  --hazard-slow-ink: #9a7a00;
  --hazard-stop-ink: #cc2a1f;
  --flag-green-bg: #e7f5ec;
  --flag-green-border: #9fd4b4;
  --flag-blue-bg: #eaf2f9;
  --flag-blue-border: #c8dcee;
  --flag-blue: #0f6fbe;
  --flag-frozen: #c9c9ce;
  --start-go-bg: #e7f5ec;
  --rc-dq-bg: #f6d7d4;
}

/* --- Info-stripe flag states (16b) ------------------------------------- */

/* Overlap the grid cells: weather/flag and session/lapslot swap via hidden. */
.info-session, .info-lapslot { grid-column: 1; grid-row: 1; justify-self: start; }
.info-weather, .info-flag { grid-column: 2; grid-row: 1; }
.info-right { grid-column: 3; grid-row: 1; }

.info-lapslot {
  font-size: 17px;
  font-weight: 700;
}

.info-lapslot span { color: var(--muted); font-weight: 600; }

.info-flag {
  display: inline-flex;
  align-items: baseline;
  gap: 10px;
}

.if-word {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.18em;
}

.if-name { font-size: 15px; font-weight: 600; }
.if-gap { font-size: 15px; font-weight: 700; }
.if-sub { font-size: 13.5px; }

.dash-info[data-flag] { border-top-color: transparent; }

.dash-info[data-flag='green'] {
  background: var(--flag-green-bg);
  box-shadow: inset 0 0 0 1px var(--flag-green-border);
}
.dash-info[data-flag='green'] .if-word { color: var(--green); }

.dash-info[data-flag='yellow'] {
  background: var(--flag-yellow);
  color: var(--flag-yellow-ink);
}
.dash-info[data-flag='yellow'] .if-word {
  font-size: 16px;
  font-weight: 800;
  color: var(--flag-yellow-ink);
}
.dash-info[data-flag='yellow'] .info-dot,
.dash-info[data-flag='yellow'] .info-inc { color: var(--flag-yellow-ink2); }
.dash-info[data-flag='yellow'] .info-ir.gain { color: #0e6c3f; }
.dash-info[data-flag='yellow'] .info-ir.loss { color: #8f1d15; }

.dash-info[data-flag='debris'],
.dash-info[data-flag='fcy'] {
  background: var(--flag-caution-bg);
  box-shadow: inset 0 0 0 1px var(--flag-caution-border);
}
.dash-info[data-flag='debris'] .if-word { color: var(--flag-caution-text); }
.dash-info[data-flag='fcy'] .if-word { color: var(--flag-yellow); }
.dash-info[data-flag='fcy'] .if-sub { color: var(--muted); }

.dash-info[data-flag='blue'] {
  background: var(--flag-blue-bg);
  box-shadow: inset 0 0 0 1px var(--flag-blue-border);
}
.dash-info[data-flag='blue'] .if-word,
.dash-info[data-flag='blue'] .if-gap { color: var(--flag-blue); }
.dash-info[data-flag='blue'] .if-sub { color: var(--muted); }

.dash-info[data-flag='final'] {
  background: var(--flag-white);
  color: var(--flag-white-ink);
}
.dash-info[data-flag='final'] .if-word {
  font-size: 18px;
  font-weight: 800;
  letter-spacing: 0.22em;
  color: var(--flag-white-ink);
}
.dash-info[data-flag='final'] .info-lapslot span,
.dash-info[data-flag='final'] .info-dot,
.dash-info[data-flag='final'] .info-inc { color: var(--flag-white-ink2); }
.dash-info[data-flag='final'] .info-ir.gain { color: #0e6c3f; }
.dash-info[data-flag='final'] .info-ir.loss { color: #8f1d15; }

/* Cooldown stripe under the finish frame. */
.dash-info[data-flag='finish'] .if-sub { color: var(--muted); }
.dash-info[data-flag='finish'] .if-gap { color: var(--accent); }
.dash-info[data-flag='finish'] .info-session,
.dash-info[data-flag='finish'] .info-lapslot { color: var(--muted); }

/* --- Full-course yellow frame (16d subset) ------------------------------ */

.dash.fcy .dash-leds .led,
.dash.fcy .dash-leds .led.z-green.lit,
.dash.fcy .dash-leds .led.z-amber.lit,
.dash.fcy .dash-leds .led.z-red.lit { background: var(--flag-yellow); }

.dash-traffic-frozen {
  font-size: 12px;
  letter-spacing: 0.1em;
  color: var(--dim);
  flex-shrink: 0;
}

/* 32a learning state: LEARNING TRAFFIC + a confidence rail + a percentage,
   everything at dim weight so it reads "not yet", not "warning". The rail
   fills once and never loops — an indeterminate shimmer would imply the model
   is working on something it cannot finish. */
.dash-traffic-learn {
  display: flex;
  flex: 1;
  align-items: center;
  gap: 14px;
  margin-left: 20px;
  min-width: 0;
}

.tl-track {
  flex: 1;
  height: 2px;
  border-radius: 1px;
  background: var(--traffic-border);
  overflow: hidden;
}

.tl-fill {
  display: block;
  width: 0;
  height: 100%;
  background: var(--dim);
}

.tl-pct {
  font-size: 13px;
  letter-spacing: 0.08em;
  color: var(--dim);
  flex-shrink: 0;
}

/* Green confirm banner in the traffic slot (start/restart). */
.bnr-green {
  justify-content: center;
  background: var(--flag-green-bg);
  border-bottom: 1px solid var(--flag-green-border);
}

.bnr-green-word {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.18em;
  color: var(--green);
}

/* "form up" beside 1 LAP TO GREEN (the word swaps; the banner is one
   centred block, so nothing else on the row moves). */
.bnr-green-sub {
  margin-left: 10px;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--flag-caution-text);
}

/* Slow/stopped-car takeover (19a; four-state 08-03). Two axes: [data-kind]
   slow=yellow / stopped=red, and [data-near] far=darkened / near=bright fill.
   The base rule is slow·far; the three others layer on. All four grounds
   cross-fade via this transition instead of snapping. */
.bnr-slow {
  background: var(--flag-caution-bg);
  border-bottom: 1px solid var(--flag-caution-border);
  color: var(--flag-caution-text);
  transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease;
}

.bnr-slow .bnr-tri-sm {
  width: 17px;
  height: 17px;
  /* Follow the tier ink (the base .bnr-tri-sm rule is pit-exit yellow,
     which vanishes on the imminent tier's yellow fill). */
  color: inherit;
}

.bnr-slow-title {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.18em;
}

.bnr-slow-id {
  display: inline-flex;
  align-items: center;
  gap: 9px;
}

.bnr-slow-id .sw {
  width: 10px;
  height: 10px;
  border-radius: 3px;
  /* Outline so the swatch never disappears when its class colour matches the
     banner ground — a GTP's yellow on the imminent tier's yellow fill, or any
     bright class on the paper theme's pale caution fill. Near-black on the dark
     tiers, where it just blends in. Border-box keeps the 10px footprint. */
  border: 1px solid var(--flag-yellow-ink);
}

.bnr-slow-name {
  font-size: 16px;
  font-weight: 600;
  /* Legible even in the far state — knowing who it is is the point. */
  color: var(--text);
  transition: color 160ms ease;
}

.bnr-slow-eta {
  font-size: 17px;
  font-weight: 700;
  color: var(--flag-caution-text);
  transition: color 160ms ease;
}

/* The left title and right pad balance so the identity sits centered. */
.bnr-slow-pad {
  width: 120px;
}

/* stopped · far — darkened red ground */
.bnr-slow[data-kind='stopped'] {
  background: var(--flag-danger-bg);
  border-bottom-color: var(--flag-danger-border);
  color: var(--flag-danger-text);
}
.bnr-slow[data-kind='stopped'] .bnr-slow-eta { color: var(--flag-danger-text); }

/* slow · near — bright yellow fill */
.bnr-slow[data-near='y'] {
  background: var(--flag-yellow);
  border-bottom-color: var(--flag-yellow);
  color: var(--flag-yellow-ink);
}
.bnr-slow[data-near='y'] .bnr-slow-name,
.bnr-slow[data-near='y'] .bnr-slow-eta {
  color: var(--flag-yellow-ink);
}

/* stopped · near — bright red fill (compound selector outranks slow·near) */
.bnr-slow[data-kind='stopped'][data-near='y'] {
  background: var(--flag-red);
  border-bottom-color: var(--flag-red);
  color: var(--flag-red-ink);
}
.bnr-slow[data-kind='stopped'][data-near='y'] .bnr-slow-name,
.bnr-slow[data-kind='stopped'][data-near='y'] .bnr-slow-eta {
  color: var(--flag-red-ink);
}

/* Weather items get fixed slots so a value change (NE → N, M.CLDY →
   OVERCAST) never shifts its neighbours. */
.info-weather > .w-wind { min-width: 58px; }
.info-weather > span:nth-of-type(2) { min-width: 42px; }
.info-weather > span:nth-of-type(3) { min-width: 68px; }
.info-weather > #d-hum-wrap { min-width: 68px; }
.info-weather > #d-skies { min-width: 76px; }
/* Same for the wet format's pair, each sized past its widest word + percentage
   (measured at 14px Inter: 'TORRENTIAL 100%' = 127.72px, 'VL.WET 33%' =
   87.83px), so a band or percentage change shifts nothing beside them. */
.info-weather > #d-rain { min-width: 132px; }
.info-weather > #d-wet { min-width: 92px; }

.dash.fcy .dash-traffic .chip { color: var(--dim); }
.dash.fcy .dash-traffic .chip .sw { background: var(--flag-frozen) !important; }
/* Pass costs don't apply while the field is frozen — no loss lines under FCY. */
.dash.fcy .dash-traffic .chip .loss { background: transparent; }
.dash.fcy .lt-deltas { opacity: 0.55; }
.dash.fcy .b-swap { opacity: 0.55; }
.dash.fcy .pitcell { background: var(--pit-bg); }

.c-fcyplate {
  font-size: 13px;
  letter-spacing: 0.2em;
  font-weight: 700;
  color: var(--flag-yellow);
  background: var(--flag-caution-bg);
  border: 1px solid var(--flag-caution-border);
  border-radius: 5px;
  padding: 4px 12px;
}

.dash.fcy #c-default .c-rpm { display: none; }

/* --- Start-sequence takeover (16c) -------------------------------------- */

.c-start {
  justify-content: space-between;
  align-items: center;
  padding-top: 12px;
  border-left: 3px solid var(--start-amber);
  border-right: 3px solid var(--start-amber);
}

.st-lbl {
  font-size: 14px;
  letter-spacing: 0.18em;
  color: var(--start-amber-lbl);
}

.st-lights { display: flex; gap: 16px; }

.st-light {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--led-off);
}

.c-start[data-start='ready'] .st-light:nth-child(1),
.c-start[data-start='set'] .st-light:nth-child(-n + 2) {
  background: var(--start-amber);
  box-shadow: 0 0 18px rgba(255, 176, 32, 0.4);
}

.st-word {
  font-size: 56px;
  font-weight: 700;
  line-height: 1;
  color: var(--start-amber);
}

.c-start[data-start='go'] {
  background: var(--start-go-bg);
  border-color: var(--green);
}
.c-start[data-start='go'] .st-lbl { color: var(--start-go-lbl); }
.c-start[data-start='go'] .st-light {
  background: var(--green);
  box-shadow: 0 0 18px rgba(33, 208, 122, 0.5);
}
.c-start[data-start='go'] .st-word { color: var(--green); }

.st-foot {
  width: 100%;
  border-top: 1px solid var(--hairline);
  padding: 6px 0 7px;
  font-size: 14px;
  text-align: center;
  color: var(--dim);
}

.st-foot b { font-weight: 600; color: var(--muted); }

/* --- Personal race-control takeovers (16f) ------------------------------ */

/* 16f revised (Marcus 09-16): header band over a gear-only body. The band's
   height is its own content (static for the flag's life); the body takes the
   rest and centres the live gear digit — the one thing that moves. */
.c-race {
  justify-content: flex-start;
  align-items: center;
  padding: 0;
  border-left: 3px solid var(--rc-dom);
  border-right: 3px solid var(--rc-dom);
}

.rc-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 13px 0 11px;
  width: 100%;
  border-bottom: 1px solid #1c1e26;
}
.c-race[data-flag='dq'] .rc-head { border-bottom-color: #3a1712; }

.rc-body {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
}
.rc-gear {
  font-size: 124px;
  font-weight: 700;
  line-height: 1;
  color: var(--text);
}
.c-race[data-flag='dq'] .rc-gear { color: var(--rc-furled-text); }

.c-race[data-flag='black'] { --rc-dom: var(--rc-red); }
.c-race[data-flag='repair'] { --rc-dom: var(--rc-orange); }
.c-race[data-flag='furled'] { --rc-dom: var(--rc-furled); }
.c-race[data-flag='dq'] { --rc-dom: var(--rc-red); background: var(--rc-dq-bg); }

.rc-lbl {
  font-size: 12px;
  letter-spacing: 0.18em;
  color: var(--rc-red-lbl);
}
.c-race[data-flag='repair'] .rc-lbl { color: var(--rc-orange-lbl); }
.c-race[data-flag='dq'] .rc-lbl { color: var(--rc-furled-text); }

.rc-hero { display: flex; align-items: center; gap: 10px; }

.rc-ball {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: #1c1e26;
  border: 2px solid #3a3d4a;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.rc-ball span {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--rc-orange);
}

.rc-word {
  font-size: 34px;
  font-weight: 700;
  line-height: 1;
  white-space: nowrap;
  color: var(--rc-red-text);
}
.c-race[data-flag='repair'] .rc-word { color: var(--rc-orange); }
.c-race[data-flag='furled'] .rc-word { color: var(--rc-furled-text); }
.c-race[data-flag='dq'] .rc-word { font-size: 32px; }

.rc-sub {
  font-size: 14px;
  color: var(--text2);
  white-space: nowrap;
}
.rc-sub b { color: var(--text); }
.c-race[data-flag='dq'] .rc-sub { color: var(--rc-furled-text); }

/* --- Checkered debrief (16e) -------------------------------------------- */

.dash-finish {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-evenly;
  padding: 10px 24px;
}

.fin-title {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 15px;
  letter-spacing: 0.22em;
  font-weight: 700;
}

.fin-check {
  width: 16px;
  height: 12px;
  border-radius: 2px;
  border: 1px solid var(--frame);
  background: repeating-conic-gradient(var(--text) 0% 25%, var(--bg) 0% 50%);
  background-size: 8px 8px;
}

.fin-pos {
  display: flex;
  align-items: baseline;
  gap: 14px;
  font-size: 96px;
  font-weight: 700;
  line-height: 0.95;
}

.fin-gained { font-size: 28px; font-weight: 600; }
.fin-gained.up { color: var(--green); }
.fin-gained.down { color: var(--red); }

.fin-stats {
  display: grid;
  grid-auto-flow: column;
  gap: 40px;
}

.fin-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}

.fin-lbl {
  font-size: 13px;
  letter-spacing: 0.1em;
  color: var(--muted);
  font-weight: 700;
}

.fin-val { font-size: 21px; font-weight: 600; }
.fin-val.gain { color: var(--green); }
.fin-val.loss { color: var(--red); }
.fin-val.best { color: var(--accent); }

.dash-finfoot {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 40px;
  height: 68px;
  border-top: 1px solid var(--frame);
  font-size: 14px;
  color: var(--dim);
  white-space: nowrap;
  flex-shrink: 0;
}

.dash-finfoot b { font-weight: 600; color: var(--muted); }

/* --- Qualifying mode (18a/18b, lone qualifying) --------------------------
   Stripe: quiet rail in the 40px strip slot — uniform-thickness 6px bars on
   a fixed baseline, hairlines for unreached segments, nothing changes height
   between states. Panel: hero delta top-right, holder beneath, one
   prediction line at the bottom. Nocturne values from the handoff; other
   themes carry derived tokens. */

.dash-quali {
  position: relative;
  height: 40px;
  margin-top: 4px;
  background: var(--traffic-bg);
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--traffic-border);
  flex-shrink: 0;
}

.qs-rail {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: flex-end;
  padding: 0 10px 9px;
  gap: 4px;
}

.qs-seg {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
}

.qs-bar {
  width: 100%;
  height: 6px;
  border-radius: 2px;
  background: var(--q-neutral);
}

.qs-seg[data-s='unreached'] .qs-bar {
  height: 1px;
  border-radius: 0;
  margin-bottom: 2.5px; /* hairline center sits on the bar centerline */
  background: var(--q-hair);
}

.qs-seg[data-s='yellow'] .qs-bar { background: var(--q-yellow); }
.qs-seg[data-s='green'] .qs-bar { background: var(--q-green); }
.qs-seg[data-s='purple'] .qs-bar { background: var(--q-purple); }

/* Purple -> green demotion: a lingering purple outline fades over ~1.5s —
   perceptible, not alarming. */
.qs-seg.demote .qs-bar {
  animation: qs-demote 1.5s ease-out forwards;
}

@keyframes qs-demote {
  from { box-shadow: 0 0 0 1.5px var(--q-purple); }
  to { box-shadow: 0 0 0 1.5px transparent; }
}

/* Per-µsector deltas surface ONLY in review — zero numbers while driving. */
.qs-d {
  font-size: 12px;
  font-weight: 600;
  line-height: 1;
  color: var(--q-delta);
  visibility: hidden;
  white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   Strategy console (design v9 nocturne, docs/design/strategy-console-v9).
   Phase V build: the panel ground only — regions land one step at a time
   inside this fixed 696x380 box (heights measured across all 18 reference
   scenes; 380 is the tallest). Every fill/tint in the design derives as
   color-mix off these inks; nocturne is the only skin in the drop — the
   amber/paper/slate token tables slot in as data-theme variants when the
   design delivers them.
--------------------------------------------------------------------------- */
/* The console shares the dashboard's house palette and all four skins (Marcus
   08-08): the values below MIRROR the dashboard tokens (.dash --bg/--text/…),
   not the v9 concept's standalone darks, so the card sits in the same skin as
   its neighbours. The theme is mirrored onto .stratcard in applyTheme (siblings
   of .dash, like every other card). Nocturne (default): */
.stratcard {
  --sc-ground: #161826; /* --bg */
  --sc-line: #292b31; /* --hairline (cell inset rules) */
  --sc-line2: #3f424d; /* --frame (card border) */
  --sc-text: #e9e9ed; /* --text */
  --sc-muted: #9397ab; /* --muted (labels) */
  --sc-dim: #75798c; /* --dim (fainter: captions, stub) */
  --sc-green: #21d07a;
  --sc-amber: #ffb020;
  --sc-red: #ff5252;
  --sc-blue: #56b8ff; /* --sel-blue: calm fact (NO STOP, AUTO), pour, pending */
  --sc-est: #8a7550;
  --sc-edge: #cfd3e5; /* --text2: committed-plan inset edge + ACTIVE tag */
  --sc-basis-max: #9184d9; /* MAX basis role ink (violet) */
  --sc-basis-last: #7fb2a6; /* LAST basis role ink (teal) */

  /* Stripe chip fills (canvas reads these via scResolveInks). Derived as a
     color-mix off the skin's own inks + ground, so the three DARK skins follow
     from their tokens automatically; paper restates them because on a light
     ground a filled chip must run DARKER than the panel, not lighter. */
  --sc-chip-grey: color-mix(in srgb, var(--sc-muted) 16%, var(--sc-ground));
  --sc-chip-steel: color-mix(in srgb, var(--sc-muted) 38%, var(--sc-ground));
  --sc-chip-green: color-mix(in srgb, var(--sc-green) 50%, var(--sc-ground));
  --sc-chip-amber: color-mix(in srgb, var(--sc-amber) 55%, var(--sc-ground));
  --sc-chip-red: color-mix(in srgb, var(--sc-red) 50%, var(--sc-ground));
  --sc-chip-pour: color-mix(in srgb, var(--sc-blue) 35%, var(--sc-ground));
  --sc-chip-stub: color-mix(in srgb, var(--sc-dim) 18%, var(--sc-ground));
  /* The chequered chip is the maximum-contrast ink; its own token so the
     monochrome amber skin (text == amber) keeps it apart from the amber chip. */
  --sc-chip-flag: var(--sc-text);

  width: 696px;
  height: 380px;
  box-sizing: border-box;
  background: var(--sc-ground);
  border-radius: 5px;
  overflow: hidden;
  padding: 10px;
  color: var(--sc-text);
  font-family: Inter, 'Segoe UI', system-ui, sans-serif;
  font-variant-numeric: tabular-nums;
}

.stratcard[data-theme='amber'] {
  --sc-ground: #0b0a07;
  --sc-line: #241f12;
  --sc-line2: #2e2617;
  --sc-text: #ffb020;
  --sc-muted: #8a7245;
  --sc-dim: #6b5a38;
  --sc-red: #ff4438;
  --sc-est: #8a6a2c;
  --sc-edge: #ffcf6b;
  /* Text IS the amber here, so the chequered needs a paler ink to stay apart
     from the amber (second-to-last-lap) chip beside it. */
  --sc-chip-flag: #ffe6b8;
}

.stratcard[data-theme='paper'] {
  --sc-ground: #f5f5f6;
  --sc-line: #e0e0e4;
  --sc-line2: #c9c9ce;
  --sc-text: #1b1d1f;
  --sc-muted: #6f7379;
  --sc-dim: #9a9da3;
  --sc-green: #149655;
  --sc-amber: #a06800;
  --sc-red: #d92b20;
  --sc-blue: #2d7dd2;
  --sc-est: #8a6a2c;
  --sc-edge: #43474d; /* committed inset edge is DARK on the light ground */
  --sc-basis-max: #6558b8;
  --sc-basis-last: #2f7d6d;
  /* On a light ground the mixes above run the wrong way — a chip must be DARKER
     than the panel to read as filled. Restated (mirrors the fuel-calc paper
     skin), not derived, since "toward the ground" is the wrong direction. */
  --sc-chip-grey: #d3d4d9;
  --sc-chip-steel: #8f939c;
  --sc-chip-green: #149655;
  --sc-chip-amber: #a06800;
  --sc-chip-red: #d92b20;
  --sc-chip-pour: #2d7dd2;
  --sc-chip-stub: #e4e4e8;
  --sc-chip-flag: #1b1d1f;
}

.stratcard[data-theme='slate'] {
  --sc-ground: #10141c;
  --sc-line: #1b2230;
  --sc-line2: #232c3d;
  --sc-text: #f0f3f8;
  --sc-muted: #8a92a3;
  --sc-dim: #6b7383;
  --sc-green: #3ddc84;
  --sc-amber: #f5a623;
  --sc-red: #ff5252;
  --sc-edge: #c3c9d4;
}

/* h2 dashboard-aligned horizontal form (Marcus 08-08): one stacked card —
   readout row · stripe · plans · OFF strip. Cells are the dash's raised
   panels (fill text 6% over the ground + a 1px text 11% inset hairline, no
   borders-on-nothing); state cells tint and take a lit inset edge. Fixed
   geometry throughout — nothing moves between scenes, nothing blinks. */
.sc-h2 {
  display: flex;
  flex-direction: column;
  gap: 8px;
  height: 100%;
}
/* Readout row: ADVICE (leads, 1.5) · STINT REMAINING · FILL SET. */
.sc-readout {
  flex: none;
  display: flex;
  gap: 8px;
  height: 82px;
}
.sc-chip {
  flex: 1;
  min-width: 0;
  background: color-mix(in srgb, var(--sc-text) 6%, var(--sc-ground));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sc-text) 11%, var(--sc-ground));
  border-radius: 4px;
  padding: 11px 14px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 3px;
  justify-content: center;
}
.sc-chip1 {
  flex: 1.5;
}
.sc-chiphead {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 13px;
}
.sc-headD {
  font-size: 10.5px;
  letter-spacing: 0.09em;
  color: var(--sc-muted);
  font-weight: 600;
}
.sc-chipbig {
  font-size: 30px;
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.015em;
  color: var(--sc-text);
  white-space: nowrap;
  min-height: 32px;
}
.sc-chipbig.long {
  font-size: 22px;
}
.sc-chipsub {
  font-size: 11px;
  color: var(--sc-muted);
  min-height: 14px;
}
.sc-chipsub[data-tone='amber'] {
  color: var(--sc-amber);
  font-weight: 600;
}
.sc-chipsub[data-tone='dim'] {
  color: var(--sc-dim);
}
.sc-chipauto {
  font-size: 7px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--sc-ground);
  background: var(--sc-blue);
  border-radius: 2px;
  padding: 1px 4px;
  line-height: 1.4;
}
.sc-chip[data-tone='dim'] .sc-chipbig {
  color: var(--sc-dim);
}
/* State cells: a lit inset edge in the state ink (45%) over the ground tinted
   10% — the dash's PIT tile grammar. Inset shadow, never a border, so content
   never shifts a pixel between states (reflow rule). */
.sc-chip[data-tone='red'] {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sc-red) 45%, var(--sc-ground));
  background: color-mix(in srgb, var(--sc-red) 10%, var(--sc-ground));
}
.sc-chip[data-tone='red'] .sc-chipbig {
  color: var(--sc-red);
}
.sc-chip[data-tone='blue'] {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sc-blue) 45%, var(--sc-ground));
  background: color-mix(in srgb, var(--sc-blue) 10%, var(--sc-ground));
}
.sc-chip[data-tone='blue'] .sc-chipbig {
  color: var(--sc-blue);
}
.sc-chip[data-tone='amber'] {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sc-amber) 45%, var(--sc-ground));
  background: color-mix(in srgb, var(--sc-amber) 10%, var(--sc-ground));
}
.sc-chip[data-tone='amber'] .sc-chipbig {
  color: var(--sc-amber);
}
.sc-chip[data-tone='green'] {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sc-green) 45%, var(--sc-ground));
  background: color-mix(in srgb, var(--sc-green) 10%, var(--sc-ground));
}
.sc-chip[data-tone='green'] .sc-chipbig {
  color: var(--sc-green);
}
/* STINT REMAINING readout cell: range figure + the tank line (the dash owns
   LAP/REMAINING, so this cell carries the tank note the vertical box did). */
.sc-rangerow {
  display: flex;
  align-items: baseline;
  gap: 6px;
}
.sc-rangefig {
  font-size: 30px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.015em;
  color: var(--sc-text);
}
.sc-rangeunit {
  font-size: 12px;
  color: var(--sc-dim);
}
.sc-tank {
  font-size: 11px;
  color: var(--sc-muted);
  min-height: 14px;
  transition: color 0.35s ease;
}
.sc-tank[data-tone='amber'] {
  color: var(--sc-amber);
  font-weight: 600;
}
.sc-tank[data-tone='red'] {
  color: var(--sc-red);
  font-weight: 600;
}
.sc-head {
  font-size: 10.5px;
  letter-spacing: 0.09em;
  color: var(--sc-muted);
  font-weight: 600;
}

/* Plans table (h2): PLAN · REMAINING/LENGTH · BURN · PACE IMPACT · PITS LEFT
   · FILL REQUIRED (1fr 118 92 76 84 96, gap 8). Rows are a fixed 36px so
   selection can never change table height. The region is a fixed box so the
   learning/GMH takeovers swap inside it without moving the stripe above. */
.sc-plans {
  flex: none;
  /* header 16 + gap 4 + rows 116 + gap 4 + OFF strip 52 = 192; a few px of
     headroom so the OFF strip's bottom (and its ACTIVE highlight edge) never
     clips against overflow:hidden. The stripe flexes to absorb the difference. */
  height: 196px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  overflow: hidden;
}
.sc-planhead {
  flex: none;
  display: grid;
  grid-template-columns: 1fr 118px 92px 76px 84px 96px;
  gap: 8px;
  padding: 2px 12px 3px;
  font-size: 9px;
  letter-spacing: 0.09em;
  color: var(--sc-dim);
  font-weight: 600;
}
.sc-planhead span:not(:first-child) {
  text-align: right;
}
.sc-planrows {
  flex: none;
  height: 116px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.sc-planrow {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 118px 92px 76px 84px 96px;
  gap: 8px;
  align-items: center;
  height: 36px;
  flex: none;
  background: color-mix(in srgb, var(--sc-text) 6%, var(--sc-ground));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sc-text) 11%, var(--sc-ground));
  border-radius: 4px;
  padding: 0 12px;
  font-size: 12.5px;
  overflow: hidden;
  cursor: pointer;
}
.sc-planrow.dim {
  opacity: 0.4;
}
.sc-pname {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
/* Selection dot: hollow dim ring unselected; filled blue committing; filled
   edge-ink committed (the two-pixel ground gap makes it read as a ring). */
.sc-dot {
  width: 11px;
  height: 11px;
  flex: none;
  border-radius: 50%;
  border: 1.5px solid var(--sc-dim);
  box-sizing: border-box;
}
.sc-pnamet {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--sc-muted);
  white-space: nowrap;
}
.sc-pcell {
  text-align: right;
  color: var(--sc-text);
}
.sc-pcell[data-c='fill'] {
  color: var(--sc-muted);
}
.sc-pcell[data-c='pace'][data-tone='free'] {
  color: var(--sc-green);
}
.sc-pcell[data-c='pace'][data-tone='cheap'] {
  color: var(--sc-muted);
}
.sc-pcell[data-c='pace'][data-tone='dear'] {
  color: var(--sc-amber);
  font-weight: 600;
}
.sc-pstops {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 5px;
}
.sc-planrow.del .sc-pstops {
  color: var(--sc-green);
  font-weight: 600;
}
.sc-ptag,
.sc-pdel {
  font-size: 7px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--sc-ground);
  border-radius: 2px;
  padding: 1px 5px;
  line-height: 1.4;
  white-space: nowrap;
}
.sc-ptag {
  background: var(--sc-edge);
}
.sc-pdel {
  background: var(--sc-green);
  padding: 1px 4px;
}
/* Left bar (3px) — the second selection cue beside the dot. */
.sc-pbar {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: transparent;
}
.sc-pdrain {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2px;
  background: var(--sc-blue);
  border-radius: 0 1px 1px 0;
  transition: width 0.12s linear;
}
/* Selection treatment, shared by the plan rows and the OFF label box:
   committed = edge-ink inset + bar + filled dot + ACTIVE; committing = blue.
   Inset shadow, never a border — no content shift between states. */
.sc-planrow[data-state='active'],
.sc-offlabel[data-state='active'] {
  box-shadow: inset 0 0 0 1px var(--sc-edge);
  background: color-mix(in srgb, var(--sc-edge) 8%, var(--sc-ground));
}
.sc-planrow[data-state='active'] .sc-pnamet,
.sc-planrow[data-state='active'] .sc-offoff {
  color: var(--sc-text);
}
.sc-planrow[data-state='pending'],
.sc-offlabel[data-state='pending'] {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sc-blue) 55%, var(--sc-ground));
  background: color-mix(in srgb, var(--sc-blue) 10%, var(--sc-ground));
}
.sc-planrow[data-state='pending'] .sc-pnamet {
  color: var(--sc-blue);
}
.sc-planrow[data-state='active'] .sc-pbar,
.sc-offlabel[data-state='active'] .sc-pbar {
  background: var(--sc-edge);
}
.sc-planrow[data-state='pending'] .sc-pbar,
.sc-offlabel[data-state='pending'] .sc-pbar {
  background: var(--sc-blue);
}
.sc-planrow[data-state='active'] .sc-dot,
.sc-offlabel[data-state='active'] .sc-dot {
  border-color: var(--sc-edge);
  background: var(--sc-edge);
  box-shadow: inset 0 0 0 2px var(--sc-ground);
}
.sc-planrow[data-state='pending'] .sc-dot,
.sc-offlabel[data-state='pending'] .sc-dot {
  border-color: var(--sc-blue);
  background: var(--sc-blue);
  box-shadow: inset 0 0 0 2px var(--sc-ground);
}

/* OFF strip (h2): OFF is the baseline the savers are priced against — a 120px
   label box + three basis boxes (AVG · MAX · LAST), each keyed by a 2px
   basis-role inset edge and quoting its own burn + FILL · STOP. */
.sc-offstrip {
  flex: none;
  height: 52px;
  display: flex;
  gap: 8px;
}
.sc-offlabel {
  position: relative;
  width: 120px;
  flex: none;
  background: color-mix(in srgb, var(--sc-text) 6%, var(--sc-ground));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sc-text) 11%, var(--sc-ground));
  border-radius: 4px;
  padding: 8px 12px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 3px;
  overflow: hidden;
  cursor: pointer;
}
.sc-offhead {
  display: flex;
  align-items: center;
  gap: 8px;
}
.sc-offname {
  font-size: 10px;
  letter-spacing: 0.09em;
  font-weight: 600;
  color: var(--sc-muted);
}
.sc-offval {
  display: flex;
  align-items: center;
  gap: 6px;
  padding-left: 19px;
}
.sc-offoff {
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--sc-text);
  line-height: 1.1;
}
.sc-offbasis {
  flex: 1;
  min-width: 0;
  background: color-mix(in srgb, var(--sc-text) 6%, var(--sc-ground));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sc-text) 11%, var(--sc-ground)),
    inset 2px 0 0 color-mix(in srgb, var(--sc-muted) 65%, var(--sc-ground));
  border-radius: 4px;
  padding: 8px 11px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 2px;
  justify-content: center;
}
.sc-offbasis[data-b='max'] {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sc-text) 11%, var(--sc-ground)),
    inset 2px 0 0 color-mix(in srgb, var(--sc-basis-max) 65%, var(--sc-ground));
}
.sc-offbasis[data-b='last'] {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sc-text) 11%, var(--sc-ground)),
    inset 2px 0 0 color-mix(in srgb, var(--sc-basis-last) 65%, var(--sc-ground));
}
.sc-offbhead {
  font-size: 9px;
  letter-spacing: 0.1em;
  font-weight: 600;
  color: var(--sc-muted);
}
.sc-offbasis[data-b='max'] .sc-offbhead {
  color: var(--sc-basis-max);
}
.sc-offbasis[data-b='last'] .sc-offbhead {
  color: var(--sc-basis-last);
}
.sc-offbrow {
  display: flex;
  align-items: baseline;
  gap: 6px;
}
.sc-offburn {
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--sc-text);
  line-height: 1.1;
}
.sc-offbmeta {
  font-size: 10px;
  color: var(--sc-muted);
  margin-left: auto;
  white-space: nowrap;
}

/* V7 takeovers — close replications of the widget's own forms (Marcus
   08-06), swapped inside the plans region; nothing outside it moves. Both
   fill the fixed plans box so the swap never resizes the stripe above. */
.sc-learn {
  flex: 1;
  background: color-mix(in srgb, var(--sc-text) 6%, var(--sc-ground));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sc-text) 11%, var(--sc-ground));
  border-radius: 4px;
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  justify-content: center;
}
.sc-learnmsg {
  font-size: 11px;
  color: var(--sc-muted);
}
.sc-learnbar {
  height: 5px;
  border-radius: 2px;
  background: color-mix(in srgb, var(--sc-muted) 18%, var(--sc-ground));
  position: relative;
  overflow: hidden;
}
.sc-learnfill {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  background: var(--sc-blue);
  border-radius: 2px;
  transition: width 0.3s ease;
}
.sc-gmh {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 6px;
  justify-content: center;
}
/* GMH rows reuse the plan-row raised panel + selection grammar, but lay out
   as flex (dot · name · sub) rather than the six-column grid. */
.sc-gmhrow {
  display: flex;
  align-items: center;
  gap: 8px;
}
.sc-gmhsub {
  font-size: 10px;
  letter-spacing: 0.08em;
  font-weight: 600;
  color: var(--sc-dim);
  margin-left: auto;
}
.sc-gmhsub.on {
  color: var(--sc-green);
}

/* The horizontal stripe (h2): full card width, marks above each chip, the
   green window rail underneath, caption below-left. It flexes to absorb the
   slack between the readout and the plans so the card holds 380px. The rows
   are drawn on a CANVAS (08-06 judder finding: painted DOM boxes pixel-snap
   under camera motion) — geometry, easing and colour cross-fades live in the
   renderer's draw (drawStrategyStripe), fed by the engine's partition.
   (Horizontal canvas layout is step 3; the draw is still vertical here.) */
.sc-stripebody {
  flex: 1;
  min-height: 44px;
  position: relative;
  background: color-mix(in srgb, var(--sc-text) 5%, var(--sc-ground));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sc-text) 10%, var(--sc-ground));
  border-radius: 4px;
  overflow: hidden;
}
.sc-stripe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
.sc-await {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 9px;
  letter-spacing: 0.12em;
  color: var(--sc-dim);
}
.stratcard.sc-noanim * {
  transition: none !important;
}

.dash-quali[data-mode='review'] .qs-d { visibility: visible; }
.qs-seg[data-s='yellow'] .qs-d { color: var(--q-delta-warn); }
.qs-seg[data-s='purple'] .qs-d { color: var(--q-purple); font-weight: 700; }

.qs-marker {
  position: absolute;
  bottom: 7.5px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  transform: translateX(-50%);
  background: var(--q-marker);
  box-shadow: 0 0 8px var(--q-marker);
  z-index: 2;
}

/* Out-lap / review: hollow marker — armed, unmistakably not live. */
.dash-quali:not([data-mode='flying']) .qs-marker {
  background: transparent;
  box-shadow: none;
  border: 1.5px solid var(--muted);
  box-sizing: border-box;
}

/* --- Qualifying panel (18b) --------------------------------------------- */

.qp {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  flex: 1;
  min-width: 0;
}

.qp-top {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 10px;
}

.qp-deltablock { text-align: right; }

.qp-label {
  font-size: 12px;
  letter-spacing: 0.06em;
  color: var(--dim);
}

/* Pole handover: the three amber components pop on instantly and IMMEDIATELY
   start decaying back to their normal state over 2s (one-shot, re-triggered
   per event): label amber→grey, underline amber→purple, NEW fades out and
   collapses. */
.qp-label.fade { animation: qp-fade-label 2s ease-out; }

@keyframes qp-fade-label {
  from { color: var(--amber); }
}

/* The delta and its blank-state word share ONE fixed-height slot: the two
 * swap on every mode change (flying <-> out-lap/review), and without the
 * fixed slot the holder and prediction rows below jumped ~23px each time —
 * content must never move its neighbours (CLAUDE.md rule 5). */
.qp-delta,
.qp-word {
  height: 57px;
  display: flex;
  align-items: center;
  justify-content: flex-end;
}

.qp-delta {
  font-size: 54px;
  font-weight: 700;
  line-height: 1;
  color: var(--text);
}

.qp-delta.gaining { color: var(--green); }
.qp-delta.losing { color: var(--red); }
.qp-delta.idle { color: var(--dim); font-size: 34px; }

/* Delta blank state (out-lap / session over): a word, never a stale number —
 * in ghost mode too (Marcus 08-12: a static delta here reads as live). */
.qp-word {
  font-size: 24px;
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--muted);
}

/* "NO FASTER GHOST LAP": the whole panel blanks to this one line-wrapped
 * message once the session best reaches the loaded ghost (Marcus 08-13).
 * Same fixed slot, smaller type, wraps to two lines. */
.qp-word.small {
  font-size: 15px;
  letter-spacing: 0.08em;
  line-height: 1.3;
  white-space: normal;
  text-align: right;
  color: var(--dim);
}

.qp-holder { text-align: right; }

.qp-time {
  font-size: 20px;
  font-weight: 600;
  color: var(--text);
}

.qp-name {
  font-size: 13px;
  color: var(--muted);
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 175px;
}

.qp-name.self { color: var(--q-self); font-weight: 700; }

/* Cross-car ghost's car model, its own line under the driver · date — the
 * combined line truncated ("· Porsch…", Marcus 08-15). Fixed-height slot,
 * empty in every other state, so the underline below never moves. */
.qp-car {
  font-size: 12px;
  line-height: 15px;
  height: 15px;
  color: var(--dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 175px;
  margin-left: auto;
}

/* NEW floats OUTSIDE the layout (absolute, left of the name), so the holder
   name sits in its final resting position from the first frame and the
   prefix purely fades — nothing slides (feedback). */
.qp-name .qp-nm {
  position: relative;
}

.qp-name .qp-new {
  position: absolute;
  right: 100%;
  top: 0;
  color: var(--amber);
  font-weight: 700;
  white-space: nowrap;
  animation: qp-new-out 2s ease-out forwards;
}

@keyframes qp-new-out {
  to { opacity: 0; }
}

.qp-underline {
  width: 34px;
  height: 2px;
  background: var(--q-pole);
  margin: 6px 0 0 auto;
}

.qp-underline.self { background: var(--q-self); }
.qp-underline.fade { animation: qp-fade-underline 2s ease-out; }

@keyframes qp-fade-underline {
  from { background-color: var(--amber); }
}

/* Fixed-height row sized for the big (44px) variant, contents centred: the
 * ghost chip (20px) and the 1x/2x takeover (44px) swap in this slot, and the
 * row must not breathe when they do (rule 5). */
.qp-pred {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 44px;
}

.qp-predlbl {
  font-size: 12px;
  letter-spacing: 0.06em;
  color: var(--dim);
}

.qp-predval {
  font-size: 20px;
  font-weight: 700;
  color: var(--q-self);
}

/* Live prediction while chasing/defending — the panel's key readout, so it
   gets hero-sized type (07-17 driver feedback). */
.qp-predval.big {
  font-size: 44px;
  line-height: 44px;
  font-weight: 800;
}

.qp-predval.gaining { color: var(--green); }
.qp-predval.losing { color: var(--red); }

/* Incident on the lap in progress: the prediction slot says "1x"/"2x"/"4x"
   instead, at the same size, in red — the lap cannot count (Marcus 07-27). */
.qp-predval.invalid { color: var(--red); }

/* --- Qualifying standings board (turn 29) ------------------------------- */
.dash-standings {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  font-variant-numeric: tabular-nums;
  /* Soft entry: replays whenever the board becomes visible (display none→flex). */
  animation: qst-enter 0.24s ease-out both;
}
.qst-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 44px;
  padding: 0 15px;
  border-bottom: 1px solid var(--hairline);
}
.qst-titleleft { display: flex; align-items: baseline; gap: 9px; }
.qst-h { font-size: 17px; font-weight: 700; letter-spacing: 0.02em; white-space: nowrap; }
.qst-class {
  font-size: 11.5px; font-weight: 700; letter-spacing: 0.08em; color: var(--accent);
  border: 1px solid var(--accent-muted); border-radius: 4px; padding: 1px 6px;
}
.qst-session { display: flex; align-items: baseline; gap: 8px; }
.qst-slabel { font-size: 11px; letter-spacing: 0.12em; color: var(--muted); }
.qst-stime { font-size: 23px; font-weight: 700; line-height: 1; color: var(--text); }
/* Race board: "FINAL" tag in the session slot — smaller than the live clock. */
.qst-stime.qres-final { font-size: 15px; letter-spacing: 0.08em; color: var(--muted); }

.qst-colhdr,
.qst-row {
  display: grid;
  grid-template-columns: 34px 1fr 118px 40px 86px;
  align-items: center;
  gap: 10px;
  padding: 0 15px;
}
.qst-colhdr {
  height: 21px;
  font-size: 10.5px; letter-spacing: 0.1em; color: var(--dim);
  border-bottom: 1px solid var(--q-hair);
}
.qst-r { text-align: right; }
.qst-c { text-align: center; }

.qst-row {
  height: 30px;
  border-bottom: 1px solid var(--q-hair);
}
.qst-pos { font-size: 15px; font-weight: 700; color: var(--text); }
/* Race-results board: a wider POS column carries the position + a grid-delta
   arrow (green up = places gained, red down = lost). */
#d-results .qst-colhdr,
#d-results .qst-row {
  grid-template-columns: 62px 1fr 108px 44px 92px;
}
.qres-row .qst-pos { display: flex; align-items: baseline; gap: 5px; }
.qres-p { font-size: 15px; font-weight: 700; }
.qres-gd { font-size: 11px; font-weight: 700; letter-spacing: 0.02em; }
.qres-gd.up { color: var(--green); }
.qres-gd.down { color: var(--red); }
/* Name + model share the flexible 1fr cell. min-width:0 + overflow:hidden keep
   the pair inside the cell so the model never reaches the time/gap columns. */
.qst-name {
  display: flex; align-items: baseline; gap: 6px;
  min-width: 0; overflow: hidden;
  font-size: 14.5px; color: var(--text2);
}
/* Driver name sizes to content and sits first; the model trails it (not
   right-aligned to the column). Name keeps priority: shrinks/truncates only
   after the model has (model has the higher shrink factor below). */
.qst-nm {
  flex: 0 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Car model: de-emphasised, capped, yields (shrinks) before the name does. */
.qst-model {
  flex: 0 2 auto; min-width: 0; max-width: 42%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 11px; font-weight: 400; color: var(--muted);
}
.qst-bestcell { text-align: right; }
.qst-best { font-size: 14.5px; }
.qst-lapcell { text-align: center; }
.qst-lap { font-size: 12px; color: var(--muted); }
.qst-gap { font-size: 14.5px; text-align: right; color: var(--text2); }
.qst-gap.pole { font-size: 12px; font-weight: 700; letter-spacing: 0.08em; color: var(--q-pole); }
.qst-gap.live { font-size: 14px; font-style: italic; font-weight: 600; color: var(--accent); }
.qst-gap.live.uncertain { color: var(--muted); }

/* Lap in progress: the percentage alone (the bar beside it was redundant —
   Marcus, 07-27). Right-justified like the banked lap time it replaces, so
   the column edge does not move when a row switches between the two. */
.qst-prog { display: flex; align-items: center; justify-content: flex-end; }
.qst-ppct { font-size: 12.5px; font-weight: 700; color: var(--accent); }
/* on-track LAP cell → lap number + pulsing dot. The number stays centered (so
   it lines up with the dotless lap numbers in other rows); the dot is absolutely
   positioned to its right and does not shift it. */
.qst-livelap { position: relative; display: inline-block; }
.qst-ln { font-size: 12px; color: var(--muted); }
.qst-dot {
  position: absolute;
  left: 100%;
  margin-left: 5px;
  top: 50%;
  margin-top: -3.5px;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--q-self); box-shadow: 0 0 6px var(--q-self);
  animation: qs-pulse 1.1s ease-in-out infinite;
}

/* Row states — skin roles, three washes derived from the tokens */
.qst-row[data-state='pole'] {
  background: color-mix(in srgb, var(--q-pole) 11%, transparent);
  box-shadow: inset 2px 0 0 var(--q-pole);
}
.qst-row[data-state='pole'] .qst-pos { color: var(--q-pole); }
.qst-row[data-state='pole'] .qst-name,
.qst-row[data-state='self'] .qst-name { font-weight: 600; color: var(--text); }
.qst-row[data-state='live'] {
  background: color-mix(in srgb, var(--q-self) 11%, transparent);
  box-shadow: inset 2px 0 0 var(--q-self);
}
.qst-row[data-state='self'] {
  background: color-mix(in srgb, var(--text) 6%, transparent);
  box-shadow: inset 2px 0 0 color-mix(in srgb, var(--text) 30%, transparent);
}

.qst-divider { height: 2px; background: var(--q-neutral); box-shadow: 0 1px 0 rgba(0, 0, 0, 0.4); }
.qst-scrollview { flex: 1; min-height: 0; overflow: hidden; position: relative; }
.qst-scroll { animation: qs-scroll 13s ease-in-out infinite; }

.qst-foot {
  display: flex; align-items: center; justify-content: center; gap: 40px;
  height: 28px; border-top: 1px solid var(--hairline); background: var(--bg);
  font-size: 13px; color: var(--dim); white-space: nowrap; letter-spacing: 0.02em;
}
.qst-foot b { color: var(--text2); font-weight: 600; }

@keyframes qst-enter {
  from { opacity: 0; }
}
@keyframes qs-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.35; transform: scale(0.7); }
}
@keyframes qs-scroll {
  0%, 12% { transform: translateY(0); }
  44%, 56% { transform: translateY(-58px); }
  88%, 100% { transform: translateY(0); }
}

/* Qualifying: the fuel-saver panel hides entirely (engine sets fs.hidden;
   edit mode overrides so the widget stays positionable). */
.widget.quali-hide { display: none; }

/* (The 30a `.fscard` selector block that lived here — four 96px mission
   boxes — was superseded by the 31a `.fstable` below and removed 2026-08-04;
   no markup referenced it.) */

/* --- Fuel-saver mission table (design 31a; supersedes the 30a .fscard) --- */

/* Palette mirrors the DASHBOARD tokens 1:1 per skin (pscard pattern): the
   card is a sibling of .dash (overlay.ts mirrors data-theme onto it), so the
   dash values are restated, not inherited. Tints derive from the ground via
   color-mix so they track every skin automatically. */
.fstable {
  --fst-ground: #161826; /* --bg */
  --fst-hair1: #292b31;  /* --hairline */
  --fst-hair2: #3f424d;  /* --frame (card border) */
  --fst-text: #e9e9ed;   /* --text */
  --fst-text2: #cfd3e5;  /* --text2 */
  --fst-muted: #9397ab;  /* --muted */
  --fst-dim: #75798c;    /* --dim */
  --fst-green: #21d07a;  /* --green */
  --fst-amber: #ffb020;  /* --amber */
  --fst-blue: #56b8ff;   /* selector/lock accent */
  --fst-head: color-mix(in srgb, var(--fst-ground) 94%, var(--fst-text) 6%);
  --fst-sel: color-mix(in srgb, var(--fst-ground) 89%, var(--fst-text) 11%);

  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  width: 344px;
  /* Height follows the row count (PUSH appears only when the fraction
     earns it) — pitstrat-style variable height, Marcus's 07-22 ruling. */
  min-height: 196px;
  background: var(--fst-ground);
  border: 1px solid var(--fst-hair2);
  border-radius: 6px;
  overflow: hidden;
  font-family: inherit;
  font-variant-numeric: tabular-nums;
  color: var(--fst-text);
  position: relative;
}

.fstable .fst-head {
  display: grid;
  grid-template-columns: 92px 108px 76px 66px;
  height: 26px;
  align-items: center;
  background: var(--fst-head);
  border-bottom: 1px solid var(--fst-hair1);
  flex-shrink: 0;
}

.fstable .fst-head span {
  font-size: 12px;
  letter-spacing: 0.1em;
  color: var(--fst-dim);
  padding-left: 12px;
  white-space: nowrap;
}

.fstable .fst-head span.num { text-align: right; padding-left: 0; padding-right: 12px; }
.fstable .fst-head i { font-style: normal; letter-spacing: 0.04em; }

.fstable .fst-row {
  display: grid;
  grid-template-columns: 92px 108px 76px 66px;
  height: 42px;
  align-items: center;
  position: relative;
  border-bottom: 1px solid var(--fst-hair1);
  opacity: 0.55;
  flex-shrink: 0;
}

.fstable .fst-row:last-of-type { border-bottom: none; }

.fstable .fst-row.sel { opacity: 1; background: var(--fst-sel); }
.fstable .fst-row.sel.locked { box-shadow: inset 3px 0 0 var(--fst-text2); }
/* Dead mission: the planner cannot find that much fuel. */
.fstable .fst-row.dead { opacity: 0.35; }

.fstable .fst-mode {
  font-size: 13px;
  letter-spacing: 0.1em;
  color: var(--fst-muted);
  padding-left: 12px;
  white-space: nowrap;
}

.fstable .fst-row.sel .fst-mode { color: var(--fst-text2); }

.fstable .fst-rem {
  font-size: 17px;
  font-weight: 600;
  text-align: right;
  padding-right: 12px;
  white-space: nowrap;
}

/* The selected row's REM is the dominant glance target. */
.fstable .fst-row.sel .fst-rem { font-size: 22px; font-weight: 700; }
.fstable .fst-rem .plan { font-size: 14px; font-weight: 400; color: var(--fst-muted); }

.fstable .fst-llap {
  font-size: 17px;
  font-weight: 600;
  text-align: right;
  padding-right: 12px;
  white-space: nowrap;
}

/* "NO SAVE": a word, not a number — label-sized so it fits the 76px column
   at 17px-row height without crowding the cost cell. */
.fstable .fst-llap .free {
  color: var(--fst-muted);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
}

.fstable .fst-cost {
  font-size: 14px;
  font-weight: 600;
  text-align: right;
  padding-right: 12px;
  white-space: nowrap;
}

.fstable .fst-cost .zero { color: var(--fst-green); }
.fstable .fst-cost .mid { color: var(--fst-muted); }
.fstable .fst-cost .dear { color: var(--fst-amber); }
.fstable .dim { color: var(--fst-dim); font-weight: 400; }

/* Lock-in drain: width driven by the ENGINE's lockFrac (sample clock). */
.fstable .fst-lock {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 3px;
  background: var(--fst-blue);
}

/* Calibrating: the surface below the header IS the prompt. */
.fstable .fst-calib {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
}

.fstable .fst-calib-title { font-size: 16px; font-weight: 700; letter-spacing: 0.14em; }
.fstable .fst-calib-msg { font-size: 13px; color: var(--fst-muted); }

.fstable .fst-calib-bar {
  width: 280px;
  height: 5px;
  border-radius: 2.5px;
  background: var(--fst-hair1);
  overflow: hidden;
}

.fstable .fst-calib-fill {
  display: block;
  height: 100%;
  width: 0;
  background: var(--fst-blue);
}

/* GET ME HOME: pure statement below the OFF row. */
.fstable .fst-gmh {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  opacity: 0.55;
}

.fstable .fst-gmh.sel { opacity: 1; background: var(--fst-sel); }
.fstable .fst-gmh.sel.locked { box-shadow: inset 3px 0 0 var(--fst-text2); }
.fstable .fst-gmh-name { font-size: 21px; font-weight: 700; letter-spacing: 0.16em; }
.fstable .fst-gmh-sub { font-size: 14px; font-weight: 700; letter-spacing: 0.22em; color: var(--fst-dim); }
.fstable .fst-gmh.sel .fst-gmh-sub { color: var(--fst-green); }

/* --- Hero mode-change echo (31b): takes the RPM/LAP SAVE line briefly --- */

.c-modeecho {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
}

.c-modeecho .me-line {
  display: flex;
  align-items: baseline;
  gap: 8px;
  white-space: nowrap;
}

.c-modeecho .me-name {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--sel-blue);
}

.c-modeecho .me-info { font-size: 13px; color: var(--muted); }

.c-modeecho .me-bar {
  width: 148px;
  height: 3px;
  background: var(--hairline);
  overflow: hidden;
}

.c-modeecho .me-fill { display: block; height: 100%; width: 100%; background: var(--sel-blue); }

/* Locked: the committed grammar — line and bar go white for ~1 s. */
.c-modeecho.locked .me-name { color: var(--text2); }
.c-modeecho.locked .me-fill { background: var(--text2); width: 100%; }
/* GET ME HOME echoes in green with a solid green bar. */
.c-modeecho.gmh .me-name { color: var(--green); }
.c-modeecho.gmh .me-fill { background: var(--green); width: 100%; }


/* Fuel-saver table per-skin tokens: restated dash values (pscard pattern). */
.fstable[data-theme='amber'] {
  --fst-ground: #0b0a07;
  --fst-hair1: #241f12;
  --fst-hair2: #2e2617;
  --fst-text: #ffb020;
  --fst-text2: #ffc766;
  --fst-muted: #8a7245;
  --fst-dim: #6b5a38;
  --fst-green: #21d07a;
  --fst-amber: #ffb020;
  --fst-blue: #56b8ff;
}

.fstable[data-theme='paper'] {
  --fst-ground: #f5f5f6;
  --fst-hair1: #e0e0e4;
  --fst-hair2: #c9c9ce;
  --fst-text: #1b1d1f;
  --fst-text2: #3a3d42;
  --fst-muted: #6f7379;
  --fst-dim: #9a9da3;
  --fst-green: #149655;
  --fst-amber: #a06800;
  --fst-blue: #2d7dd2;
}

.fstable[data-theme='slate'] {
  --fst-ground: #10141c;
  --fst-hair1: #1b2230;
  --fst-hair2: #232c3d;
  --fst-text: #f0f3f8;
  --fst-text2: #c6cdd9;
  --fst-muted: #8a92a3;
  --fst-dim: #6b7383;
  --fst-green: #3ddc84;
  --fst-amber: #b3791a;
  --fst-blue: #56b8ff;
}


/* ---- Settings trainer HUD (docs/TRAINING-LAP-HUD.md) --------------------
   Watch-only training-lap panel. Zones A (lap bar) / B (settings row) /
   C (message) are FIXED; the lock log (D) appears after the first lock and
   grows the panel downward only — nothing above it ever moves (rule 5).
   Skinned like every sibling card: --tr-* tokens mirroring the dash's
   values, swapped per data-theme (overlay.ts applyTheme mirrors the skin).
   Cyan is the one colour of its own — reserved for car-settings values
   (pending = dialed-but-not-locked), per the design handoff. */
#w-trainer .tr-card {
  --tr-ground: #161826; /* --bg */
  --tr-hair: #292b31; /* --hairline: bar track, log divider */
  --tr-frame: #3f424d; /* --frame (card border) */
  --tr-text: #e9e9ed; /* --text */
  --tr-dim: #75798c; /* --dim: labels, instruction line */
  --tr-green: #21d07a; /* --green: lock confirmation */
  --tr-amber: #ffb020; /* --amber: ARMED head */
  --tr-cyan: #43d9de; /* settings accent (handoff) */
  /* Theme-proof derivations (white-alpha washes vanish on paper). */
  --tr-tick: color-mix(in srgb, var(--tr-ground) 55%, var(--tr-text) 45%);
  --tr-tint-a: color-mix(in srgb, var(--tr-ground) 88%, var(--tr-text) 12%);
  --tr-tint-b: color-mix(in srgb, var(--tr-ground) 96%, var(--tr-text) 4%);
  --tr-lval: color-mix(in srgb, var(--tr-ground) 45%, var(--tr-text) 55%);

  display: none;
  width: 380px;
  background: var(--tr-ground);
  border: 1px solid var(--tr-frame);
  border-radius: 8px;
  padding: 11px 12px;
  color: var(--tr-text);
  font-family: 'Inter', 'Segoe UI', sans-serif;
  font-variant-numeric: tabular-nums;
  box-sizing: border-box;
}
#w-trainer .tr-card[data-theme='amber'] {
  --tr-ground: #0b0a07;
  --tr-hair: #241f12;
  --tr-frame: #2e2617;
  --tr-text: #ffb020;
  --tr-dim: #6b5a38;
  --tr-cyan: #ffd166; /* monochrome skin: pending = brighter, not cyan */
}
#w-trainer .tr-card[data-theme='paper'] {
  --tr-ground: #f5f5f6;
  --tr-hair: #e0e0e4;
  --tr-frame: #c9c9ce;
  --tr-text: #1b1d1f;
  --tr-dim: #9a9da3;
  --tr-green: #149655;
  --tr-amber: #a06800;
  --tr-cyan: #0f7d84; /* light ground: the bright cyan washes out */
}
#w-trainer .tr-card[data-theme='slate'] {
  --tr-ground: #10141c;
  --tr-hair: #1b2230;
  --tr-frame: #232c3d;
  --tr-text: #f0f3f8;
  --tr-dim: #6b7383;
  --tr-green: #3ddc84;
}
#w-trainer .tr-card.tr-show {
  display: flex;
  flex-direction: column;
  gap: 9px;
}
/* Edit mode: keep the (normally idle-hidden) card findable and draggable. */
body.edit #w-trainer .tr-card {
  display: flex;
  flex-direction: column;
  gap: 9px;
  min-height: 90px;
}
#w-trainer .tr-card.flash {
  animation: tr-lockflash 0.3s ease-out;
}
@keyframes tr-lockflash {
  0% {
    border-color: rgba(33, 208, 122, 0.55);
    box-shadow: 0 0 14px rgba(33, 208, 122, 0.22);
  }
  100% {
    border-color: var(--tr-frame);
    box-shadow: none;
  }
}

/* Zone A: lap bar — 14px box, 5px track centred. */
#w-trainer .tr-bar {
  position: relative;
  height: 14px;
  flex: none;
}
#w-trainer .tr-bar::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 4.5px;
  height: 5px;
  background: var(--tr-hair);
}
#w-trainer .tr-tints {
  position: absolute;
  left: 0;
  right: 0;
  top: 4.5px;
  height: 5px;
}
#w-trainer .tr-tint {
  position: absolute;
  top: 0;
  height: 100%;
}
#w-trainer .tr-tint.a {
  background: var(--tr-tint-a);
}
#w-trainer .tr-tint.b {
  background: var(--tr-tint-b);
}
#w-trainer .tr-ticks {
  position: absolute;
  inset: 0;
}
#w-trainer .tr-tick {
  position: absolute;
  top: 3px;
  width: 2px;
  height: 9px;
  margin-left: -1px;
  background: var(--tr-tick);
}
/* ARMED: only the s/f tick exists, dimmed. */
#w-trainer .tr-armed .tr-tick {
  background: var(--tr-tint-a);
}
#w-trainer .tr-locked .tr-tick.newest,
#w-trainer .tr-tick.end {
  top: 1px;
  height: 13px;
  background: var(--tr-green);
  box-shadow: 0 0 8px rgba(33, 208, 122, 0.6);
}
#w-trainer .tr-locked .tr-tint.live {
  background: color-mix(in srgb, var(--tr-ground) 75%, var(--tr-green) 25%);
}
#w-trainer .tr-marker {
  position: absolute;
  top: 0;
  width: 2px;
  height: 14px;
  margin-left: -1px;
  background: var(--tr-text);
  box-shadow: 0 0 6px color-mix(in srgb, var(--tr-text) 90%, transparent);
}

/* Zone B: settings row — 22px, baseline-aligned pairs, 18px gaps. */
#w-trainer .tr-row {
  display: flex;
  align-items: baseline;
  gap: 18px;
  height: 22px;
  flex: none;
  overflow: hidden;
}
#w-trainer .tr-slot {
  display: flex;
  align-items: baseline;
  gap: 6px;
  white-space: nowrap;
}
#w-trainer .tr-lab {
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--tr-dim);
}
#w-trainer .tr-val {
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--tr-text);
}
#w-trainer .tr-val.pending {
  color: var(--tr-cyan);
}
#w-trainer .tr-saved .tr-row {
  opacity: 0.55;
}

/* Zone C: message line — 13px slot, state-toned head + dim rest. */
#w-trainer .tr-msg {
  height: 13px;
  flex: none;
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--tr-dim);
  white-space: nowrap;
  overflow: hidden;
}
#w-trainer .tr-msg span + span {
  margin-left: 4px;
}
#w-trainer .tr-msg.amber #tr-msg-head {
  color: var(--tr-amber);
}
#w-trainer .tr-msg.green #tr-msg-head {
  color: var(--tr-green);
}

/* Zone D: lock log — grows downward; rows 15px, newest first. */
#w-trainer .tr-log {
  border-top: 1px solid var(--tr-hair);
  padding-top: 7px;
}
#w-trainer .tr-saved .tr-log {
  opacity: 0.75;
}
#w-trainer #tr-log-rows {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
#w-trainer .tr-lrow {
  display: flex;
  align-items: baseline;
  height: 15px;
  white-space: nowrap;
  overflow: hidden;
  /* Permanent, not flash-only: the flash paints a fill + 2px inset edge on
     this row, and padding appearing WITH it would shift the text (rule 5).
     Reserved always, the highlight lands around already-clear text. */
  padding: 0 6px;
}
#w-trainer .tr-lpct {
  width: 36px;
  flex: none;
  font-size: 12px;
  font-weight: 600;
  color: var(--tr-text);
}
#w-trainer .tr-llab {
  font-size: 8px;
  font-weight: 600;
  letter-spacing: 0.8px;
  color: var(--tr-dim);
  margin-right: 3px;
}
#w-trainer .tr-lval {
  font-size: 12px;
  font-weight: 600;
  color: var(--tr-lval);
  margin-right: 8px;
}
#w-trainer .tr-lval.changed {
  color: var(--tr-cyan);
}
/* Newest row rides the lock flash: green fill + inset edge + green %. */
#w-trainer .flash .tr-lrow.newest {
  background: color-mix(in srgb, var(--tr-ground) 92%, var(--tr-green) 8%);
  box-shadow: inset 2px 0 0 var(--tr-green);
}
#w-trainer .flash .tr-lrow.newest .tr-lpct {
  color: var(--tr-green);
}
#w-trainer .tr-earlier {
  margin-top: 4px;
  font-size: 8px;
  font-weight: 600;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--tr-dim);
  opacity: 0.8;
}

/* Crowded row (>4 settings, e.g. GT3's six controls): tighter type so the
   last value never clips at the 380px design width. */
#w-trainer .tr-row.crowded {
  gap: 11px;
}
#w-trainer .tr-row.crowded .tr-slot {
  gap: 4px;
}
#w-trainer .tr-row.crowded .tr-lab {
  letter-spacing: 0.8px;
  font-size: 8px;
}
#w-trainer .tr-row.crowded .tr-val {
  font-size: 16px;
}

/* ---- Race-side trained settings (docs/design/IN-CAR-ADJUSTS.md) ---------
   CAR/ADJ flanks beside the gear + the press-triggered info-strip takeover.
   Values from the design handoff verbatim; the announcing ground is
   Marcus's ruling 3 (amber while in flight — the pit-exit amber ground,
   already per-theme) and the receipt reuses the green-flag tokens. */
.c-adjmark {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  line-height: 1;
  color: var(--amber);
  border: 1px solid var(--amber);
  border-radius: 4px;
  padding: 4px 7px;
}
.c-adjmark.l { left: 34px; }
.c-adjmark.r { right: 34px; }

.info-adjusts {
  grid-column: 1 / -1;
  grid-row: 1;
  display: inline-flex;
  align-items: baseline;
  gap: 22px;
  justify-self: center;
}
#d-adjusts-pairs {
  display: inline-flex;
  align-items: baseline;
  gap: 22px;
}
.adj-word {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.18em;
  color: var(--amber);
}
.t-pair {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
}
.t-label {
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--dim);
}
/* Not-yet-applied: muted + dotted underline — the beyond-color cue; an
   underlined number on this dash is never a live reading. */
.t-val {
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  text-decoration: underline dotted;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
/* A setting lands while the display is up: muted→green in place. */
.t-pair[data-state='landed'] .t-val {
  color: var(--green);
  text-decoration: none;
}
/* In flight = quiet amber ground; all landed = green receipt (2 s). */
.dash-info[data-adjusts='announcing'] { background: var(--pit-exit-bg); }
.dash-info[data-adjusts='done'] { background: var(--flag-green-bg); }
.dash-info[data-adjusts='done'] .adj-word { color: var(--green); }
/* Full-strip yield: the other regions vanish under the takeover. Visibility,
   not hidden — their own renderers keep writing `hidden` and must never be
   fought over it. */
.dash-info[data-adjusts] .info-session,
.dash-info[data-adjusts] .info-lapslot,
.dash-info[data-adjusts] .info-weather,
.dash-info[data-adjusts] .info-right {
  visibility: hidden;
}

/* ---- Standings glance board (design 41a) --------------------------------
   Palette mirrors the DASHBOARD tokens 1:1 per skin (pscard/relcard
   pattern) — the handoff's fixed inks (#12141c ground etc.) do NOT ship
   (Marcus 08-21: it needs to match each skin). Every tinted surface — the
   YOU channel, the player highlight — DERIVES from ground/inks via
   color-mix so it tracks any skin. The class bar/chip stay data-driven from
   the sim's class colour. Geometry was quoted from the handoff (464 wide,
   27px rows, 17px column head); re-cut 2026-09-01 to the RELATIVE widget's
   glance scale after the Le Mans practice read as unreadable at speed
   (Marcus: "everything is just too small"): 600 wide, 30px rows, 19px
   column head, 14px names, 18px YOU gap, unframed 21px badges. The
   separator stays 15px. */
.sbcard {
  --sb-ground: #161826; /* --bg */
  --sb-hair: #292b31; /* --hairline */
  --sb-hair2: #3f424d; /* --frame (card border) */
  --sb-text: #e9e9ed; /* --text */
  --sb-muted: #9397ab; /* --muted */
  --sb-dim: #75798c; /* --dim */
  --sb-green: #21d07a;
  --sb-amber: #ffb020;
  --sb-red: #ff5252;
  --sb-blue: #56b8ff;
  --sb-est: #8a7550; /* warm parentheses on a race length still moving (--fm-est) */
  /* Dimmer step (the handoff's #4d5162 rank): dim sunk toward the ground. */
  --sb-dimmer: color-mix(in srgb, var(--sb-dim) 62%, var(--sb-ground) 38%);
  /* The YOU channel: ground lifted a whisper, its own hairline rails. */
  --sb-chan: color-mix(in srgb, var(--sb-ground) 90%, var(--sb-dim) 10%);
  --sb-chan-bd: color-mix(in srgb, var(--sb-ground) 70%, var(--sb-dim) 30%);
  /* Player row: the relcard's blue-lifted highlight, one family. */
  --sb-hilite: color-mix(in srgb, var(--sb-ground) 87%, var(--sb-blue) 13%);
  --sb-hilite-rule: color-mix(in srgb, var(--sb-ground) 55%, var(--sb-blue) 45%);
  /* Race trim (Marcus 09-22, RANGE + STOPS as two columns — B of
     docs/design/fuel-range): POS± 56 → 52 and CAR 40 → 36 were slack
     (the widest POS content "13 ▼10" is 50 px, the plate 32), LDR 52 → 48
     and PACE 66 → 60 hold their widest figures ("1:04" 32 px, "1:10.2"
     45 px); the driver name keeps 92 px of the 112 it had.
     09-24: a 17 px compound slot after the name (the relative box's ring
     glyph); the card grew by slot + gap (600 → 625) so the name kept its
     width. --sb-cmpd collapses to 0 in a single-compound session (nocmpd,
     decided at session start — never mid-session). */
  --sb-cmpd: 17px;
  /* 10-01: the relative box's iRating plate in its own slot after the name
     (Marcus) — 42 px holds the widest plate, "10.0k" at 41.5 ("9.9k" is
     33.7); the card grew by slot + gap (625 → 675) so every other column,
     the name included, kept its width. */
  --sb-ir: 42px;
  --sb-cols: 52px 24px 36px minmax(0, 1fr) var(--sb-ir) var(--sb-cmpd) 34px 40px 32px 48px 88px 60px;
  width: 675px;
  box-sizing: border-box;
  background: var(--sb-ground);
  border: 1px solid var(--sb-hair2);
  border-radius: 8px;
  overflow: hidden;
  color: var(--sb-text);
  font-family: Inter, 'Segoe UI', system-ui, sans-serif;
  font-variant-numeric: tabular-nums;
}
/* Edit mode: a hidden board (alone / no session) stays findable+draggable. */
body.edit .sbcard[hidden] {
  display: block;
  min-height: 64px;
}

/* Non-race layout (B, Marcus 08-21): PRJ / STOPS / LDR and the ± caret are
   dead in practice/quali/warmup, so those slots yield to LAPS and an
   absolute BEST, and DRIVER takes the freed width (the name column is the
   one flexible track in both trims). The swap only happens on a
   session-type change, never mid-session. */
.sbcard:not([data-mode='race']) {
  --sb-cols: 30px 24px 40px minmax(0, 1fr) var(--sb-ir) var(--sb-cmpd) 40px 78px 88px 66px;
}
.sbcard:not([data-mode='race']) .sb-raceonly,
.sbcard:not([data-mode='race']) .sb-chgw {
  display: none;
}
.sbcard[data-mode='race'] .sb-sess {
  display: none;
}
/* Qualifying only (Marcus 09-24): the PACE slot's head reads GAP — the
   time to pole; practice and race keep PACE. */
.sbcard:not([data-mode='quali']) .sb-quali,
.sbcard[data-mode='quali'] .sb-notquali {
  display: none;
}
/* Compound slot only exists when the session has >1 compound (the relative
   box's nocmpd rule): the track collapses and the name takes the room. The
   cell stays IN the grid (hidden, not removed): cells auto-place in source
   order, so removing one slides every later cell a track left. */
.sbcard.nocmpd {
  --sb-cmpd: 0px;
}
.sbcard.nocmpd .sb-cmpdw {
  visibility: hidden;
}
/* The separator rule spans up to the channel, which is column 10 in race
   trim (RANGE + STOPS since 09-22, the compound slot since 09-24) and
   column 8 here. */
.sbcard:not([data-mode='race']) .sb-seprule {
  grid-column: 1 / 9;
}
.sb-laps {
  font-size: 13px;
  font-weight: 600;
  color: color-mix(in srgb, var(--sb-text) 78%, var(--sb-muted) 22%);
}
.sb-laps[data-dim] {
  color: var(--sb-dimmer);
  font-weight: 400;
}
/* BEST is the non-race board's hero number, full thousandths. */
.sb-best {
  text-align: right;
  font-size: 14px;
  font-weight: 600;
  color: var(--sb-text);
}

.sbcard[data-theme='amber'] {
  --sb-ground: #0b0a07;
  --sb-hair: #241f12;
  --sb-hair2: #2e2617;
  --sb-text: #ffb020;
  --sb-muted: #8a7245;
  --sb-dim: #6b5a38;
  --sb-red: #ff4438;
}
.sbcard[data-theme='paper'] {
  --sb-ground: #f5f5f6;
  --sb-hair: #e0e0e4;
  --sb-hair2: #c9c9ce;
  --sb-text: #1b1d1f;
  --sb-muted: #6f7379;
  --sb-dim: #9a9da3;
  /* Light ground: saturated dark-skin inks wash out — paper's darker
     variants keep the meanings legible (house convention). */
  --sb-green: #149655;
  --sb-amber: #c47d00;
  --sb-red: #d92b20;
  --sb-blue: #2d7dd2;
  --sb-est: #8a6a2c;
}
.sbcard[data-theme='slate'] {
  --sb-ground: #10141c;
  --sb-hair: #1b2230;
  --sb-hair2: #232c3d;
  --sb-text: #f0f3f8;
  --sb-muted: #8a92a3;
  --sb-dim: #6b7383;
  --sb-green: #3ddc84;
}

/* Header: race clock · LAP n/total (warm est) · SOF + class chip. */
.sb-header {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 8px 11px 7px 12px;
  border-bottom: 1px solid var(--sb-hair);
}
.sb-clock {
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.01em;
}
.sb-lapwrap {
  display: flex;
  align-items: baseline;
  gap: 3px;
}
.sb-lab {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.09em;
  color: var(--sb-dim);
}
.sb-lap,
.sb-laptot,
.sb-sof {
  font-size: 14px;
  font-weight: 600;
}
.sb-lapsep {
  font-size: 12px;
  color: var(--sb-dim);
}
.sb-lapest {
  font-size: 12px;
  font-weight: 500;
  color: var(--sb-est);
}
.sb-session {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.09em;
  color: var(--sb-muted);
}
.sb-spacer {
  flex: 1;
}
.sb-class {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  border: 1px solid var(--sb-hair2);
  padding: 0 3px;
  line-height: 14px;
}

.sb-board {
  padding: 2px 0 4px;
}

/* Grid rows: 3px class bar rides as border-left on every row so columns
   line up between head, rows and separators. */
.sb-colhead,
.sb-row,
.sb-sep {
  display: grid;
  grid-template-columns: var(--sb-cols);
  column-gap: 8px;
  align-items: center;
  padding: 0 11px 0 8px;
  box-sizing: border-box;
  border-left: 3px solid transparent;
}
.sb-colhead {
  height: 19px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.1em;
  color: var(--sb-dim);
}
.sb-colhead .sb-c {
  justify-self: center;
}
.sb-colhead .sb-r {
  text-align: right;
}
.sb-colhead .sb-stops-lab {
  font-size: 9px;
  letter-spacing: 0.03em;
}
.sb-colhead .sb-you-lab {
  font-weight: 700;
  color: var(--sb-text);
}
.sb-row {
  height: 30px;
  border-left-color: var(--rowc, transparent);
}
.sb-row.me {
  background: var(--sb-hilite);
  box-shadow: inset 0 0 0 1px var(--sb-hilite-rule);
}
/* Retired / gone (out of the world, not in the pits, not finished): dimmed,
   as the pit wall tower does — opacity only, 0 px of layout (09-25). */
.sb-row.gone {
  opacity: 0.45;
}

/* Separator: hairline rule with the skip count in the middle; the YOU
   channel keeps running through it (its own cell below). */
.sb-sep {
  height: 15px;
}
.sb-sep .sb-seprule {
  grid-column: 1 / 11;
  display: flex;
  align-items: center;
  gap: 6px;
}
.sb-sep .sb-seprule::before,
.sb-sep .sb-seprule::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--sb-hair);
}
.sb-sep .sb-sepn {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.09em;
  color: var(--sb-dimmer);
}
.sb-sep .sb-sepn:empty {
  display: none;
}

/* POS ± — one cell, one fact: 13px digits, caret+count vs the grid. */
.sb-posw {
  display: flex;
  align-items: center;
  gap: 6px;
}
.sb-pos {
  font-size: 15px;
  font-weight: 600;
  color: color-mix(in srgb, var(--sb-text) 88%, var(--sb-ground) 12%);
}
.sb-row.me .sb-pos {
  font-weight: 700;
  color: var(--sb-text);
}
.sb-chgw {
  width: 24px;
}
.sb-chg {
  display: flex;
  align-items: center;
  gap: 1px;
  font-size: 12px;
  font-weight: 700;
}
.sb-chg .car {
  font-size: 9px;
  line-height: 1;
}
.sb-chg[data-d='up'] {
  color: var(--sb-green);
}
.sb-chg[data-d='dn'] {
  color: var(--sb-red);
}
.sb-chg[data-d='none'] {
  color: var(--sb-dimmer);
  font-size: 10px;
  font-weight: 400;
}

/* Manufacturer badge: an UNFRAMED 24x22 slot with the mono SVG as a CSS mask
   (house pattern — the svc-tri mask) in near-text ink. The handoff's 1px
   frame around a 12px glyph read as "a square around a smudge" at speed
   (Marcus 09-01); the glyph now fills the row height instead. */
.sb-mf {
  display: block;
  width: 24px;
  height: 22px;
  box-sizing: border-box;
}
.sb-mf::after {
  content: '';
  display: block;
  width: 100%;
  height: 100%;
  background: color-mix(in srgb, var(--sb-text) 80%, var(--sb-muted) 20%);
  -webkit-mask: var(--mf, none) no-repeat center / 21px 21px;
  mask: var(--mf, none) no-repeat center / 21px 21px;
}
.sb-mf[data-none]::after {
  content: '–';
  background: none;
  color: var(--sb-dimmer);
  font-size: 11px;
  text-align: center;
  line-height: 22px;
}

/* CAR plate: bordered number; the player's inverts. In the lane the plate
   swaps to the amber PIT block — same slot, nothing moves. */
.sb-plate {
  display: block;
  min-width: 32px;
  text-align: center;
  box-sizing: border-box;
  color: color-mix(in srgb, var(--sb-text) 78%, var(--sb-muted) 22%);
  border: 1px solid var(--sb-chan-bd);
  padding: 0 4px;
  font-size: 12.5px;
  font-weight: 600;
  line-height: 18px;
}
.sb-row.me .sb-plate {
  background: var(--sb-text);
  color: var(--sb-ground);
  border-color: var(--sb-text);
}
.sb-plate[data-pit] {
  background: var(--sb-amber);
  color: var(--sb-ground);
  border-color: var(--sb-amber);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.02em;
}

.sb-name {
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sb-row.me .sb-name {
  font-weight: 600;
}

/* iRating: the relative box's plate (relbox.css .rr-ir) in this card's
   tokens — the box IS the licence (iRacing's ladder colours, the same on
   every skin), dark ink on it, Pro inverts with the skin. */
.sb-ir {
  border-radius: 2px;
  padding: 0 4px;
  font-size: 12px;
  font-weight: 700;
  line-height: 17px;
  color: #12141c;
  background: var(--sb-dim);
}
.sb-ir[data-lic='R'] { background: #ff5252; }
.sb-ir[data-lic='D'] { background: #ff8b3d; }
.sb-ir[data-lic='C'] { background: #ffd23d; }
.sb-ir[data-lic='B'] { background: #21d07a; }
.sb-ir[data-lic='A'] { background: #56b8ff; }
.sb-ir[data-lic='P'] {
  background: var(--sb-text);
  color: var(--sb-ground);
}

/* Compound: the relative box's glyph (relbox.css .rr-cmpd) — the letter in a
   ring sharing its ink, wet only changes the ink — in this card's tokens. */
.sb-cmpd {
  width: 17px;
  height: 17px;
  box-sizing: border-box;
  border: 1px solid var(--sb-muted);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10.5px;
  font-weight: 700;
  color: var(--sb-muted);
}
.sb-cmpd[data-c='W'] {
  color: var(--sb-blue);
}

/* PRJ: hairline outline box (never the live position's solid weight) with
   the evidence rail on its bottom edge — part-filled warm while pace
   evidence is thin, full and grey once settled, empty with a dash bare. */
.sb-prjbox {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 21px;
  box-sizing: border-box;
  border: 1px solid color-mix(in srgb, var(--sb-hair2) 80%, var(--sb-ground) 20%);
}
.sb-prjnum {
  font-size: 13px;
  font-weight: 600;
  line-height: 1;
}
.sb-prjbox[data-st='none'] .sb-prjnum {
  color: var(--sb-dimmer);
}
.sb-prjbox[data-st='settling'] .sb-prjnum {
  color: var(--sb-muted);
}
.sb-prjrail {
  position: absolute;
  left: 0;
  bottom: -1px;
  height: 1.5px;
  width: 0;
  background: var(--sb-amber);
}
.sb-prjbox[data-st='settled'] .sb-prjrail {
  background: var(--sb-muted);
}

.sb-stops {
  font-size: 13px;
  font-weight: 600;
}
.sb-stops[data-dim] {
  color: var(--sb-dimmer);
  font-weight: 400;
}
/* RANGE = laps of fuel the model thinks the car has left (Marcus 09-22):
   the STOPS voice, one decimal, right-aligned; amber when the model says
   the car must stop within ~3 laps (the FUEL chip's threshold); dim dash
   in the lane and without a model. Never blue/red — not a comparison. */
.sb-range {
  font-size: 13px;
  font-weight: 600;
}
.sb-range[data-dim] {
  color: var(--sb-dimmer);
  font-weight: 400;
}
.sb-range[data-soon] {
  color: var(--sb-amber);
}
/* An ESTIMATED range (no model anchor — a full tank at the last stop run
   down by the class burn) is the figure in italics (Marcus 09-28). */
.sb-range[data-est] {
  font-style: italic;
}

/* The two gap columns — the widget's failure mode. LDR: dim, unsigned,
   plain. YOU: bright, signed, inside the inked channel that runs the full
   board height (drawn on EVERY cell of the column, head to foot). */
.sb-ldr {
  text-align: right;
  font-size: 13px;
  font-weight: 500;
  color: var(--sb-muted);
}
.sb-chan {
  height: 100%;
  box-sizing: border-box;
  background: var(--sb-chan);
  border-left: 1px solid var(--sb-chan-bd);
  border-right: 1px solid var(--sb-chan-bd);
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding-right: 6px;
}
/* Channel cells that are also roll clips keep their right alignment (the
   later .sb-cellclip rule would otherwise reset it). */
.sb-chan.sb-cellclip {
  justify-content: flex-end;
}
/* YOU is the board's hero number — the relative widget's gap size. */
.sb-you {
  font-size: 18px;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.sb-pace {
  text-align: right;
  font-size: 13.5px;
  font-weight: 600;
}
/* Pace ink = this car against YOU (Marcus 09-22): a gradient from blue
   (quicker than you) through plain to red (slower). --vs is the mix, 0–100 %
   in 5 % steps, full at 1.5 % of your own lap time (the engine's scale);
   the sign picks the hue. Green/amber stay the relative box's language for
   closing rate, so the two columns never look alike; red also reads with
   the CHG column's red — both mean "going backwards". Your own row and
   other classes carry no data-vs and so stay plain. Colour transitions on
   the cell come from the roll kit's inline transition (kit/flip.ts). */
.sb-pace[data-vs='faster'] {
  color: color-mix(in srgb, var(--sb-blue) var(--vs, 100%), var(--sb-text));
}
.sb-pace[data-vs='slower'] {
  color: color-mix(in srgb, var(--sb-red) var(--vs, 100%), var(--sb-text));
}

/* The roll (from 41c): rows never move — a changed cell wipes its value
   through its own clipped 30px window over 260ms. Outgoing exits upward
   fading, incoming rises from below. */
.sb-cellclip {
  position: relative;
  overflow: hidden;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: flex-start;
}
.sb-cc {
  justify-content: center;
}
.sb-cr {
  justify-content: flex-end;
}
.sb-roll-in {
  animation: sbRollIn 260ms ease-out;
}
/* Ghost carries the outgoing value: same alignment as its cell (inherit),
   absolutely positioned so the incoming value never reflows. */
.sb-roll-ghost {
  position: absolute;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: inherit;
  animation: sbRollOut 260ms ease-out forwards;
}
@keyframes sbRollIn {
  from {
    transform: translateY(19px);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}
@keyframes sbRollOut {
  from {
    transform: translateY(0);
    opacity: 0.38;
  }
  to {
    transform: translateY(-19px);
    opacity: 0;
  }
}

/* ---------------------------------------------------------------------------
   Strategy console LITE — the 7a reskin (2026-09-03; handoff in
   docs/design/strategy-console-lite-7a): one panel in the dashboard's own
   frame, ground and rules — 560x261, four stacked sections divided by 1px
   hairlines, no boxes, no shadows. Shares .stratcard's token block and skins:
   every colour below is a token or a mix of tokens (the handoff's sampled
   hexes are the nocturne values of the same tokens), so all four skins
   retheme it. Fixed slots throughout — every section has a set height and
   every cell a set column, so nothing moves between states (reflow rule).
--------------------------------------------------------------------------- */
.stratlite {
  /* Lite-local inks, derived: secondary text (inactive values, row names),
     chip text, the chip ground, the footer ground, and the selected-row
     tints (the standings' highlight family — ground lifted toward an ink). */
  --sl-text2: color-mix(in srgb, var(--sc-text) 76%, var(--sc-muted));
  --sl-text3: color-mix(in srgb, var(--sc-text) 84%, var(--sc-muted));
  --sl-chip: color-mix(in srgb, var(--sc-text) 5%, var(--sc-ground));
  --sl-foot: color-mix(in srgb, var(--sc-ground) 82%, #000);
  --sl-active: color-mix(in srgb, var(--sc-ground) 91%, var(--sc-basis-max));
  --sl-pending: color-mix(in srgb, var(--sc-ground) 90%, var(--sc-blue));
  width: 560px;
  height: 261px;
  padding: 0;
  border: 1px solid var(--sc-line2);
  border-radius: 6px;
}
.sl-root {
  display: flex;
  flex-direction: column;
  height: 100%;
}

/* 1 · Stats row: engineer · FUEL REMAINING · FILL SET. Cells divided by
   hairlines; label over a big line. */
.sl-stats {
  flex: none;
  height: 63px;
  box-sizing: border-box;
  display: grid;
  grid-template-columns: 1.25fr 1fr 1fr;
  border-bottom: 1px solid var(--sc-line);
}
.sl-stat {
  padding: 11px 14px 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  border-right: 1px solid var(--sc-line);
  overflow: hidden;
}
.sl-stat:last-child {
  border-right: 0;
}
.sl-labelrow {
  display: flex;
  align-items: baseline;
  gap: 8px;
  height: 12px;
  line-height: 12px;
  white-space: nowrap;
}
.sl-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.12em;
  color: var(--sc-muted);
}
.sl-label.assumed {
  color: var(--amber, #ffb020);
}
/* The big line: numbers big, the words around them in the unit voice; the
   tone colours the whole line (figure AND words) so a toned message reads
   as one; untoned words stay muted. */
.sl-big {
  display: flex;
  align-items: baseline;
  gap: 6px;
  height: 22px;
  line-height: 1;
  white-space: nowrap;
  color: var(--sc-text);
}
.sl-bign {
  font-size: 22px;
  font-weight: 700;
  color: inherit;
}
.sl-big.long .sl-bign {
  font-size: 17px;
}
.sl-bigw {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--sc-muted);
}
.sl-big[data-tone] .sl-bigw {
  color: inherit;
}
.sl-big[data-tone='green'] {
  color: var(--sc-green);
}
.sl-big[data-tone='amber'] {
  color: var(--sc-amber);
}
.sl-big[data-tone='red'] {
  color: var(--sc-red);
}
.sl-big[data-tone='blue'] {
  color: var(--sc-blue);
}
.sl-big[data-tone='dim'] {
  color: var(--sc-dim);
}
/* FILL SET: the litres' unit rides the figure's colour at 12px. */
.sl-fillstat .sl-big {
  gap: 4px;
}
.sl-fillstat .sl-bigw {
  font-size: 12px;
  letter-spacing: 0;
  color: inherit;
}
/* The fill box's one-word explainer, in the figure's colour. */
.sl-word {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.12em;
  color: var(--sc-muted);
}
.sl-word[data-tone='green'] {
  color: var(--sc-green);
}
.sl-word[data-tone='amber'] {
  color: var(--sc-amber);
}
.sl-word[data-tone='red'] {
  color: var(--sc-red);
}
/* Tags: the ACTIVE mark on the committed row, AUTO in place of the fill
   label — 8px caps on a 14% tint of their ink. */
.sl-tag {
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.12em;
  line-height: 1;
  border-radius: 2px;
  padding: 2px 5px;
  white-space: nowrap;
}
.sl-tagactive {
  color: var(--sc-amber);
  background: color-mix(in srgb, var(--sc-amber) 14%, transparent);
}
.sl-tagauto {
  color: var(--sc-blue);
  background: color-mix(in srgb, var(--sc-blue) 14%, transparent);
}

/* 2 · Stint strip: the bordered strip and its window rail, on canvas. */
.sl-stripsec {
  flex: none;
  height: 59px;
  box-sizing: border-box;
  padding: 12px 14px 0;
  border-bottom: 1px solid var(--sc-line);
}
.sl-stripe {
  display: block;
  width: 100%;
  height: 37px;
}

/* 3 · Saver rows: radio · name (+ ACTIVE) · BURN · PIT · FILL, three fixed
   31px rows (30 + rule). A takeover form takes the same 93px. */
.sl-list {
  flex: 1;
  min-height: 93px;
  display: flex;
  flex-direction: column;
}
.sl-row {
  position: relative;
  flex: none;
  height: 31px;
  box-sizing: border-box;
  display: grid;
  /* Radio column 10px + a 10px gap to the name (the grid's own gap is 14,
     so the name pulls back 4px): text starts at 34px, the same x as the
     footer's SAVER OFF beside its radio (Marcus 09-03: radios and their
     text align). */
  grid-template-columns: 10px minmax(0, 1fr) 96px 58px 88px;
  column-gap: 14px;
  align-items: center;
  padding: 0 14px;
  border-bottom: 1px solid var(--sc-line);
}
.sl-row > .sl-name,
.sl-row > .sl-gmhbody {
  margin-left: -4px;
}
.sl-row.dim {
  opacity: 0.4;
}
.sl-radio {
  width: 10px;
  height: 10px;
  box-sizing: border-box;
  flex: none;
  border-radius: 50%;
  border: 1.5px solid var(--sc-dim);
  display: flex;
  align-items: center;
  justify-content: center;
}
.sl-radio::after {
  content: '';
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: transparent;
}
.sl-name {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
}
.sl-namet {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--sl-text2);
}
.sl-namelen {
  color: var(--sc-muted);
  font-weight: 600;
}
.sl-cell {
  display: flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: 6px;
  white-space: nowrap;
}
.sl-lab {
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.12em;
  color: var(--sc-muted);
}
.sl-val {
  font-size: 13px;
  font-weight: 600;
  color: var(--sl-text2);
  /* A fixed, right-aligned slot per column so the leading label never
     slides when the figure changes width (3.56 ↔ —); reflow rule. */
  display: inline-block;
  text-align: right;
}
.sl-val[data-c='burn'],
.sl-val[data-c='fill'] {
  min-width: 34px;
}
.sl-val[data-c='pit'] {
  min-width: 14px;
}
/* Selection: active = tinted row, filled radio, bright bold name and
   values (+ ACTIVE where the row carries the tag); pending = the blue form
   with the lock-in drain along the row's bottom edge. */
.sl-row[data-state='active'],
.sl-foot[data-state='active'] .sl-footname {
  background: var(--sl-active);
}
.sl-row[data-state='pending'],
.sl-foot[data-state='pending'] .sl-footname {
  background: var(--sl-pending);
}
.sl-row[data-state='active'] .sl-radio,
.sl-foot[data-state='active'] .sl-radio {
  border-color: var(--sc-text);
}
.sl-row[data-state='active'] .sl-radio::after,
.sl-foot[data-state='active'] .sl-radio::after {
  background: var(--sc-text);
}
.sl-row[data-state='pending'] .sl-radio,
.sl-foot[data-state='pending'] .sl-radio {
  border-color: var(--sc-blue);
}
.sl-row[data-state='pending'] .sl-radio::after,
.sl-foot[data-state='pending'] .sl-radio::after {
  background: var(--sc-blue);
}
.sl-row[data-state='active'] .sl-namet,
.sl-foot[data-state='active'] .sl-namet {
  color: var(--sc-text);
  font-weight: 700;
}
.sl-row[data-state='pending'] .sl-namet,
.sl-foot[data-state='pending'] .sl-namet {
  color: var(--sc-blue);
  font-weight: 700;
}
.sl-row[data-state='active'] .sl-val {
  color: var(--sc-text);
  font-weight: 700;
}
.sl-drain {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2px;
  background: var(--sc-blue);
  transition: width 0.12s linear;
}
/* Final stint: GET ME HOME in the three rows' space — radio on the title
   line, title + state word over one short explainer, centred as a group. */
.sl-gmh {
  height: 93px;
  grid-template-columns: 10px minmax(0, 1fr);
}
.sl-gmh .sl-radio {
  transform: translateY(-8px);
}
.sl-gmhbody {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.sl-gmhline {
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.sl-gmhsub {
  margin-left: auto;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.12em;
  color: var(--sc-blue);
}
.sl-gmhsub[data-on='1'] {
  color: var(--sc-green);
}
.sl-gmhdesc {
  font-size: 11px;
  color: var(--sc-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Learning: the list and the footer's space together (no options are
   presented) — title with the percentage as its figure, one sentence, bar. */
.sl-learn {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 8px;
  padding: 0 14px;
}
.sl-learnline {
  display: flex;
  align-items: baseline;
  gap: 12px;
}
.sl-learnhead {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--sc-text);
}
.sl-learnpct {
  margin-left: auto;
  font-size: 22px;
  font-weight: 700;
  line-height: 1;
  color: var(--sc-blue);
}
.sl-learnmsg {
  font-size: 11px;
  color: var(--sc-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sl-learnbar {
  height: 6px;
  border-radius: 3px;
  background: var(--sl-chip);
  box-shadow: inset 0 0 0 1px var(--sc-line);
  position: relative;
  overflow: hidden;
}
.sl-learnfill {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  background: var(--sc-blue);
}

/* 4 · SAVER OFF footer: a heavier top rule (it reads as a footer, not a
   fourth option) on the darker ground; the OFF selector and AVG · MAX ·
   LAST, each burn (role ink) beside its fill — or, on the final stint, its
   AT END (fuel left at the flag under that burn; green makes it, amber runs
   dry) behind a small END label. */
.sl-foot {
  position: relative;
  flex: none;
  height: 44px;
  box-sizing: border-box;
  display: grid;
  grid-template-columns: 150px 1fr 1fr 1fr;
  border-top: 1px solid var(--sc-line2);
  background: var(--sl-foot);
}
.sl-footname {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 14px;
  border-right: 1px solid var(--sc-line);
}
.sl-footcell {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 3px;
  padding: 0 14px;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  border-right: 1px solid var(--sc-line);
}
.sl-footcell:last-child {
  border-right: 0;
}
.sl-footline {
  display: flex;
  align-items: baseline;
  gap: 5px;
}
.sl-burn {
  font-size: 13px;
  font-weight: 700;
  color: var(--sc-text);
}
.sl-footcell[data-b='avg'] .sl-burn {
  color: var(--sc-green);
}
.sl-footcell[data-b='max'] .sl-burn {
  color: var(--sc-amber);
}
.sl-fillv {
  font-size: 11px;
  font-weight: 600;
  color: var(--sc-muted);
}
.sl-fillv[data-tone='green'] {
  color: var(--sc-green);
}
.sl-fillv[data-tone='amber'] {
  color: var(--sc-amber);
}

/* ---- stint timer (Marcus 09-26) ----
   Reuses the fuel matrix's skin tokens and type (.fuelmatrix, .fm-name,
   .fm-fig, .fm-unit). Fixed 320x112 box, three 30px rows. Each figure sits
   right-aligned in a FIXED slot wide enough for its longest form (measured
   09-26: "1h 16m 45s" 108 px, "10h 59m 59s" 119 px, "188" + LAPS 68 px), so a growing string never moves a neighbour (no-dynamic-reposition). */
.stintcard {
  width: 320px;
  height: 112px;
  padding: 11px 14px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.st-row {
  display: grid;
  grid-template-columns: 58px 1fr 124px; /* 58 + 124 + 2 gaps = 202 of the 292 inner px; laps take the 90 left ("312.5" + LAPS = 85), right-aligned */
  column-gap: 10px;
  align-items: baseline;
  height: 30px;
}

.st-row + .st-row {
  border-top: 1px solid var(--fm-hair1);
}

.st-row .fm-name {
  color: var(--fm-dim);
}

.st-laps {
  grid-column: 2;
  display: flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: 5px;
  white-space: nowrap;
}

.st-time {
  grid-column: 3;
  text-align: right;
  white-space: nowrap;
}

.st-row .fm-fig {
  color: var(--fm-text);
}
