/* ============================================================================
   ANPR dashboard — app.css
   Self-contained. No @import, no webfonts, no external anything.

   Theme decision (deliberate, see notes):
   The edge box's own dashboard is amber-dark (#0f0a05 / #f59e0b) AND it pulls
   Google Fonts + jsDelivr from the public internet. We match its *family
   resemblance* (dark, amber accent) but NOT its CDN habit, and we move the
   ground to a neutral cool ink so the categorical data hues stay separable.
   Amber (#f59e0b) is reserved for CHROME ONLY — brand mark, active nav, focus
   ring. It is never used as a data colour, so it can never be confused with
   the categorical yellow (#c98500).
   ========================================================================= */

*, *::before, *::after { box-sizing: border-box; }
html, body, h1, h2, h3, h4, p, figure, ul, ol, dl, dd { margin: 0; padding: 0; }
ul, ol { list-style: none; }
button, input, select, textarea { font: inherit; color: inherit; }
img, svg { display: block; max-width: 100%; }

:root {
  /* ======================================================================
     ASPHALT + PLATE SYSTEM — the palette is derived from what an ANPR camera
     actually looks at: warm asphalt at night, and India's colour-coded plate
     classes. Yellow is the commercial plate (every bus, auto, taxi and truck
     on a TN road) and is CHROME ONLY — it never appears as a data hue, so a
     yellow mark always means "the product is talking", never "this series".
     IND blue (the HSRP hot-stamp strip) carries links and structure.
     ====================================================================== */

  /* ---- ground: warm asphalt, lifted panels (elevation by lightness) ---- */
  --bg:        #12100c;
  --panel:     #1e1b14;
  --panel-2:   #272318;
  --panel-3:   #332e20;
  --line:      #3b3524;
  --line-2:    #4e4632;

  /* ---- ink: plate white, warmed ---- */
  --ink:       #f4f1e9;
  --ink-2:     #bab3a2;
  --ink-3:     #9a9280;
  --ink-4:     #7a7362;

  /* ---- chrome accent = COMMERCIAL PLATE YELLOW (never a data hue) ---- */
  --accent:      #f2c200;
  --accent-fill: #f2c200;   /* solid pills: literally a commercial plate */
  --accent-dim:  rgba(242, 194, 0, 0.16);
  --accent-ink:  #17130a;   /* plate black, for text ON the yellow */
  --accent-text: #f2c200;   /* accent AS text on a dim wash */
  --ind:         #5a90e0;   /* the IND strip — links + structure */

  /* ---- categorical data hues. Re-cut for the warm ground and validated:
     every set that shares a chart (traffic / utilisation / quality /
     temperature / latency) clears 15 OKLab dE normal and 8 under both
     protanopia and deuteranopia. Fixes a real pre-existing flaw where
     blue and violet were dE 2.8 apart for protanopes. ---- */
  --d-blue:    #2e86e0;
  --d-orange:  #ff9a5c;
  --d-aqua:    #1e8e52;
  --d-teal:    #25a7be;
  --d-magenta: #f07ab6;
  --d-violet:  #c3acff;
  --d-red:     #d24b4b;
  --d-neutral: #7d7460;

  /* ---- status (reserved; never a series colour) ---- */
  --s-good:     #35a85c;
  --s-warning:  #e08a1e;
  --s-serious:  #e8663c;
  --s-critical: #e05252;

  /* ---- type ---- */
  --font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, "Noto Sans", sans-serif;
  --mono: ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Mono", Menlo,
          Consolas, "Liberation Mono", monospace;

  --r-sm: 4px;  --r-md: 8px;  --r-lg: 12px;
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 24px; --sp-6: 32px;

  --shadow: 0 10px 30px rgba(0,0,0,.5);
  --card-shadow: 0 1px 0 rgba(255, 240, 200, .05) inset, 0 3px 14px rgba(0, 0, 0, .4);

  --topbar-bg:  rgba(18, 16, 12, .9);
  --tip-bg:     rgba(30, 27, 20, .97);
  --scrim:      rgba(10, 8, 5, .86);
  --lb-img-bg:  #0c0a07;
  --swatch-ring: rgba(255, 248, 230, .28);

  --warn-ink: #f0cf90;
  --crit-ink: #f3b0b0;
  --ok-ink:   #92d6a8;

  --veil: color-mix(in srgb, var(--bg) 86%, transparent);
  --img-scrim: rgba(10, 8, 5, .78);
  --img-ink:   #f4f1e9;
  /* ink of the page background renderer (ground = --bg) */
  --bg-ink: #8a806a;

  color-scheme: dark;
}

/* ============================ light theme ================================
   Not an inversion: same hue anchors re-stepped for a white surface and
   re-validated (contrast >= 4.6:1, adjacent-pair OKLab dE >= 15 normal /
   >= 8 protan+deutan). Amber chrome survives as burnt amber so the brand
   reads identically in both shifts. Dark stays the default: wall displays
   that never touch the toggle keep exactly their old behaviour. */
:root[data-theme="light"] {
  /* Daylight side of the same world: a private plate — warm paper canvas,
     white plate-face cards, plate-black ink. The yellow stays plate-bright
     for FILLS (--accent-fill) while strokes and text use a deeper gold that
     survives a white surface. */
  --bg:        #e5dfd2;
  --panel:     #fffdf8;
  --panel-2:   #f7f2e8;
  --panel-3:   #ede6d8;
  --line:      #e4dccb;
  --line-2:    #cfc5af;

  --ink:       #1a160d;
  --ink-2:     #57503f;
  --ink-3:     #6e6753;
  --ink-4:     #8a8270;

  --accent:      #a87f00;
  --accent-fill: #f2c200;
  --accent-dim:  rgba(168, 127, 0, .14);
  --accent-ink:  #17130a;
  --accent-text: #6e5200;
  --ind:         #1b5fbf;

  --d-blue:    #2a72cc;
  --d-orange:  #c2601f;
  --d-aqua:    #0a5e36;
  --d-teal:    #0e6c80;
  --d-magenta: #a83c77;
  --d-violet:  #4c2e9e;
  --d-red:     #b02e2e;
  --d-neutral: #6f6754;

  --s-good:     #0e7a3c;
  --s-warning:  #9a5b00;
  --s-serious:  #b23f14;
  --s-critical: #a82222;

  --shadow: 0 10px 30px rgba(60, 48, 24, .16);
  --card-shadow: 0 1px 2px rgba(60, 48, 24, .07), 0 6px 22px rgba(60, 48, 24, .10);

  --topbar-bg:  rgba(255, 253, 248, .92);
  --tip-bg:     rgba(255, 253, 248, .98);
  --scrim:      rgba(36, 30, 18, .55);
  --lb-img-bg:  #ede6d8;
  --swatch-ring: rgba(0, 0, 0, .22);

  --warn-ink: #9a5b00;
  --crit-ink: #a82222;
  --ok-ink:   #0e7a3c;

  --bg-ink: #6b6350;

  color-scheme: light;
}

/* Light-theme veil contexts: pigment bleeds through 14% of the veil, so the
   muted tier computes under 4.5:1 there — promote to --ink-2 (worst-case 4.65:1). */
:root[data-theme="light"] .footnote,
:root[data-theme="light"] main > .emptystate,
:root[data-theme="light"] #rv-empty,
:root[data-theme="light"] main > p.small {
  color: var(--ink-2);
}

