/* ==========================================================================
   FuzzyReg — "Instrument Cluster"
   An alternative front end dressed as a night-lit binnacle: ice-blue backlight
   on anthracite, tell-tale lamps, a redline sweep on ignition and gauges in
   place of bar charts. Same API, same behaviour as the classic UI.
   ========================================================================== */

:root {
  /* Housing */
  --void:        #08090a;
  --binnacle:    #14181b;
  --binnacle-lo: #0d1013;
  --well:        #090b0d;
  --tile:        #191e21;
  --tile-lit:    #1f262a;
  --rim:         #2b3438;
  --rim-lit:     #3b474c;

  /*
   * Backlight.
   *
   * Ice-blue, and deliberately not amber. The panel was lit amber for its
   * first life, which put a saturated orange on near-black across every
   * surface of the page -- a pairing so strongly owned by one very large
   * adult site that readers arrived at a vehicle-records tool already
   * thinking of it. Hue is the whole of that association: the anthracite
   * housing, the layout and the instrument conceit carry no such baggage,
   * so only the light they are lit by changed. Cool white-blue is what a
   * modern cluster actually backlights with, so the concept reads truer
   * for it, not weaker.
   *
   * Tuned to hold the contrast the amber had rather than merely to look
   * different: 9.8:1 on the binnacle against amber's 9.45, with the soft
   * tone -- which is what links wear -- at 6.33 against 6.72. Both clear
   * AA at body size with room to spare.
   *
   * --backlight-rgb carries the same colour as bare channels, because the
   * glows and washes across this file are the backlight at low alpha and
   * rgba() cannot take a hex. Keep the two in step.
   */
  --backlight:       #4fd1e0;
  --backlight-rgb:   79, 209, 224;
  --backlight-soft:  #3fa7ba;
  --backlight-dim:   #1e5a64;
  --ink:         #e7ecea;
  --ink-dim:     #93a2a5;
  --ink-faint:   #5d6b6f;

  /* Lamps.
   *
   * Amber survives here and only here. On a dash it is the caution colour,
   * sat between the green tell-tale and the red one, and nothing else says
   * "we do not know" as immediately. It lights two small things -- an
   * unknown MOT chip and a notice rule -- rather than the page. */
  --redline:     #ff3b23;
  --lamp-green:  #3ad489;
  --lamp-amber:  #ffb43c;

  /* The one thing that stays daylight-coloured */
  --plate:       #f2c200;
  --plate-band:  #11338a;

  --disp: "Michroma", "Saira", Impact, sans-serif;
  --ui:   "Saira", "Barlow Semi Condensed", Helvetica, sans-serif;
  --data: "Martian Mono", ui-monospace, "Courier New", monospace;
}

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

/*
 * The toolbar is toggled with the `hidden` attribute, but an author `display:`
 * rule beats the UA stylesheet's `[hidden] { display: none }` -- without this
 * the export button sits on screen before the first search.
 */
[hidden] {
  display: none !important;
}

/*
 * ── No sideways drift ────────────────────────────────────────────────────
 *
 * On a phone this page is tens of thousands of pixels tall, and any horizontal
 * play in it turns a long scroll into a drift: the content slides out from
 * under the thumb and the reader ends up looking at the margin. Two separate
 * things can open that play, and both are shut here.
 *
 * The first is a box wider than the viewport. Nothing on the page is -- every
 * state was measured at 320, 360 and 390 CSS px with the real webfonts, fully
 * populated, and the widest was exactly the viewport -- so `clip` costs
 * nothing today and is a guarantee tomorrow. `clip` rather than `hidden`
 * deliberately: `hidden` on one axis forces the other to `auto`, which turns
 * the root into a scroll container and breaks `position: sticky` and
 * scroll-anchoring with it. `clip` was added for exactly this case and leaves
 * the vertical axis alone. Safari below 16 ignores it, which is harmless --
 * there is nothing to clip. It is a backstop, not the fix: overflow is still
 * asserted directly in the tests, where `clip` cannot hide it, because a box
 * silently cut off is a bug too.
 *
 * The second is the browser's own zoom, and that was the real one. iOS Safari
 * zooms the whole page in when a text field it focuses has a computed
 * font-size under 16px, and it does not reliably zoom back out. Every field on
 * this page was under that line -- the sentence box and the switch bank at
 * 15.68px, the ledger filter at 12.48px, the fuzzy select at 12.8px -- with
 * the plate the single exception at 23.2px, which is why the one control the
 * reader touches most was the one that never misbehaved. Once zoomed, the
 * visual viewport is narrower than the layout viewport and the page pans
 * sideways for the rest of the session. Raising the floor removes the trigger;
 * see the coarse-pointer block below.
 */
html {
  -webkit-text-size-adjust: 100%;
  overflow-x: clip;
  /* Nothing here should chain a sideways gesture outward -- not the ledger's
   * own scroller reaching its end, and not the browser's back-swipe. */
  overscroll-behavior-x: none;
}

body {
  margin: 0;
  color: var(--ink);
  font-family: var(--ui);
  font-variation-settings: "wdth" 100;
  font-size: 16px;
  line-height: 1.55;
  background-color: var(--void);
  min-height: 100vh;
}

/* Cabin light: a pool of instrument backlight behind a fine grille mesh. */
.ambient {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    repeating-linear-gradient(60deg, rgba(var(--backlight-rgb), 0.035) 0 1px, transparent 1px 11px),
    repeating-linear-gradient(-60deg, rgba(var(--backlight-rgb), 0.035) 0 1px, transparent 1px 11px),
    radial-gradient(90% 55% at 50% -8%, rgba(var(--backlight-rgb), 0.17) 0%, rgba(var(--backlight-rgb), 0) 62%),
    /*
     * The far corner of the cabin. This was a red wash, which worked as a
     * second warm light under an amber panel and reads as a stain under a
     * cool one -- and warm-on-black is the thing this page is getting away
     * from. Deep instrument blue instead: same job of stopping the corner
     * going flat, no heat, and it leaves red meaning only "fault".
     */
    radial-gradient(70% 50% at 88% 108%, rgba(46, 96, 150, 0.14) 0%, rgba(46, 96, 150, 0) 70%),
    radial-gradient(120% 90% at 50% 40%, #0f1416 0%, var(--void) 78%);
}

::selection {
  background: var(--backlight);
  color: var(--void);
}

/* ── Housing ─────────────────────────────────────────────────────────── */

.binnacle {
  position: relative;
  z-index: 1;
  max-width: 64rem;
  margin: clamp(0.75rem, 3vw, 2.5rem) auto;
  padding: clamp(1.25rem, 3.5vw, 2.5rem) clamp(1rem, 3.5vw, 2.5rem) 2.5rem;
  border: 1px solid var(--rim);
  border-radius: 20px;
  background:
    linear-gradient(180deg, rgba(var(--backlight-rgb), 0.05), rgba(var(--backlight-rgb), 0) 22%),
    linear-gradient(178deg, var(--binnacle), var(--binnacle-lo));
  box-shadow:
    /* The lip catching the backlight, so it takes the backlight's hue. */
    inset 0 1px 0 rgba(198, 236, 245, 0.09),
    inset 0 0 60px rgba(0, 0, 0, 0.65),
    0 40px 80px -40px #000;
}

/* ── Cowl ────────────────────────────────────────────────────────────── */

.cowl-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.badge {
  margin: 0;
  font-family: var(--disp);
  font-size: clamp(1.35rem, 4.2vw, 2.1rem);
  letter-spacing: 0.06em;
  color: var(--ink);
  text-shadow: 0 0 22px rgba(var(--backlight-rgb), 0.25);
}

.badge-sub {
  color: var(--backlight);
  text-shadow: 0 0 18px rgba(var(--backlight-rgb), 0.55);
}

/*
 * The model designation under the marque. Set in the UI face rather than the
 * display one and at a fraction of the size, so it reads as a line stamped
 * under the badge and never competes with it -- the heading is still
 * "FuzzyReg", this just finishes the sentence.
 *
 * `text-shadow: none` because .badge lights itself, and a glow at 0.68rem is
 * a smear rather than a light.
 */
.badge-line {
  display: block;
  margin-top: 0.3rem;
  font-family: var(--ui);
  font-size: 0.68rem;
  font-weight: 400;
  letter-spacing: 0.19em;
  text-transform: uppercase;
  /* --ink-dim, not --ink-faint: at 0.68rem the faint tone lands around 3:1 on
     the binnacle, and this is the line that says what the page is. */
  color: var(--ink-dim);
  text-shadow: none;
}

/* Tell-tales ---------------------------------------------------------- */

.tell-tales {
  display: flex;
  gap: 0.85rem;
  align-items: center;
}

.lamp {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-family: var(--disp);
  font-size: 0.56rem;
  letter-spacing: 0.1em;
  color: var(--ink-faint);
  transition: color 0.45s ease;
}

.lamp i {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: #202829;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.07);
  transition: background 0.45s ease, box-shadow 0.45s ease;
}

.lamp.is-on {
  color: var(--backlight);
}

.lamp.is-on i {
  background: var(--backlight);
  box-shadow: 0 0 9px rgba(var(--backlight-rgb), 0.9), inset 0 0 0 1px rgba(255, 255, 255, 0.3);
}

.lamp-warn.is-on {
  color: var(--redline);
}

.lamp-warn.is-on i {
  background: var(--redline);
  box-shadow: 0 0 9px rgba(255, 59, 35, 0.9), inset 0 0 0 1px rgba(255, 255, 255, 0.3);
}

/* The bulb check every car does at ignition: everything lights, then settles. */
.tell-tales.bulb-check .lamp {
  color: var(--backlight);
}

.tell-tales.bulb-check .lamp i {
  background: var(--backlight);
  box-shadow: 0 0 9px rgba(var(--backlight-rgb), 0.9), inset 0 0 0 1px rgba(255, 255, 255, 0.3);
}

.tell-tales.bulb-check .lamp-warn {
  color: var(--redline);
}

.tell-tales.bulb-check .lamp-warn i {
  background: var(--redline);
  box-shadow: 0 0 9px rgba(255, 59, 35, 0.9), inset 0 0 0 1px rgba(255, 255, 255, 0.3);
}

/* Rev strip ----------------------------------------------------------- */

.rev-strip {
  position: relative;
  height: 6px;
  margin: 1rem 0 1.15rem;
  border-radius: 3px;
  overflow: hidden;
  background:
    repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.09) 0 1px, transparent 1px 3.333%),
    var(--well);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05);
}

