/* ── 00-layers.css ─────────────────────────────────────────────── */
/* Expliciete cascade. Alles hierna staat in precies één van deze lagen, zodat
   specificiteit nooit het middel hoeft te zijn om iets te overschrijven. */
@layer reset, tokens, layout, components, utilities;

/* Cross-document overgang tussen zoekpagina en resultaatblad. Puur CSS; zonder
   ondersteuning navigeert de browser gewoon zoals altijd. */
@view-transition {
  navigation: auto;
}

/* De kentekenplaat is hetzelfde object op beide pagina's: op het eerste scherm
   groot en in te vullen, op het resultaatblad klein en bovenaan. Door hem op
   allebei dezelfde naam te geven verzorgt de browser de verplaatsing zelf —
   geen animatiecode, geen framework. */
::view-transition-group(kenteken) {
  /* Ruimer dan de rest: de plaat legt hier een hele buiteling af terwijl hij
     omhoog gaat en krimpt, en dat moet je kunnen volgen. */
  animation-duration: 820ms;
  animation-timing-function: cubic-bezier(0.3, 0.6, 0.2, 1);
}

::view-transition-group(woordmerk) {
  animation-duration: 420ms;
  animation-timing-function: cubic-bezier(0.2, 0.7, 0.2, 1);
}

/* Standaard vloeit de browser de oude en de nieuwe momentopname over elkaar met
   `mix-blend-mode: plus-lighter` — dat telt kleuren op, en twee gele platen over
   elkaar worden dus wit. Hier is overvloeien sowieso onzin: het ís hetzelfde
   voorwerp, alleen op een andere plek. Dus geen kruisvervaging, alleen de
   nieuwe, en de groep verzorgt de beweging. */
::view-transition-old(kenteken),
::view-transition-new(kenteken) {
  animation: none;
  mix-blend-mode: normal;
}

::view-transition-old(kenteken) {
  opacity: 0;
}

::view-transition-new(kenteken) {
  opacity: 1;
}

/* Twee hele slagen om de hórizontale as, maar alléén als het kenteken bestaat.

   Dat onderscheid komt gratis: de zoekpagina en de niet-gevonden-pagina staan
   allebei op het donkere podium (body.stage), het resultaatblad niet. De CSS van
   het aankomende document bepaalt de overgang, dus deze regel vuurt precies
   wanneer er een voertuig gevonden is — bij een onbekend kenteken schudt de
   plaat in plaats daarvan.

   De draai zit op het image-pair, niet op de group: die laatste verzorgt de
   verplaatsing en het verkleinen, en die wil je niet overschrijven. */
:root:not(:has(body.stage)) {
  /* Let op de ontbrekende spatie: deze pseudo-elementen hángen aan de root, ze
     zijn er geen afstammeling van. Met een spatie ertussen matcht de regel
     niets — en dan blijft de draai stil zonder dat er iets stukgaat. */

  /* Het perspectief hoort op de groep: die verzorgt de verplaatsing en het
     verkleinen, en is dus het vlak waarin de plaat kantelt. */
  &::view-transition-group(kenteken) {
    perspective: 1200px;
  }

  /* De draai zit op het image-pair. Zou hij op de groep staan, dan overschreef
     hij precies de beweging waar hij overheen hoort te lopen. */
  &::view-transition-image-pair(kenteken) {
    animation: plate-flip 820ms cubic-bezier(0.3, 0.6, 0.2, 1);
  }
}

/* Terug naar de zoekpagina: dezelfde buiteling, de andere kant op, terwijl de
   plaat omlaag komt en weer groeit.

   Niet op de niet-gevonden-pagina. Die staat óók op het podium, maar daar draait
   de plaat al op zijn plek bij de foutmelding — twee draaien over elkaar heen is
   één te veel. Het onderscheid zit in het attribuut dat de server daar zet. */
:root:has(body.stage):not(:has(.plate-holder[data-error="true"])) {
  &::view-transition-group(kenteken) {
    perspective: 1200px;
  }

  &::view-transition-image-pair(kenteken) {
    animation: plate-flip-back 820ms cubic-bezier(0.3, 0.6, 0.2, 1);
  }
}

@keyframes plate-flip {
  from {
    transform: rotateX(0turn);
  }
  to {
    transform: rotateX(2turn);
  }
}

@keyframes plate-flip-back {
  from {
    transform: rotateX(0turn);
  }
  to {
    transform: rotateX(-2turn);
  }
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-image-pair(kenteken) {
    animation: none;
  }
}

/* ── 01-reset.css ──────────────────────────────────────────────── */
@layer reset {
  *,
  *::before,
  *::after {
    box-sizing: border-box;
  }

  * {
    margin: 0;
  }

  html {
    -webkit-text-size-adjust: none;
  }

  /* Vangnet tegen horizontaal wegscrollen. `clip` en niet `hidden`: hidden maakt
     er een scroll-container van, en dan verankeren `position: fixed`-elementen
     zich aan dít element in plaats van aan het venster — precies wat de
     achtergrondlagen kapotmaakt. `clip` knipt alleen af.

     Dit dekt de fouten af die ik nog niet gevonden heb; de bekende oorzaak is
     apart opgelost bij .plate-cloud. */
  html,
  body {
    overflow-x: clip;
  }

  body {
    min-block-size: 100svb;
    -webkit-font-smoothing: antialiased;
  }

  img,
  svg {
    display: block;
    max-inline-size: 100%;
  }

  input,
  button,
  textarea,
  select {
    font: inherit;
    color: inherit;
  }

  ul,
  ol {
    padding-inline-start: 0;
    list-style: none;
  }

  table {
    border-collapse: collapse;
  }

  :where(a) {
    color: inherit;
  }

  :focus-visible {
    outline: 2px solid var(--accent-text);
    outline-offset: 2px;
  }

  @media (prefers-reduced-motion: reduce) {
    ::view-transition-group(*),
    ::view-transition-old(*),
    ::view-transition-new(*) {
      animation: none !important;
    }

    *,
    *::before,
    *::after {
      animation-duration: 0.01ms !important;
      transition-duration: 0.01ms !important;
    }
  }
}