body {
  font-family: var(--font);
  background: var(--bg);
  color: var(--ink);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
}

a { color: var(--ind); text-decoration: none; }
a:hover { text-decoration: underline; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

.mono { font-family: var(--mono); }
.num  { font-variant-numeric: tabular-nums; }
.muted { color: var(--ink-2); }
.dim   { color: var(--ink-3); }
.tiny  { font-size: 11px; }
.small { font-size: 12px; }
.nowrap { white-space: nowrap; }
.right { text-align: right; }
.hidden { display: none !important; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* ============================ shell ====================================== */

.topbar {
  position: sticky; top: 0; z-index: 50;
  display: flex; align-items: center; gap: var(--sp-5);
  padding: 0 var(--sp-5);
  height: 54px;
  background: var(--topbar-bg);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}
.topbar::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px;
  pointer-events: none; opacity: .4;
  background: linear-gradient(90deg,
    transparent, color-mix(in srgb, var(--accent) 70%, transparent) 30%,
    var(--accent) 55%, color-mix(in srgb, var(--accent) 45%, transparent) 78%,
    transparent);
}

.brand { display: flex; align-items: center; gap: var(--sp-3); flex: none; }
.brand-mark {
  width: 26px; height: 26px; flex: none;
  color: var(--accent);
}
.brand-name {
  font-size: 13px; font-weight: 650; letter-spacing: .10em;
  text-transform: uppercase; color: var(--ink);
}
.brand-sub {
  font-size: 11px; letter-spacing: .06em; color: var(--ink-3);
  border-left: 1px solid var(--line-2); padding-left: var(--sp-3);
}

/* Pill Nav (reactbits.dev port, CSS-only): a hugging pill bar, solid accent
   pill on the active item, and the signature hover circle that swells up from
   the item's bottom edge. */
.nav {
  display: flex; gap: 4px; flex: 0 1 auto; margin-inline: auto;
  padding: 4px; border: 1px solid var(--line-2); border-radius: 999px;
  background: var(--panel);
}
.nav a {
  position: relative; overflow: hidden; z-index: 0;
  padding: 5px 14px; border-radius: 999px;
  color: var(--ink-2); font-size: 13px; font-weight: 500;
  text-decoration: none;
  transition: color .22s ease;
}
.nav a::before {
  content: ""; position: absolute; left: 50%; bottom: -2px; z-index: -1;
  width: 132%; aspect-ratio: 1; border-radius: 50%;
  background: var(--accent-dim);
  transform: translateX(-50%) scale(0); transform-origin: 50% 100%;
  transition: transform .28s cubic-bezier(.4, 0, .2, 1);
}
.nav a:hover { color: var(--ink); text-decoration: none; }
.nav a:hover::before { transform: translateX(-50%) scale(1.15); }
.nav a[aria-current="page"] { color: var(--accent-ink); background: var(--accent-fill); }
.nav a[aria-current="page"]::before { display: none; }
@media (prefers-reduced-motion: reduce) {
  .nav a::before { transition: none; }
}

.topbar-right { display: flex; align-items: center; gap: var(--sp-3); flex: none; }

.clock { font-family: var(--mono); font-size: 12px; color: var(--ink-2); }
.clock b { color: var(--ink); font-weight: 500; }

/* link pill: device online/offline at a glance, on every page */
.linkpill {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 4px 10px 4px 8px; border-radius: 999px;
  border: 1px solid var(--line-2); background: var(--panel);
  font-size: 12px; color: var(--ink-2);
}
.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ink-4); flex: none; }
.dot.live     { background: var(--s-good);     box-shadow: 0 0 0 3px color-mix(in srgb, var(--s-good) 18%, transparent); }
.dot.stale    { background: var(--s-warning);  box-shadow: 0 0 0 3px color-mix(in srgb, var(--s-warning) 18%, transparent); }
.dot.offline  { background: var(--s-critical); box-shadow: 0 0 0 3px color-mix(in srgb, var(--s-critical) 18%, transparent); }
.dot.never    { background: var(--ink-4); }

.btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px; border-radius: var(--r-md);
  border: 1px solid var(--line-2); background: var(--panel);
  color: var(--ink-2); font-size: 12.5px; cursor: pointer;
}
.btn:hover { background: var(--panel-3); color: var(--ink); }
.btn:disabled { opacity: .45; cursor: default; }
.btn.primary { border-color: color-mix(in srgb, var(--accent) 50%, transparent); color: var(--accent-text); background: color-mix(in srgb, var(--accent) 15%, var(--panel)); }

/* theme toggle — dark shows the sun (what you get), light shows the moon */
.btn.iconbtn { padding: 5px 7px; }
.iconbtn svg { width: 16px; height: 16px; }
.iconbtn .ic-moon { display: none; }
:root[data-theme="light"] .iconbtn .ic-sun  { display: none; }
:root[data-theme="light"] .iconbtn .ic-moon { display: block; }

/* segmented window selector */
.seg { display: inline-flex; border: 1px solid var(--line-2); border-radius: var(--r-md); overflow: hidden; }
.seg button {
  padding: 5px 11px; background: var(--panel); border: 0; cursor: pointer;
  color: var(--ink-3); font-size: 12.5px; border-right: 1px solid var(--line-2);
}
.seg button:last-child { border-right: 0; }
.seg button:hover { color: var(--ink); background: var(--panel-3); }
.seg button[aria-pressed="true"] { background: color-mix(in srgb, var(--accent) 15%, var(--panel)); color: var(--accent-text); }

main { padding: var(--sp-5); max-width: 1680px; margin: 0 auto; }

/* Anything textual that sits DIRECTLY on the animated ground gets the veil:
   a translucent bg-derived wash + blur, so ink never fights the pigment. */
.page-head, .resultbar, main > .emptystate, #rv-empty, main > p.small, #dev-sub {
  background: var(--veil);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  border-radius: var(--r-md);
  padding: var(--sp-2) var(--sp-3);
}

.page-head {
  display: flex; align-items: baseline; gap: var(--sp-4);
  flex-wrap: wrap; margin-bottom: var(--sp-4);
}
.page-head h1 { font-size: 19px; font-weight: 620; letter-spacing: -.01em; }
.page-head .spacer { flex: 1 1 auto; }

/* offline / error banner — sticky so a stale wall display admits it is stale */
.banner {
  display: none; align-items: center; gap: var(--sp-3);
  padding: 9px var(--sp-4); margin-bottom: var(--sp-4);
  border-radius: var(--r-md); font-size: 13px;
  border: 1px solid color-mix(in srgb, var(--s-critical) 40%, transparent);
  /* opaque over PANEL, not transparent: an alert must survive the moving ground */
  background: color-mix(in srgb, var(--s-critical) 10%, var(--panel));
  color: var(--crit-ink);
}
.banner.show { display: flex; }
.banner.warn { border-color: color-mix(in srgb, var(--s-warning) 40%, transparent); background: color-mix(in srgb, var(--s-warning) 10%, var(--panel)); color: var(--warn-ink); }

/* ============================ layout ===================================== */