.rev-sweep {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0;
  background: linear-gradient(90deg, var(--backlight-soft), var(--backlight));
  box-shadow: 0 0 14px rgba(var(--backlight-rgb), 0.6);
  animation: ignition-sweep 2.1s cubic-bezier(0.3, 0.05, 0.2, 1) 0.15s both;
}

.rev-redline {
  position: absolute;
  inset: 0 0 0 88%;
  background: repeating-linear-gradient(
    90deg,
    rgba(255, 59, 35, 0.55) 0 1px,
    rgba(255, 59, 35, 0.14) 1px 4px
  );
}

@keyframes ignition-sweep {
  0%   { width: 0; }
  46%  { width: 100%; }
  62%  { width: 100%; }
  100% { width: 21%; }
}

/* 62ch rather than 46: the lead paragraph now has to say what the tool finds
   and what you need to bring to it, and at 46ch that ran to five short lines
   which read as a stack rather than a sentence. */
.tagline {
  margin: 0;
  max-width: 62ch;
  color: var(--ink-dim);
  font-weight: 300;
}

/* The two terms the paragraph exists to land. Weight, not colour: the
   backlight here would put three highlights above a console that uses the
   backlight to mean "this filter is engaged". */
.tagline strong {
  font-weight: 600;
  color: var(--ink);
}

code {
  font-family: var(--data);
  font-size: 0.82em;
  color: var(--backlight);
  background: rgba(var(--backlight-rgb), 0.09);
  border: 1px solid rgba(var(--backlight-rgb), 0.22);
  border-radius: 3px;
  padding: 0.02em 0.3em;
}

/* ── Console ─────────────────────────────────────────────────────────── */

.console {
  margin: 1.75rem 0 1.5rem;
}

.bay-label,
.switch-label {
  display: block;
  font-family: var(--disp);
  font-size: 0.58rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 0.5rem;
}

/*
 * The wildcard syntax, beside the field it applies to rather than in the
 * header. It was in the tagline, above a box that does not take wildcards at
 * all — so the one line explaining the plate grammar sat furthest from the
 * plate. Set in the body font, since it is a sentence rather than a label.
 */
.bay-note {
  font-family: var(--ui);
  font-size: 0.72rem;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-faint);
  margin-left: 0.7rem;
}

.bay-note code {
  font-family: var(--data);
  font-size: 0.68rem;
  color: var(--backlight-soft);
}

/* Under the label on a narrow screen rather than pushed off the end of it. */
@media (max-width: 34rem) {
  .bay-note {
    display: block;
    margin: 0.3rem 0 0;
  }
}

/*
 * The plate and the fuzzy toggle in one column, Search and Reset in the other.
 * The second track sizes to whichever button is wider and both stretch to fill
 * it, so they match by construction rather than by a width someone has to keep
 * in step with the labels — or with a display font that may load late, or not
 * at all.
 */
.plate-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: 0.85rem;
  row-gap: 9px;
  align-items: stretch;
}

/* The one daylight object on the whole page, lit by its own lamp. */
.plate-slab {
  position: relative;
  /* Sized by .plate-grid's first track now, not by a flex basis. */
  min-width: 0;
  display: flex;
  align-items: stretch;
  background: var(--plate);
  border: 3px solid #07080a;
  border-radius: 8px;
  box-shadow:
    0 0 0 1px rgba(var(--backlight-rgb), 0.18),
    0 0 34px rgba(242, 194, 0, 0.16),
    0 14px 30px -18px #000;
  transition: box-shadow 0.25s ease, transform 0.2s ease;
}

.plate-slab:focus-within {
  transform: translateY(-2px);
  box-shadow:
    0 0 0 1px rgba(var(--backlight-rgb), 0.5),
    0 0 52px rgba(242, 194, 0, 0.34),
    0 18px 34px -18px #000;
}

.plate-band {
  flex: 0 0 2.4rem;
  position: relative;
  display: grid;
  place-items: end center;
  padding-bottom: 0.4rem;
  background: linear-gradient(180deg, #1a49ad, var(--plate-band));
  color: #fff;
  font-family: var(--ui);
  font-variation-settings: "wdth" 82;
  font-weight: 700;
  font-size: 0.78rem;
  letter-spacing: 0.05em;
}

.plate-band::before {
  content: "";
  position: absolute;
  top: 0.5rem;
  left: 50%;
  transform: translateX(-50%);
  width: 1.05rem;
  height: 1.05rem;
  border-radius: 50%;
  border: 2px dotted var(--plate);
}

.plate-input {
  flex: 1;
  min-width: 0;
  font-family: "Charles Wright", "UKNumberPlate", var(--ui);
  font-variation-settings: "wdth" 84;
  font-weight: 800;
  font-size: clamp(1.45rem, 5.2vw, 2.05rem);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: #07080a;
  background: transparent;
  border: 0;
  padding: 0.5rem 0.9rem;
}

.plate-input::placeholder {
  color: rgba(7, 8, 10, 0.3);
}

.plate-input:focus {
  outline: none;
}

/* The number-plate lamp: a hard sheen across the top of the pressing. */
.plate-lamp {
  position: absolute;
  inset: 0 0 auto 0;
  height: 55%;
  pointer-events: none;
  border-radius: 5px 5px 0 0;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.42), rgba(255, 255, 255, 0));
}

.is-searching .plate-lamp {
  animation: lamp-flicker 1.05s ease-in-out infinite;
}

@keyframes lamp-flicker {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}

/* Ignition ------------------------------------------------------------ */

/*
 * Green, not red. Red is this palette's danger colour — the redline zone on
 * the rev strip, the SCAN warn lamp, the expired-MOT tell-tale — so the one
 * button that makes the page go was wearing the one colour that everywhere
 * else means stop. Green is already in the system as --lamp-green, and "go"
 * needs no explaining.
 */
.ignition {
  font-family: var(--disp);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #04150c;
  background: linear-gradient(180deg, #3ad489, #1d9159);
  border: 1px solid #6ff0af;
  border-radius: 8px;
  padding: 0.7rem 1.6rem;
  cursor: pointer;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.35),
    0 0 24px rgba(58, 212, 137, 0.28),
    0 6px 0 #0d4a2c;
  transition: transform 0.1s ease, box-shadow 0.1s ease, filter 0.2s ease;
}

.ignition:hover {
  filter: brightness(1.12);
}

.ignition:active {
  transform: translateY(5px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.35),
    0 0 34px rgba(58, 212, 137, 0.45),
    0 1px 0 #0d4a2c;
}

/* Ask ----------------------------------------------------------------- */

/*
 * The lead control, so it is the lit one: a backlit ring on a bay above the
 * console, in the palette's accent rather than the plate's daylight yellow.
 * Those two must not compete — the plate slab is the one daylight object on
 * the page and stays that way, so this reads as primary by being *lit*, not by
 * being brighter.
 */
.ask {
  margin-top: 1.75rem;
  padding: 0.95rem 1rem 0.85rem;
  border: 1px solid var(--rim);
  border-top: 2px solid var(--backlight-dim);
  border-radius: 10px;
  background: linear-gradient(180deg, rgba(var(--backlight-rgb), 0.05), rgba(var(--backlight-rgb), 0));
  box-shadow: 0 0 26px -14px rgba(var(--backlight-rgb), 0.5);
}

/* Same two-track shape as the plate grid, so the button lines up with Search
 * below it and neither has a width to keep in step with the other. */
.ask-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: 0.85rem;
  align-items: stretch;
}

/* Holds the input and the clear button that sits inside its right edge. */
.ask-field {
  position: relative;
  display: flex;
  min-width: 0;
}

.ask-input {
  width: 100%;
  min-width: 0;
  font-family: var(--ui);
  font-size: clamp(0.98rem, 2.6vw, 1.12rem);
  color: var(--ink);
  background: var(--well);
  border: 1px solid var(--rim);
  border-bottom: 2px solid var(--backlight-dim);
  border-radius: 6px;
  /* Right padding keeps the sentence clear of the button, and matches it when
   * the button is hidden so the text does not shift as it appears. */
  padding: 0.72rem 2.6rem 0.72rem 0.85rem;
  box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.6);
  transition: border-color 0.18s ease, box-shadow 0.18s ease;
}

/*
 * The clear button: a circled glyph, the same idiom as the conflict and needs
 * tags and the chips' remove control.
 *
 * Dim until wanted. It is a small mark inside a lit field, and a backlit one
 * would read as another indicator rather than a control.
 */
.ask-clear {
  position: absolute;
  right: 0.62rem;
  top: 50%;
  transform: translateY(-50%);
  width: 1.4rem;
  height: 1.4rem;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid var(--rim-lit);
  border-radius: 50%;
  background: var(--tile);
  color: var(--ink-dim);
  font-family: var(--ui);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}