/* ── 02-tokens.css ─────────────────────────────────────────────── */
@layer tokens {
  :root {
    /* Op contrast gemeten, niet op gevoel gekozen. Niet aanpassen zonder opnieuw
       te meten met scripts/check-contrast.ts. */
    --vellum: #16191a; /* de pagina zelf: bijna zwart */
    --sheet: #1f2324; /* verhoogd vlak: blokjes, tegels, kaarten */
    --ink: #f1f2ee; /* tekst en zware lijnen */
    --graphite: #6c726b; /* bijschriften, identificaties */
    --rule: #3a403a; /* hairlines */
    --accent: #0047e0; /* aandacht: sectienummers, focus, urgentie */
    --peil: #0b5fa5; /* registerblauw: herkomst, bronvermelding */
    --oxide: #a84a16; /* waarschuwing: lijnen en randen */
    /* Als tekst haalt --oxide op bijna zwart maar 3,1:1. Richting wit brengt
       hem op 6,4:1 zonder van kleur te veranderen; het origineel blijft voor
       randen en vlakken, waar de drempel niet geldt. */
    --oxide-text: color-mix(in srgb, var(--oxide) 55%, #fff);

    /* Op een gevuld accentvlak wordt álle tekst wit; grijs haalt de drempel
       niet. Rangorde zit daar in grootte en letterafstand. */
    --on-fill: #ffffff;

    /* Afgeleide tint voor tékst: --graphite haalt 3,97:1 op vellum, net onder
       AA, en draagt juist alle 10px-bijschriften. Een duw van 10% richting
       --ink brengt hem op 4,53:1 zonder zichtbaar van kleur te veranderen.
       De oorspronkelijke waarde blijft voor vlakken en lijnen.

       --accent haalt als tekst 5,67:1 op het blad en heeft geen bijstelling
       nodig; op het donkere podium wel, daar gaat hij richting wit. */
    --graphite-text: #949a94;
    /* Het accent van de knop is te donker om op bijna zwart te lezen; richting
       wit brengt hem op 4,6:1. Gemeten, niet geschat. */
    --accent-text: color-mix(in srgb, var(--accent) 70%, #fff);

    /* Puntenwolk op de achtergrond, als terugval zonder JavaScript. */
    --dot-far: rgb(255 255 255 / 0.16);
    --dot-near: rgb(255 255 255 / 0.3);

    /* --peil is nog uitsluitend het blauw van de EU-band op de plaat: een
       letterlijke kleur, geen betekenis. Herkomst stond hier ook in, maar met
       een blauw accent zouden twee blauwen verschillende dingen betekenen —
       precies de verwarring die kleur-als-enige-drager oplevert. Herkomst staat
       nu in grafiet en onderscheidt zich door de dataletter. */

    /* Het geel van de kentekenplaat. Geen decoratie maar identiteit: dit is het
       enige warme vlak op het blad, en het draagt de invoer. Ink erop haalt
       10,6:1.

       De plaat is ook het énige object op de site met diepte. Een echt kenteken
       heeft geperste tekens en een geperste rand, en dat is wat je herkent —
       vlak getekend leest het als een gele knop. Die uitzondering blijft
       nadrukkelijk bij dit ene object: verder doen hairlines het werk. */
    --plate: #f3c300;
    /* Bewust niet --ink: een kentekenplaat is zwart-op-geel, ook wanneer de
       pagina eromheen omkeert naar het donkere podium. */
    --plate-ink: #161a17;
    --plate-hint: color-mix(in srgb, var(--plate-ink) 70%, var(--plate));
    --plate-lit: color-mix(in srgb, var(--plate) 62%, #fff);
    --plate-shade: color-mix(in srgb, var(--plate) 55%, #000);
    /* De houder: de zwarte omlijsting met de dealerregel eronder. Bewust
       donkerder dan het podium — een zwarte lijst die lichter is dan de pagina
       leest niet als een lijst maar als een vlek. Het randlicht doet de rest. */
    --holder: #070807;
    /* De sterren van de EU-band zijn goud, alleen de landcode is wit. */
    --eu-star: #ffcc00;

    /* Het woordmerk staat in een eigen letter. De tekstletter is te licht om
       op tabbladformaat nog een vorm te zijn; een logo moet dat wel.
       Kandidaten staan allebei in public/fonts — wisselen is deze ene regel
       plus het gewicht eronder. Syne 800 of Archivo 900. */
    --font-logo: "Archivo", "Space Grotesk", sans-serif;
    --font-logo-weight: 900;

    --font-display: "Space Grotesk", ui-sans-serif, system-ui, sans-serif;
    --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

    --step--2: 0.625rem; /* 10px — identificaties, herkomst, kolomkoppen */
    --step--1: 0.8125rem; /* 13px — bijschriften */
    --step-0: 0.9375rem; /* 15px — basis; ruimer dan de 13px van de tracker */
    --step-1: 1.125rem;
    --step-2: 1.5rem;
    --display: clamp(2rem, 7vw, 3.4rem);

    /* Bewegingstokens. Dit is het idee achter Open Props — gedeelde easings en
       tijden als losse variabelen — maar dan zonder de afhankelijkheid: drie
       regels doen wat een bibliotheek hier zou doen. Eén plek om aan te draaien,
       en alles beweegt in dezelfde taal. */
    --ease-out: cubic-bezier(0.2, 0.7, 0.2, 1);
    --duur-kort: 160ms;
    --duur-veeg: 340ms;

    --tracking-index: 0.14em;
    --hair: 1px solid var(--rule);
    --radius: 2px; /* dit is een tekening; nooit meer dan dit */
    --sheet-inline: 68rem;
    --nav-block-size: 3rem;
  }

  html {
    color-scheme: dark;
  }

  /* Er stond hier een blok dat `body.stage` zes tokens liet omkeren: de
     zoekpagina was donker, het resultaatblad licht. Dat onderscheid is weg — één
     kleur voor de hele site, want de plaat die van de ene naar de andere pagina
     vliegt landde anders midden in een kleurwissel. De class `stage` blijft
     bestaan, maar zegt nu alleen nog "hier staat de grote plaat", niet "hier is
     het donker".

     Gemeten op #16191a: tekst 15,7:1, bijschrift 6,4:1, accent 4,6:1. */

  body {
    background: var(--vellum);
    color: var(--ink);
    font-family: var(--font-display);
    font-size: var(--step-0);
    line-height: 1.5;
    /* De displayletter heeft proportionele cijfers die elke uitlijning breken.
       Dit vangt de gevallen af waar toch een getal in lopende tekst staat. */
    font-variant-numeric: tabular-nums lining-nums;
  }
}

/* ── 03-layout.css ─────────────────────────────────────────────── */
@layer layout {
  .sheet {
    max-inline-size: var(--sheet-inline);
    margin-inline: auto;
    padding-inline: 1.5rem;
  }

  /* ── De puntenwolk ────────────────────────────────────────────────────────
     Twee rasters van stippen over elkaar. De achterste is fijn en donker, de
     voorste grover en lichter; ze schuiven verschillend mee met de muis. Die
     parallax doet het werk dat een schaduw hier niet kan: op een bijna zwarte
     pagina is zwart-op-zwart onzichtbaar, maar punten die de andere kant op
     bewegen dan de plaat kantelt lezen meteen als diepte.

     Twee lagen op één element kan niet: de traag draaiende achtergrond
     gebruikt background-position, de parallax gebruikt transform. */
  /* Het doek waar de puntenwolk op getekend wordt. Zie public/js/point-cloud.js:
     diepte, verbindingslijnen en het schijnsel van de plaat. */
  #wolk {
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    /* Alleen op het podium; op het resultaatblad staat geen wolk. */
    display: none;

    body.stage & {
      display: block;
    }
  }

  /* De twee CSS-stippenlagen zijn de terugval voor wie geen JavaScript heeft.
     Draait het doek, dan gaan ze uit — anders staan er twee wolken over elkaar. */
  body.wolk-actief.stage::before,
  body.wolk-actief.stage::after {
    display: none;
  }

  body.stage::before,
  body.stage::after {
    content: "";
    position: fixed;
    /* Ruim over de rand heen, zodat schuiven nooit een lege hoek toont. */
    inset: -12vmax;
    z-index: -1;
    pointer-events: none;
    transition: transform 400ms ease-out;
  }

  body.stage::before {
    background-image: radial-gradient(circle at center, var(--dot-far) 1px, transparent 1.5px);
    background-size: 26px 26px;
    animation: dot-drift 40s linear infinite;
    transform: translate3d(calc(var(--px, 0) * 20px), calc(var(--py, 0) * 20px), 0);
  }

  body.stage::after {
    background-image: radial-gradient(circle at center, var(--dot-near) 1.4px, transparent 2px);
    background-size: 68px 68px;
    animation: dot-drift 24s linear infinite reverse;
    transform: translate3d(calc(var(--px, 0) * 46px), calc(var(--py, 0) * 46px), 0);
  }

  @keyframes dot-drift {
    to {
      background-position: 260px 180px;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    body.stage::before,
    body.stage::after {
      animation: none;
      transform: none;
      transition: none;
    }
  }

  /* Het eerste scherm: niets dan de plaat, midden in beeld. Alles wat uitleg
     geeft staat eronder, buiten het eerste zicht. Het schijnsel eronder tilt de
     zwarte houder los van de achtergrond — die twee zijn anders bijna even
     donker en dan lijkt de lijst geen lijst. */
  .hero {
    position: relative;

    &::before {
      content: "";
      position: absolute;
      inset-inline: -20%;
      inset-block: 18% 8%;
      z-index: -1;
      pointer-events: none;
      background: radial-gradient(
        ellipse at center,
        rgb(255 255 255 / 0.07),
        transparent 65%
      );
    }
  }

  .hero {
    min-block-size: 100svb;
    display: grid;
    /* De kolom expliciet noemen in plaats van justify-items: center. Anders
       krijgt de kolom max-content en krimpt de plaat tot zijn plaatshouder. */
    grid-template-columns: min(52rem, 100%);
    justify-content: center;
    align-content: center;
    /* Ruimer dan de rest van het blad: het woordmerk mag niet tegen de plaat
       aan liggen, en de slogan eronder heeft lucht nodig om te rollen — de
       trommel draait letterlijk buiten zijn eigen vak. */
    gap: clamp(2rem, 5vh, 3.25rem);
    text-align: center;
  }

  .quicknav {
    position: sticky;
    inset-block-start: 0;
    z-index: 10;
    background: var(--vellum);
    border-block: var(--hair);

    & ol {
      display: flex;
      gap: 1.25rem;
      overflow-x: auto;
      scrollbar-width: thin;
      padding-block: 0.85rem;
      margin-inline: auto;
      max-inline-size: var(--sheet-inline);
      padding-inline: 1.5rem;
    }

    & a {
      display: flex;
      gap: 0.45em;
      white-space: nowrap;
      text-decoration: none;
      font-size: var(--step--2);
      text-transform: uppercase;
      letter-spacing: var(--tracking-index);
      color: var(--graphite-text);

      & .no {
        font-family: var(--font-mono);
        color: var(--accent-text);
      }

      &:hover {
        color: var(--ink);
      }
    }
  }

  /* Op de zoekpagina staat ook alles ónder het eerste scherm gecentreerd, in
     één kolom, zodat de bladspiegel dezelfde as houdt als de plaat. */
  body.stage .section {
    text-align: center;

    & .notice {
      margin-inline: auto;
      border-inline-start: none;
      padding-inline-start: 0;
      max-inline-size: 42rem;
    }

    & .cards {
      justify-content: center;
    }

    & .card {
      text-align: start;
    }
  }

  .section {
    border-block-start: var(--hair);
    padding-block-start: 1.75rem;
    margin-block-start: 2.5rem;
    scroll-margin-block-start: calc(var(--nav-block-size) + 1.5rem);
  }

  .section > * + * {
    margin-block-start: 1rem;
  }

  .colophon {
    margin-block: 4rem 3rem;
    border-block-start: var(--hair);
    padding-block-start: 1.25rem;
    font-size: var(--step--1);
    color: var(--graphite-text);

    & p + p {
      margin-block-start: 0.6rem;
    }

    & a {
      color: var(--accent-text);
    }
  }

  /* Het blad moet als document afdrukken. Dit is meteen de test die bewijst dat
     de betekenis in tekst zit en niet in kleur: op papier verdwijnt het vlak
     onder een urgente tegel, maar het woord VERLOPEN blijft staan. */
  @media print {
    .quicknav,
    .search,
    .colophon a {
      display: none;
    }

    :root {
      --vellum: #fff;
      --sheet: #fff;
      --ink: #000;
      --graphite: #444;
      --rule: #999;
    }

    .sheet {
      max-inline-size: none;
      padding-inline: 0;
    }

    .section {
      break-inside: avoid;
    }

    .tile[data-level="urgent"] {
      background: none;
      box-shadow: 0 0 0 2px #000;

      &,
      & * {
        color: #000;
      }
    }

    .card::after {
      display: none;
    }

    /* Uitgeklapt afdrukken zou tien pagina's JSON opleveren. */
    .source {
      display: none;
    }
  }
}

/* ── 04-components.css ─────────────────────────────────────────── */
@layer components {
  /* ── Sectiekopje ──────────────────────────────────────────────────────────
     De titel draagt nu de accentkleur die eerst bij het volgnummer zat. Dat
     nummer is weg: het beloofde een vaste indeling die er niet is — secties
     vallen weg als een voertuig ze niet heeft, en dan telt "02, 03, 05" als
     een gat in plaats van als een keuze.

     Kleur is hier versiering, geen betekenisdrager: de toelichting ernaast
     staat in woorden, en op papier blijft het onderscheid in grootte en
     letterafstand overeind. */
  .index {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.5em;
    /* Dit is een <h1>/<h2>, dus grootte en gewicht van de browser moeten er
       expliciet af; de rangorde zit hier in letterafstand, niet in punten. */
    font-size: var(--step--2);
    font-weight: 400;
    text-transform: uppercase;
    letter-spacing: var(--tracking-index);
    color: var(--graphite-text);

    .hero & {
      justify-content: center;
    }

    & .no {
      font-family: var(--font-mono);
      font-weight: 500;
      color: var(--accent-text);
      letter-spacing: 0.04em;
    }

    & .title {
      color: var(--accent-text);
      font-weight: 500;
    }
  }

  /* ── De kentekenplaat ─────────────────────────────────────────────────────
     Geperste tekens op een gewelfd geel vlak, zoals een echte plaat. Het licht
     komt van linksboven: elk teken vangt daar een lichte richel en werpt een
     schaduw naar rechtsonder. */
  .plate,
  .plate-field {
    /* Twee lagen: de richel waar het licht op valt en de schaduw die het teken
       op het gele vlak werpt. Samen leest dat als uitgeperst in plaats van
       gedrukt. */
    --emboss:
      0 -1px 0 var(--plate-lit),
      1px 1px 0 var(--plate-shade),
      2px 3px 3px rgb(0 0 0 / 0.3);

    /* De welving van het plaatmateriaal zelf. */
    --sheet-relief:
      inset 0 1px 0 rgb(255 255 255 / 0.5),
      inset 0 -1px 0 rgb(0 0 0 / 0.22);
  }

  .plate {
    display: inline-flex;
    align-items: stretch;
    position: relative;
    color: var(--plate-ink);
    /* Zelfde opbouw als het invoerveld, maar in em zodat hij met font-size
       meeschaalt: geel randje, dan het zwarte kader, dan het veld. */
    --plate-margin: 0.1em;
    --plate-frame: 0.055em;
    padding: calc(var(--plate-margin) + var(--plate-frame));
    border-radius: 0.16em;
    overflow: hidden;
    font-family: var(--font-mono);
    /* Een echte plaat heeft zware, geperste tekens; 500 is daarvoor te licht. */
    font-weight: 700;
    line-height: 1;
    background: linear-gradient(
      180deg,
      color-mix(in srgb, var(--plate) 93%, #fff),
      var(--plate) 45%,
      color-mix(in srgb, var(--plate) 93%, #000)
    );
    box-shadow: var(--sheet-relief);

    &::before {
      content: "";
      position: absolute;
      inset: var(--plate-margin);
      border: var(--plate-frame) solid var(--plate-ink);
      border-radius: 0.1em;
      pointer-events: none;
      z-index: 1;
    }

    & .band {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 0.15em;
      padding-inline: 0.4em;
      padding-block: 0.3em;
      background: var(--peil);
      color: var(--on-fill);
      font-size: 0.42em;
      letter-spacing: 0.08em;

      & svg {
        inline-size: 1.6em;
        fill: var(--eu-star);
      }
    }

    & .chars {
      padding: 0.32em 0.5em;
      letter-spacing: 0.08em;
      white-space: nowrap;
      text-shadow: var(--emboss);
    }
  }

  /* De kleine, ingevulde versie boven aan het resultaatblad. Deelt zijn naam
     met het invoerveld, dus de browser verplaatst hem er zelf naartoe. */
  /* Op het resultaatblad is de plaat een invoerveld zonder lijst eromheen: je
     corrigeert er een letter in, je hangt hem niet op. Alleen het gele bord
     blijft over, onder de hoek waarin hij landt. */
  .search--kaal {
    inline-size: min(20rem, 100%);
    gap: 0.5rem;

    & .plate-face--front {
      background: none;
      padding: 0;
      box-shadow: none;
    }

    & .plate-holder {
      --ry-base: -13deg;
      box-shadow: 10px 12px 22px rgb(22 26 23 / 0.18);
    }

  }

  /* ── Voertuigtitel ────────────────────────────────────────────────────────*/
  /* Plaat bovenaan, naam eronder, allebei gecentreerd. Daardoor legt het bord
     vanaf het podium een rechte lijn omhoog af in plaats van ook opzij te
     schuiven. */
  .lede {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 1rem;
    margin-block-start: 1.25rem;
  }

  .lede h1 {
    font-size: var(--display);
    font-weight: 500;
    line-height: 1.05;
    letter-spacing: -0.015em;
  }

  .lede .qualifier {
    display: block;
    font-size: var(--step--2);
    text-transform: uppercase;
    letter-spacing: var(--tracking-index);
    color: var(--graphite-text);
    margin-block-end: 0.5rem;
  }

  /* ── Woordmerk en slogan ──────────────────────────────────────────────────
     Woordmerk en slogan staan in kapitalen; in lopende tekst blijft de naam in
     zinsvorm. De bron blijft daarom gewoon "Kennum" — het hoofdlettergebruik is
     vormgeving, geen spelling, en de verborgen kop voor schermlezers houdt zo
     zijn normale zinsvorm. */
  /* De koptekst boven aan het resultaatblad: dezelfde twee regels, klein. */
  .masthead {
    display: grid;
    justify-items: center;
    gap: 0.15rem;
    text-decoration: none;
    /* Gecentreerd, net als op de zoekpagina: woordmerk, slogan en plaat leggen
       daardoor met z'n drieën een rechte lijn omhoog af in plaats van ook opzij
       te schuiven. */
    text-align: center;
    text-transform: uppercase;
    /* Boven en onder evenveel. De ruimte eronder kwam eerst van de marge van de
       volgende sectie (40px) tegen 20px erboven, en dan hangt de kop scheef
       tussen de bovenrand en de lijn. Die marge wordt hieronder uitgezet voor de
       sectie die direct op de kop volgt. */
    padding-block: 1.75rem;

    &:hover .wordmark {
      color: var(--accent-text);
    }

    /* Eén niveau specifieker dan `.wordmark .mark` hieronder, anders wint die
       regel op volgorde en staat het woordmerk op het blad even groot als op
       het podium. */
    & .wordmark .mark {
      font-size: var(--step-1);
    }

    & .descriptor {
      color: var(--graphite-text);
      font-weight: 400;
      font-size: var(--step--2);
      letter-spacing: 0.22em;
      /* De laatste letter neemt zijn spatiëring mee; anders staat de regel een
         halve letter uit het midden. */
      text-indent: 0.22em;
    }
  }

  /* De eerste sectie na de kop brengt zijn eigen marge niet mee: die zou boven
     op de padding van de kop komen en de boel scheeftrekken. */
  .masthead + .section {
    margin-block-start: 0;
  }

  /* KEN·NUM. */
  .wordmark {
    /* Vliegt mee naar de volgende pagina, net als de plaat. */
    view-transition-name: woordmerk;
    display: block;
    line-height: 1.05;
    /* Kapitalen alleen hier en in de slogan; in lopende tekst blijft de naam
       in zinsvorm. */
    text-transform: uppercase;

    & .mark {
      font-family: var(--font-logo);
      font-size: clamp(2rem, 6vw, 3.25rem);
      font-weight: var(--font-logo-weight);
      /* Kapitalen staan zonder spatiëring op elkaar gepropt; dit is de ruimte
         die ze nodig hebben om als woordmerk te lezen in plaats van als woord.
         De spatiëring achter de laatste N valt weg tegen het midden — bij 0.04em
         is dat een halve punt en dus onzichtbaar. */
      letter-spacing: 0.04em;
    }

    /* Het scheidingsteken splitst de naam, en doet dat met niets dan positie:
       zelfde kleur, zelfde gewicht. Twee kleuren of twee gewichten zouden dit
       tot een techlogo maken. Niet selecteerbaar, zodat kopiëren "KENNUM"
       oplevert — dat is wat iemand in een zoekveld plakt. */
    & .sep {
      user-select: none;
      /* Het teken staat strak tussen de twee helften; de letterspatiëring
         eromheen doet de rest. */
      margin-inline: -0.02em;
    }
  }

  /* Alle vier de regels liggen in dezelfde rastercel, dus de hoogte is die van
     de langste — ook wanneer regel 4 op een smal scherm over twee regels loopt.
     Geen meting in JavaScript, geen verspringende layout. */
  /* De ondertitel onder het woordmerk. Bij binnenkomst staat er gewoon
     "de gratis kentekencheck"; daarna rolt diezelfde regel door naar de
     slagzinnen. Eén element, dus geen sprong in de layout op het moment dat de
     eerste wissel komt. */
  .rotator {
    display: grid;
    /* Ruim genoeg dat de trommel — die buiten zijn eigen vak draait — het
       woordmerk niet raakt. */
    margin-block-start: 0.9rem;
    font-size: clamp(0.8rem, 1.9vw, 1.05rem);
    text-transform: uppercase;
    letter-spacing: 0.2em;
    /* De laatste letter neemt zijn spatiëring mee; anders staat de regel een
       halve letter uit het midden. */
    text-indent: 0.2em;
    color: var(--accent-text);

    /* Een trommel die omhoog rolt. De regels draaien om een as áchter de tekst,
       dus ze lopen over een bolling in plaats van vlak te schuiven —
       toepasselijk voor een kentekensite: zo rolt een kilometerteller ook.

       De afgelegde weg is straal × sin(hoek). Met een kleinere straal én een
       kwartslag minder blijft de bolling zichtbaar terwijl de regels veel
       dichter bij hun plek beginnen en eindigen. */
    --slogan-radius: 1.6em;
    --slogan-angle: 58deg;
    perspective: 14em;
    transform-style: preserve-3d;

    /* Eén bron voor de standtijd: slogan-rotator.js leest deze waarde uit. */
    --slogan-dwell: 4000ms;
    --slogan-roll: 380ms;

    & [data-slogan] {
      grid-area: 1 / 1;
      /* Elke regel is een eigen vlakje in dezelfde kleur als de zoekknop; het
         draait mee met de trommel in plaats van eromheen stil te staan. Elke
         regel is even lang als hij is, dus het vlakje verspringt van breedte —
         dat hoort bij een rollende teller. */
      justify-self: center;
      background: var(--ink);
      color: var(--accent);
      padding: 0.42em 0.85em;
      border-radius: var(--radius);
      transform-origin: 50% 50% calc(-1 * var(--slogan-radius));
      backface-visibility: hidden;
      /* Wachtstand: onder de as, met het vlak van je af gedraaid. */
      transform: rotateX(calc(-1 * var(--slogan-angle)));
      opacity: 0;
      transition:
        transform var(--slogan-roll) cubic-bezier(0.3, 0.7, 0.2, 1),
        opacity var(--slogan-roll) ease-out;
    }

    & [data-state="active"] {
      transform: rotateX(0deg);
      opacity: 1;
    }

    /* De vertrekkende regel rolt over de bovenkant weg. */
    & [data-state="leaving"] {
      transform: rotateX(var(--slogan-angle));
      opacity: 0;
    }

    @media (prefers-reduced-motion: reduce) {
      perspective: none;

      & [data-slogan] {
        transition: none;
        transform: none;
      }

      /* Geen rotatie: alleen regel 1, statisch. */
      & [data-slogan]:not(:first-child) {
        display: none;
      }

      & [data-slogan]:first-child {
        opacity: 1;
      }
    }
  }

  /* ── Kerngegevens ─────────────────────────────────────────────────────────
     De vijf dingen waar je een kenteken voor opzoekt, direct onder de naam.
     Hier wél auto-fit en niet auto-fill: het aantal ligt vast, dus de rij hoort
     zich netjes te verdelen in plaats van een gat rechts te laten. */
  /* Feitenblokjes en statustegels zijn hetzelfde soort ding: één label, één
     waarde, één toelichting. Ze staan onder elkaar op dezelfde pagina, dus ze
     krijgen ook hetzelfde raster en dezelfde maat. `1fr` op de impliciete rijen
     trekt binnen elk raster alles naar de hoogste; `--blok-hoogte` zorgt dat de
     twee rasters onderling ook uitkomen. */
  .facts,
  .tiles {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
    grid-auto-rows: 1fr;
    gap: 4px;
    --blok-hoogte: 8.5rem;

    /* Op een telefoon past er maar één blok van 13rem naast elkaar, en dan
       staat er een bijna lege strook van rand tot rand met "2012" erin. Twee
       kolommen leest als een register en scheelt de helft van het scrollen.
       Vaste kolommen en geen auto-fill: bij die breedte valt er niets meer te
       kiezen, dus liever expliciet. */
    @media (width < 40rem) {
      grid-template-columns: 1fr 1fr;
      --blok-hoogte: 6rem;
      /* Rijen op eigen hoogte in plaats van allemaal even hoog. Op halve
         breedte loopt de langste toelichting over drie regels, en met `1fr`
         wordt dán élk blok zo hoog — inclusief dat ene met "2006" erin. Binnen
         een rij blijven ze gelijk (dat doet het raster zelf), en dat is wat je
         ziet. */
      grid-auto-rows: auto;
    }
  }

  .facts {
    margin-block-start: 2rem;

    /* Twee groepen: eerst wat voor auto het is, dan wanneer. Ze staan op
       dezelfde afstand als de blokjes onderling, zodat het één blad blijft. */
    & + & {
      margin-block-start: 4px;
    }
  }

  .fact {
    box-shadow: 0 0 0 1px var(--rule);
    /* Het verhoogde vlak, niet de paginakleur: op een donkere pagina is
       --vellum de achtergrond zelf, en dan is een blokje een onzichtbaar
       kader. */
    background: var(--sheet);
    padding: 1rem 1rem 1.15rem;
    min-block-size: var(--blok-hoogte);

    @media (width < 40rem) {
      padding: 0.8rem 0.8rem 0.9rem;
    }
    /* Inhoud bovenaan, niet uitgesmeerd over de hoogte. De blokjes zijn even
       hoog (grid-auto-rows: 1fr hierboven), en met subgrid werd die extra
       hoogte over label, waarde en toelichting verdeeld — dan staat er een gat
       van vijftig pixels tussen "BOUWJAAR" en "2012". Nu zakt de ruimte naar
       onderen, waar hij niet opvalt. */
    display: flex;
    flex-direction: column;
    gap: 0.4rem;

    /* Kantelt onder de muis, net als de plaat op de zoekpagina — maar naar zijn
       eigen midden. De hoeken worden door fact-tilt.js gezet en blijven nul
       zolang die niet draait. Transform is puur verf: de subgrid-uitlijning
       hierboven verandert er niet van. */
    --rx: 0deg;
    --ry: 0deg;
    transform: perspective(800px) rotateX(var(--rx)) rotateY(var(--ry));
    transition: transform 160ms ease-out;

    @media (prefers-reduced-motion: reduce) {
      transform: none;
    }

    & .label {
      font-size: var(--step--2);
      text-transform: uppercase;
      letter-spacing: var(--tracking-index);
      color: var(--graphite-text);
      /* Altijd twee regels hoog, ook als er maar één woord staat. Daarmee
         beginnen álle waarden op dezelfde lijn — wat subgrid hier eerst deed —
         zonder dat de rest van het blokje mee uitrekt. "Eerste tenaamstelling
         in NL" is het langste label en past daarbinnen. */
      min-block-size: 2lh;
    }

    /* Een datum van tien tekens moet op één regel passen, anders breekt de rij.
       Alleen waarden mét een cijfer gaan naar de dataletter; "Grijs" hoort in
       de displayletter. */
    & .value {
      font-size: clamp(1.05rem, 2.2vw, 1.45rem);
      font-weight: 500;
      line-height: 1.1;
      white-space: nowrap;

      /* Twee kolommen op een smal scherm: een datum van tien tekens moet daar
         nog steeds op één regel passen. */
      @media (width < 40rem) {
        font-size: 0.95rem;
      }
    }

    & .value.num {
      font-family: var(--font-mono);
      font-variant-numeric: tabular-nums;
    }

    & .sub {
      font-size: var(--step--1);
      color: var(--graphite-text);
      font-family: var(--font-mono);
      font-variant-numeric: tabular-nums;
    }

    /* Kleur én woord: een verlopen APK zegt het ook als je de kleur niet ziet. */
    &[data-level="urgent"] {
      box-shadow: 0 0 0 1px var(--accent);

      & .value {
        color: var(--accent-text);
      }
    }

    &[data-level="warn"] {
      box-shadow: 0 0 0 1px var(--oxide);

      & .value {
        color: var(--oxide-text);
      }
    }
  }

  /* ── Zoeken ───────────────────────────────────────────────────────────────
     Het invoerveld ís de kentekenplaat. Eén ding op de pagina, je typt erin,
     enter zoekt. De plaat draagt zelf geen betekenis — hij zegt alleen wat je
     hier moet doen, en dat doet hij zonder één woord uitleg. */
  .search {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    align-items: stretch;
    max-inline-size: 34rem;

    /* Op het eerste scherm mag de plaat de volle kolom pakken. Deze regel staat
       in dezelfde laag als de regel die hij overschrijft — layout komt vóór
       components, dus daar zou hij het verliezen. */
    .hero & {
      max-inline-size: none;
    }
  }

  /* Zonder registratie animeren custom properties met sprongen; als <angle>
     interpoleren ze vloeiend. Nodig omdat de draai ín de transform zit, naast
     de ruststand en de muisafwijking. */
  /* Het wiegen. Twee eigen assen met een eigen periode, zodat de plaat nooit
     precies dezelfde stand herhaalt — elf en zeventien seconden hebben pas na
     drie minuten weer dezelfde fase. Geregistreerd met @property, want anders
     zijn custom properties niet te animeren: de browser weet dan niet dat er een
     hoek in staat en springt van waarde naar waarde. */
  @property --rx-wieg {
    syntax: "<angle>";
    inherits: false;
    initial-value: 0deg;
  }

  @property --ry-wieg {
    syntax: "<angle>";
    inherits: false;
    initial-value: 0deg;
  }

  @keyframes plaat-wieg-x {
    50% {
      --rx-wieg: 1.1deg;
    }
  }

  @keyframes plaat-wieg-y {
    50% {
      --ry-wieg: 2.2deg;
    }
  }

  @property --spin {
    syntax: "<angle>";
    inherits: false;
    initial-value: 0deg;
  }

  @keyframes plate-spin {
    to {
      --spin: 1turn;
    }
  }

  /* De houder: de zwarte omlijsting met de dealerregel eronder, zoals een
     merkdealer hem om de plaat schroeft. */
  /* Het draaiende voorwerp: lijst, plaat en dealerregel als één geheel. Het
     uiterlijk zit op .plate-face--front, want een zwart vlak op de houder zelf
     zou de draaiende plaat halverwege doorsnijden. */
  .plate-holder {
    position: relative;
    container-type: inline-size;

    /* De naam staat hier en niet op de gele plaat: anders vliegt alleen het geel
       mee en ploft de zwarte lijst met de URL er aan het eind ineens bij. */
    view-transition-name: kenteken;

    /* De slagschaduw van het hele voorwerp, die meezwaait met de kanteling.
       calc(var(--rx) / 1deg) maakt van de hoek een kaal getal, zodat je hem in
       pixels kunt uitdrukken. */
    box-shadow:
      calc(var(--ry, 0deg) / 1deg * -1.6px) calc(14px + var(--rx, 0deg) / 1deg * 1.6px) 34px
        rgb(0 0 0 / 0.7);

    /* De plaat ligt hier al onder een hoek, gespiegeld aan de stand waarin hij
       op het resultaatblad landt. Twee hoeken per as: een vaste ruststand en de
       afwijking die de muis eroverheen legt, zodat plate-tilt.js alleen de
       tweede hoeft te zetten. --spin komt van de foutdraai. */
    --rx-base: 7deg;
    --ry-base: 13deg;
    --rx: 0deg;
    --ry: 0deg;
    --spin: 0deg;
    --rx-wieg: 0deg;
    --ry-wieg: 0deg;
    transform: perspective(1200px)
      rotateX(calc(var(--rx-base) + var(--rx) + var(--rx-wieg) + var(--spin)))
      rotateY(calc(var(--ry-base) + var(--ry) + var(--ry-wieg)));
    transform-style: preserve-3d;
    will-change: transform;

    /* Hij staat nooit helemaal stil. Twee trage assen met verschillende periode:
       genoeg om te leven, te weinig om af te leiden of om scheef in te typen.
       Dit loopt óók op het resultaatblad — daar hangt hetzelfde bord. */
    animation:
      plaat-wieg-x 11s ease-in-out infinite,
      plaat-wieg-y 17s ease-in-out infinite;

    /* Onbekend kenteken: één slag op zijn plek. De vlucht naar een gevonden
       kenteken draait in de overgang zelf — zie 00-layers.css. Het wiegen moet
       in deze lijst herhaald worden: één animation-eigenschap vervangt de hele
       set, hij vult niet aan. */
    &[data-error="true"] {
      animation:
        plate-spin 760ms cubic-bezier(0.35, 0.65, 0.25, 1),
        plaat-wieg-x 11s ease-in-out infinite,
        plaat-wieg-y 17s ease-in-out infinite;
    }

    @media (prefers-reduced-motion: reduce) {
      transform: none;
      animation: none;

      &[data-error="true"] {
        animation: none;
      }
    }

    /* Geen blauwe ring om de plaat — die snijdt het object doormidden. Maar
       hélemaal geen focusteken laat toetsenbordgebruikers stranden, dus in
       plaats daarvan gaat de plaat zelf aan: het geel licht op en de randlichten
       op het voorvlak worden sterker. Een zichtbare toestandswisseling van het
       voorwerp in plaats van eromheen. */
    &:focus-within {
      outline: none;
      --plate: #ffd21a;

      & .plate-face--front {
        box-shadow:
          inset 0 1px 0 rgb(255 255 255 / 0.3),
          0 0 0 1px rgb(255 255 255 / 0.22);
      }
    }
  }

  /* Voor- en achterkant van hetzelfde voorwerp. Een view transition kan dit
     niet: die heeft alleen twee momentopnames van de vóórkant, dus daar bestaat
     een achterkant niet. */
  .plate-face {
    backface-visibility: hidden;
    border-radius: 4px;
  }

  .plate-face--front {
    /* Geen zwart achter de plaat, ook niet als vlak: het zwart is nu uitsluitend
       de strook onder de plaat, en die draagt zijn eigen achtergrond. Zolang het
       hier stond piepte het in de bovenhoeken onder het geel vandaan — de plaat
       heeft een rondere hoek dan de houder, dus daar bleef een zwart sikkeltje
       staan. Clippen loste dat maar half op; niets tekenen lost het helemaal op. */
    background: none;
    padding: 0;
    display: flex;
    flex-direction: column;
    box-shadow:
      inset 0 1px 0 rgb(255 255 255 / 0.14),
      /* Op het donkere podium valt de lijst anders weg tegen de achtergrond. */
      0 0 0 1px rgb(255 255 255 / 0.08);
  }

  /* De achterkant van het plaatmateriaal: hetzelfde geel, maar hol in plaats van
     bol, en met de tekens van voren doorgedrukt — dus in spiegelbeeld en
     ingedeukt in plaats van uitstekend. */
  .plate-back {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    transform: rotateY(180deg);
    background: linear-gradient(
      180deg,
      color-mix(in srgb, var(--plate) 88%, #000),
      color-mix(in srgb, var(--plate) 97%, #000) 55%,
      color-mix(in srgb, var(--plate) 92%, #fff)
    );
    /* Hol: de bovenrand ligt in de schaduw, de onderrand vangt licht. */
    box-shadow:
      inset 0 3px 8px rgb(0 0 0 / 0.35),
      inset 0 -2px 0 rgb(255 255 255 / 0.35);

    & .chars {
      /* Doorgedrukt vanaf de voorkant, dus omgekeerd te lezen. */
      transform: scaleX(-1);
      font-family: var(--font-mono);
      font-weight: 700;
      font-size: 15cqi;
      letter-spacing: 0.04em;
      line-height: 1;
      color: color-mix(in srgb, var(--plate) 82%, #000);
      /* Licht van linksboven op een holle vorm: schaduw bovenin, glans onderin
         — precies omgekeerd aan de voorkant. */
      text-shadow:
        0 1px 0 var(--plate-lit),
        0 -1px 1px rgb(0 0 0 / 0.45);
    }
  }

  .plate-holder .dealer {
    /* De zwarte strook zelf. Onderhoeken rond, bovenhoeken vierkant: hij sluit
       aan op de onderrand van de plaat. */
    background: var(--holder);
    border-radius: 0 0 4px 4px;
    color: #fff;
    text-align: center;
    font-family: var(--font-mono);
    font-size: clamp(0.6rem, 1.8cqi, 0.9rem);
    text-transform: uppercase;
    letter-spacing: 0.3em;
    /* De laatste letter krijgt zijn spatiëring ook mee; die duwt de regel
       anders een halve letter uit het midden. */
    text-indent: 0.3em;
    padding-block: 0.5rem 0.55rem;
  }

  .plate-field {
    display: flex;
    align-items: stretch;
    position: relative;
    /* 520 × 110 mm is de enige maat die ik in officiële bronnen bevestigd kreeg,
       samen met de opbouw: geel veld, zwart kader, blauw EU-vlak links. De
       tekenmaten van de moderne plaat staan niet in de bereikbare regelgeving —
       die verhoudingen zijn afgeleid uit de referentiefoto. Alles staat in cqi
       (procent van de plaatbreedte), dus de plaat klopt op elk formaat.

       Plaathoogte = 100 / 4,727 = 21,15cqi. Reken daarmee terug als je iets
       in millimeters wilt uitdrukken. */
    aspect-ratio: 520 / 110;
    --plate-margin: 0.95cqi; /* geel dat buiten het kader zichtbaar blijft */
    --plate-frame: 0.6cqi; /* dikte van het zwarte kader */

    background: linear-gradient(
      180deg,
      color-mix(in srgb, var(--plate) 93%, #fff),
      var(--plate) 45%,
      color-mix(in srgb, var(--plate) 93%, #000)
    );
    border-radius: 1.7cqi;
    /* De inhoud begint precies binnen het kader. */
    padding: calc(var(--plate-margin) + var(--plate-frame));
    overflow: hidden;
    container-type: inline-size;
    box-shadow: var(--sheet-relief);

    /* Het zwarte kader: een lijn ín het gele vlak, met geel eromheen. Geen
       border op het element zelf, want dan zou het geel aan de buitenkant
       ontbreken. */
    &::before {
      content: "";
      position: absolute;
      inset: var(--plate-margin);
      border: var(--plate-frame) solid var(--plate-ink);
      border-radius: 1.1cqi;
      pointer-events: none;
      z-index: 1;
    }

    &:has(input:user-invalid)::before {
      border-color: var(--oxide);
    }

    & .band {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 0.55em;
      /* 40 van 520 mm. */
      flex: 0 0 7.7%;
      padding-inline: 0;
      background: var(--peil);
      color: var(--on-fill);
      font-family: var(--font-mono);
      font-size: 2.4cqi;
      letter-spacing: 0.08em;
      line-height: 1;

      & svg {
        inline-size: 2em;
        fill: var(--eu-star);
      }
    }

    & input {
      flex: 1 1 auto;
      min-inline-size: 0;
      background: none;
      border: none;
      color: var(--plate-ink);
      font-family: var(--font-mono);
      font-weight: 700;
      /* Tekenhoogte ≈ 71% van de plaathoogte: 0,71 × 21,15cqi ≈ 15cqi. */
      font-size: 15cqi;
      line-height: 1;
      letter-spacing: 0.04em;
      text-align: center;
      text-transform: uppercase;
      padding: 0;
      padding-inline: 0.12em;
      text-shadow: var(--emboss);

      &:focus {
        outline: none;
      }

      &::placeholder {
        color: var(--plate-hint);
      }
    }
  }

  /* ── De knoppenrij ───────────────────────────────────────────────────────
     Foto links, ZOEKEN rechts. Niet even zwaar: de fotoknop is een tekstknop
     met een icoon, ZOEKEN is het gevulde vlak. Dat is de rangorde — typen is
     de hoofdweg, de camera is de afslag. */
  .knoppen {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.5rem 1.25rem;
    margin-block-start: 1.75rem;
  }

  .foto button {
    display: inline-flex;
    align-items: center;
    gap: 0.5em;
    font-size: var(--step--1);
    text-transform: uppercase;
    letter-spacing: var(--tracking-index);
    color: var(--graphite-text);
    background: none;
    border: 0;
    padding: 0.4em 0.2em;
    cursor: pointer;
    transition: color var(--duur-kort) var(--ease-out);

    & svg {
      inline-size: 1.35em;
      fill: none;
      stroke: currentcolor;
      stroke-width: 1.5;
      stroke-linejoin: round;
    }

    &:hover,
    &:focus-visible {
      color: var(--ink);
    }

    /* Terwijl de lezer wordt opgehaald en de foto wordt gelezen. */
    &:disabled {
      cursor: progress;
      opacity: 0.6;
    }
  }

  /* Staat onder de rij, niet erin: een melding die de knoppen opzij duwt is
     erger dan het wachten zelf. */
  .foto__melding {
    margin-block-start: 0.75rem;
    font-size: var(--step--1);
    color: var(--graphite-text);
    max-inline-size: 26rem;
    margin-inline: auto;
    text-align: center;
    text-wrap: balance;

    &:empty {
      display: none;
    }
  }

  /* Alleen de verzendknop. De fotoknop ernaast is bewust een tekstknop; zonder
     het type in de selector kreeg die hetzelfde gevulde vlak en stonden er twee
     even zware knoppen naast elkaar. De uitlijning zit nu op .knoppen. */
  .search button[type="submit"] {
    padding: 0.9em 2em;
    font-size: var(--step--1);
    text-transform: uppercase;
    letter-spacing: var(--tracking-index);
    color: var(--vellum);
    border: 1px solid var(--ink);
    border-radius: var(--radius);
    cursor: pointer;

    /* De blauwe veeg is een schuin verloopvlak van tweeënhalf keer de knop. In
       rust staat het venster op de doorzichtige helft; bij hover schuift het naar
       de gekleurde, en dan loopt het blauw diagonaal binnen vanaf linksonder.
       Eén eigenschap in beweging, dus goedkoop. */
    background-color: var(--ink);
    background-image: linear-gradient(to top right, var(--accent) 50%, transparent 50%);
    background-size: 250% 250%;
    background-position: 100% 0%;
    transition:
      background-position var(--duur-veeg) var(--ease-out),
      color var(--duur-kort) var(--ease-out),
      border-color var(--duur-veeg) var(--ease-out);

    &:hover,
    &:focus-visible {
      background-position: 0% 100%;
      border-color: var(--accent);
      /* Wit haalt 7,05:1 op het accentblauw; de paginakleur zou op het donkere
         podium onder de drempel duiken. */
      color: var(--on-fill);
    }

    @media (prefers-reduced-motion: reduce) {
      transition: none;
    }
  }

  /* Een knop die zich als tekstlink gedraagt — voor "lijst wissen", waar een
     volwaardige knop te veel gewicht zou geven. */
  .linkish {
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    font-size: var(--step--1);
    color: var(--accent-text);
    text-decoration: underline;
    text-underline-offset: 0.2em;
  }

  /* ── Laadindicatie ────────────────────────────────────────────────────────
     Verschijnt pas als het formulier op weg is. Hij staat áltijd in de HTML —
     zo hoeft er niets bijgetekend te worden op het moment dat de pagina al
     bezig is weg te gaan. */
  .loader {
    display: none;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    margin-block-start: 0.5rem;

    [data-bezig="true"] & {
      display: flex;
    }

    & svg {
      inline-size: 7rem;
      overflow: visible;
    }

    & .loader__body {
      fill: none;
      stroke: var(--ink);
      stroke-width: 3;
      stroke-linejoin: round;
    }

    & .loader__glass {
      fill: none;
      stroke: var(--ink);
      stroke-width: 2;
      opacity: 0.5;
    }

    & .loader__wheel {
      fill: none;
      stroke: var(--ink);
      stroke-width: 3;
      transform-box: fill-box;
      transform-origin: center;
      animation: wiel-draait 700ms linear infinite;
    }

    & .loader__hub {
      fill: var(--ink);
      stroke: none;
    }

    /* Het wegdek schuift, de auto staat stil — anders rijdt hij het beeld uit. */
    & .loader__road {
      stroke: var(--graphite-text);
      stroke-width: 3;
      stroke-linecap: round;
      stroke-dasharray: 10 14;
      animation: weg-schuift 700ms linear infinite;
    }

    & .loader__text {
      font-size: var(--step--2);
      text-transform: uppercase;
      letter-spacing: var(--tracking-index);
      color: var(--graphite-text);
      font-family: var(--font-mono);
    }
  }

  @keyframes wiel-draait {
    to {
      transform: rotate(1turn);
    }
  }

  @keyframes weg-schuift {
    to {
      stroke-dashoffset: -24;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .loader__wheel,
    .loader__road {
      animation: none;
    }
  }

  .search-error {
    font-size: var(--step--2);
    text-transform: uppercase;
    letter-spacing: var(--tracking-index);
    color: var(--plate);
    font-family: var(--font-mono);

    &:empty {
      display: none;
    }
  }

  /* ── Statustegels ─────────────────────────────────────────────────────────
     Urgente tegels staan vooraan én dragen een woord; kleur is nooit het enige
     signaal. Op een magenta vlak wordt alle tekst wit. */
  .tile {
    box-shadow: 0 0 0 1px var(--rule);
    background: var(--sheet);
    /* Gelijk aan .fact — zie de opmerking bij .facts hierboven. */
    padding: 1rem 1rem 1.15rem;

    @media (width < 40rem) {
      padding: 0.8rem 0.8rem 0.9rem;
    }
    min-block-size: var(--blok-hoogte);
    display: flex;
    flex-direction: column;
    gap: 0.4rem;

    & .label {
      min-block-size: 2lh;
      font-size: var(--step--2);
      text-transform: uppercase;
      letter-spacing: var(--tracking-index);
      color: var(--graphite-text);
    }

    & .value {
      font-size: var(--step-1);
      font-weight: 500;
    }

    /* Bijna elke toelichting bevat een datum of een aantal dagen. */
    & .detail {
      font-size: var(--step--1);
      color: var(--graphite-text);
      font-family: var(--font-mono);
      font-variant-numeric: tabular-nums;
    }

    &[data-level="urgent"] {
      background: var(--accent);
      box-shadow: 0 0 0 1px var(--accent);

      &,
      & * {
        color: var(--on-fill);
      }
    }

    &[data-level="warn"] {
      box-shadow: 0 0 0 1px var(--oxide);

      & .value {
        color: var(--oxide-text);
      }
    }
  }

  /* ── Kaartraster ──────────────────────────────────────────────────────────
     auto-fill, niet auto-fit: één kaart hoort normale breedte te houden.
     De 4px gap is er omdat elke kaart aan weerszijden een hairline draagt. */
  .cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
    grid-auto-rows: 1fr;
    gap: 4px;

    /* Op de zoekpagina staan hier modelnamen in, en die zijn lang:
       "Mercedes-Benz C63 AMG Black Series" viel op 15rem over drie regels uit
       elkaar. Vier rem breder scheelt op een gewoon scherm één kolom en zet
       vrijwel elke naam op twee regels. */
    body.stage & {
      grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
    }
  }

  .card {
    box-shadow: 0 0 0 1px var(--rule);
    background: var(--sheet);
    padding: 0.9rem 0.9rem 1.15rem;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    position: relative;
    text-decoration: none;
    color: inherit;

    /* De bewust opgeschreven decoratieve uitzondering: dit voetje wijst niets
       aan, het bindt het raster tot één blad. */
    &::after {
      content: "";
      position: absolute;
      inset-block-end: 0;
      inset-inline: 0;
      block-size: 3px;
      background: var(--accent);
    }

    & .label {
      font-size: var(--step--2);
      text-transform: uppercase;
      letter-spacing: var(--tracking-index);
      color: var(--graphite-text);
    }

    & .value {
      font-size: var(--step-1);
      font-weight: 500;
    }
  }

  /* Kaarten krijgen dezelfde diagonale veeg als de knop: één bewegingstaal voor
     de hele site, in plaats van per component iets anders. */
  a.card {
    background-image: linear-gradient(to top right, var(--accent) 50%, transparent 50%);
    background-size: 250% 250%;
    background-position: 100% 0%;
    transition:
      background-position var(--duur-veeg) var(--ease-out),
      box-shadow var(--duur-veeg) var(--ease-out),
      color var(--duur-kort) var(--ease-out);

    @media (prefers-reduced-motion: reduce) {
      transition: none;
    }
  }

  a.card:hover,
  a.card:focus-visible {
    background-position: 0% 100%;
    box-shadow: 0 0 0 1px var(--accent);

    &,
    & * {
      color: var(--on-fill);
    }
  }

  /* ── Register ─────────────────────────────────────────────────────────────
     Voor lijsten die je doorleest. Labelkolom links, waarde ernaast; getallen
     in de dataletter zodat ze langs één linkerlijn uitkomen. Bedragen die je
     wél vergelijkt krijgen .num en lijnen rechts uit. */
  .register {
    inline-size: 100%;
    font-size: var(--step-0);

    & caption {
      text-align: start;
      font-size: var(--step--2);
      text-transform: uppercase;
      letter-spacing: var(--tracking-index);
      color: var(--graphite-text);
      padding-block-end: 0.5rem;
    }

    & thead th {
      font-size: var(--step--2);
      text-transform: uppercase;
      letter-spacing: var(--tracking-index);
      color: var(--graphite-text);
      font-weight: 400;
      text-align: start;
    }

    & th,
    & td {
      border-block-end: var(--hair);
      padding-block: 0.55rem;
      padding-inline-end: 1.25rem;
      vertical-align: baseline;
      text-align: start;
    }

    & tbody th {
      font-weight: 400;
      color: var(--graphite-text);
      inline-size: 40%;
      min-inline-size: 11rem;
    }

    & tbody tr:hover {
      background: var(--sheet);
    }

    & td:last-child,
    & th:last-child {
      padding-inline-end: 0;
    }

    & .unit {
      color: var(--graphite-text);
      font-family: var(--font-display);
      font-size: var(--step--1);
      margin-inline-start: 0.35em;
    }

    /* Cijfers en identificaties in de dataletter, langs de linkerlijn van de
       waardekolom — de eenheid staat er direct achter en die lees je mee. */
    & .num {
      font-family: var(--font-mono);
      font-variant-numeric: tabular-nums;
    }

    /* Alleen bedragen die je onderling vergelijkt lijnen rechts uit. */
    & .amount {
      text-align: end;
    }
  }

  /* ── Tijdlijn van tenaamstellingen ────────────────────────────────────────*/
  .timeline {
    display: flex;
    flex-direction: column;
    border-block-start: var(--hair);
  }

  .timeline li {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.4rem 1rem;
    padding-block: 0.7rem;
    border-block-end: var(--hair);

    & time {
      font-family: var(--font-mono);
      font-variant-numeric: tabular-nums;
      min-inline-size: 7rem;
    }

    & .what {
      font-weight: 500;
    }

    & .span {
      color: var(--graphite-text);
      font-size: var(--step--1);
      margin-inline-start: auto;
      font-variant-numeric: tabular-nums;
    }

    /* Urgent draagt het magenta; de huidige stand krijgt gewicht, geen kleur. */
    &[data-level="urgent"] .what {
      color: var(--accent-text);
    }

    &[data-level="warn"] .what {
      color: var(--oxide-text);
    }

    &[data-current="true"] {
      background: var(--sheet);
      box-shadow: inset 3px 0 0 var(--ink);
      padding-inline-start: 0.75rem;
    }
  }

  /* ── APK-beurt ────────────────────────────────────────────────────────────*/
  .beurt .defects {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    margin-block-start: 0.5rem;
  }

  .beurt .defect {
    display: flex;
    gap: 0.6rem;
    align-items: baseline;
    font-size: var(--step--1);

    & code {
      font-family: var(--font-mono);
      color: var(--graphite-text);
      flex: none;
    }
  }

  /* ── Uitleg en lege staat ─────────────────────────────────────────────────*/
  .notice {
    border-inline-start: 3px solid var(--rule);
    padding-inline-start: 0.9rem;
    font-size: var(--step--1);
    color: var(--graphite-text);
    max-inline-size: 46rem;
  }

  .notice strong {
    color: var(--ink);
    font-weight: 500;
  }

  .empty {
    font-size: var(--step--1);
    color: var(--graphite-text);
    padding-block: 0.5rem;
  }

  /* ── Brongegevens ─────────────────────────────────────────────────────────*/
  .source {
    border-block-end: var(--hair);

    & summary {
      cursor: pointer;
      padding-block: 0.6rem;
      font-size: var(--step--2);
      text-transform: uppercase;
      letter-spacing: var(--tracking-index);
      color: var(--graphite-text);
      display: flex;
      gap: 0.6em;

      & .id {
        font-family: var(--font-mono);
        color: var(--graphite-text);
        font-weight: 500;
      }

      &::marker {
        color: var(--accent-text);
      }
    }

    & pre {
      font-family: var(--font-mono);
      font-size: var(--step--1);
      overflow-x: auto;
      padding-block-end: 1rem;
      color: var(--graphite-text);
    }
  }

  /* ── Het cijferblad ───────────────────────────────────────────────────────
     Balkjes zonder grafiekbibliotheek: een tabelcel met een gevuld blokje erin.
     De breedte komt uit een data-attribuut in stappen van vijf procent — een
     style-attribuut zou de CSP weigeren, en dat is het niet waard. */
  .cijfers .balk {
    inline-size: 60%;
    padding-block: 0.35rem;

    & span {
      display: block;
      block-size: 0.6rem;
      background: var(--accent);
      border-radius: 1px;
      min-inline-size: 1px;
    }

    & [data-deel="0"] { inline-size: 0%; }
    & [data-deel="5"] { inline-size: 5%; }
    & [data-deel="10"] { inline-size: 10%; }
    & [data-deel="15"] { inline-size: 15%; }
    & [data-deel="20"] { inline-size: 20%; }
    & [data-deel="25"] { inline-size: 25%; }
    & [data-deel="30"] { inline-size: 30%; }
    & [data-deel="35"] { inline-size: 35%; }
    & [data-deel="40"] { inline-size: 40%; }
    & [data-deel="45"] { inline-size: 45%; }
    & [data-deel="50"] { inline-size: 50%; }
    & [data-deel="55"] { inline-size: 55%; }
    & [data-deel="60"] { inline-size: 60%; }
    & [data-deel="65"] { inline-size: 65%; }
    & [data-deel="70"] { inline-size: 70%; }
    & [data-deel="75"] { inline-size: 75%; }
    & [data-deel="80"] { inline-size: 80%; }
    & [data-deel="85"] { inline-size: 85%; }
    & [data-deel="90"] { inline-size: 90%; }
    & [data-deel="95"] { inline-size: 95%; }
    & [data-deel="100"] { inline-size: 100%; }
  }

  /* ── Het camerascherm ────────────────────────────────────────────────────
     Een <dialog> op volledig scherm: het beeld vult alles, de knoppen liggen
     eronder in de duim­zone. Geen registerblad hier — dit is een camera, en die
     hoort eruit te zien als een camera. */
  .camera {
    inline-size: 100dvw;
    max-inline-size: 100dvw;
    block-size: 100dvh;
    max-block-size: 100dvh;
    margin: 0;
    padding: 0;
    border: 0;
    background: #000;
    color: #fff;
    overflow: hidden;

    &::backdrop {
      background: #000;
    }

    & video {
      inline-size: 100%;
      block-size: 100%;
      /* Vullen en bijsnijden: zwarte balken maken het beeld kleiner en dus de
         plaat ook, en die moet juist zo groot mogelijk in beeld. */
      object-fit: cover;
    }
  }

  /* Het richtkader. Verhouding van een kentekenplaat, zodat je hem er letterlijk
     in legt — en dat is meteen de beste hulp die je kunt geven: een plaat die
     dit kader vult is groot genoeg om te lezen. */
  .camera__richt {
    position: absolute;
    inset-inline: 8%;
    inset-block-start: 50%;
    translate: 0 -50%;
    aspect-ratio: 520 / 110;
    border: 2px solid rgb(255 255 255 / 0.85);
    border-radius: 4px;
    box-shadow: 0 0 0 100vmax rgb(0 0 0 / 0.45);
    pointer-events: none;
  }

  .camera__uitleg {
    position: absolute;
    inset-inline: 0;
    inset-block-start: calc(50% + 12vw);
    text-align: center;
    font-size: var(--step--1);
    text-transform: uppercase;
    letter-spacing: var(--tracking-index);
    color: rgb(255 255 255 / 0.8);
    pointer-events: none;
  }

  .camera__knoppen {
    position: absolute;
    inset-inline: 0;
    inset-block-end: 0;
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 1rem;
    padding: 1.5rem 1.25rem calc(1.5rem + env(safe-area-inset-bottom));
    background: linear-gradient(to top, rgb(0 0 0 / 0.75), transparent);
  }

  .camera__tekst {
    background: none;
    border: 0;
    color: #fff;
    font-size: var(--step--1);
    text-transform: uppercase;
    letter-spacing: var(--tracking-index);
    cursor: pointer;
    padding: 0.6em 0.2em;

    &:last-child {
      justify-self: end;
    }
  }

  /* De sluiter: de bekende ring met een schijf erin. */
  .camera__sluiter {
    inline-size: 4.4rem;
    block-size: 4.4rem;
    border-radius: 50%;
    border: 3px solid #fff;
    background: none;
    cursor: pointer;
    padding: 0;

    &::after {
      content: "";
      display: block;
      inline-size: 100%;
      block-size: 100%;
      border-radius: 50%;
      background: #fff;
      scale: 0.82;
      transition: scale var(--duur-kort) var(--ease-out);
    }

    &:active::after {
      scale: 0.7;
    }
  }
}

/* ── 05-utilities.css ──────────────────────────────────────────── */
@layer utilities {
  .mono {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums lining-nums;
  }

  .muted {
    color: var(--graphite-text);
  }

  /* Losse blokken die op afstand van hun voorganger moeten staan. Dit stond
     tien keer als style-attribuut in de views; daarmee stapte het buiten de
     lagen om én stond het een Content-Security-Policy zonder 'unsafe-inline' in
     de weg. Twee redenen die los van elkaar al genoeg zijn. */
  .gap-blok {
    margin-block-start: 2rem;
  }

  .gap-blok-sm {
    margin-block-start: 1.25rem;
  }

  .stack-sm > * + * {
    margin-block-start: 0.5rem;
  }

  .stack > * + * {
    margin-block-start: 1rem;
  }

  .vh {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* ── Testpaneel ─────────────────────────────────────────────────────────────
   Staat bewust in de utilities-laag en niet bij de componenten: dit hoort niet
   bij het ontwerp, het is gereedschap dat alleen buiten productie meekomt. Het
   mag er daarom ook uitzien als gereedschap. */
@layer utilities {
  .debug {
    position: fixed;
    z-index: 100;
    inline-size: 17rem;
    max-inline-size: calc(100vw - 1rem);
    background: #121412;
    color: #e8eae7;
    border: 1px solid #3a403a;
    border-radius: 3px;
    box-shadow: 0 10px 30px rgb(0 0 0 / 0.5);
    font-family: var(--font-mono);
    font-size: 11px;
    overflow: hidden;

    &[data-collapsed="true"] .debug__list {
      display: none;
    }
  }

  .debug__grip {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.4rem 0.5rem;
    background: #1d201d;
    border-block-end: 1px solid #3a403a;
    cursor: grab;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: #9aa09a;
    touch-action: none;

    &:active {
      cursor: grabbing;
    }
  }

  .debug__toggle {
    background: none;
    border: none;
    color: inherit;
    font: inherit;
    cursor: pointer;
    padding-inline: 0.4rem;
  }

  .debug__list {
    max-block-size: 60vh;
    overflow-y: auto;
  }

  .debug__list a {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    padding: 0.4rem 0.5rem;
    text-decoration: none;
    border-block-end: 1px solid #23262300;

    &:hover {
      background: #1d201d;
    }

    &[aria-current="page"] {
      background: #0047e0;
      color: #fff;
    }
  }

  .debug__plate {
    color: var(--plate);
    font-weight: 700;
    letter-spacing: 0.06em;
  }

  .debug__list a[aria-current="page"] .debug__plate {
    color: #fff;
  }

  .debug__what {
    color: #8b918b;
    font-size: 10px;
  }

  .debug__list a[aria-current="page"] .debug__what {
    color: #cfe0ff;
  }
}