.grid { display: grid; gap: var(--sp-4); }
.g-tiles  { grid-template-columns: repeat(auto-fit, minmax(min(178px, 100%), 1fr)); }
.g-2      { grid-template-columns: repeat(auto-fit, minmax(min(380px, 100%), 1fr)); }
.g-3      { grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); }
.g-main   { grid-template-columns: minmax(0, 2.1fr) minmax(280px, 1fr); }
@media (max-width: 1080px) { .g-main { grid-template-columns: 1fr; } }

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  box-shadow: var(--card-shadow);
  border-radius: var(--r-lg);
  padding: var(--sp-4);
  min-width: 0;
}
.card > header {
  display: flex; align-items: baseline; gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}
.card > header h2 {
  font-size: 12px; font-weight: 620; letter-spacing: .07em;
  text-transform: uppercase; color: var(--ink-2);
}
.card > header .hint { font-size: 11px; color: var(--ink-4); margin-left: auto; text-align: right; }

/* ---- stat tiles ---- */
.tile {
  background: var(--panel); border: 1px solid var(--line);
  box-shadow: var(--card-shadow);
  border-radius: var(--r-lg); padding: var(--sp-4);
  display: flex; flex-direction: column; gap: 2px; min-width: 0;
}
.tile .label {
  font-size: 11px; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tile .value {
  font-size: 27px; font-weight: 600; line-height: 1.15;
  font-variant-numeric: tabular-nums; letter-spacing: -.02em;
}
.tile .value small { font-size: 14px; font-weight: 500; color: var(--ink-2); margin-left: 3px; }
.tile .foot { font-size: 11.5px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.tile .foot .up   { color: var(--s-good); }
.tile .foot .down { color: var(--s-serious); }
.tile.is-good     .value { color: var(--s-good); }
.tile.is-warning  .value { color: var(--s-warning); }
.tile.is-critical .value { color: var(--s-critical); }
.tile .sparkhost { margin-top: 6px; height: 30px; }

/* ---- tile identity hues — fixed per data-tile NAME (entity, not position),
   so "reads" is blue on every page. Hues come from the validated data ramp;
   status classes (is-good/warning/critical) still override the value colour. */
.tile { --th: var(--d-neutral); }
[data-tile="reads"], [data-tile="fps"]                       { --th: var(--d-blue); }
[data-tile="plates"], [data-tile="cams"], [data-tile="uptime"] { --th: var(--d-aqua); }
[data-tile="rate"], [data-tile="lag"], [data-tile="model"]   { --th: var(--d-violet); }
[data-tile="valid"], [data-tile="disk"], [data-tile="trust"] { --th: var(--d-teal); }
[data-tile="brand"], [data-tile="vlm"], [data-tile="outbox"] { --th: var(--d-magenta); }
[data-tile="review"]                                          { --th: var(--accent-fill); }
[data-tile="quar"]                                            { --th: var(--d-red); }
.tile::before {
  content: ""; position: absolute; top: 0; left: 14px; right: 14px; height: 2px;
  border-radius: 0 0 2px 2px; pointer-events: none;
  background: linear-gradient(90deg, var(--th),
              color-mix(in srgb, var(--th) 15%, transparent));
  opacity: .9;
}
.tile {
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--th) 7%, transparent), transparent 46%),
    var(--panel);
}
.tile .label { color: color-mix(in srgb, var(--th) 40%, var(--ink-2)); }

/* ---- chart host ---- */
.charthost { width: 100%; min-height: 40px; }
.charthost svg.chart { display: block; overflow: visible; }
.chart-empty {
  display: flex; align-items: center; justify-content: center;
  height: 100%; min-height: 120px; color: var(--ink-3); font-size: 12.5px;
  border: 1px dashed var(--line-2); border-radius: var(--r-md);
}

.legend {
  display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-4);
  margin-bottom: var(--sp-2); font-size: 11.5px; color: var(--ink-2);
}
.legend .item { display: inline-flex; align-items: center; gap: 6px; }
.legend .swatch { width: 9px; height: 9px; border-radius: 2px; flex: none; }
.legend .swatch.line { height: 3px; width: 14px; border-radius: 2px; }
.legend .swatch.gap {
  width: 14px; height: 9px; border-radius: 2px;
  background: repeating-linear-gradient(135deg, var(--line-2) 0 2px, transparent 2px 4px);
  border: 1px solid var(--line-2);
}

/* shared floating tooltip */
#tip {
  position: fixed; z-index: 200; pointer-events: none; opacity: 0;
  transition: opacity .08s linear;
  background: var(--tip-bg); border: 1px solid var(--line-2);
  border-radius: var(--r-md); box-shadow: var(--shadow);
  padding: 8px 10px; font-size: 12px; color: var(--ink);
  max-width: 280px;
}
#tip.on { opacity: 1; }
#tip .t-head {
  font-family: var(--mono); font-size: 11px; color: var(--ink-2);
  margin-bottom: 5px; white-space: nowrap;
}
#tip .t-row { display: flex; align-items: center; gap: 7px; white-space: nowrap; }
#tip .t-row + .t-row { margin-top: 2px; }
#tip .t-sw { width: 8px; height: 8px; border-radius: 2px; flex: none; }
#tip .t-lab { color: var(--ink-2); }
#tip .t-val { margin-left: auto; font-variant-numeric: tabular-nums; font-weight: 550; }
#tip .t-note { margin-top: 5px; color: var(--ink-3); font-size: 11px; }

/* ---- ranked horizontal bars ---- */
.rank { display: flex; flex-direction: column; gap: 7px; }
.rank .row { display: grid; grid-template-columns: minmax(76px, 118px) 1fr 62px; gap: var(--sp-3); align-items: center; }
.rank .k { font-size: 12px; color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rank .track { height: 9px; background: var(--panel-3); border-radius: 3px; overflow: hidden; }
.rank .fill { height: 100%; border-radius: 3px; min-width: 2px; }
.rank .v { font-size: 12px; text-align: right; font-variant-numeric: tabular-nums; color: var(--ink); }
.rank .v span { color: var(--ink-3); font-size: 11px; margin-left: 4px; }

/* ---- 100% segmented split bar ---- */
.split { margin-bottom: var(--sp-3); }
.split:last-child { margin-bottom: 0; }
.split .cap {
  display: flex; align-items: baseline; gap: var(--sp-2);
  font-size: 11.5px; color: var(--ink-2); margin-bottom: 5px;
}
.split .cap b { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }
.split .cap .pct { margin-left: auto; font-variant-numeric: tabular-nums; }
.split .bar { display: flex; height: 12px; border-radius: 3px; overflow: hidden; background: var(--panel-3); }
.split .bar > span { display: block; height: 100%; min-width: 0; }
.split .keys { display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 6px; font-size: 11px; color: var(--ink-3); }
.split .keys .item { display: inline-flex; align-items: center; gap: 5px; }
.split .keys .sw { width: 8px; height: 8px; border-radius: 2px; }

/* ---- meters (bounded metrics, fixed 0..100 domain) ---- */
.meters { display: flex; flex-direction: column; gap: var(--sp-3); }
.meter .cap {
  display: flex; align-items: baseline; gap: var(--sp-2);
  font-size: 12px; color: var(--ink-2); margin-bottom: 4px;
}
.meter .cap .v { margin-left: auto; color: var(--ink); font-variant-numeric: tabular-nums; font-weight: 600; }
.meter .track {
  position: relative; height: 8px; border-radius: 4px; background: var(--panel-3);
  overflow: hidden;
}
.meter .fill { height: 100%; border-radius: 4px; transition: width .3s ease; }
.meter .ticks { display: flex; justify-content: space-between; font-size: 10px; color: var(--ink-4); margin-top: 3px; }

/* ============================ tables ===================================== */

.tablewrap { overflow-x: auto; }
table.data { width: 100%; border-collapse: collapse; font-size: 12.5px; }
table.data th {
  text-align: left; font-weight: 550; font-size: 11px; letter-spacing: .05em;
  text-transform: uppercase; color: var(--ink-3);
  padding: 6px 10px; border-bottom: 1px solid var(--line-2); white-space: nowrap;
}
table.data td {
  padding: 7px 10px; border-bottom: 1px solid var(--line);
  vertical-align: middle; white-space: nowrap;
}
table.data tbody tr:hover { background: var(--panel-2); }
table.data td.wrap { white-space: normal; }
table.data .plate { font-family: var(--mono); font-weight: 600; letter-spacing: .04em; }

/* chips / badges */
.chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 1px 8px; border-radius: 999px;
  font-size: 11px; border: 1px solid var(--line-2); color: var(--ink-2);
  background: var(--panel-2); white-space: nowrap;
}
.chip .cdot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.chip.warn { border-color: color-mix(in srgb, var(--s-warning) 40%, transparent); color: var(--warn-ink); background: color-mix(in srgb, var(--s-warning) 8%, transparent); }
.chip.bad  { border-color: color-mix(in srgb, var(--s-critical) 40%, transparent);  color: var(--crit-ink); background: color-mix(in srgb, var(--s-critical) 8%, transparent); }
.chip.ok   { border-color: color-mix(in srgb, var(--s-good) 40%, transparent);  color: var(--ok-ink); background: color-mix(in srgb, var(--s-good) 8%, transparent); }