/*
 * The reason this control exists is that clearing a sentence on a phone is
 * awkward, so the tap target is the full height of the field and about 44px
 * wide -- well beyond the ring you can see. Drawn as a pseudo-element so the
 * visible mark stays small.
 */
.ask-clear::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 2.75rem;
  height: 2.75rem;
  transform: translate(-50%, -50%);
}

.ask-clear:hover {
  background: var(--redline);
  border-color: var(--redline);
  color: var(--void);
}

.ask-clear:focus-visible {
  outline: none;
  border-color: var(--backlight);
  color: var(--ink);
  box-shadow: 0 0 0 2px rgba(var(--backlight-rgb), 0.35);
}

.ask-input::placeholder {
  color: #444f53;
  font-weight: 300;
  font-style: italic;
}

.ask-input:focus {
  outline: none;
  border-bottom-color: var(--backlight);
  box-shadow:
    inset 0 2px 8px rgba(0, 0, 0, 0.6),
    0 0 0 1px rgba(var(--backlight-rgb), 0.35),
    0 0 18px rgba(var(--backlight-rgb), 0.16);
}

/* Reading is the one thing this button does; while it is doing it, it says so
 * rather than looking pressable. */
.ask-go[aria-busy='true'] {
  filter: saturate(0.5) brightness(0.85);
  cursor: progress;
}

/*
 * The line-height here is a tap target, not typography, and it only works
 * together with the line-height pinned on .example -- see the note there.
 *
 * Three of the things in this paragraph are buttons. On a phone it wraps to
 * three or four lines with a chip on nearly every one, and each chip's bottom
 * border sat 0.2px off the top border of the chip below: two separate tap
 * targets, touching, with nothing between them to aim at. A mis-tap runs a
 * search the reader did not choose.
 *
 * Raising this number alone does nothing, which is the trap. An example is
 * `font: inherit`, so it inherits this line-height as a *number* and scales
 * its own box by exactly the same factor -- the line box and the thing inside
 * it grew in lockstep and the 0.2px never budged. It looked like it was
 * working, because the chips themselves got taller. Pinning .example's line
 * height is what lets this one open a gap.
 *
 * 2.2 puts 6.8px of daylight around a 21.4px chip. Loose for prose, and that
 * is the trade: this paragraph is a row of buttons wearing a sentence, and the
 * buttons win. Both numbers are one measurement -- change either and re-run
 * the guard in frontEndWiring.test.js.
 */
.ask-help {
  margin: 0.7rem 0 0;
  font-size: 0.8rem;
  line-height: 2.2;
  color: var(--ink-faint);
  max-width: 74ch;
}

/* What the sentence was taken to mean. Sits between the box and the help text
 * so the reading is the first thing under what was typed. */
.ask-readout {
  margin-top: 0.7rem;
}

.ask-chips .chip {
  /* Understood, not chosen: these describe a reading rather than holding a
   * value, so they sit a shade quieter than the country chips they borrow. */
  background: rgba(var(--backlight-rgb), 0.06);
  font-size: 0.78rem;
}

/*
 * What it could not read, and what it declined. Backlight rather than red: neither
 * is an error — the parse worked and said so — and dressing "I don't know the
 * word Narnia" as a failure would teach people to distrust a box that is being
 * honest with them.
 */
.ask-note {
  margin: 8px 0 0;
  padding: 7px 10px;
  border-left: 2px solid var(--backlight-dim);
  border-radius: 0 4px 4px 0;
  background: rgba(var(--backlight-rgb), 0.05);
  color: var(--ink-dim);
  font-size: 0.8rem;
  line-height: 1.55;
  max-width: 74ch;
}

.ask-note b {
  color: var(--ink);
  font-weight: 600;
}

.ask-note.is-error {
  border-left-color: var(--redline);
  background: rgba(255, 59, 35, 0.06);
}

/* A rule with the relationship written into it: the console is the same search
 * by another route, not a second one. */
.ask-or {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  margin: 1.15rem 0 0;
  font-family: var(--disp);
  font-size: 0.55rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.ask-or::before,
.ask-or::after {
  content: '';
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--rim), transparent);
}

/* The console follows the divider, so it no longer needs its own top margin. */
.ask-or + .console {
  margin-top: 1.1rem;
}

@media (max-width: 30rem) {
  /* Below this the button under the box beats a cramped two-track row. */
  .ask-grid {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 9px;
  }
}

/* Switch bank --------------------------------------------------------- */

.switch-bank {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));
  gap: 0.9rem;
  margin-top: 1.5rem;
}

.switch-input {
  width: 100%;
  font-family: var(--ui);
  font-size: 0.98rem;
  color: var(--ink);
  background: var(--well);
  border: 1px solid var(--rim);
  border-bottom: 2px solid var(--backlight-dim);
  border-radius: 6px;
  padding: 0.5rem 0.65rem;
  box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.6);
  transition: border-color 0.18s ease, box-shadow 0.18s ease;
}

/* Dim enough that a hint never reads as an entered value on a lit well. */
.switch-input::placeholder {
  color: #444f53;
  font-weight: 300;
  font-style: italic;
}

.switch-input:focus {
  outline: none;
  border-bottom-color: var(--backlight);
  box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(var(--backlight-rgb), 0.35),
    0 0 18px rgba(var(--backlight-rgb), 0.16);
}

/* A filter that needs two columns' worth of room — the country chips. Applied
 * only once the grid is wide enough to have two columns to give, since a span
 * wider than the grid would force an implicit column and break the bank. */
@media (min-width: 46rem) {
  .switch-wide {
    grid-column: span 2;
  }
}

/* Chosen countries. The chips are the filter; the select above them is only the
 * way in, which is what keeps a list legible in a bank of single-value
 * controls — every choice stays visible and individually removable. */
.chips {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 8px 0 0;
  padding: 0;
}

.chips:empty {
  display: none;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  padding: 3px 4px 3px 9px;
  border: 1px solid var(--rim-lit);
  border-radius: 999px;
  background: rgba(var(--backlight-rgb), 0.08);
  color: var(--ink);
  font-size: 0.82rem;
  line-height: 1.4;
}

.chip-flag {
  flex: none;
  font-size: 0.95em;
}

/* The longest mission name runs to 61 characters, so it clips here and lives in
 * full in the title attribute. */
.chip-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chip-remove {
  flex: none;
  width: 1.15rem;
  height: 1.15rem;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-faint);
  font-family: var(--ui);
  font-size: 0.95rem;
  line-height: 1;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}

.chip-remove:hover {
  background: var(--redline);
  color: var(--void);
}

.chip-remove:focus-visible {
  outline: 2px solid var(--backlight);
  outline-offset: 1px;
}

.select-wrap {
  position: relative;
}

.select-wrap select {
  appearance: none;
  cursor: pointer;
  padding-right: 1.8rem;
}

.select-wrap::after {
  content: "▾";
  position: absolute;
  right: 0.6rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--backlight);
  pointer-events: none;
}

.console-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 1.4rem;
}

.hint {
  margin: 0;
  max-width: 54ch;
  font-size: 0.88rem;
  font-weight: 300;
  color: var(--ink-dim);
}

/*
 * `font: inherit` brings the line-height with it, and for a button sitting in
 * running text that is the wrong half of the shorthand to accept: inherited as
 * a unitless number, it makes the chip's own box scale with whatever leading
 * its paragraph is set to. The chip and the line box then grow together and no
 * amount of line-height on the parent can put space between two chips on
 * consecutive lines -- which is exactly how they ended up 0.2px apart.
 *
 * So the line height is pinned to the chip's own text and nothing else. What
 * makes the box bigger is padding, which is the part a thumb can actually use.
 */
.example {
  font: inherit;
  font-family: var(--data);
  font-size: 0.78rem;
  line-height: 1.25;
  color: var(--backlight);
  background: rgba(var(--backlight-rgb), 0.1);
  border: 1px solid rgba(var(--backlight-rgb), 0.3);
  border-radius: 4px;
  padding: 0.12rem 0.4rem;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

.example:hover {
  background: var(--backlight);
  color: var(--void);
}

/* Buttons ------------------------------------------------------------- */

.btn-ghost,
.btn-more,
.btn-reset {
  font-family: var(--disp);
  font-size: 0.6rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-dim);
  background: linear-gradient(180deg, var(--tile-lit), var(--tile));
  border: 1px solid var(--rim);
  border-radius: 6px;
  padding: 0.6rem 1.1rem;
  cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
  transition: color 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease;
}

/* :hover still matches a disabled button, so every hover state here is guarded
   rather than relying on the pointer never arriving. */
.btn-ghost:not(:disabled):hover,
.btn-more:not(:disabled):hover,
.btn-reset:not(:disabled):hover {
  color: var(--backlight);
  border-color: var(--backlight-dim);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 0 18px rgba(var(--backlight-rgb), 0.15);
}

.btn-ghost:disabled,
.btn-more:disabled,
.btn-reset:disabled {
  opacity: 0.45;
  cursor: default;
}

/*
 * Reset wears a banked-down version of --redline: enough of it to read as the
 * undo next to a green go, not enough to compete with the redline zone or the
 * warn lamps, which are the loud reds. Scoped to .btn-reset rather than folded
 * into the shared block above — Export CSV and Next page are neutral controls
 * and stay that way.
 */