/* colour swatch for the vehicle's own colour — the entity IS a colour */
.cswatch {
  display: inline-block; width: 10px; height: 10px; border-radius: 2px;
  border: 1px solid var(--swatch-ring); vertical-align: -1px; margin-right: 5px;
}

/* "unknown" must read as an explicit absence, never as a real value */
.unk { color: var(--ink-3); font-style: italic; }

/* ============================ gallery ==================================== */

.filters {
  display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: flex-end;
  padding: var(--sp-3) var(--sp-4);
  background: var(--panel); border: 1px solid var(--line);
  box-shadow: var(--card-shadow);
  border-radius: var(--r-lg); margin-bottom: var(--sp-4);
}
.field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.field > label {
  font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase; color: var(--ink-3);
}
.field input[type="text"], .field input[type="date"], .field select {
  background: var(--panel-2); border: 1px solid var(--line-2);
  border-radius: var(--r-md); padding: 6px 9px; color: var(--ink);
  font-size: 13px; min-width: 0;
}
.field input[type="text"] { min-width: 190px; font-family: var(--mono); letter-spacing: .04em; }
/* every dropdown shares one width so the bar reads as a grid, not a ransom note */
.filters .field select { min-width: 148px; max-width: 190px; }
.filters .field select:disabled { opacity: .45; }
/* thin separators mark the filter groups: search/class | vehicle identity | ops */
.filters .field.group-start { border-left: 1px solid var(--line-2); padding-left: var(--sp-4); }
@media (max-width: 720px) {
  .filters .field.group-start { border-left: 0; padding-left: 0; }
}
.field input:hover, .field select:hover { border-color: var(--ink-4); }
.field input::placeholder { color: var(--ink-4); font-family: var(--font); letter-spacing: 0; }
.chipset { display: flex; flex-wrap: wrap; gap: 5px; }
.chipset button {
  padding: 4px 10px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--line-2); background: var(--panel-2);
  color: var(--ink-2); font-size: 12px;
  display: inline-flex; align-items: center; gap: 6px;
}
.chipset button:hover { color: var(--ink); border-color: var(--ink-4); }
.chipset button[aria-pressed="true"] { background: var(--panel-3); color: var(--ink); border-color: currentColor; }
.chipset button .cdot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.chipset button .n { font-size: 10.5px; color: var(--ink-4); font-variant-numeric: tabular-nums; }

.resultbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3);
  margin-bottom: var(--sp-3); font-size: 12.5px; color: var(--ink-2);
}
.resultbar .spacer { flex: 1 1 auto; }

.gallery {
  display: grid; gap: var(--sp-3);
  grid-template-columns: repeat(auto-fill, minmax(232px, 1fr));
}
.gcard {
  background: var(--panel); border: 1px solid var(--line);
  box-shadow: var(--card-shadow);
  border-radius: var(--r-md); overflow: hidden; cursor: pointer;
  text-align: left; padding: 0; display: flex; flex-direction: column;
  transition: border-color .12s, transform .12s;
}
.gcard:hover { border-color: var(--ink-4); transform: translateY(-1px); }
.gcard:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.gthumb {
  position: relative; width: 100%; aspect-ratio: 16 / 10;
  background: var(--panel-3); overflow: hidden;
}
/* Object-position keeps the VEHICLE in frame whether the backend hands us a
   vehicle crop or the full 680x260 composite card. */