.btn-reset {
  color: #cf9086;
  background: linear-gradient(180deg, #2a1614, #1d0f0d);
  border-color: #4a2620;
}

.btn-reset:not(:disabled):hover {
  color: #ff9384;
  border-color: #7a3a30;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 0 18px rgba(255, 59, 35, 0.16);
}

.btn-more {
  padding: 0.75rem 2rem;
  font-size: 0.65rem;
}

/* ── Trip computer ───────────────────────────────────────────────────── */

/* A little air above it when app.js scrolls it to the top, so it does not sit
 * flush against the edge of the screen. */
.trip {
  scroll-margin-top: 10px;

  position: relative;
  display: flex;
  align-items: center;
  gap: 0.8rem;
  min-height: 3rem;
  padding: 0.6rem 0.9rem;
  border: 1px solid var(--rim);
  border-radius: 8px;
  background: var(--well);
  box-shadow: inset 0 3px 14px rgba(0, 0, 0, 0.8);
  overflow: hidden;
}

/* Dot-matrix grid laid over the readout. */
.trip::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    repeating-linear-gradient(90deg, rgba(0, 0, 0, 0.5) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.5) 0 1px, transparent 1px 3px);
  opacity: 0.45;
}

.trip-tag {
  flex: 0 0 auto;
  font-family: var(--disp);
  font-size: 0.52rem;
  letter-spacing: 0.12em;
  color: var(--void);
  background: var(--backlight-soft);
  border-radius: 3px;
  padding: 0.18rem 0.35rem;
}

.status {
  font-family: var(--data);
  font-size: 0.78rem;
  font-weight: 300;
  color: var(--backlight);
  text-shadow: 0 0 12px rgba(var(--backlight-rgb), 0.45);
}

.status::after {
  content: "_";
  margin-left: 0.1rem;
  animation: blink 1.05s steps(1) infinite;
}

.status:empty::after {
  content: "standby_";
  color: var(--backlight-dim);
}

.status.error {
  color: var(--redline);
  text-shadow: 0 0 12px rgba(255, 59, 35, 0.5);
}

@keyframes blink {
  0%, 49%   { opacity: 1; }
  50%, 100% { opacity: 0; }
}

.results-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin: 1.1rem 0 1.35rem;
}

/* Odometer ------------------------------------------------------------ */

.count {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
}

.odo {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  border-radius: 4px;
  background: #05070a;
  box-shadow: inset 0 0 0 1px var(--rim);
}

.odo-digit {
  font-family: var(--data);
  font-weight: 500;
  font-size: 0.86rem;
  line-height: 1;
  color: var(--ink);
  background: linear-gradient(180deg, #2c3438, #171c1f 52%, #23292d);
  padding: 0.3rem 0.28rem;
  border-radius: 2px;
  animation: roll-in 0.4s cubic-bezier(0.2, 0.9, 0.25, 1) both;
  animation-delay: calc(var(--i, 0) * 45ms);
}

.odo-digit:last-child {
  color: var(--backlight);
}

@keyframes roll-in {
  from { opacity: 0; transform: translateY(-60%); }
  to   { opacity: 1; transform: translateY(0); }
}

.odo-unit {
  font-family: var(--disp);
  font-size: 0.55rem;
  letter-spacing: 0.12em;
  color: var(--ink-faint);
}

/* ── Gauge bank (breakdown) ──────────────────────────────────────────── */

.gauges {
  border: 1px solid var(--rim);
  border-radius: 12px;
  background: linear-gradient(180deg, var(--tile), var(--binnacle-lo));
  padding: 1.1rem 1.25rem 1.3rem;
  margin-bottom: 1.6rem;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
}

.gauges-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding-bottom: 0.85rem;
  margin-bottom: 1rem;
  border-bottom: 1px solid var(--rim);
}

.gauges-title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
  font-variation-settings: "wdth" 88;
  letter-spacing: 0.01em;
  color: var(--ink);
}

.gauges-toggle {
  display: inline-flex;
  border: 1px solid var(--rim);
  border-radius: 6px;
  overflow: hidden;
}

.toggle {
  font-family: var(--disp);
  font-size: 0.58rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  background: var(--tile);
  border: 0;
  padding: 0.45rem 0.85rem;
  cursor: pointer;
  transition: color 0.16s ease, background 0.16s ease;
}

.toggle + .toggle {
  border-left: 1px solid var(--rim);
}

.toggle:hover {
  color: var(--ink-dim);
}

.toggle.active {
  background: var(--backlight);
  color: var(--void);
}

/*
 * One grid for the whole bank, with the rows as `display: contents`, so all
 * three columns are measured across every channel at once.
 *
 * Each .bar-row used to carry its own grid, which meant the `auto` count column
 * was sized per row: "536 (14.5%)" is wider than "203 (5.5%)", so those two
 * rows had tracks 7px apart and the same percentage drew a different length of
 * bar. Bars in one chart have to be measured against the same face to be worth
 * comparing -- and while a filter is being typed the counts change on every
 * keystroke, so every track was resizing under its own needle.
 */
.chart {
  display: grid;
  grid-template-columns: 8.5rem 1fr auto;
  align-items: center;
  column-gap: 0.75rem;
  row-gap: 0.45rem;
}

/* Only while withChartResize is animating between two content-derived heights,
   where the channels on the way out would otherwise spill past the panel. A
   permanent clip would crop the focus ring on the first and last channels. */
.chart.is-resizing {
  overflow: hidden;
}

/* Not a channel — it stands in place of the whole bank. */
.chart-note {
  grid-column: 1 / -1;
}

/*
 * Two pending states, both of which keep the panel in place: it sits above the
 * results, so taking it down and putting it back a full scan later moved the
 * whole grid.
 *
 * `is-pending` — a new reading is being measured. The title says so.
 * `is-stale`   — the *previous* reading is still on the faces. Dimmed hard, so
 *                a number that is about to change can't be read as current.
 */
.gauges.is-pending .gauges-title {
  color: var(--ink-dim);
  animation: measuring 1.5s ease-in-out infinite;
}

.gauges.is-stale .chart {
  opacity: 0.3;
}

@keyframes measuring {
  0%, 100% { opacity: 0.5; }
  50%      { opacity: 1; }
}

/*
 * Blank channels holding the space open: lit gauge faces with no needle on
 * them, sweeping the way a self-test does. Deliberately *not* dimmed — there is
 * no stale number here to distrust, and a panel faded to nothing reads as a
 * failed render rather than a pending one.
 */
.bar-row-idle .bar-track {
  background:
    linear-gradient(90deg, transparent 0 88%, rgba(255, 59, 35, 0.1) 88% 100%),
    repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.05) 0 1px, transparent 1px 5%),
    var(--well);
}

.bar-row-idle .bar-track::after {
  content: "";
  display: block;
  height: 100%;
  width: 34%;
  background: linear-gradient(90deg, transparent, rgba(var(--backlight-rgb), 0.16), transparent);
  animation: self-test 1.6s ease-in-out infinite;
  animation-delay: calc(var(--i, 0) * 70ms);
}

@keyframes self-test {
  0%   { transform: translateX(-120%); }
  100% { transform: translateX(400%); }
}

/*
 * Every channel is a button — clicking one reads its value back into the
 * search. `subgrid` is what lets the row be a real box and still be measured
 * against the bank: it spans all three columns and borrows .chart's tracks
 * rather than defining its own, so every needle stays on one face. (This was
 * `display: contents`, which shares the tracks but leaves no row box to click.)
 */
.bar-row {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  align-items: center;
  font: inherit;
  font-size: 0.82rem;
  color: inherit;
  text-align: left;
  background: none;
  border: 0;
  padding: 0;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.14s ease;
}

/* A channel with nothing to act on — a blank, or an "Unknown" bucket that is
   the absence of a value rather than one you can search for. */
.bar-row:disabled {
  cursor: default;
}

.bar-row:not(:disabled):hover {
  background: rgba(var(--backlight-rgb), 0.07);
}

.bar-row:not(:disabled):hover .bar-label {
  color: var(--ink);
}

/* The value this channel is already filtered to: clicking it clears it. */
.bar-row[aria-pressed="true"] .bar-label {
  color: var(--backlight);
  font-weight: 700;
}

.bar-row[aria-pressed="true"] .bar-track {
  box-shadow:
    inset 0 0 0 1px var(--backlight-dim),
    inset 0 2px 6px rgba(0, 0, 0, 0.7);
}

.bar-row:focus-visible {
  outline: 2px solid var(--backlight);
  outline-offset: 2px;
}

.bar-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-variation-settings: "wdth" 88;
  font-weight: 500;
  color: var(--ink-dim);
}

/* A gauge face: tick marks the whole way, a redline zone at the top end. */
.bar-track {
  position: relative;
  height: 1.05rem;
  border-radius: 3px;
  background:
    linear-gradient(90deg, transparent 0 88%, rgba(255, 59, 35, 0.16) 88% 100%),
    repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.08) 0 1px, transparent 1px 5%),
    var(--well);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05), inset 0 2px 6px rgba(0, 0, 0, 0.7);
  overflow: hidden;
}

/*
 * A needle sweeps up from zero when its channel first appears, and travels
 * between readings after that.
 *
 * The fill mode is `backwards`, not `both`, and that is the whole trick: it
 * holds the start frame through the delay, then hands `width` back to the rule
 * below once the sweep is done, where the transition can pick it up. With
 * `both` the finished animation kept ownership of `width` for the life of the
 * element, so the only way to show a new reading was to build a new row -- and
 * then every needle in the bank dropped to zero and swept again.
 */