.gthumb img {
  width: 100%; height: 100%; object-fit: cover; object-position: 14% 50%;
  opacity: 0; transition: opacity .25s ease;
}
.gthumb img.loaded { opacity: 1; }
.gthumb.skel::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(100deg, var(--panel-3) 30%, var(--panel-2) 50%, var(--panel-3) 70%);
  background-size: 300% 100%; animation: shimmer 1.4s linear infinite;
}
@keyframes shimmer { from { background-position: 150% 0; } to { background-position: -150% 0; } }
.gthumb.nocard::after {
  content: "no snapshot"; position: absolute; inset: 0; animation: none;
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-4); font-size: 11.5px; background: var(--panel-2);
}
.gthumb .ttime {
  position: absolute; right: 5px; bottom: 5px; z-index: 2;
  font-family: var(--mono); font-size: 10.5px; color: var(--img-ink);
  background: var(--img-scrim); padding: 1px 5px; border-radius: 3px;
}
.gthumb .tclass {
  position: absolute; left: 5px; top: 5px; z-index: 2;
  font-size: 10.5px; padding: 1px 7px; border-radius: 999px;
  color: var(--img-ink);   /* was inheriting body ink: invisible in light theme */
  background: var(--img-scrim); display: inline-flex; align-items: center; gap: 5px;
}
.gthumb .tclass .cdot { width: 6px; height: 6px; border-radius: 50%; }
.gthumb .tflag {
  position: absolute; right: 5px; top: 5px; z-index: 2;
  font-size: 10px; padding: 1px 6px; border-radius: 3px;
  background: color-mix(in srgb, var(--d-magenta) 90%, #000); color: #fff;
}
.gthumb .tflag.ok  { background: color-mix(in srgb, var(--s-good) 88%, #000); }
.gthumb .tflag.bad { background: color-mix(in srgb, var(--s-critical) 90%, #000); }
.gmeta { padding: 7px 9px 9px; display: flex; flex-direction: column; gap: 3px; }
.gmeta .plate {
  font-family: var(--mono); font-size: 14.5px; font-weight: 650; letter-spacing: .05em;
  color: var(--ink); display: flex; align-items: center; gap: 6px;
}
.gmeta .plate.invalid { color: var(--crit-ink); }
.gmeta .plate .st { font-size: 10px; font-weight: 500; letter-spacing: .04em; color: var(--ink-3); }
.gmeta .bm { font-size: 12px; color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gmeta .sub { font-size: 11px; color: var(--ink-3); display: flex; align-items: center; gap: 8px; }

.sentinel { height: 1px; }
.loadmore { display: flex; justify-content: center; padding: var(--sp-5) 0; }

/* ---- lightbox ---- */
.lb {
  position: fixed; inset: 0; z-index: 300; display: none;
  background: var(--scrim); backdrop-filter: blur(3px);
  align-items: center; justify-content: center; padding: var(--sp-5);
}
.lb.on { display: flex; }
.lb-inner {
  background: var(--panel); border: 1px solid var(--line-2);
  border-radius: var(--r-lg); box-shadow: var(--shadow);
  max-width: 1040px; width: 100%; max-height: 92vh; overflow: auto;
}
.lb-head {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--line);
  position: sticky; top: 0; background: var(--panel); z-index: 2;
}
.lb-head .plate { font-family: var(--mono); font-size: 19px; font-weight: 650; letter-spacing: .06em; }
.lb-head .spacer { flex: 1 1 auto; }
.lb-img {
  background: var(--lb-img-bg); padding: var(--sp-4);
  display: flex; align-items: center; justify-content: center;
}
/* Cards are normally 680x260, but never trust that: cap the height and letterbox
   so a mis-sized card cannot push the metadata below the fold. */
.lb-img img {
  width: 100%; height: auto; max-height: 58vh; object-fit: contain;
  border-radius: var(--r-md);
}
.lb-body { padding: var(--sp-4); }
.kv { display: grid; grid-template-columns: repeat(auto-fit, minmax(196px, 1fr)); gap: var(--sp-3) var(--sp-5); }
.kv .k { font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase; color: var(--ink-3); }
.kv .v { font-size: 13px; color: var(--ink); overflow-wrap: anywhere; }
.kv .v.mono { font-family: var(--mono); font-size: 12px; }

/* ============================ misc ======================================= */

.emptystate {
  padding: var(--sp-6); text-align: center; color: var(--ink-3); font-size: 13px;
  border: 1px dashed var(--line-2); border-radius: var(--r-lg);
}

/* Footer: a quiet contained bar, not loose text bleeding into the ground. */
.footnote {
  margin-top: var(--sp-6);
  padding: var(--sp-3) var(--sp-4);
  background: var(--veil);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  border: 1px solid var(--line); border-radius: var(--r-lg);
  color: var(--ink-3); font-size: 11.5px; line-height: 1.6;
  display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-5);
}
.footnote .mono { color: var(--ink-2); }

.skel-line {
  height: 10px; border-radius: 3px; background: var(--panel-3);
  animation: pulse 1.3s ease-in-out infinite;
}
@keyframes pulse { 0%,100% { opacity: .55; } 50% { opacity: .95; } }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

/* ---- responsive ladder (desktop-first). The topbar's natural width is
   ~1330px logged-in, so it must start shedding long before phones. ---- */
@media (max-width: 1360px) {
  .clock { display: none; }
}
@media (max-width: 1200px) {
  .topbar { gap: var(--sp-3); }
  .brand-sub { display: none; }
  .nav { min-width: 0; overflow-x: auto; scrollbar-width: none; }
  .nav::-webkit-scrollbar { display: none; }
  .nav a { flex: none; }
}
@media (max-width: 990px) {
  .topbar { height: auto; flex-wrap: wrap; padding: var(--sp-2) var(--sp-3); gap: var(--sp-2); }
  .nav { order: 3; width: 100%; }
}
@media (max-width: 720px) {
  main { padding: var(--sp-3); }
  .gallery { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); }
  .g-tiles { grid-template-columns: repeat(auto-fit, minmax(min(140px, 100%), 1fr)); }
  .lb { padding: var(--sp-2); }
  .lb-head { flex-wrap: wrap; row-gap: var(--sp-2); }
}
@media (max-width: 560px) {
  .filters .field { flex: 1 1 calc(50% - var(--sp-3)); }
  .filters .field input, .filters .field select { width: 100%; }
  .field input[type="text"] { min-width: 0; }
}
@media (max-width: 480px) {
  .topbar-right { flex-wrap: wrap; }
  .linkpill .dim, #who .dim { display: none; }
  .linkpill > span:not(.dot) {
    max-width: 44vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
}

/* quarantine error strings must wrap, or the table is ten screens wide */
#quar td:last-child { white-space: normal; overflow-wrap: anywhere; max-width: 340px; }

/* ============================ sign-in page ===============================
   A product page, not a bare form: the left side runs the characteristic
   moment (a plate being recognised) as an inline SVG the page draws itself —
   no external image, no CDN, so the strict CSP and the offline-capable
   deployment both still hold. The right side is the actual work.
   ====================================================================== */
.loginwrap {
  min-height: 100dvh; max-width: none; padding: 0;
  margin: 0; width: 100%;                 /* undo the main{} shell centring */
  display: grid;
  /* the form wants a comfortable fixed measure; the hero takes what is left */
  grid-template-columns: minmax(0, 1fr) clamp(348px, 25vw, 424px);
  align-items: stretch;
}
.login-hero {
  display: flex; flex-direction: column; justify-content: center; gap: var(--sp-4);
  padding: var(--sp-5) clamp(var(--sp-4), 3.5vw, 44px);
  background:
    linear-gradient(160deg, color-mix(in srgb, var(--accent) 6%, transparent), transparent 60%),
    var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--card-shadow);
}
.login-hero .loginbrand { margin-bottom: var(--sp-2); }
.hero-copy { display: flex; flex-direction: column; gap: var(--sp-4); }
.hero-head {
  font-size: clamp(28px, 3.4vw, 44px); line-height: 1.12; font-weight: 680;
  letter-spacing: -.022em; color: var(--ink);
}
.hero-sub {
  font-size: 14.5px; line-height: 1.65; color: var(--ink-2); max-width: 52ch;
}
.hero-points {
  display: flex; flex-direction: column; gap: 7px;
  font-size: 12.5px; color: var(--ink-3); margin-top: var(--sp-2);
}
.hero-points li { display: flex; align-items: baseline; gap: 9px; }
.hero-points li::before {
  content: ""; flex: none; width: 6px; height: 6px; border-radius: 1px;
  background: var(--accent); transform: translateY(-1px);
}
.hero-points b { color: var(--ink-2); font-weight: 600; }

/* ---- the capture stage ---- */
.stage { perspective: 1100px; margin: var(--sp-2) 0; }
.stage-card {
  max-width: 520px; padding: var(--sp-4) var(--sp-4) var(--sp-3);
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-lg); box-shadow: var(--card-shadow);
  transform: rotateY(-7deg) rotateX(3deg); transform-origin: 60% 50%;
}
.capture { width: 100%; height: auto; display: block; }

/* Vehicles are built in cabinet projection: every body is three faces — a lit
   top, a mid-tone rear and a shaded side — so they read as solid volumes
   rather than drawings. Each vehicle sets its own --v base colour inline and
   the faces are derived from it, which keeps the whole fleet consistent. */
.capture .f-top  { fill: color-mix(in srgb, var(--v) 72%, #ffffff); }
.capture .f-rear { fill: var(--v); }
.capture .f-side { fill: color-mix(in srgb, var(--v) 58%, #000000); }
.capture .f-top.cabin { fill: color-mix(in srgb, var(--v) 84%, #ffffff); }
.capture .glass-s {
  fill: color-mix(in srgb, var(--ind) 34%, #05070c); opacity: .92;
}
.capture .glass-s.side { fill: color-mix(in srgb, var(--ind) 22%, #05070c); }
.capture .cargo-door {
  fill: color-mix(in srgb, var(--v) 84%, #000); stroke: color-mix(in srgb, var(--v) 40%, #000);
  stroke-width: 1;
}
.capture .lamp-s {
  fill: color-mix(in srgb, var(--s-critical) 86%, #000);
}
.capture .lamp-s.big { fill: color-mix(in srgb, var(--s-critical) 92%, #fff); }
.capture .miniplate { fill: color-mix(in srgb, var(--accent-fill) 88%, #000); }
.capture .tyre { fill: #17171b; }
.capture .plate-recess { fill: #14141a; }

/* the road and its surroundings */
.g-road-1 { stop-color: color-mix(in srgb, var(--ink) 13%, var(--bg)); }
.g-road-2 { stop-color: color-mix(in srgb, var(--ink) 22%, var(--bg)); }
.g-beam-1 { stop-color: color-mix(in srgb, var(--accent) 30%, transparent); }
.g-beam-2 { stop-color: color-mix(in srgb, var(--accent) 2%, transparent); }
.capture .verge { fill: color-mix(in srgb, var(--d-aqua) 14%, var(--bg)); }
.capture .edgeline {
  stroke: color-mix(in srgb, var(--ink) 24%, transparent); stroke-width: 1.5; fill: none;
}
.capture .lane path {
  stroke: color-mix(in srgb, var(--ink) 34%, transparent); stroke-width: 3.5;
  fill: none; stroke-linecap: butt;
}
.capture .tree ellipse { fill: color-mix(in srgb, var(--d-aqua) 46%, var(--bg)); }
.capture .tree rect    { fill: color-mix(in srgb, var(--ink) 20%, var(--bg)); }
.capture .tree.far     { opacity: .55; }

/* pole, arm and camera housing */
.capture .pole       { fill: color-mix(in srgb, var(--ink) 30%, var(--bg)); }
.capture .pole-arm   { stroke: color-mix(in srgb, var(--ink) 30%, var(--bg)); stroke-width: 7;
                       stroke-linecap: round; fill: none; }
.capture .pole-brace { stroke: color-mix(in srgb, var(--ink) 22%, var(--bg)); stroke-width: 3.5;
                       stroke-linecap: round; fill: none; }
.capture .cam-body { fill: color-mix(in srgb, var(--ink) 40%, var(--bg)); }
.capture .cam-hood { fill: color-mix(in srgb, var(--ink) 28%, var(--bg)); }
.capture .cam-lens { fill: color-mix(in srgb, var(--ind) 60%, #000); }
.capture .cam-ir   { fill: var(--s-critical); opacity: .9; }
.capture .beam { opacity: 0; transition: opacity .5s ease; }
.stage.locked .beam { opacity: 1; }
.stage.locked .cam-ir { animation: irpulse 1.6s ease-in-out infinite; }
@keyframes irpulse { 0%, 100% { opacity: .4; } 50% { opacity: 1; } }

/* the plate: commercial yellow, plate-black characters */
.capture .plate-face { fill: var(--accent-fill); }
.capture .plate-edge { fill: none; stroke: color-mix(in srgb, #000 45%, var(--accent-fill)); stroke-width: 1.2; }
.capture .plate-text {
  font-family: var(--mono); font-size: 9.5px; font-weight: 700;
  letter-spacing: .02em; fill: #17130a;
}

/* detector overlay — hidden until the lock fires */
.capture .det path, .capture .platebox rect {
  fill: none; stroke: var(--accent); stroke-width: 2.5; stroke-linecap: round;
}
.capture .det { opacity: 0; transition: opacity .45s ease; }
.capture .platebox rect {
  stroke-width: 2; opacity: 0; transform-origin: 115px 239px; transform: scale(1.35);
  transition: opacity .3s ease, transform .3s cubic-bezier(.2,.9,.3,1.4);
}
.capture .scan { fill: var(--accent); opacity: 0; }
.car { opacity: 0; transform: translateY(10px) scale(.985); transform-origin: 50% 70%;
       transition: opacity .7s ease, transform .7s cubic-bezier(.2,.8,.3,1); }

.stage.lit .car { opacity: 1; transform: none; }
.stage.locked .det { opacity: 1; }
.stage.locked .platebox rect { opacity: 1; transform: none; }
.stage.scanned .scan { animation: platescan 1.5s ease-in-out; }
@keyframes platescan {
  0%   { opacity: 0;   transform: translateY(0); }
  12%  { opacity: .85; }
  88%  { opacity: .85; }
  100% { opacity: 0;   transform: translateY(16px); }
}

.readout {
  display: flex; align-items: center; gap: var(--sp-3);
  margin-top: var(--sp-3); padding-top: var(--sp-3); border-top: 1px solid var(--line);
  min-height: 30px;
}
.readout .chip-cls {
  font-size: 11px; padding: 1px 9px; border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--d-blue) 45%, transparent);
  color: var(--d-blue);
}
.readout .read-plate {
  font-size: 16px; font-weight: 680; letter-spacing: .07em; color: var(--ink);
}
.readout .read-conf { margin-left: auto; font-size: 12px; color: var(--ink-3); }
.stage.read .readout .read-conf { color: var(--ok-ink); }

/* ---- the form side ---- */
.login-side {
  display: grid; place-items: center; padding: var(--sp-5);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--card-shadow);
}
/* the form is already inside a panel — drop its own card chrome so the
   two do not nest and read as a box in a box */
.login-side .loginbox {
  background: none; border: 0; box-shadow: none; padding: 0;
}
.loginbox {
  width: 100%; max-width: 372px; padding: var(--sp-5);
  display: flex; flex-direction: column; gap: var(--sp-4);
}
.loginbox-head { font-size: 19px; font-weight: 640; letter-spacing: -.01em; }
.loginbox .field input { width: 100%; box-sizing: border-box; min-width: 0; }
.loginbox .btn { justify-content: center; padding: 10px 12px; font-size: 14px; }
.loginnote { margin-top: 2px; line-height: 1.6; }
.loginbrand { display: flex; align-items: center; gap: var(--sp-3); }
.loginbrand .brand-mark { color: var(--accent); width: 30px; height: 30px; }

.field input[type="password"] {
  background: var(--panel-2); border: 1px solid var(--line-2); color: var(--ink);
  border-radius: var(--r-sm); padding: 8px 9px; font: inherit; font-size: 13px;
  outline: none;
}
.field input[type="password"]:hover { border-color: var(--ink-4); }
.field input:focus-visible, .field select:focus-visible {
  border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-dim);
}

@media (max-width: 880px) {
  .loginwrap { grid-template-columns: 1fr; }
  .login-hero { border-right: 0; border-bottom: 1px solid var(--line); order: 2;
                padding: var(--sp-5); }
  .login-side { order: 1; padding-top: var(--sp-6); }
  .stage-card { transform: none; max-width: 460px; }
  .hero-head { font-size: 26px; }
}
@media (max-width: 560px) {
  .stage { display: none; }
  .hero-sub { font-size: 13.5px; }
}
@media (prefers-reduced-motion: reduce) {
  .car, .capture .det, .capture .platebox rect { transition: none; }
  .stage.scanned .scan { animation: none; }
}

/* ============================ review panel =============================== */

/* .gallery narrows to 160px tracks under 720px at the same specificity (0,1,0),
   so a bare .rv-grid only wins on source order. Qualifying it makes the review
   cards keep their readable width however the file is later reordered. */
.gallery.rv-grid { grid-template-columns: repeat(auto-fill, minmax(min(330px, 100%), 1fr)); }
.rvcard { padding: 0; overflow: hidden; display: flex; flex-direction: column; }
/* native card aspect (680x260): show the WHOLE composite incl. plate strip */
.rvcard .rv-thumb { aspect-ratio: 680 / 260; }
/* only the thumbs pages.js actually wired for zoom (it sets role=button when
   the event has a card) should advertise the affordance */
.rvcard .rv-thumb[role="button"] { cursor: zoom-in; }
.rvcard .rv-thumb:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.rv-lb-inner { max-width: min(1280px, 96vw); }
.rv-lb-imgwrap {
  overflow: auto; max-height: 80vh; background: var(--lb-img-bg);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
}
.rv-lb-imgwrap img { display: block; width: 100%; height: auto; cursor: zoom-in; }
.rv-lb-imgwrap.zoomed img { width: auto; max-width: none; cursor: zoom-out; }
.rvcard .rv-thumb img { object-position: center; }
.rv-body { padding: var(--sp-3) var(--sp-4) var(--sp-4); display: flex; flex-direction: column; gap: 8px; }
.rv-row { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; min-width: 0; }
.rv-lab {
  font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ink-3); flex: none; width: 42px;
}
.rv-plate {
  background: var(--panel-2); border: 1px solid var(--line-2);
  border-radius: var(--r-md); padding: 5px 9px; color: var(--ink);
  font-family: var(--mono); font-size: 15px; font-weight: 650;
  letter-spacing: .06em; text-transform: uppercase; width: 170px; min-width: 0;
}
.rv-plate:focus-visible { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-dim); outline: none; }
.rv-plate.rv-edited { border-color: color-mix(in srgb, var(--accent) 60%, transparent); }
.rv-fix select {
  background: var(--panel-2); border: 1px solid var(--line-2);
  border-radius: var(--r-md); padding: 5px 8px; color: var(--ink);
  font-size: 12.5px; min-width: 0; flex: 1 1 120px;
}
/* give the slack to the model, which carries "Model (body_type)" */
.rv-fix .rv-brand { flex: 1 1 104px; }
.rv-fix .rv-model { flex: 2 1 132px; text-overflow: ellipsis; }
.chip.rv-m-fuzzy { border-color: color-mix(in srgb, var(--d-blue) 45%, transparent);   color: var(--d-blue); }
.chip.rv-m-pop   { border-color: color-mix(in srgb, var(--s-warning) 45%, transparent); color: var(--warn-ink); }
.chip.rv-m-body  { border-color: color-mix(in srgb, var(--d-violet) 45%, transparent);  color: var(--d-violet); }
/* the resolver produced nothing, and the two gap chips that say what is
   missing — deliberately quieter than the accent so they read as state,
   not as an action */
.chip.rv-m-none  { border-color: var(--line-2); color: var(--ink-3); }
.chip.rv-g-brand,
.chip.rv-g-model { border-color: color-mix(in srgb, var(--accent) 40%, transparent);
                   color: var(--accent-text); }
/* the per-bucket backlog count inside each filter button */
.seg .n { font-variant-numeric: tabular-nums; opacity: .62; margin-left: 5px; }
.seg .n:empty { display: none; }
.rv-actions { display: flex; gap: var(--sp-2); margin-top: 2px; }
.rv-actions .rv-ok   { border-color: color-mix(in srgb, var(--s-good) 45%, transparent); color: var(--ok-ink); }
.rv-actions .rv-save { border-color: color-mix(in srgb, var(--accent) 50%, transparent); color: var(--accent-text); }
.rv-actions .rv-no   { border-color: color-mix(in srgb, var(--s-critical) 40%, transparent); color: var(--crit-ink); margin-left: auto; }
.rvcard.rv-busy { opacity: .45; pointer-events: none; }
.rvcard.rv-done { opacity: 0; transform: scale(.98); transition: opacity .2s ease, transform .2s ease; }

/* ---- Magic Bento border glow (reactbits.dev port, driven by magic-bento.js).
   The gradient is masked to the 2px border ring only: card interiors — and
   therefore chart plots — never receive the glow. --mb (0..1) is proximity. */
.card, .tile, .gcard { position: relative; }
.card::after, .tile::after, .gcard::after {
  content: ""; position: absolute; inset: -1px; border-radius: inherit;
  padding: 2px; pointer-events: none; z-index: 1;
  background: radial-gradient(300px circle at var(--mx, 50%) var(--my, 50%),
              color-mix(in srgb, var(--accent) 85%, transparent) 0%,
              color-mix(in srgb, var(--accent) 28%, transparent) 45%,
              transparent 72%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  opacity: calc(var(--mb, 0) * 0.9);
}
@media (prefers-reduced-motion: reduce) {
  .card::after, .tile::after, .gcard::after { display: none; }
}

/* ======================= pipeline band (Magic Transform) =================
   Ported from the React Bits Pro component to the job it actually describes:
   documents -> capture frames, transformation axis -> the recogniser, result
   chips -> the fields that land in the database. Beat is 4 s, matching the
   original's documentDuration. Pure DOM + CSS; particles are short-lived
   spans. Reduced motion gets a still composition instead of the loop.
   ====================================================================== */
.pipeband {
  border-top: 1px solid var(--line);
  padding: var(--sp-6) clamp(var(--sp-4), 5vw, 72px) var(--sp-5);
  background: linear-gradient(180deg,
              color-mix(in srgb, var(--accent) 4%, transparent), transparent 45%);
}
.pipe-head h2 { font-size: 17px; font-weight: 650; letter-spacing: -.01em; color: var(--ink); }
.pipe-head p  { font-size: 13px; color: var(--ink-3); margin-top: 4px; max-width: 62ch; }
.pipe-stage {
  position: relative; height: 172px; margin: var(--sp-4) 0 var(--sp-2);
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(170px, 250px); gap: var(--sp-4);
}
.pipe-lane { position: relative; overflow: hidden; border-radius: var(--r-lg); }

/* the recognition axis */
.pipe-axis { position: absolute; left: 62%; top: 0; bottom: 0; width: 0; }
.axis-line {
  position: absolute; left: 0; top: 8%; bottom: 8%; width: 2px;
  background: linear-gradient(180deg, transparent, var(--accent) 22%,
              var(--accent) 78%, transparent);
  box-shadow: 0 0 18px color-mix(in srgb, var(--accent) 55%, transparent);
}
.axis-lens {
  position: absolute; left: 50%; top: 50%; width: 46px; height: 46px;
  transform: translate(-50%, -50%);
  display: grid; place-items: center; color: var(--accent);
  background: var(--panel); border: 1px solid color-mix(in srgb, var(--accent) 50%, transparent);
  border-radius: 50%; box-shadow: 0 0 22px color-mix(in srgb, var(--accent) 30%, transparent);
}
.axis-lens svg { width: 24px; height: 24px; }

/* a capture crossing the lane */
.pcard {
  position: absolute; top: 50%; left: 2%; width: 108px;
  transform: translateY(-50%);
  background: var(--panel-2); border: 1px solid var(--line-2);
  border-radius: var(--r-md); padding: 7px 7px 6px; box-shadow: var(--card-shadow);
  animation: pipeRun 2s linear forwards;
}
.pcard svg { width: 100%; height: auto; display: block; opacity: .9; }
.pcard .pplate {
  display: block; margin-top: 5px; text-align: center;
  font-family: var(--mono); font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
  background: var(--accent-fill); color: #17130a; border-radius: 2px; padding: 1px 0;
}
@keyframes pipeRun {
  from { left: 2%;  opacity: 0; }
  12%  { opacity: 1; }
  to   { left: 56%; opacity: 1; }
}
.pcard.shred { animation: pipeShred .6s ease forwards; }
@keyframes pipeShred {
  to { opacity: 0; transform: translateY(-50%) scaleX(.06); filter: blur(3px); }
}

/* the burst at the axis */
.ppart {
  position: absolute; left: 62%; top: 50%; width: 5px; height: 5px; border-radius: 50%;
  background: var(--accent); pointer-events: none;
  animation: partFly .75s cubic-bezier(.2,.7,.3,1) forwards;
}
@keyframes partFly {
  from { transform: translate(0, 0) scale(1); opacity: .95; }
  to   { transform: translate(var(--dx), var(--dy)) scale(0); opacity: 0; }
}

/* what comes out the other side */
.pipe-out { display: flex; flex-direction: column; gap: 6px; justify-content: center; }
.pchip {
  display: flex; align-items: center; gap: 8px; white-space: nowrap;
  padding: 5px 10px; border-radius: 999px; font-size: 11.5px;
  background: var(--panel); border: 1px solid var(--line-2); color: var(--ink-2);
  animation: chipIn .45s cubic-bezier(.2,.9,.3,1.35) forwards;
}
.pchip b { color: var(--ink); font-weight: 600; }
.pchip .k { color: var(--ink-3); font-size: 10px; letter-spacing: .06em; text-transform: uppercase; }
.pchip.plate b { font-family: var(--mono); letter-spacing: .05em; }
.pchip.out { animation: chipOut .4s ease forwards; }
@keyframes chipIn  { from { opacity: 0; transform: translateX(-14px) scale(.92); } to { opacity: 1; } }
@keyframes chipOut { to { opacity: 0; transform: translateX(10px); } }
/* The legend names three things that live in the stage above it, so it
   mirrors the stage's own track structure rather than splitting itself into
   equal thirds — otherwise the labels drift off their subjects as the band
   widens. "recognition" is centred on the axis, which sits at 62% of the lane. */
.pipe-legend {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(170px, 250px);
  gap: var(--sp-4); max-width: none;
  font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase; color: var(--ink-4);
}
.pipe-legend span:nth-child(1) { grid-area: 1 / 1; justify-self: start; }
.pipe-legend span:nth-child(2) {
  grid-area: 1 / 1; justify-self: start;
  margin-left: 62%; transform: translateX(-50%);   /* % resolves on the lane track */
}
.pipe-legend span:nth-child(3) { grid-area: 1 / 2; justify-self: start; }

@media (max-width: 880px) { .pipeband { display: none; } }
@media (prefers-reduced-motion: reduce) {
  .pcard, .ppart, .pchip { animation: none !important; opacity: 1 !important; }
}

/* ---- the login page fits ONE viewport: the pipeline band is part of the
   composition, not something to scroll for. Only above the split
   breakpoint — stacked/phone layout keeps normal document flow. ---- */
@media (min-width: 881px) {
  body[data-page="login"] {
    height: 100dvh; overflow: hidden;
    display: grid; grid-template-rows: minmax(0, 1fr) auto;
    gap: var(--sp-3); padding: var(--sp-3);
  }
  .loginwrap { min-height: 0; height: 100%; padding: 0; gap: var(--sp-3); }
  /* the band is a panel too, so the ground shows in every gutter */
  .pipeband {
    margin: 0; border: 1px solid var(--line); border-radius: var(--r-lg);
    background:
      linear-gradient(180deg, color-mix(in srgb, var(--accent) 4%, transparent), transparent 45%),
      var(--panel);
    box-shadow: var(--card-shadow);
    padding: var(--sp-3) clamp(var(--sp-4), 5vw, 56px);
  }
  .login-hero { gap: var(--sp-3); padding-block: var(--sp-4); justify-content: center; }
  .hero-head { font-size: clamp(26px, 2.6vw, 36px); }
  .hero-sub { font-size: 13.5px; max-width: 46ch; }
  .stage { margin: 2px 0; }
  .stage-card { max-width: 452px; padding: var(--sp-3) var(--sp-3) var(--sp-2); }
  .hero-points { gap: 5px; margin-top: 0; font-size: 12px; }
  .readout { margin-top: var(--sp-2); padding-top: var(--sp-2); min-height: 26px; }
  .readout .read-plate { font-size: 15px; }

  .pipe-head { display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap; }
  .pipe-head h2 { font-size: 15px; }
  .pipe-head p { margin-top: 0; font-size: 12px; }
  .pipe-stage { height: 132px; margin: var(--sp-2) 0 4px; }
  .pcard { width: 88px; padding: 5px 5px 4px; }
  .pcard .pplate { font-size: 9.5px; margin-top: 4px; }
  .axis-lens { width: 40px; height: 40px; }
  .axis-lens svg { width: 21px; height: 21px; }
  .pchip { padding: 4px 9px; font-size: 11px; }
  .pipe-legend { font-size: 10px; }
}

/* Wide window: the hero stops being a tall single column and reads as a
   landing page — copy on the left, the capture stage large on the right.
   Placement is done on the existing children, so the markup is unchanged. */
@media (min-width: 1240px) {
  .login-hero {
    display: grid;
    grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr);
    column-gap: clamp(28px, 3.2vw, 64px);
    align-items: center;
    padding-inline: clamp(32px, 3.4vw, 64px);
  }
  .hero-copy { display: flex; flex-direction: column; gap: var(--sp-3); }
  .login-hero > .stage { margin: 0; }
  .stage-card { max-width: min(100%, 620px); }
  .hero-head { font-size: clamp(30px, 2.9vw, 42px); }
  .hero-sub { font-size: 14.5px; max-width: 48ch; }
  .hero-points { font-size: 12.5px; gap: 7px; }
}
@media (min-width: 1700px) {
  .stage-card { max-width: min(100%, 700px); }
}

/* A short window (a 768px-tall laptop) cannot hold the composition. Locking
   it to the viewport there would hide the bottom of the hero rather than
   shrink it, so give that case normal document flow and let it scroll. */
@media (min-width: 881px) and (max-height: 739px) {
  body[data-page="login"] { height: auto; overflow: visible; }
  .loginwrap { height: auto; }
}

/* the WebGL hero fills the stage card; aspect keeps it stable before first paint */
.hero3d { width: 100%; aspect-ratio: 16 / 9; border-radius: var(--r-md); overflow: hidden; }
.hero3d canvas { border-radius: var(--r-md); }