.bar-fill {
  display: block;
  height: 100%;
  min-width: 3px;
  width: var(--w, 0);
  background: var(--backlight);
  box-shadow: 0 0 14px rgba(var(--backlight-rgb), 0.45);
  animation: needle 0.7s cubic-bezier(0.16, 0.9, 0.25, 1) backwards;
  animation-delay: calc(var(--i, 0) * 45ms);
  transition: width 0.4s cubic-bezier(0.2, 0.8, 0.3, 1), background-color 0.3s ease;
}

@keyframes needle {
  from { width: 0; }
  to   { width: var(--w); }
}

/* A blank channel has no reading, so it carries no needle — just the face and
   its self-test sweep. */
.bar-row-idle .bar-fill {
  display: none;
}

/*
 * --ink-dim, not --ink-faint. The faint tone lands at 3.04:1 on this panel,
 * and at 0.72rem that is under the 4.5:1 floor -- on the one column in the
 * bank that is the actual reading. Being quiet is right for a label the eye
 * skips; this is the number the reader came for, and it was quieter than the
 * marque beside it.
 *
 * The hierarchy the faint tone was buying is still there without it: this is
 * a smaller, lighter, monospaced figure against a wider 500-weight label, so
 * the label still leads the row. Colour was doing that job twice.
 */
.bar-count {
  font-family: var(--data);
  font-size: 0.72rem;
  font-weight: 300;
  color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
}

/* ── The Other bar, and the full ledger it opens ─────────────────────── */

/* The Other channel opens the ledger rather than filtering, so it is marked
   as the one control in the bank that goes somewhere. */
.bar-row-other .bar-label {
  color: var(--backlight);
}

.bar-row-other .bar-label::after {
  content: " ▸";
  display: inline-block;
  transition: transform 0.16s ease;
}

.bar-row-other[aria-expanded="true"] .bar-label::after {
  transform: rotate(90deg) translateX(0.08em);
}

/* The aggregate is not a marque; drawn hollow so it doesn't read as one.
   renderChart leaves this row's inline background unset so this can apply. */
.bar-row-other .bar-fill {
  background: repeating-linear-gradient(
    -45deg,
    var(--backlight-dim) 0 4px,
    transparent 4px 8px
  );
  box-shadow: none;
}

.chart-full {
  margin-top: 1.1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--rim);
  overflow: hidden;
}

.chart-full-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.75rem;
  margin-bottom: 0.7rem;
}

.chart-full-filter {
  flex: 0 1 12rem;
}

.chart-full-filter .switch-input {
  padding: 0.35rem 0.6rem;
  font-size: 0.78rem;
}

.chart-full-note {
  flex: 1 1 16rem;
  margin: 0;
  font-size: 0.74rem;
  color: var(--ink-faint);
}

/* Bounded, or a grouping with a few thousand marques in it becomes the page. */
.ledger-scroll {
  max-height: 22rem;
  overflow-y: auto;
  border: 1px solid var(--rim);
  border-radius: 8px;
  background: var(--well);
}

/*
 * Fixed layout, so the browser sizes the columns from the header alone instead
 * of measuring every cell in a list that can run to thousands of rows — and so
 * the columns hold still as the list is filtered.
 */
.ledger {
  width: 100%;
  table-layout: fixed;
  border-collapse: collapse;
  font-size: 0.78rem;
}

.ledger-key {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Fills its cell, so the target is the column rather than the words in it. */
.ledger-pick {
  display: block;
  width: 100%;
  font: inherit;
  color: inherit;
  text-align: left;
  background: none;
  border: 0;
  padding: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
}

.ledger-pick:hover {
  color: var(--backlight);
}

.ledger-pick[aria-pressed="true"] {
  color: var(--backlight);
  font-weight: 700;
}

.ledger-pick:focus-visible {
  outline: 2px solid var(--backlight);
  outline-offset: 1px;
}

.ledger th {
  position: sticky;
  top: 0;
  z-index: 1;
  text-align: left;
  padding: 0.5rem 0.75rem;
  font-family: var(--disp);
  font-size: 0.55rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  background: var(--tile);
  border-bottom: 1px solid var(--rim);
}

.ledger td {
  padding: 0.34rem 0.75rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.04);
  color: var(--ink-dim);
}

.ledger tr:last-child td {
  border-bottom: 0;
}

.ledger tr:hover td {
  background: rgba(var(--backlight-rgb), 0.05);
  color: var(--ink);
}

.ledger-rank,
.ledger-num {
  font-family: var(--data);
  font-weight: 300;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.ledger-rank {
  width: 3.5rem;
  color: var(--ink-faint);
}

.ledger-num {
  text-align: right;
}

/*
 * The two numeric columns, measured rather than guessed. Both were 6.5rem,
 * which was simultaneously too narrow and too wide: at the ledger's own type a
 * fleet-sized "12,345,678" wants 88px of ink and had 80, while "100.0%" wants
 * 53 and also had 80. So the count clipped on every screen and the share sat on
 * 27px it never used -- width the value column needed and could not reach.
 */
.ledger-count {
  width: 7rem;
}

.ledger-share {
  width: 5rem;
}

/*
 * The ledger on a phone.
 *
 * Same fault the chart had, and the note on `.chart` below records it: the
 * value column is whatever the fixed columns leave, so when they do not shrink
 * there is nothing left. At 390px it left 48px of column and 24px of ink, so
 * every marque read "M…", "A…", "LA…"; at 320px it left *nothing* and the table
 * overflowed its own box by 22px, which made the ledger the one thing on the
 * page that still dragged sideways.
 *
 * Everything here buys ink for that column: the padding, the rank, both
 * numbers and the header type, each cut to what its own content measures at
 * this size rather than to what looked comfortable on a desktop.
 *
 * And the column wraps instead of ellipsising. A wrapped marque costs a line
 * of a box that already scrolls; a truncated one costs the answer.
 */
@media (max-width: 34rem) {
  .ledger th {
    padding: 0.5rem 0.45rem;
    font-size: 0.5rem;
    letter-spacing: 0.06em;
  }

  .ledger td {
    padding: 0.34rem 0.45rem;
  }

  .ledger-rank,
  .ledger-num {
    font-size: 0.68rem;
  }

  .ledger-rank {
    width: 2.4rem;
  }

  .ledger-count {
    width: 5.7rem;
  }

  .ledger-share {
    width: 3.8rem;
  }

  .ledger-key,
  .ledger-pick {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    /* Only where a word genuinely cannot fit the column -- "MERCEDES-BENZ"
       should break at its hyphen, not down the middle of "MERCEDES". */
    overflow-wrap: break-word;
  }
}

/*
 * Below this the sums stop working: four columns and a common marque do not
 * both fit, whatever the padding. Share is the one to give up -- it is the
 * chart directly above restated as a number, where the count is the thing the
 * ledger alone carries -- and handing its column over is the difference
 * between "MERCEDES-BENZ" on one line and on three.
 *
 * `display: none` takes the header with the cells, so the column leaves the
 * accessibility tree whole rather than as a row of unlabelled figures.
 */
@media (max-width: 24rem) {
  .ledger-share {
    display: none;
  }
}

/* Where the chart stopped: everything below this line was the Other bar. */
.ledger-folded td {
  color: var(--ink-faint);
}

/* :first-of-type would pick the first <tr>, not the first folded one — the
   line belongs on whichever folded row follows a charted one. */
tr:not(.ledger-folded) + .ledger-folded td {
  border-top: 1px solid var(--backlight-dim);
}

/* ── Result tiles ────────────────────────────────────────────────────── */

.results {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: 0.9rem;
}

.tile {
  position: relative;
  border: 1px solid var(--rim);
  border-radius: 10px;
  background: linear-gradient(180deg, var(--tile-lit), var(--tile) 45%, var(--binnacle-lo));
  padding: 1.1rem 1rem 1rem;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 16px 26px -22px #000;
  animation: power-on 0.5s cubic-bezier(0.2, 0.85, 0.25, 1) both;
  animation-delay: calc(var(--i, 0) * 34ms);
  transition: border-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
}

.tile:hover {
  transform: translateY(-3px);
  border-color: var(--rim-lit);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.07), 0 22px 34px -22px #000,
    0 0 26px rgba(var(--backlight-rgb), 0.08);
}

@keyframes power-on {
  from { opacity: 0; transform: translateY(14px) scale(0.985); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

/*
 * A break in the roll, every fifty vehicles. app.js emits these empty and
 * hidden, so until something fills one it is `display: none` and the grid does
 * not lay a track for it -- no gap, no reserved space, nothing to see.
 *
 * When one is filled it spans every column rather than sitting in one, so it
 * reads as an interruption in the roll instead of a card impersonating a
 * vehicle. `1 / -1` is the whole explicit grid whatever `auto-fill` resolved
 * to, which is the point: the column count here changes with the viewport.
 *
 * No border, background or padding of its own -- an empty frame is worse than
 * no frame, and whatever goes in brings its own.
 */
.roll-break {
  grid-column: 1 / -1;
  min-width: 0;
}

.tile-plate {
  display: inline-flex;
  align-items: center;
  font-family: "Charles Wright", "UKNumberPlate", var(--ui);
  font-variation-settings: "wdth" 84;
  font-weight: 800;
  font-size: 1.12rem;
  letter-spacing: 0.07em;
  color: #07080a;
  background: var(--plate);
  border: 2px solid #07080a;
  border-radius: 5px;
  padding: 0.14rem 0.5rem;
  margin-bottom: 0.75rem;
  text-decoration: none;
  box-shadow: 0 0 18px rgba(242, 194, 0, 0.2);
  transition: box-shadow 0.18s ease, transform 0.12s ease;
}

.tile-plate:hover {
  transform: translateY(-1px);
  box-shadow: 0 0 26px rgba(242, 194, 0, 0.5);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.tile-plate:active {
  transform: translateY(1px);
}

.tile-make {
  font-size: 1.05rem;
  font-weight: 700;
  font-variation-settings: "wdth" 88;
  letter-spacing: 0.005em;
  line-height: 1.2;
  color: var(--ink);
}

.tile-model {
  font-family: var(--data);
  font-size: 0.7rem;
  font-weight: 300;
  color: var(--ink-faint);
  margin-bottom: 0.8rem;
}

.tile-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 0.9rem;
  padding-top: 0.7rem;
  border-top: 1px solid var(--rim);
  font-size: 0.8rem;
  font-weight: 300;
  color: var(--ink-dim);
}

.swatch {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.swatch-dot {
  width: 0.8rem;
  height: 0.8rem;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.28);
  box-shadow: 0 0 8px rgba(255, 255, 255, 0.12), inset 0 -2px 3px rgba(0, 0, 0, 0.45);
}

/* MOT status as a dashboard tell-tale rather than a badge. */
.tile-mot {
  margin-top: 0.85rem;
}

.tell {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-family: var(--disp);
  font-size: 0.55rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  padding: 0.3rem 0.55rem 0.3rem 0.45rem;
  border-radius: 999px;
  border: 1px solid currentColor;
  animation: lamp-on 0.45s ease-out both;
  animation-delay: calc(var(--i, 0) * 34ms + 190ms);
}

.tell::before {
  content: "";
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 8px currentColor;
}

@keyframes lamp-on {
  0%   { opacity: 0; filter: brightness(3); }
  40%  { opacity: 1; filter: brightness(1.9); }
  100% { opacity: 1; filter: brightness(1); }
}

.tell-valid {
  color: var(--lamp-green);
  background: rgba(58, 212, 137, 0.09);
}

.tell-expired {
  color: var(--redline);
  background: rgba(255, 59, 35, 0.1);
}

.tell-unknown {
  color: var(--lamp-amber);
  background: rgba(255, 180, 60, 0.09);
}

.tile-mot-meta {
  margin-top: 0.5rem;
  font-family: var(--data);
  font-size: 0.66rem;
  font-weight: 300;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

/* Zero-height marker after the results roll; the observer watches it. */
.scroll-sentinel {
  height: 1px;
}

.load-more-wrap {
  text-align: center;
  margin-top: 1.6rem;
}

/*
 * The next page arriving: a lit channel sweeping the way the idle gauge faces
 * do, so a page landing mid-scroll reads as the instrument feeding rather than
 * as the list having stalled.
 */
.feeding {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.55rem;
}

.feeding-track {
  position: relative;
  display: block;
  width: min(220px, 60%);
  height: 3px;
  overflow: hidden;
  border-radius: 2px;
  background: var(--well);
}

.feeding-track::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 34%;
  background: linear-gradient(90deg, transparent, var(--backlight), transparent);
  opacity: 0.55;
  animation: self-test 1.2s ease-in-out infinite;
}

.feeding-label {
  font-family: var(--disp);
  font-size: 0.6rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

/*
 * The global reduced-motion rule freezes the sweep, which would leave a static
 * bar reading as a stalled progress meter. Hide the track and lean on the
 * ticking label, which reports elapsed time without moving anything.
 */
@media (prefers-reduced-motion: reduce) {
  .feeding-track {
    display: none;
  }
}

/* ── Handbook ────────────────────────────────────────────────────────────
 *
 * The prose, which is its own page at /handbook/ and used to be a thousand
 * words underneath the console. It was accurate, and it turned a tool into a
 * document with a search box at the top of it: nothing on the page said where
 * the app stopped and the reading started, and a reader who came to run a
 * search had to scroll past an essay to see they had already finished.
 *
 * So it moved. The console keeps one link, in the hint paragraph where
 * somebody stuck is already looking, and the footer keeps three. Everything
 * else is here.
 *
 * Same chrome as the app deliberately -- same binnacle, same cowl, same
 * stylesheet already in cache -- but set quieter: the UI face at a size below
 * the controls, on --ink-dim rather than --ink. It is the manual in the
 * glovebox, not a second dashboard.
 */

.handbook {
  margin-top: 2.25rem;
  color: var(--ink-dim);
  font-weight: 300;
}

/*
 * Narrower housing than the console's 64rem. The console earns that width --
 * it is a grid of switches and a results table. A column of prose does not:
 * at 64rem the measure ran to about 70 characters and left the right half of
 * the panel empty, which reads as a layout that lost something rather than as
 * a document.
 */
.page-handbook {
  max-width: 48rem;
}

.handbook-block + .handbook-block {
  margin-top: 2.5rem;
}

/* The section a link arrived at should not open flush against the top of the
   window. */
.handbook-block {
  scroll-margin-top: 1.25rem;
}

/*
 * Larger than .gauges-title, which the two shared when this was a block on the
 * app page. They are no longer siblings: that one names a widget inside a
 * result, this one opens a section of a document that is six sections long,
 * and a reader scrolling for one of them has to be able to find it at a
 * glance.
 */
.handbook h2 {
  margin: 0 0 0.7rem;
  font-size: 1.08rem;
  font-weight: 600;
  font-variation-settings: "wdth" 92;
  letter-spacing: 0.01em;
  color: var(--ink);
}

/* The display face, small and tracked out, so an h3 reads as a label on the
   paragraph under it rather than as a smaller h2. */
.handbook h3 {
  margin: 1.35rem 0 0.4rem;
  font-family: var(--disp);
  font-size: 0.62rem;
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--backlight-soft);
}

.handbook p {
  margin: 0.55rem 0 0;
  max-width: 72ch;
  font-size: 0.88rem;
  line-height: 1.62;
}

.handbook strong {
  font-weight: 600;
  color: var(--ink);
}

.handbook-links {
  margin: 0.75rem 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 0.4rem;
  font-size: 0.88rem;
}

/* A dash in the gutter rather than a bullet: the page has no other list
   markers and a disc would be the only round thing on it. */
.handbook-links li {
  padding-left: 1.1rem;
  position: relative;
}

.handbook-links li::before {
  content: "—";
  position: absolute;
  left: 0;
  color: var(--backlight-dim);
}

/* The handbook page's own furniture ------------------------------------- */

/*
 * The way back, above the title. A document reached from an app needs the
 * return trip somewhere predictable and unmissable, and the badge alone is not
 * it -- a wordmark is only obviously a home link to people who already know
 * the site.
 */
.crumb {
  display: inline-block;
  margin-bottom: 1rem;
  font-family: var(--disp);
  font-size: 0.6rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/*
 * The handbook's h1. Not .badge: on this page the heading is the subject, not
 * the marque -- the marque is the small link above it. Set in the display face
 * so the two pages are plainly the same site, at a size that reads as a title
 * rather than as branding.
 */
.doc-title {
  margin: 0;
  font-family: var(--disp);
  font-size: clamp(1.15rem, 3.2vw, 1.6rem);
  line-height: 1.32;
  letter-spacing: 0.02em;
  color: var(--ink);
  text-shadow: 0 0 22px rgba(var(--backlight-rgb), 0.18);
}

.doc-lead {
  margin: 1rem 0 0;
  max-width: 62ch;
  color: var(--ink-dim);
  font-weight: 300;
}

/*
 * Contents. Worth the room here and nowhere else: the page is six sections
 * long, and the one most readers came for -- whether this finds the owner --
 * is in the fifth.
 */
.toc {
  margin: 1.6rem 0 0;
  padding: 1rem 0 0;
  border-top: 1px solid var(--rim);
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.1rem;
  font-size: 0.82rem;
}

.doc-cta {
  margin-top: 2.75rem;
  padding-top: 1.75rem;
  border-top: 1px solid var(--rim);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
}

.doc-cta p {
  margin: 0;
  color: var(--ink-dim);
  font-weight: 300;
  font-size: 0.88rem;
}

/* One line of links at the foot of both pages. */
.footer-links {
  margin: 0.6rem 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.1rem;
}

/* ── Links ───────────────────────────────────────────────────────────────
 *
 * Underlined, always. This is a dark panel where the backlight already means
 * "lit", so colour alone would make a link indistinguishable from an engaged
 * filter -- and from the two-thirds of readers who would have to compare it
 * against the text beside it to be sure.
 */

a {
  color: var(--backlight-soft);
  text-decoration: underline;
  text-decoration-color: var(--backlight-dim);
  text-underline-offset: 0.18em;
  transition: color 0.18s ease, text-decoration-color 0.18s ease;
}

a:hover {
  color: var(--backlight);
  text-decoration-color: var(--backlight);
}

/* The one link that is a button: "Run a search" at the foot of the handbook.
   It wears the ignition, so it must not also wear the underline. */
a.ignition {
  display: inline-block;
  color: #04150c;
  text-decoration: none;
}

/* ── Footer ──────────────────────────────────────────────────────────── */

.footer {
  margin-top: 2.75rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--rim);
  font-size: 0.78rem;
  font-weight: 300;
  color: var(--ink-faint);
}

.footer p {
  margin: 0;
  max-width: 56ch;
}

/* ── Page-load choreography ──────────────────────────────────────────── */

/*
 * The page wakes top to bottom, one block every 100ms. Two rules keep it that
 * way, and both are easy to break by accident:
 *
 * 1. Every block between the cowl and the footer must be listed. `wake` fills
 *    backwards, so a listed block holds at opacity 0 until its turn -- which
 *    means an *unlisted* block paints at full opacity immediately and lands
 *    on screen before everything above it. Leaving the footer and the More
 *    filters panel out is what had the help text and the small print arriving
 *    a second before the title and the search box.
 * 2. The delays run in document order. A block added in the middle takes the
 *    beat its position asks for and the ones below it shift down; the beats
 *    are not tied to anything else, so renumbering them is free.
 *
 * #conflict is deliberately absent. It is `hidden` at load, so it cannot
 * paint early, and display:none restarts an animation from the top every time
 * the element comes back -- listing it would put this delay in front of a
 * refusal that has to answer a click straight away.
 */

.cowl-row,
.rev-strip,
.tagline,
.plate-bay,
/* Scoped to the console's own bank: the More filters panel holds a second
   .switch-bank, and it wakes with the panel around it rather than on the
   beat belonging to the bank three blocks above it. */
.console > .switch-bank,
.advanced,
.console-foot,
.readouts,
.handbook,
.footer {
  animation: wake 0.6s cubic-bezier(0.16, 0.84, 0.28, 1) both;
}

.cowl-row                { animation-delay: 0.04s; }
/* Just before .rev-sweep starts its 0.15s ignition sweep, so the strip is
   already coming up as the needle begins to travel. */
.rev-strip               { animation-delay: 0.14s; }
.tagline                 { animation-delay: 0.24s; }
.plate-bay               { animation-delay: 0.34s; }
.console > .switch-bank  { animation-delay: 0.44s; }
.advanced                { animation-delay: 0.54s; }
.console-foot            { animation-delay: 0.64s; }
.readouts                { animation-delay: 0.74s; }
.footer                  { animation-delay: 0.84s; }

/* /handbook/ is the same chrome with a document where the console goes, so it
   takes the beats the console would have had rather than waiting out delays
   set for blocks that are not on it. Both pages still wake in document
   order -- their own. */
.page-handbook .handbook { animation-delay: 0.34s; }
.page-handbook .footer   { animation-delay: 0.44s; }

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

/* ── Focus, small screens, reduced motion ────────────────────────────── */

:where(button, input, select, a):focus-visible {
  outline: 2px solid var(--backlight);
  outline-offset: 2px;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

@media (max-width: 34rem) {
  .binnacle {
    border-radius: 14px;
  }

  /* One column: plate, Search, the fuzzy toggle and Reset each full width,
     rather than a 125px button marooned at the end of a narrow row. */
  .plate-grid {
    grid-template-columns: 1fr;
  }

  /*
   * The columns belong to .chart now that .bar-row is `display: contents` —
   * setting them on the row does nothing. font-size still inherits through,
   * which is what hid this: the type shrank on a phone but the label column
   * stayed at 8.5rem, leaving barely 40px of track to read the bar in.
   */
  .chart {
    grid-template-columns: 5.5rem 1fr auto;
    column-gap: 0.5rem;
  }

  .bar-row {
    font-size: 0.76rem;
  }

  /* The share in brackets is what makes this column wide; at this width the
     track needs the room more than the second figure does. */
  .bar-count {
    font-size: 0.64rem;
  }

  .tell-tales {
    gap: 0.6rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    /* Delays too, or the staggers survive the cut: collapsing the durations
       alone leaves every block holding its opacity-0 start frame until its
       beat, so the page still arrives as a run of pops over ~0.9s. Reduced
       motion should mean the page is simply there. */
    animation-delay: 0s !important;
    transition-delay: 0s !important;
  }

  .bar-fill {
    width: var(--w);
  }

  .rev-sweep {
    width: 21%;
  }

  .gauges.is-pending .gauges-title {
    opacity: 0.7;
  }

  .bar-row-idle .bar-track::after {
    display: none;
  }
}



/* --- Advanced filter panel -------------------------------------------------
 *
 * Collapsed by default: these refine a search rather than start one, and nine
 * more controls on first paint would bury the plate field that does.
 *
 * The summary is styled as a button, with its own chevron. Two reasons it
 * cannot rely on the browser's: a <summary> loses its disclosure marker the
 * moment `display` is anything but `list-item`, and this needs flex to lay out
 * the label, the hint and the count. Without an affordance the panel reads as a
 * section heading and nobody finds the filters -- which is exactly what
 * happened.
 */
.advanced {
  margin-top: 14px;
}

.advanced > summary {
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 8px;
  width: fit-content;
  max-width: 100%;
  padding: 7px 12px 7px 10px;
  border: 1px solid currentColor;
  border-radius: 6px;
  font-size: 0.82rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.72;
  user-select: none;
  transition: opacity 120ms ease, background-color 120ms ease;
}

/* Suppress the native marker in every engine, since we draw our own. */
.advanced > summary::-webkit-details-marker {
  display: none;
}
.advanced > summary::marker {
  content: '';
}

/* The chevron: right when closed, down when open. */
.advanced > summary::before {
  content: '';
  flex: none;
  width: 0;
  height: 0;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform 140ms ease;
  transform-origin: 25% 50%;
}

.advanced[open] > summary::before {
  transform: rotate(90deg);
}

.advanced > summary:hover,
.advanced[open] > summary {
  opacity: 1;
}

.advanced > summary:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

.advanced[open] > summary {
  margin-bottom: 12px;
}

/* What is inside, so the panel does not have to be opened to find out. */
.advanced-hint {
  text-transform: none;
  letter-spacing: 0.01em;
  opacity: 0.7;
  font-size: 0.95em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* How many advanced filters are currently applied. Hidden at zero, so it only
 * ever appears when it is explaining something about the results on screen. */
.advanced-count {
  flex: none;
  min-width: 1.35em;
  padding: 1px 5px;
  border-radius: 999px;
  background: currentColor;
  font-size: 0.9em;
  text-align: center;
  line-height: 1.4;
}

.advanced-count:empty {
  display: none;
}

/* The digit is punched out of the pill rather than drawn on it, so this needs
 * no colour of its own and works on any skin. */
.advanced-count > span {
  filter: invert(1) grayscale(1) contrast(3);
}

.advanced-note {
  margin: 12px 0 0;
  max-width: 70ch;
  font-size: 0.8rem;
  line-height: 1.5;
  opacity: 0.7;
}

.filter-narrow input {
  max-width: 9ch;
}

/* ── Filters that cannot be true at once ─────────────────────────────────
 *
 * Two states, and they are deliberately not the same weight. The *hint* warns
 * which filters a chosen plate style has nothing to match against — advisory,
 * nothing is blocked, so it is lit in the instrument's own backlight. The
 * *conflict* is a refusal: the search is held until the user resolves it, and
 * it is lit on the redline like any other fault.
 */

/* The two filters a conflict names, ringed so "those disagree" is something to
 * see rather than something to decode out of the sentence below. */
.switch.is-conflict .switch-input,
.plate-slab.is-conflict {
  border-bottom-color: var(--redline);
  box-shadow:
    inset 0 2px 8px rgba(0, 0, 0, 0.6),
    0 0 0 1px rgba(255, 59, 35, 0.45),
    0 0 18px rgba(255, 59, 35, 0.18);
}

.switch.is-conflict .switch-label {
  color: var(--redline);
}

/*
 * The fields that would release a search held for breadth. Backlit and dashed
 * where a conflict is red and solid: one is "these two disagree", the other is
 * "put something here" — a prompt, not a fault, and it should not read like
 * one.
 */
.switch.is-wanted .switch-input,
.plate-slab.is-wanted {
  border-bottom-color: var(--backlight);
  box-shadow:
    inset 0 2px 8px rgba(0, 0, 0, 0.6),
    0 0 0 1px rgba(var(--backlight-rgb), 0.5),
    0 0 18px rgba(var(--backlight-rgb), 0.16);
}

.switch.is-wanted .switch-label {
  color: var(--backlight);
}

/* Not on the plate — its own lamp and the daylight slab are loud enough. */
.switch.is-wanted .switch-input {
  outline: 1px dashed rgba(var(--backlight-rgb), 0.55);
  outline-offset: 2px;
}

/* ── Search held for breadth ─────────────────────────────────────────── */

/*
 * Sibling to .conflict and deliberately not the same colour. A refusal is red
 * because something is wrong; this is backlit because nothing is — the search is
 * one filter short of running, and the panel says which ones would do it.
 */
.needs {
  margin-top: 14px;
  padding: 11px 13px;
  border: 1px solid rgba(var(--backlight-rgb), 0.32);
  border-left: 3px solid var(--backlight);
  border-radius: 6px;
  background: rgba(var(--backlight-rgb), 0.05);
  max-width: 78ch;
}

.needs-head {
  display: flex;
  gap: 9px;
  align-items: baseline;
  margin: 0;
  color: var(--ink);
  font-size: 0.84rem;
  line-height: 1.55;
}

/* A lit tell-tale rather than a warning one: the same idiom as the cowl, in
   the colour that means "engaged" instead of "fault". */
.needs-tag {
  flex: none;
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-family: var(--disp);
  font-size: 0.7rem;
  line-height: 1;
  color: var(--void);
  background: var(--backlight);
  box-shadow: 0 0 12px rgba(var(--backlight-rgb), 0.45);
}

.needs-fixes {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}

.needs-fixes:empty {
  display: none;
}

/* Advisory: what the chosen plate style has nothing to match against. Lives
 * inside the panel, next to the controls it is talking about. */
.filter-hint {
  margin: 12px 0 0;
  max-width: 70ch;
  padding: 7px 10px;
  border: 1px solid var(--rim-lit);
  border-left: 3px solid var(--backlight-dim);
  border-radius: 5px;
  background: rgba(var(--backlight-rgb), 0.05);
  color: var(--ink-dim);
  font-size: 0.8rem;
  line-height: 1.5;
}

/* The refusal. Outside the collapsible panel, because a held search must be
 * visible whether or not the panel that caused it is open. */
.conflict {
  margin-top: 14px;
  padding: 11px 13px;
  border: 1px solid rgba(255, 59, 35, 0.4);
  border-left: 3px solid var(--redline);
  border-radius: 6px;
  background: rgba(255, 59, 35, 0.06);
  max-width: 78ch;
}

.conflict-head {
  display: flex;
  gap: 9px;
  align-items: baseline;
  margin: 0;
  color: var(--ink);
  font-size: 0.84rem;
  line-height: 1.55;
}

/* The warning lamp, in the same idiom as the cowl's tell-tales. */
.conflict-tag {
  flex: none;
  width: 1.15rem;
  height: 1.15rem;
  display: grid;
  place-items: center;
  border: 1px solid var(--redline);
  border-radius: 50%;
  color: var(--redline);
  font-family: var(--disp);
  font-size: 0.66rem;
  line-height: 1;
}

.conflict-fixes {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 11px 0 0 calc(1.15rem + 9px);
}

/* Deliberately quiet next to the ignition key: these are ways out of a fault,
 * not the primary action on the page. */
.btn-fix {
  font-family: var(--disp);
  font-size: 0.58rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-dim);
  background: linear-gradient(180deg, var(--tile-lit), var(--tile));
  border: 1px solid var(--rim-lit);
  border-radius: 5px;
  padding: 0.45rem 0.8rem;
  cursor: pointer;
  transition: color 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease;
}

.btn-fix:hover {
  color: var(--backlight);
  border-color: var(--backlight-dim);
  box-shadow: 0 0 18px rgba(var(--backlight-rgb), 0.15);
}

.btn-fix:focus-visible {
  outline: 2px solid var(--backlight);
  outline-offset: 2px;
}

/* Searches that ran but are expected to find nothing -- a plate that could not
 * have been issued by the date being asked about. Advisory, not an error: the
 * query was legitimate, the answer is just already known. */
.notices {
  list-style: none;
  margin: 0.6rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.4rem;
}

.notice {
  padding: 8px 11px;
  border: 1px solid var(--rim-lit);
  border-left: 3px solid var(--lamp-amber);
  border-radius: 5px;
  background: rgba(255, 180, 60, 0.06);
  color: var(--ink-dim);
  font-size: 0.82rem;
  line-height: 1.5;
}

/* Explanation shown in the chart's place when a breakdown could not be
 * measured. The panel stays on screen rather than vanishing: a chart that
 * silently disappears is indistinguishable from one that is broken. */
.chart-note {
  margin: 0;
  padding: 4px 2px 8px;
  max-width: 68ch;
  font-size: 0.86rem;
  line-height: 1.55;
  opacity: 0.72;
}

/* --- Diplomatic mission badge ---------------------------------------------
 *
 * Sits directly under the plate on a result card, because it is a fact about
 * the mark rather than about the vehicle. Only ever rendered for the handful
 * of plates in the diplomatic scheme.
 */
.tile-diplomatic {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 6px;
  font-size: 0.82rem;
  line-height: 1.3;
}

.dip-flag {
  /* Emoji flags render from the system font, not the page's; give them their
   * own stack so a missing glyph falls back visibly rather than to tofu in a
   * face that has no flags at all. */
  font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
  font-size: 1.05rem;
  line-height: 1;
}

.dip-name {
  font-weight: 600;
  letter-spacing: 0.01em;
}

.dip-status {
  padding: 1px 6px;
  border: 1px solid currentColor;
  border-radius: 999px;
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.7;
}

/* --- Lookalike matching --------------------------------------------------- */

.plate-aux {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 14px;
  min-width: 0;
}

.fuzzy-toggle {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  cursor: pointer;
  font-size: 0.8rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  opacity: 0.72;
  user-select: none;
}

.fuzzy-toggle:hover,
.fuzzy-toggle:has(select:not([value=''])) {
  opacity: 1;
}

.fuzzy-toggle select {
  cursor: pointer;
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
  background: transparent;
  border: 1px solid currentColor;
  border-radius: 4px;
  padding: 2px 6px;
}

.fuzzy-note {
  font-size: 0.8rem;
  opacity: 0.65;
}

.fuzzy-note:empty {
  display: none;
}

/* On a result card: this one was reached by substituting a character. */
.tile-matched {
  margin-top: 6px;
  font-size: 0.75rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  opacity: 0.75;
}

/* ── MOT pips ────────────────────────────────────────────────────────── */

/*
 * A row of toggles in place of the old single-value dropdown, so several
 * states can be asked for at once — "valid or untested" is the roadworthy
 * fleet, and no one value said it.
 */
/*
 * Three columns, always — so six states are a 3x2 block that does not
 * rearrange itself. As a wrapping flex row the layout was a function of the
 * numbers in it: small counts fitted 3+2+1, millions fitted 2+2+2, and every
 * search reshuffled the row while the reader was looking at it.
 *
 * `1fr` columns take their width from the container rather than their
 * contents, which is what keeps all six the same size and holds them there
 * when a count goes from three digits to eight.
 */
.pips {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px;
}

.pip {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  min-width: 0;
  font-family: var(--ui);
  font-size: 0.78rem;
  letter-spacing: 0.02em;
  color: var(--ink-dim);
  background: linear-gradient(180deg, var(--tile-lit), var(--tile));
  border: 1px solid var(--rim);
  border-radius: 999px;
  padding: 0.34rem 0.7rem;
  cursor: pointer;
  transition: color 0.14s ease, border-color 0.14s ease, background-color 0.14s ease,
    box-shadow 0.14s ease;
}

.pip:hover {
  color: var(--ink);
  border-color: var(--rim-lit);
}

/* Lit, in the same backlight as an engaged tell-tale. */
.pip[aria-pressed="true"] {
  color: var(--void);
  background: var(--backlight);
  border-color: var(--backlight);
  box-shadow: 0 0 14px rgba(var(--backlight-rgb), 0.32);
}

.pip:focus-visible {
  outline: 2px solid var(--backlight);
  outline-offset: 2px;
}

/*
 * Every state selected excludes nothing, so the search is not narrowed by this
 * at all. Dimmed rather than blocked: the selection is legitimate, it just is
 * not a filter, and the row should say so instead of looking like one that is
 * being ignored.
 */
.pips-moot .pip[aria-pressed="true"] {
  background: var(--backlight-dim);
  border-color: var(--backlight-dim);
  color: var(--ink-dim);
  box-shadow: none;
}

/*
 * How many vehicles each state holds in the current match. Reserves a little
 * width even when empty so the row does not shift when the counts land a
 * second after the results.
 */
/*
 * Sized for the largest count the fleet can produce -- tens of millions, so
 * ten characters with the separators -- and never narrower. The number is the
 * part that changes on every search, so it is the part that must not be
 * allowed to resize anything.
 */
.pip-count {
  flex: none;
  min-width: 10ch;
  text-align: right;
  font-family: var(--data);
  font-size: 0.66rem;
  font-weight: 300;
  font-variant-numeric: tabular-nums;
  opacity: 0.72;
}

/* Nothing in this state for the other filters as they stand. Still clickable —
   loosening one of those may bring it back. */
.pip-empty {
  opacity: 0.45;
}

/* The label yields before the count does: a long one clips rather than pushing
   the number out of the pip or the pip out of its column. */
.pip-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/*
 * Six pips at three across need the whole row. On two columns of the switch
 * bank each one came out around 110px, which is not enough for "Failed last
 * test" and an eight-digit count side by side. Unconditional, unlike
 * .switch-wide: below the bank's own breakpoint there is only one column, so
 * spanning to the end is what it already does.
 */
.switch-mot {
  grid-column: 1 / -1;
}

/*
 * Three across only while three can hold a label and an eight-digit count.
 * Below that it steps to two and then to one, rather than keeping the shape
 * and clipping every label to fit it. Each step is still a fixed grid, so the
 * row never rearranges itself as the numbers change -- which was the whole
 * point of leaving the wrapping flex row behind.
 */
@media (max-width: 46rem) {
  .pips {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 30rem) {
  .pips {
    grid-template-columns: minmax(0, 1fr);
  }
}

/*
 * ── The 16px floor on touch ──────────────────────────────────────────────
 *
 * The other half of "no sideways drift" -- see the note beside the `html`
 * rule at the top for why a field under 16px makes a phone pan. Last in the
 * file because these selectors have to outrank the ones they raise, and each
 * of those is a plain class declared further up.
 *
 * Keyed on the pointer, not the width, because it is the pointer that decides:
 * an iPad has a wide layout and zooms on focus just the same, while a narrow
 * desktop window does neither.
 *
 * Buttons are absent on purpose. Only fields that take text entry -- and
 * selects, which open a picker -- trigger the zoom, so the small display type
 * on the toggles, pips and examples is left as drawn; what those need is a
 * 44px tap target, which they already have.
 *
 * The plate is absent for the opposite reason: it is set at 23.2px and clears
 * the floor by half again, so a rule here could only make it worse.
 */
@media (pointer: coarse) {
  .switch-input,
  .chart-full-filter .switch-input,
  .fuzzy-toggle select {
    font-size: 16px;
  }

  /* `max()` so the sentence box keeps growing on a tablet, where the clamp
   * has room to resolve above the floor rather than being pinned to it. */
  .ask-input {
    font-size: max(16px, clamp(0.98rem, 2.6vw, 1.12rem));
  }
}
