/* ============================================================
   THESEO V2 · "Het Instituut voor Zichtbaarheid"
   Eén systeem dat Gianluca's keuzes versmelt:
     · Canvas    = blueprint-raster (C8) op elke pagina
     · Stem      = kinetische typografie, regel-maskers (C9)
     · Bewijs    = Zwitsers mono-annotatiesysteem (C10)
     · Afsluiting= de gigantische "Volgende stap" (C4-04)
     · Instrument A = terminalvenster voor alles-AI (C7)
     · Instrument B = levende zoekmachine, kennisbank (C6)
   De annotatie-grammatica (SRV/FIG/INS/DOC + mono) is het
   bindmiddel: blueprint, Zwitsers en terminal spreken haar alle
   drie. Zo voelt niets out of place.
   ============================================================ */

/* ------------------------------------------------------------
   Lettertypen · zelf gehost, geen verbinding met een externe
   partij. Precies de sneden die de pagina's hiervoor extern
   opvroegen (Inter 400/600/700/800, JetBrains Mono 400/500/700,
   Instrument Serif cursief), zodat de typografie niet
   verschuift. De rechte Instrument Serif
   staat erbij voor koppen die de cursieve em los gebruiken.
   Bestanden staan in /assets/fonts/, licentie SIL Open Font.
   ------------------------------------------------------------ */
@font-face { font-family: Inter; src: url('/assets/fonts/inter-400.woff2') format('woff2'); font-style: normal; font-weight: 400; font-display: swap; }
@font-face { font-family: Inter; src: url('/assets/fonts/inter-600.woff2') format('woff2'); font-style: normal; font-weight: 600; font-display: swap; }
@font-face { font-family: Inter; src: url('/assets/fonts/inter-700.woff2') format('woff2'); font-style: normal; font-weight: 700; font-display: swap; }
@font-face { font-family: Inter; src: url('/assets/fonts/inter-800.woff2') format('woff2'); font-style: normal; font-weight: 800; font-display: swap; }
@font-face { font-family: "JetBrains Mono"; src: url('/assets/fonts/jetbrains-mono-400.woff2') format('woff2'); font-style: normal; font-weight: 400; font-display: swap; }
@font-face { font-family: "JetBrains Mono"; src: url('/assets/fonts/jetbrains-mono-500.woff2') format('woff2'); font-style: normal; font-weight: 500; font-display: swap; }
@font-face { font-family: "JetBrains Mono"; src: url('/assets/fonts/jetbrains-mono-700.woff2') format('woff2'); font-style: normal; font-weight: 700; font-display: swap; }
@font-face { font-family: "Instrument Serif"; src: url('/assets/fonts/instrument-serif-400.woff2') format('woff2'); font-style: normal; font-weight: 400; font-display: swap; }
@font-face { font-family: "Instrument Serif"; src: url('/assets/fonts/instrument-serif-400-italic.woff2') format('woff2'); font-style: italic; font-weight: 400; font-display: swap; }

:root {
  /* De haarlijn van de startpagina, sitebreed sinds 10-09-2026.
     De startpagina zet deze waarde zelf in home-atelier.css en gebruikt hem
     voor de FAQ en voor de uitklapper. Overal elders viel hij terug op --hair,
     rgba(255,255,255,.08): half zo zichtbaar en zonder de blauwe zweem. Dat is
     een van de redenen dat dezelfde vorm op de startpagina rustiger oogt dan
     op de rest van de site. Nu staat de waarde hier, dus valt niets meer terug.
     De startpagina houdt zijn eigen definitie; die is identiek. */
  --home-border: rgba(174, 185, 255, .17);
  --home-border-strong: rgba(174, 185, 255, .3);
  --ink: #e5edff;
  --mut: #94a3b8;
  --mut2: #64748b;
  --blue: #1652f0;
  --per: #6e79ff;
  --per2: #8b95ff;
  /* Knopachtergrond. Donkerder dan --per2 zodat witte tekst erop 5,6:1 haalt;
     --per2 blijft de kleur voor tekstlinks, die staan op een donkere ondergrond. */
  --btn: #4c5bd4;
  --cyan: #67e8f9;
  --paper: #050a16;
  --paper2: #070e1f;
  --line: rgba(139,149,255,.14);
  --hair: rgba(255,255,255,.08);
  --mono: "JetBrains Mono", ui-monospace, monospace;
  --serif: "Instrument Serif", Georgia, serif;
  --ease: cubic-bezier(.16,1,.3,1);
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; overflow-x: hidden; -webkit-tap-highlight-color: transparent; }
body {
  margin: 0; background: var(--paper); color: var(--ink);
  font-family: Inter, -apple-system, sans-serif; font-size: 16px; line-height: 1.65;
}
a { color: inherit; }
::selection { background: rgba(110,121,255,.32); color: #fff; }
* { scrollbar-width: thin; scrollbar-color: rgba(110,121,255,.4) transparent; }
::-webkit-scrollbar { width: 10px; }
::-webkit-scrollbar-thumb { background: rgba(110,121,255,.3); border: 3px solid transparent; background-clip: content-box; border-radius: 999px; }

/* De skiplink blijft buiten beeld totdat een toetsenbordgebruiker hem kiest.
   Hij blijft wel het eerste focusdoel en verschijnt dan boven iedere laag. */
.v2skip {
  position: fixed;
  top: 8px;
  left: max(8px, env(safe-area-inset-left));
  z-index: 10000;
  padding: 10px 14px;
  border: 2px solid var(--per2);
  border-radius: 8px;
  background: #fff;
  color: #050914;
  font: 700 14px/1.25 Inter, -apple-system, sans-serif;
  transform: translateY(calc(-100% - 16px));
  transition: transform 160ms var(--ease);
}
.v2skip:focus {
  transform: translateY(0);
  outline: 3px solid #050914;
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .v2skip { transition: none; }
}

/* ============================================================
   CANVAS · het blueprint-papier (C8): fijn tekenraster met een
   zachte lichtvlek linksboven, plus heel subtiele maatstrepen
   aan de randen. Vast, kalm, op elke pagina identiek.
   ============================================================ */
.v2bg { position: fixed; inset: 0; z-index: -1; pointer-events: none; overflow: hidden;
  background:
    radial-gradient(110% 80% at 22% -10%, #0a1430 0%, transparent 55%),
    linear-gradient(180deg, #050a16 0%, #060d1d 50%, #04070f 100%);
}
.v2bg .grid {
  position: absolute; inset: 0;
  background-image:
    linear-gradient(var(--line) 1px, transparent 1px),
    linear-gradient(90deg, var(--line) 1px, transparent 1px),
    linear-gradient(rgba(139,149,255,.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(139,149,255,.05) 1px, transparent 1px);
  background-size: 96px 96px, 96px 96px, 24px 24px, 24px 24px;
  mask-image: radial-gradient(140% 100% at 50% 0%, #000 30%, rgba(0,0,0,.35) 100%);
  -webkit-mask-image: radial-gradient(140% 100% at 50% 0%, #000 30%, rgba(0,0,0,.35) 100%);
}
/* meetlat langs de linkerrand · het "blad" is opgespannen */
.v2bg .ruler {
  position: absolute; top: 0; bottom: 0; left: 0; width: 18px;
  background: repeating-linear-gradient(180deg, rgba(139,149,255,.24) 0 1px, transparent 1px 24px);
  opacity: .35;
}
.v2bg .glow {
  position: absolute; left: 50%; top: 0; width: min(1400px,150vw); height: 800px;
  transform: translate(-52%, -56%);
  background: radial-gradient(50% 50% at 50% 50%, rgba(70,110,255,.2), transparent 70%);
  animation: v2glow 24s ease-in-out infinite;
}
@keyframes v2glow {
  0%,100% { opacity:.7; transform: translate(-56%,-58%) scale(1); }
  50%     { opacity:1;  transform: translate(-47%,-53%) scale(1.07); }
}
.v2bg .grain { position:absolute; inset:0; opacity:.12;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='.4'/%3E%3C/svg%3E"); }

/* ============================================================
   ANNOTATIES · de gedeelde grammatica (C10/C8/C7)
   ============================================================ */
.ann {
  font-family: var(--mono); font-size: 11px; font-weight: 500;
  letter-spacing: .08em; text-transform: uppercase; color: var(--mut2);
  font-variant-numeric: tabular-nums;
}
.ann b { color: var(--per2); font-weight: 700; }
.annR { text-align: right; }
/* maatlijn met pijltjes (blueprint) */
.dim { display:flex; align-items:center; gap:8px; }
.dim::before, .dim::after { content:""; flex:1; height:1px; background: var(--line); }

/* ============================================================
   SHELL · header + footer (identiek op alle 10 pagina's)
   ============================================================ */
/* ============================================================
   NAVIGATIE · "de index van het instituut"
   Geen menubalk maar een register: een dunne strook met alleen
   het merk en één woord ("Index"). Klik en het blad vouwt open
   tot een volledig overzicht met genummerde regels, korte
   omschrijvingen en de AI-familie apart in mono. Subtiel in rust,
   volkomen duidelijk zodra je hem opent.
   ============================================================ */
.v2hdr { position: sticky; top: 0; z-index: 60; pointer-events: none; }
/* Open menu: header wordt fixed (zelfde plek als sticky-top), zodat hij
   op zijn plaats blijft terwijl de body op slot staat via .navLock */
.v2hdr.is-open { position: fixed; left: 0; right: 0; }
.v2hdr-in {
  pointer-events: auto;
  /* boven het register: het taalblad (.v2tk-uit) hangt onder de knop en viel achter het open register weg
     (Gianluca 16-09: "als ik het menu open heb en ik wil de taal selecteren"). Het register krijgt bij open een
     backdrop-filter en dus een eigen stapelcontext; de strook moet dan zelf gepositioneerd en hoger staan. */
  position: relative; z-index: 2;
  display: flex; align-items: center; gap: 22px;
  padding: 24px max(36px, env(safe-area-inset-right)) 22px max(36px, env(safe-area-inset-left));
  border-bottom: 1px solid transparent;
  transition: background .6s var(--ease), border-color .6s var(--ease),
              backdrop-filter .6s var(--ease);
}
.v2hdr.isScrolled .v2hdr-in {
  background: rgba(5,9,20,.72);
  border-bottom-color: var(--hair);
  backdrop-filter: blur(16px) saturate(140%);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
}
@media (max-width: 620px) {
  .v2hdr.isScrolled .v2hdr-in {
    background: #050914;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}
/* Open register: de strook en het blad worden één doorlopend vlak -
   zelfde achtergrond, geen scheidingslijn ertussen */
.v2hdr.is-open .v2hdr-in {
  background: rgba(5,9,20,.94);
  border-bottom-color: transparent;
  backdrop-filter: blur(22px) saturate(140%);
  -webkit-backdrop-filter: blur(22px) saturate(140%);
}
.v2logo {
  display: block; line-height: 1.1;
  font-weight: 800; font-size: 1.7rem; letter-spacing: -.045em;
  color: #fff; text-decoration: none;
}
.v2logo i { font-style: normal; color: var(--per2); margin-right: 1px; animation: v2dot 4s ease-in-out infinite; }
@keyframes v2dot { 0%,100%{opacity:1} 50%{opacity:.45} }
.v2logo .ann { display: block; margin: 3px 0 0; font-size: 10.5px; letter-spacing: .14em; }

/* De enige knop in rust: het register openen */
.v2toggle {
  margin-left: auto; display: inline-flex; align-items: center; gap: 11px;
  background: none; border: 0; cursor: pointer; padding: 6px 2px;
  font-family: var(--mono); font-size: 11.5px; letter-spacing: .16em;
  text-transform: uppercase; color: #9aa6c6;
  transition: color .5s var(--ease);
}
.v2toggle:hover { color: #fff; }
.v2toggle .lines { display: inline-block; width: 22px; }
.v2toggle .lines i { display: block; height: 1px; background: currentColor; margin: 4px 0;
  transition: transform .5s var(--ease), opacity .4s var(--ease), width .5s var(--ease); }
.v2toggle .lines i:nth-child(1) { width: 22px; }
.v2toggle .lines i:nth-child(2) { width: 14px; }
.v2toggle .lines i:nth-child(3) { width: 18px; }
.v2toggle:hover .lines i { width: 22px; }
.v2toggle[aria-expanded="true"] .lines i:nth-child(1) { transform: translateY(5px) rotate(45deg); }
.v2toggle[aria-expanded="true"] .lines i:nth-child(2) { opacity: 0; }
.v2toggle[aria-expanded="true"] .lines i:nth-child(3) { width: 22px; transform: translateY(-5px) rotate(-45deg); }
.v2toggle .lbl-open { display: inline; } .v2toggle .lbl-close { display: none; }
.v2toggle[aria-expanded="true"] .lbl-open { display: none; }
.v2toggle[aria-expanded="true"] .lbl-close { display: inline; }

/* Het register zelf: geen paneel dat voor de pagina schuift maar de
   header die doorgroeit. grid-template-rows 0fr naar 1fr vouwt exact naar
   de werkelijke inhoudshoogte, geen dode animatietijd zoals bij een
   max-height-sluier, en sluiten begint dus meteen.

   ============================================================================
   DE CHOREOGRAFIE, HERZIEN 09-09-2026. Gianluca: "als ik het menu dichtdoe is
   dat niet helemaal smooth en je krijgt echt 0,3 seconden dit te zien, waardoor
   het clunky voelt. Smoothness is echt belangrijk. En als er animaties zijn wil
   ik dat ze ook ergens vandaan komen, dus het menu moet echt van boven komen."

   WAT ER MIS WAS, uitgerekend uit de regels zelf en niet uit gevoel.
     openen : de vouw duurde 0,85 s, en de regels landden met een trap die
              opliep tot 0,64 s. De laatste regel stond dus pas na 1,24 s stil.
     sluiten: de vouw duurde 0,55 s, maar de regels vervaagden in 0,3 s zonder
              trap. En drie blokken bewogen helemaal niet mee: de navigatierij
              bovenin, de kolomkoppen en de voet.
   Gevolg: vanaf 0,3 s tot 0,55 s stond er een leeg paneel met alleen die drie
   blokken erin. Dat kwart seconde is precies wat er op zijn schermafdruk staat.

   WAT ER NU STAAT.
   1. Sluiten is EEN beweging. Niets verdwijnt eerder dan het paneel: alle
      inhoud houdt zijn open stand vast tot de vouw dicht is, en valt daarna
      terug op zijn beginstand terwijl het paneel al onzichtbaar is. Dat gebeurt
      met een overgang van 0 s met een vertraging die gelijk is aan de vouw, dus
      zonder een extra klasse en zonder JavaScript.
   2. Alles komt van BOVEN. De regels kwamen van 10 px eronder omhoog, wat bij
      een blad dat naar beneden uitklapt tegen de richting in leest. Ze komen nu
      van 10 px erboven mee naar beneden, en de drie blokken die niet bewogen
      doen dat nu ook.
   3. Korter. Openen 0,52 s in plaats van 0,85 s, met een trap van 40 ms per
      regel over hooguit zes regels in plaats van 60 ms over elf. De laatste
      regel staat nu na 0,76 s stil in plaats van na 1,24 s. Sluiten 0,42 s, dus
      weggaan blijft korter dan aankomen; dat is dezelfde huisregel als bij de
      paginaovergang verderop.
   ============================================================================ */
.v2reg {
  /* EEN plek voor de twee tijden van het register. Ze stonden op twee
     plekken (hier en in het apptempo-blok verderop) en dat is precies hoe
     de haperende sluiting kon ontstaan: het ene blok zette de vouw op 0,55 s
     en het andere op 260 ms, en de regel die de inhoud liet vervagen hoorde
     bij de eerste. Beide blokken lezen nu deze twee waarden. */
  --reg-in: 380ms;
  --reg-uit: 260ms;
  pointer-events: auto; position: absolute; left: 0; right: 0; top: 100%;
  background: rgba(5,9,20,.94);
  backdrop-filter: blur(22px) saturate(140%); -webkit-backdrop-filter: blur(22px) saturate(140%);
  border-bottom: 1px solid transparent;
  display: grid; grid-template-rows: 0fr;
  overflow: hidden;
  max-height: calc(100dvh - 92px);
  visibility: hidden;
  transition: grid-template-rows var(--reg-uit) var(--ease), box-shadow var(--reg-uit) var(--ease),
              border-color var(--reg-uit) var(--ease), visibility 0s var(--reg-uit);
}
.v2hdr.is-open .v2reg {
  grid-template-rows: 1fr;
  border-bottom-color: var(--hair);
  box-shadow: 0 30px 80px rgba(0,0,0,.5);
  visibility: visible;
  overflow-y: auto;
  overscroll-behavior: contain;
  transition: grid-template-rows var(--reg-in) var(--ease), box-shadow var(--reg-in) var(--ease),
              border-color var(--reg-in) var(--ease), visibility 0s;
}
.v2reg-in {
  min-height: 0; overflow: hidden;
  max-width: 1180px; margin: 0 auto; width: 100%; box-sizing: border-box;
  padding: 0 max(36px, env(safe-area-inset-left));
  transition: padding var(--reg-uit) var(--ease);
}
.v2hdr.is-open .v2reg-in {
  padding: 34px max(36px, env(safe-area-inset-left)) 40px;
  transition: padding var(--reg-in) var(--ease);
  /* Zichtbaar laten overlopen zodra het register open is: anders knipt
     .v2reg-in de inhoud af en valt er niets te scrollen op een laptop
     of telefoon · de scrollbalk hoort bij .v2reg, de hoogtebegrenzer. */
  overflow: visible;
}
/* Op lage schermen wordt alles krapper in plaats van afgeknipt. */
@media (max-height: 900px) {
  .v2hdr.is-open .v2reg-in { padding-top: 22px; padding-bottom: 26px; }
  .v2reg-row { padding: 11px 4px; }
  .v2reg-bij { margin-bottom: 16px !important; padding-bottom: 12px !important; }
  .v2reg-foot { margin-top: 20px; padding-top: 16px; }
}

/* Scrollteken · een rand die vervaagt plus een pil die zegt wat er kan.
   v2.js zet de klassen zodra het register hoger is dan het scherm. */
.v2reg.kan-scrollen { box-shadow: inset 0 -38px 28px -28px rgba(5,9,20,.96); }
.v2reg.kan-scrollen.aan-eind { box-shadow: none; }
.v2reg-meer {
  position: sticky; bottom: 12px; z-index: 3; width: fit-content; margin: -6px auto 0;
  display: flex; align-items: center; gap: 7px;
  padding: 7px 15px; border: 1px solid var(--hair); border-radius: 999px;
  background: rgba(10,16,34,.92); backdrop-filter: blur(10px);
  font: 500 11px/1 var(--mono); letter-spacing: .07em; color: var(--mut);
  pointer-events: none; opacity: 1; transition: opacity .35s var(--ease);
}
.v2reg-meer i { font-style: normal; animation: meerWip 1.5s var(--ease) infinite; }
.v2reg.aan-eind .v2reg-meer { opacity: 0; }
@keyframes meerWip { 0%, 100% { transform: translateY(-1px); } 50% { transform: translateY(2px); } }
@media (prefers-reduced-motion: reduce) { .v2reg-meer i { animation: none; } }
/* Ruimte onder de laatste regel, anders ligt de pil eroverheen. Staat
   bewust na de hoogte-mediaquery hierboven, zodat deze wint. */
.v2reg.kan-scrollen .v2reg-in { padding-bottom: 70px; }
@supports not (grid-template-rows: 1fr) {
  .v2reg { max-height: 0; overflow: hidden; transition: max-height .55s var(--ease), visibility 0s .55s; }
  .v2hdr.is-open .v2reg { max-height: 88vh; }
}
/* Drie kolommen: diensten, eigen instrumenten, gratis materiaal. Elke hub
   staat in het register, zodat elke pagina binnen drie klikken bereikbaar is. */
.v2reg-grid { display: grid; grid-template-columns: 1.2fr 1fr 1fr; gap: 40px; }
@media (max-width: 1080px) { .v2reg-grid { grid-template-columns: 1fr 1fr; gap: 34px; } }
@media (max-width: 720px) { .v2reg-grid { grid-template-columns: 1fr; gap: 30px; } }
.v2reg-col > .ann { display: block; padding-bottom: 12px; border-bottom: 1px solid var(--hair); margin-bottom: 6px; }

/* Regels in het register · genummerd, met omschrijving */
.v2reg-row {
  display: grid; grid-template-columns: 42px 1fr auto; align-items: baseline; gap: 16px;
  padding: 15px 4px; border-bottom: 1px solid var(--hair);
  text-decoration: none; position: relative;
  opacity: 0; transform: translateY(-10px);
  transition: padding var(--reg-uit) var(--ease), background var(--reg-uit) var(--ease),
              opacity .44s var(--ease), transform .44s var(--ease);
}
/* De choreografie zelf, dus de trap, het sluiten en de entree van de drie
   blokken die niet meebewogen, staat verderop in het apptempo-blok. Die staat
   later in dit blad en wint daarvan, en er zijn er hier al twee te veel
   geweest. Wat hier blijft is de beginstand: alles begint tien pixels BOVEN
   zijn plek, want het blad komt van boven. */

.v2reg-row:hover { padding-left: 14px; background: rgba(110,121,255,.06); }
.v2reg-row:active { transform: scale(.995); transition-duration: .1s; }
.v2reg-n { font-family: var(--mono); font-size: 11px; color: var(--per2); letter-spacing: .06em; }
.v2reg-t { font-size: 1.16rem; font-weight: 700; color: #fff; letter-spacing: -.02em; }
.v2reg-d { display: block; margin-top: 2px; font-size: .86rem; font-weight: 400; color: var(--mut); letter-spacing: 0; }
.v2reg-row .ann { justify-self: end; }
.v2reg-row.is-here .v2reg-t { color: var(--per2); }
.v2reg-row.is-here .v2reg-n::after { content: " ●"; color: var(--cyan); font-size: 8px; vertical-align: middle; }
@media (max-width: 620px) { .v2reg-row { grid-template-columns: 34px 1fr; } .v2reg-row .ann { display: none; } }

/* AI-kolom: mono, cyaan, duidelijk een ander instrument */
.v2reg-col--ai .v2reg-row { border-color: rgba(103,232,249,.12); }
.v2reg-col--ai .v2reg-n { color: var(--cyan); }
.v2reg-col--ai .v2reg-t { font-family: var(--mono); font-size: .98rem; font-weight: 700; }
.v2reg-col--ai .v2reg-row:hover { background: rgba(103,232,249,.05); }

/* Gratis-kolom: zelfde mono-idioom, eigen accent · downloads en kennis */
.v2reg-col--vrij .v2reg-row { border-color: rgba(139,149,255,.14); }
.v2reg-col--vrij .v2reg-n { color: var(--per2); }
.v2reg-col--vrij .v2reg-t { font-family: var(--mono); font-size: .98rem; font-weight: 700; }
.v2reg-col--vrij .v2reg-row:hover { background: rgba(139,149,255,.06); }
.v2reg-vrij-b { color: var(--cyan); }

/* Rust in het register · de diensten dragen het gewicht, de twee
   code-kolommen zijn lijsten: één regel per ingang, toelichting erachter
   in plaats van eronder. Zonder dit leest het register als 22 gelijke
   blokken en vindt het oog geen ingang. */
.v2reg-col--ai .v2reg-row,
.v2reg-col--vrij .v2reg-row { padding: 10px 4px; align-items: baseline; }
.v2reg-col--ai .v2reg-d,
.v2reg-col--vrij .v2reg-d { display: inline; margin: 0 0 0 9px; font-family: var(--mono); font-size: .74rem; color: var(--mut2); }
.v2reg-col--ai .v2reg-t,
.v2reg-col--vrij .v2reg-t { font-size: .92rem; }
@media (min-width: 721px) {
  .v2reg-col--ai .v2reg-row .ann,
  .v2reg-col--vrij .v2reg-row .ann { display: none; }
  .v2reg-col--ai .v2reg-row,
  .v2reg-col--vrij .v2reg-row { grid-template-columns: 30px 1fr; }
}

/* Bijrol-strook: home, cases, over ons, contact · aanwezig maar stil */
.v2reg-bij { display: flex; flex-wrap: wrap; gap: 6px 22px; margin: 0 0 22px; padding-bottom: 16px; border-bottom: 1px solid var(--hair); }
.v2reg-bij a { font: 500 12px/1 var(--mono); letter-spacing: .05em; color: var(--mut2); text-decoration: none; transition: color .3s var(--ease); }
.v2reg-bij a:hover { color: #fff; }
.v2reg-bij a.is-here { color: var(--per2); }

/* Voetregel van het register: contact plus stempel, landt als laatste.

   HIER STOND OOK DE BEWEGING, en dat was een dubbele. Deze regel kwam na het
   choreografieblok hierboven en won daarvan, want gelijke soortelijkheid en
   later in het blad. De voet kwam daardoor nog steeds van acht pixels ERONDER
   omhoog in plaats van van boven mee naar beneden, en bij het sluiten vervaagde
   hij in 0,3 s terwijl de rest bleef staan. De beweging staat nu op een plek,
   in het blok hierboven; hier blijft alleen de vorm. */
.v2reg-foot { display: flex; flex-wrap: wrap; gap: 14px 26px; align-items: center;
  margin-top: 30px; padding-top: 20px; border-top: 1px solid var(--hair); }
@media (prefers-reduced-motion: reduce) {
  .v2reg, .v2reg-in, .v2reg-row, .v2reg-foot,
  .v2reg-bij, .v2reg-col > .ann { transition: none !important; }
  .v2reg-row, .v2reg-foot, .v2reg-bij, .v2reg-col > .ann { opacity: 1; transform: none; }
}
.v2reg-foot .v2cta { margin-left: auto; }
.v2cta {
  display: inline-flex; align-items: center; padding: 12px 24px; border-radius: 999px;
  background: linear-gradient(135deg, var(--blue), var(--btn));
  color: #fff; font-weight: 600; font-size: .9rem; text-decoration: none;
  box-shadow: 0 8px 24px rgba(22,82,240,.35);
  /* 26-08-2026: was .55s. Een halve seconde is een filmovergang, geen
     knopreactie. De indruk ging al in 90 ms omlaag maar deed er daarna 550 ms
     over om terug te veren, en dat is precies wat "reageert nauwelijks"
     betekent: je bent allang los en de knop staat nog te bewegen. Nu 170 ms
     heen en terug, met dezelfde easing en dezelfde eindstanden. Alleen
     transform en box-shadow, dus geen enkele herberekening van de opmaak. */
  transition: transform 170ms var(--ease), box-shadow 170ms var(--ease);
}
.v2cta:hover { transform: translateY(-1px); box-shadow: 0 12px 32px rgba(22,82,240,.5); }
.v2cta:active { transform: scale(.97); transition-duration: 90ms; }
/* Scroll-lock die ook op iOS houdt: body wordt fixed gezet op de
   huidige scrollpositie (JS bewaart en herstelt die). De scrim dimt
   en blurt de pagina achter het open register, zodat altijd zichtbaar
   is dat je in het menu zit; een klik erop sluit het. */
body.navOpen { overflow: hidden; }
body.navLock { position: fixed; left: 0; right: 0; width: 100%; overflow: hidden; }
.v2scrim {
  position: fixed; inset: 0; z-index: 55;
  background: rgba(3,6,14,.55);
  backdrop-filter: blur(5px) saturate(120%);
  -webkit-backdrop-filter: blur(5px) saturate(120%);
  opacity: 0; visibility: hidden;
  transition: opacity .55s var(--ease), visibility 0s .55s;
}
body.navOpen .v2scrim {
  opacity: 1; visibility: visible;
  transition: opacity .85s var(--ease), visibility 0s;
}

@media (max-width: 880px) {
  /* alleen de open stand: gesloten moet padding 0 houden, anders
     blijft het blad 58px hoog hangen in de vouw */
  .v2hdr.is-open .v2reg-in { padding: 26px 20px 32px; }
  .v2reg-foot .v2cta { margin-left: 0; width: 100%; justify-content: center; }
}

.v2main { max-width: 1180px; margin: 0 auto; padding: 44px 24px 0; }
@media(max-width:640px){ .v2main { padding: 0 18px; } }

/* Sectiekop in de gedeelde grammatica */
.v2sec { padding: 150px 0 10px; }
@media(max-width:700px){ .v2sec { padding: 76px 0 6px; } } /* 15-09 (avond): korter op een telefoon, minder lege lucht tussen de blokken, meer app */
.v2sec-head { display:flex; align-items:baseline; gap:16px; border-bottom:1px solid var(--hair); padding-bottom:16px; margin-bottom:48px; }
.v2sec-head h2 { margin:0; font-size: clamp(1.5rem,3vw,2.2rem); font-weight:800; letter-spacing:-.04em; }
.v2sec-head h2 em { font-family: var(--serif); font-style: italic; font-weight:400; font-size:1.12em; color:#c7d2fe; }
.v2sec-head .ann:last-child { margin-left:auto; }

/* ============================================================
   STEM · kinetische typografie (C9): regels schuiven uit maskers
   ============================================================ */
/* De regelonthulling. Het masker schuift de regel van onderen binnen.

   WAAROM HIER PADDING ONDER ZIT, gemeten 26-08-2026. Gianluca zag op de Engelse
   en Spaanse pagina's dat de staarten van de g en de y werden afgesneden:
   "TheSEO: a Dutch SEO agency" en "that builds visibility" hielden allebei een
   platte onderkant.

   De oorzaak is de combinatie van drie dingen. Het masker zet overflow:hidden,
   .v2h1 heeft line-height 1.02, en overflow knipt GESCHILDERDE INKT weg en niet
   alleen dozen. Bij een regelhoogte die nauwelijks groter is dan de letter zelf
   valt de staart van een g, j, p, q of y onder de regeldoos, en daar knipt het
   masker hem af.

   Waarom het in het Engels en Spaans erger is: de Nederlandse koppen zijn
   geschreven zodat elke .lm op een regel past. Een vertaling breekt af, en dan
   staat er een tweede regel strak tegen de onderrand van het masker. Gemeten op
   /en/: de eerste .lm is 187px hoog bij een regelhoogte van 93,8px, dus twee
   regels.

   De reparatie verandert niets aan de opmaak: de padding maakt het masker
   0,18em hoger zodat de staart erin past, en de negatieve marge haalt diezelfde
   ruimte er in de bladspiegel weer af. De verschuiving gaat van 110% naar 135%
   omdat de regel anders vóór de onthulling met zijn bovenkant in die extra
   ruimte zou piepen. */
.lm { display:block; overflow:hidden; padding-bottom:.18em; margin-bottom:-.18em; }
/* APPTEMPO 26-08-2026. Deze regel stond op 1,1 seconde: dat is filmtijd, en de
   kop is het eerste wat een bezoeker ziet. 320 ms is de bovenkant van de
   apptempoband, en die bovenkant is hier de juiste keuze omdat de reisafstand
   groter is dan bij .rv: de regel schuift 135% van zijn eigen hoogte omhoog,
   niet 26 px. Wat NIET verandert: alleen transform beweegt, nooit opacity. Dat
   is met opzet zo gebouwd en het is ook de reden dat deze versnelling de
   Largest Contentful Paint niet raakt. Een element met opacity 0 telt niet als
   geschilderd en zou de LCP dus verzetten; een element dat alleen verschoven
   staat telt wel mee. Op de startpagina is deze h1 de LCP, dus dat verschil is
   geen theorie. */
.lm > span { display:block; transform: translateY(135%); transition: transform 320ms var(--ease); }
.isIn .lm > span, .lm.isIn > span { transform: translateY(0); }
.lm:nth-of-type(2) > span { transition-delay:70ms; }
.lm:nth-of-type(3) > span { transition-delay:140ms; }
.v2h1 { margin:0; font-size: clamp(44px,7vw,92px); line-height:1.02; letter-spacing:-.05em; font-weight:800; color:#f2f5ff; }
.v2h1 em { font-family: var(--serif); font-style:italic; font-weight:400; color:#aeb9ff; }

/* Kinetische rijen (C9-01): flip-woord + micro-annotatie */
.krows { border-top:1px solid var(--hair); }
/* Grid kent VIER kinderen (nummer, flip, beschrijving, annotatie):
   nummer en annotatie spannen beide rijen, flip en beschrijving
   stapelen in de middenkolom. Eerdere 3-koloms aanname liet de
   annotatie naar een eigen rij onder het nummer wrappen (afgekapte
   "RV.01"-labels op mobiel). */
.krow {
  display:grid; grid-template-columns: 52px 1fr auto;
  grid-template-rows: auto auto;
  align-items:center; column-gap:20px; row-gap:4px;
  padding: 24px 8px; border-bottom:1px solid var(--hair); text-decoration:none;
  position:relative; overflow:hidden;
  transition: padding .6s var(--ease);
}
.krow::before { content:""; position:absolute; inset:0; z-index:-1;
  background: linear-gradient(90deg, rgba(110,121,255,.09), transparent 60%);
  transform: scaleX(0); transform-origin:0 50%; transition: transform .7s var(--ease); }
.krow:hover::before { transform: scaleX(1); }
.krow:hover { padding-left: 20px; }
.krow:active { transform: scale(.99); transition-duration:.1s; }
.krow-i { grid-column:1; grid-row:1 / span 2; align-self:start; padding-top:.35em;
  font-family:var(--mono); font-size:12px; color:var(--per2); }
/* Flip: de maat van het woord bepaalt de venster-hoogte; woorden
   MOETEN block zijn, anders staan ze naast elkaar ("SEOSEO") en
   snijdt het venster ze halverwege af. */
.krow-flip { grid-column:2; grid-row:1; display:block; overflow:hidden;
  font-size: clamp(1.35rem,2.4vw,2rem); line-height:1.18; height:1.18em; }
.krow-flip > span { display:block; transition: transform .55s var(--ease); }
.krow:hover .krow-flip > span { transform: translateY(-50%); }
.krow-t { display:block; font-size:inherit; line-height:inherit;
  font-weight:800; letter-spacing:-.035em; color:#fff; }
.krow-d { grid-column:2; grid-row:2; display:block; color:var(--mut);
  font-size:.9rem; font-weight:400; letter-spacing:0; }
.krow .ann { grid-column:3; grid-row:1 / span 2; justify-self:end; align-self:center; }
@media(max-width:700px){
  .krow { grid-template-columns:40px 1fr; column-gap:14px; }
  .krow .ann { display:none; }
}

/* ============================================================
   BEWIJS · Zwitserse instrumentkaarten (C10-3, "uit eigen lab")
   ============================================================ */
.lab { display:grid; grid-template-columns:repeat(auto-fit,minmax(min(250px,100%),1fr)); gap:1px; background:var(--hair); border:1px solid var(--hair); }
/* De min() erbij op 10-09-2026, om dezelfde reden als op de startpagina: een
   vaste ondergrens in minmax krimpt niet mee, dus onder 250 px plus de marges
   duwt het spoor de kaart buiten beeld. Op een breed scherm verandert er
   niets, want daar is 250px kleiner dan 100%. */
.lab-tool { background: var(--paper2); padding: 26px 24px 22px; position:relative;
  transition: background .6s var(--ease); }
.lab-tool:hover { background:#0a1226; }
.lab-tool .ann { display:block; margin-bottom:14px; }
.lab-tool h3 { margin:0 0 8px; font-size:1.5rem; font-weight:800; letter-spacing:-.03em; }
.lab-tool p { margin:0 0 16px; color:var(--mut); font-size:.92rem; }
.lab-tool .ann:last-child { margin:0; color:var(--mut2); }
.lab-tool--soon h3 { color: var(--mut); }

/* Zwitserse statregel */
.stats { display:flex; flex-wrap:wrap; gap: 18px 44px; padding: 22px 0; border-top:1px solid var(--hair); border-bottom:1px solid var(--hair); }
.stat b { display:block; font-size: clamp(1.8rem,3.4vw,2.6rem); font-weight:800; letter-spacing:-.04em; font-variant-numeric: tabular-nums; }
.stat b span { color: var(--per2); }
.stat .ann { margin-top:2px; display:block; }

/* ============================================================
   TEKENINGEN · blueprint-figuren (C8 hoofdstuk 02)
   ============================================================ */
.figs { display:grid; grid-template-columns:repeat(auto-fit,minmax(280px,1fr)); gap:22px; }
.fig { border:1px solid var(--line); background: rgba(8,14,30,.45); padding: 20px; position:relative; }
.fig::before, .fig::after { content:""; position:absolute; width:14px; height:14px; border-color: var(--per2); border-style:solid; opacity:.5; transition: opacity .6s var(--ease); }
.fig::before { top:-1px; left:-1px; border-width:1px 0 0 1px; }
.fig::after { bottom:-1px; right:-1px; border-width:0 1px 1px 0; }
.fig:hover::before, .fig:hover::after { opacity:1; }
.fig .ann { display:flex; justify-content:space-between; margin-bottom:14px; }
.fig h3 { margin:0 0 6px; font-size:1.1rem; font-weight:700; }
.fig p { margin:0; color:var(--mut); font-size:.9rem; }
.fig-art { height:120px; margin:14px 0 6px; border:1px dashed var(--line); position:relative; overflow:hidden; }
.fig-art .bar { position:absolute; bottom:0; background:linear-gradient(180deg,var(--per),var(--blue)); opacity:.75; transform-origin:bottom; transform:scaleY(0); transition: transform 1.2s var(--ease); }
.isIn .fig-art .bar { transform:scaleY(1); }

/* ============================================================
   INSTRUMENT A · het terminalvenster (C7, alles-AI)
   Eén venster-stijl, overal identiek: op AI-pagina's is het de
   hoofdrolspeler, elders hooguit een klein hulpinstrument.
   ============================================================ */
.term { border:1px solid rgba(103,232,249,.18); border-radius:12px; overflow:hidden;
  background:#04080f; box-shadow: 0 30px 80px rgba(0,0,0,.45); }
.term-bar { display:flex; align-items:center; gap:8px; padding:10px 14px; background:rgba(103,232,249,.05); border-bottom:1px solid rgba(103,232,249,.12); }
.term-bar i { width:10px; height:10px; border-radius:999px; background:#233047; }
.term-bar i:nth-child(1){ background:#31435f; } .term-bar i:nth-child(2){ background:#27364e; }
.term-bar .ann { margin-left:8px; text-transform:none; letter-spacing:.04em; }
.term-body { padding: 20px 18px; font-family: var(--mono); font-size: 13.5px; line-height: 1.8; color:#c8d6f5; }
.term-body .p { color: var(--cyan); }
.term-body .c { color: var(--mut2); }
.term-body .ok { color: #86efac; }
.term-body .hl { color: var(--per2); }
.term-caret { display:inline-block; width:8px; height:1.1em; background:var(--cyan); vertical-align:text-bottom; animation: v2caret 1.1s steps(1) infinite; }
@keyframes v2caret { 0%,55%{opacity:1} 56%,100%{opacity:0} }
.term-bars { display:flex; align-items:flex-end; gap:6px; height:64px; margin:10px 0; }
.term-bars i { flex:1; background:linear-gradient(180deg,var(--cyan),var(--blue)); opacity:.7; transform-origin:bottom; transform:scaleY(var(--v,.4)); }

/* ============================================================
   INSTRUMENT B · de levende zoekmachine (C6, kennisbank)
   ============================================================ */
.seek { max-width:820px; margin:0 auto; }
.seek-bar { display:flex; align-items:center; gap:14px; padding:18px 22px;
  border:1px solid rgba(255,255,255,.14); border-radius:999px; background:rgba(8,13,28,.6);
  box-shadow: 0 20px 60px rgba(0,0,0,.35); }
.seek-bar svg { width:22px; height:22px; stroke:var(--per2); flex:0 0 22px; }
.seek-q { font-size: clamp(17px,2.6vw,24px); font-weight:600; color:#f2f5ff; min-height:1.4em; }
.seek-q em { font-family:var(--serif); font-style:italic; color:#aeb9ff; }
.seek-kbd { margin-left:auto; font-family:var(--mono); font-size:11px; color:var(--mut2); border:1px solid var(--hair); border-radius:6px; padding:4px 8px; }
.seek-meta { text-align:center; margin:14px 0 0; }
.seek-res { display:block; padding:20px 4px; border-bottom:1px solid var(--hair); text-decoration:none; transition: padding .5s var(--ease); }
.seek-res:hover { padding-left:14px; }
.seek-url { font-family:var(--mono); font-size:12px; color:#7ea88f; }
.seek-res h3 { margin:6px 0 4px; font-size:1.15rem; color:#c7d2fe; font-weight:700; }
.seek-res p { margin:0; color:var(--mut); font-size:.92rem; }

/* ============================================================
   AFSLUITING · de gigantische Volgende stap (C4-04)
   ============================================================ */
.giant-wrap { padding: 160px 0 60px; }
@media(max-width:700px){ .giant-wrap { padding: 48px 0 20px; } .v2foot { margin-top: 36px; } } /* 16-09: de afsluiter zweefde met 110 px boven en 80 px onder los van de rest */
.giant-head { display:flex; flex-wrap:wrap; gap:4px 16px; justify-content:space-between; align-items:baseline; border-top:1px solid var(--hair); padding-top:16px; } /* wikkelt op 390 (nakijkronde 16-09: twee labels braken midden in de zin) */
.giant {
  display:block; text-decoration:none; margin-top:10px;
  font-size: clamp(52px, 11vw, 150px); line-height:1.02; font-weight:800; letter-spacing:-.05em;
  color:#f2f5ff;
  transition: color .6s var(--ease);
}
.giant em { font-family: var(--serif); font-style:italic; font-weight:400; color:#aeb9ff; }
.giant .arr { display:inline-block; transition: transform .6s var(--ease); }
.giant:hover { color:#fff; }
.giant:hover .arr { transform: translate(10px,-6px); }
.giant:active { transform: scale(.99); }

/* ============================================================
   FOOTER
   ============================================================ */
.v2foot { border-top:1px solid var(--hair); margin-top:80px; }
.v2foot-in { max-width:1180px; margin:0 auto; padding: 30px 24px calc(30px + env(safe-area-inset-bottom));
  display:flex; flex-wrap:wrap; gap:14px 30px; }
.v2foot-in .ann a { color:inherit; text-decoration:none; }
.v2foot-in .ann a:hover { color:#fff; }
.v2foot-in .ann:last-child { margin-left:auto; }
/* margin-left auto duwt het laatste item naar rechts. Op een brede regel is dat
   de bedoeling; op 375 px valt de voettekst uit elkaar in korte regels en komt
   dat laatste item alleen op een regel te staan, ver naar rechts, met een gat
   ernaast. Gezien 25-08 op /jarvis/kennis/ (AI-transparantie) en in de demo
   (reviewomgeving). Onder 560 px sluit het gewoon aan bij de rij. */
@media (max-width: 560px) { .v2foot-in .ann:last-child { margin-left:0; } }

/* ============================================================
   REVEALS + PAGINAOVERGANGEN
   ============================================================ */
/* APPTEMPO 26-08-2026: de duur van 1,1 s en 1 s die hier stond is vervangen
   door apptempo. De beginstand, de richting, de afstand en de easing blijven
   precies zoals ze waren; alleen de klok loopt sneller. Het blok in hoofdstuk
   3.2b verderop in dit bestand zet dezelfde tijden nog een keer, zodat een
   pagina die alleen dat blok leest hetzelfde antwoord krijgt. */
.rv { opacity:0; transform: translateY(26px); transition: opacity 280ms var(--ease), transform 280ms var(--ease); }
.rv.isIn { opacity:1; transform:none; }
.st > * { opacity:0; transform: translateY(20px); transition: opacity 260ms var(--ease), transform 260ms var(--ease); transition-delay: calc(var(--i,0) * 50ms); }
.st.isIn > * { opacity:1; transform:none; }

/* gewone overgang: cinematisch gordijn */
body::after { content:""; position:fixed; inset:0; z-index:999; pointer-events:none;
  background: radial-gradient(120% 140% at 50% 100%, #0a1330 0%, #050a16 55%, #03060f 100%);
  opacity:0; transform: translateY(40%) scaleY(1.4);
  transition: opacity .7s cubic-bezier(.4,0,.2,1), transform .9s cubic-bezier(.16,1,.3,1); }
body.leave::after { opacity:1; transform: none; pointer-events:auto; }
/* terminal-overgang (naar/van AI-pagina's): het scherm "boot" */
.bootveil { position:fixed; inset:0; z-index:1000; background:#04080f; display:none;
  font-family:var(--mono); color:var(--cyan); font-size:14px; padding: 20vh 8vw; }
.bootveil.is-on { display:block; }
.bootveil .ln { opacity:0; animation: v2ln .12s forwards; white-space:pre-wrap; }
.bootveil .ln:nth-child(2){ animation-delay:.12s; } .bootveil .ln:nth-child(3){ animation-delay:.26s; }
.bootveil .ln:nth-child(4){ animation-delay:.4s; } .bootveil .ln:nth-child(5){ animation-delay:.5s; }
@keyframes v2ln { to { opacity:1; } }

@media (hover:none) {
  .krow:active, .lab-tool:active, .fig:active, .seek-res:active { transform: scale(.985); transition-duration:.12s; }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
  .rv, .st > *, .lm > span { opacity:1 !important; transform:none !important; }
}

/* ============================================================
   DOSSIER · gedeeld systeem voor tekstvolume-secties.
   Elke dienstpagina krijgt een dossier: echte lopende tekst met
   het focus-zoekwoord, subkoppen en interne verwijzingen. Eén
   vormtaal voor allemaal, zodat niets uit de toon valt.
   ============================================================ */
.dossier { padding: 120px 0 10px; }
/* DE LINKERRAND, rechtgezet 10-09-2026.

   WAT ER MIS WAS, gemeten in de browser op 1269 px breed.
   .dos-in stond op max-width 860 met margin 0 auto, dus de hele dossiersectie
   werd GECENTREERD in een blad van 1180. Gevolg: elke dossiersectie begon 136
   px verder naar binnen dan de secties eromheen. Op /wordpress-specialist/
   leverde dat 20 secties met twee verschillende linkerranden op, tien op x=69
   en tien op x=205, en drie verschillende linkerranden voor lopende tekst
   (93, 94 en 229). Dat is precies wat Gianluca omschreef als "nog steeds het
   template van de blog, teksten in het midden" en als "niet ineens veel marge
   zijkant". De twee pagina's die hij wel goedkeurt hebben een dominante rand:
   de startpagina 11 van de 13 secties op x=39, /seo-verbeteren/ 13 van de 17
   op x=69.

   WAAROM DE LEESMAAT ER NIET ONDER LIJDT, en dat is de reden dat dit kan.
   De regellengte werd niet door deze kolom bepaald maar door .dos-kop
   (max-width 660px) en .dos-body p (max-width 68ch) hieronder. Die staan er
   nog steeds. Het blok schuift dus naar links tot het gelijk ligt met de rest,
   en de tekst blijft even breed als hij was. Alleen de lijsten en de subkoppen
   hadden geen grens; die krijgen hem hier, op dezelfde maat als de alinea's,
   anders zouden die als enige wel uitlopen.

   .dos-in.breed hieronder blijft bestaan en betekent nog steeds iets: die zet
   width op 100 procent, waardoor een figuur ook de laatste 24 px padding mag
   gebruiken. Het verschil is nu de padding en niet meer de plaats. */
.dos-in { max-width: 1180px; margin: 0 auto; padding: 0 24px; }

/* Een sectie die een figuur draagt mag breder dan de leeskolom.

   WAAROM DIT ER IS, gemeten 31-08-2026. Gianluca over /wordpress-specialist/:
   "alles lijkt in blog style te zijn, alleen de hero lijkt goed". Nagemeten:
   17 van de 18 secties zaten in .dos-in, en dat is een kolom van 860px. Op de
   pagina's die hij wel goed vindt is dat de helft: /ai-trainingen/ 4 van de 8,
   /sea-specialist/ 12 van de 22. Die wisselen af tussen een smalle leeskolom en
   een blok dat over de volle breedte gaat, en juist dat ritme haalt het
   blogsjabloon eruit.

   De smalle kolom blijft de norm voor lopende tekst, want 860px is een prettige
   regellengte en die verandert niet. Alleen een sectie MET een figuur krijgt deze
   variant, want een tekening van 860px breed is te klein om iets te laten zien.

   Bewust geen aparte wikkel maar een modifier op dezelfde klasse: zo verandert er
   niets aan de binnenkant en is het met een enkel woord terug te draaien. */
.dos-in.breed { width: 100%; max-width: 1180px; }

.dos-in > .ann { display: block; margin-bottom: 26px; }
.dos-kop { font-size: clamp(1.7rem, 3.4vw, 2.5rem); font-weight: 800;
  letter-spacing: -.03em; color: #fff; margin: 0 0 22px; max-width: 660px;
  /* line-height erbij op 11-09-2026. Zonder deze regel erfde de kop de 1,65
     van de lopende tekst: op 40 px letter is dat 66 px per regel, op een
     telefoon 45 px op 27 px letter, en een kop van drie regels werd 135 px
     hoog. Gianluca: "de ruimte tussen de h2 is te veel, de tekst moet
     dichter op elkaar". Een kop krijgt zijn eigen maat. */
  line-height: 1.12; text-wrap: balance; }
.dos-body p { color: var(--mut); font-size: 1.04rem; line-height: 1.75;
  margin: 0 0 18px; max-width: 68ch; }
.dos-body h3 { color: #fff; font-size: 1.12rem; font-weight: 700;
  letter-spacing: -.01em; margin: 34px 0 10px; max-width: 68ch; }
.dos-body ul { margin: 0 0 18px; padding-left: 20px; max-width: 68ch; }
.dos-body li { color: var(--mut); line-height: 1.7; margin-bottom: 8px; }
/* Alleen echte tekstlinks krijgen de linkopmaak. Een knop binnen een dossier
   (klassen met cta of btn) houdt zijn eigen kleur; anders wint deze regel op
   specificiteit van de knopregel en wordt witte tekst blauw op blauw. */
.dos-body a:not([class*="cta"]):not([class*="btn"]) { color: var(--per2); text-decoration: underline;
  text-underline-offset: 3px; text-decoration-thickness: 1px;
  transition: color .5s var(--ease); }
.dos-body a:not([class*="cta"]):not([class*="btn"]):hover { color: #fff; }
.dos-body strong { color: #e6e9f5; font-weight: 600; }
.dos-links { display: flex; flex-wrap: wrap; gap: 10px 22px; margin-top: 30px;
  padding-top: 18px; border-top: 1px solid var(--hair); }
.dos-links a { font-family: var(--mono); font-size: 11.5px; letter-spacing: .12em;
  text-transform: uppercase; color: #9aa6c6; text-decoration: none;
  transition: color .5s var(--ease); }
.dos-links a:hover { color: #fff; }
.dos-links a::after { content: " ↗"; color: var(--per2); }
@media (max-width: 700px) { .dossier { padding: 84px 0 6px; } }


/* ============================================================
   PROG · vormsysteem voor programmatic landingspagina's (fase 4).
   Eén grammatica, structuurvolgorde verschilt per variant; de
   tekstsecties hergebruiken het dossier-systeem.
   ============================================================ */
.prog-hero { padding: 120px 0 8px; }
.prog-kop { font-size: clamp(1.9rem, 4.2vw, 3rem); font-weight: 800;
  letter-spacing: -.035em; color: #fff; margin: 12px 0 16px; max-width: 720px; }
.prog-lead { color: var(--mut); font-size: 1.07rem; line-height: 1.7;
  max-width: 64ch; margin: 0; }
.prog-feiten { display: flex; flex-wrap: wrap; gap: 10px 28px; margin-top: 26px;
  padding-top: 18px; border-top: 1px solid var(--hair); }
.prog-feiten span { font-family: var(--mono); font-size: 11px; letter-spacing: .1em;
  text-transform: uppercase; color: #9aa6c6; }
.prog-cta-rij { margin-top: 26px; }


/* ============================================================
   BINNENKOMST & VERTREK · het blad spant zich op bij laden
   (raster en meetlat tekenen in), en bij vertrek zoomt de hele
   pagina zacht weg achter het gordijn.
   ============================================================ */
.v2bg .grid { animation: v2gridin 1.6s var(--ease) both; }
.v2bg .ruler { animation: v2rulerin 1.4s var(--ease) .25s both; }
@keyframes v2gridin { from { opacity: 0; transform: scale(1.015); } to { opacity: 1; transform: none; } }
@keyframes v2rulerin { from { opacity: 0; } to { opacity: .35; } }
body { transition: opacity .65s var(--ease), transform .85s var(--ease), filter .7s var(--ease); }
body.leave { opacity: 0; transform: scale(.97) translateY(-18px); filter: blur(6px); }
@media (prefers-reduced-motion: reduce) {
  .v2bg .grid, .v2bg .ruler { animation: none; }
  body, body.leave { transition: none; transform: none; }
}

/* ============================================================
   MENS & PLAATS · fotoshoot-laag (2026-07-28)
   Feitenstrook wordt specificatiekaarten, auteurblok met echte
   foto op elke inhoudspagina, portretsectie op over-ons.
   ============================================================ */
.prog-feiten { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 10px; border-top: 0; padding-top: 0; }
.prog-feiten span { border: 1px solid var(--hair); border-radius: 12px; padding: 14px 16px;
  background: rgba(110,121,255,.05); font-size: 11.5px; line-height: 1.5; }

.auteur { padding: 36px 0 8px; }
.auteur-kaart { display: flex; gap: 16px; align-items: center; border: 1px solid var(--hair);
  border-radius: 14px; padding: 18px 20px; background: rgba(110,121,255,.04); max-width: 660px; }
.auteur-kaart img { width: 64px; height: 64px; border-radius: 50%; object-fit: cover;
  object-position: top; flex-shrink: 0; }
.auteur-tekst strong { display: block; color: #fff; font-size: .98rem; margin-bottom: 2px; }
.auteur-tekst span { color: var(--mut); font-size: .88rem; line-height: 1.55; }
.auteur-tekst a { color: var(--per2); }

.oa-grid { display: grid; grid-template-columns: minmax(220px, 340px) 1fr; gap: 36px; align-items: center; }
.oa-foto { width: 100%; height: auto; border-radius: 16px; border: 1px solid var(--hair); display: block; }
@media (max-width: 700px) { .oa-grid { grid-template-columns: 1fr; } .oa-foto { max-width: 320px; } }

/* ============================================================
   INSTRUMENTEN · per structuurvariant een geanimeerd
   blueprint-figuur. Alle beweging via transform/opacity op de
   bestaande .rv/.isIn-reveal; illustratief, zonder cijfers.
   ============================================================ */
.instr { padding: 26px 0 0; }
/* Een instrumentblad dat op een dossier of een band volgt krijgt lucht; 26 px
   was bedoeld voor een blad dat bij het dossier erboven hoort, maar Gianluca
   15-09 over FIG.02 op /google-ads-bedrijf/: "plakt gelijk onder de sectie". */
.v2sec.dossier + .v2sec.instr { padding-top: clamp(48px, 6vw, 80px); }
.instr-paneel { border: 1px solid var(--hair); border-radius: 14px;
  padding: 26px 24px 24px; background: rgba(110,121,255,.04); overflow: hidden; }
.instr-paneel > .ann { display: block; margin-bottom: 20px; }

/* seizoen · vier kwartalen groeien op */
.iz { display: flex; gap: 14px; align-items: flex-end; height: 110px; }
.iz i { flex: 1; border-radius: 6px 6px 2px 2px; font-style: normal;
  background: linear-gradient(180deg, rgba(139,149,255,.55), rgba(22,82,240,.18));
  transform-origin: bottom; transform: scaleY(.05); transition: transform 1.2s var(--ease); }
.rv.isIn .iz i:nth-child(1) { transform: scaleY(.3); }
.rv.isIn .iz i:nth-child(2) { transform: scaleY(1); transition-delay: .12s; }
.rv.isIn .iz i:nth-child(3) { transform: scaleY(.85); transition-delay: .24s; }
.rv.isIn .iz i:nth-child(4) { transform: scaleY(.5); transition-delay: .36s; }
.iz-labels { display: flex; gap: 14px; margin-top: 10px; }
.iz-labels span { flex: 1; text-align: center; }

/* spoed · beldruk over de dag */
.sp { display: flex; gap: 8px; align-items: flex-end; height: 90px; }
.sp i { flex: 1; border-radius: 4px; font-style: normal; background: rgba(139,149,255,.25);
  transform-origin: bottom; transform: scaleY(.08); transition: transform .9s var(--ease); }
.sp i.piek { background: linear-gradient(180deg, rgba(103,232,249,.6), rgba(22,82,240,.25)); }
.rv.isIn .sp i { transform: scaleY(.3); }
.rv.isIn .sp i.piek { transform: scaleY(1); }
.rv.isIn .sp i:nth-child(2n) { transition-delay: .1s; }
.rv.isIn .sp i:nth-child(3n) { transition-delay: .18s; }
.sp-labels { display: flex; justify-content: space-between; margin-top: 10px; }

/* veiling · biedbalken schuiven in, slim bod wint zonder hoogste te zijn */
.vl { display: flex; flex-direction: column; gap: 12px; }
.vl .rij { display: flex; align-items: center; gap: 14px; }
.vl .rij .ann { margin: 0; min-width: 92px; }
.vl .balk { height: 14px; border-radius: 999px; background: rgba(139,149,255,.22);
  transform-origin: left; transform: scaleX(0); transition: transform 1s var(--ease); flex: 1; }
.vl .balk.eigen { background: linear-gradient(90deg, var(--blue), var(--per)); }
.rv.isIn .vl .b1 { transform: scaleX(.9); }
.rv.isIn .vl .b2 { transform: scaleX(.6); transition-delay: .12s; }
.rv.isIn .vl .b3 { transform: scaleX(.72); transition-delay: .24s; }

/* offertemoment · drie offertes, de goede komt naar voren */
.of { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.of .kaart { border: 1px solid var(--hair); border-radius: 12px; padding: 16px 14px;
  background: rgba(5,10,22,.5); opacity: 0; transform: translateY(14px);
  transition: opacity .8s var(--ease), transform .8s var(--ease), border-color .8s var(--ease); }
.of .kaart b { display: block; color: #fff; font-size: .92rem; margin: 6px 0 2px; }
.of .kaart span { color: var(--mut); font-size: .8rem; }
.rv.isIn .of .kaart { opacity: 1; transform: none; }
.rv.isIn .of .kaart:nth-child(2) { transition-delay: .14s; }
.rv.isIn .of .kaart:nth-child(3) { transition-delay: .28s; }
.rv.isIn .of .kaart.win { border-color: rgba(139,149,255,.5); transform: translateY(-4px); transition-delay: .42s; }
@media (max-width: 620px) { .of { grid-template-columns: 1fr; } .rv.isIn .of .kaart.win { transform: none; } }

/* specialisatie · één brede campagne splitst in drie gerichte */
.spl .breed { height: 16px; border-radius: 999px; background: rgba(139,149,255,.2);
  margin-bottom: 14px; transform-origin: left; transform: scaleX(0); transition: transform .9s var(--ease); }
.rv.isIn .spl .breed { transform: scaleX(1); }
.spl .drie { display: flex; gap: 10px; }
.spl .drie i { flex: 1; height: 16px; border-radius: 999px; font-style: normal;
  background: linear-gradient(90deg, var(--blue), var(--per));
  transform-origin: left; transform: scaleX(0); transition: transform .9s var(--ease); }
.rv.isIn .spl .drie i:nth-child(1) { transform: scaleX(1); transition-delay: .3s; }
.rv.isIn .spl .drie i:nth-child(2) { transform: scaleX(1); transition-delay: .42s; }
.rv.isIn .spl .drie i:nth-child(3) { transform: scaleX(1); transition-delay: .54s; }
.spl .drie-labels { display: flex; gap: 10px; margin-top: 10px; }
.spl .drie-labels span { flex: 1; text-align: center; }

/* vertrouwen · waarborgen verschijnen één voor één */
.vt { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.vt .zegel { border: 1px solid var(--hair); border-radius: 12px; padding: 18px 14px; text-align: center;
  background: rgba(5,10,22,.5); opacity: 0; transform: scale(.9);
  transition: opacity .7s var(--ease), transform .7s var(--ease); }
.vt .zegel b { display: block; color: #fff; font-size: .9rem; margin-top: 8px; }
.vt .vink { display: inline-flex; width: 26px; height: 26px; border-radius: 50%;
  border: 1px solid rgba(139,149,255,.5); color: var(--per2); align-items: center; justify-content: center; }
.rv.isIn .vt .zegel { opacity: 1; transform: none; }
.rv.isIn .vt .zegel:nth-child(2) { transition-delay: .15s; }
.rv.isIn .vt .zegel:nth-child(3) { transition-delay: .3s; }
@media (max-width: 620px) { .vt { grid-template-columns: 1fr; } }

/* hub · steden verbinden met het vak */
.hb { position: relative; height: 120px; }
.hb .kern, .hb .stip { position: absolute; border-radius: 50%; }
.hb .kern { width: 14px; height: 14px; left: 50%; top: 50%; transform: translate(-50%,-50%);
  background: var(--per2); box-shadow: 0 0 24px rgba(139,149,255,.8); }
.hb .stip { width: 8px; height: 8px; background: rgba(139,149,255,.55);
  opacity: 0; transition: opacity .8s var(--ease); }
.hb .lijn { position: absolute; height: 1px; background: rgba(139,149,255,.3);
  transform-origin: left; transform: scaleX(0); transition: transform 1s var(--ease); }
.rv.isIn .hb .stip { opacity: 1; }
.rv.isIn .hb .lijn { transform: scaleX(1); }
.rv.isIn .hb .stip:nth-of-type(2), .rv.isIn .hb .lijn:nth-of-type(2) { transition-delay: .15s; }
.rv.isIn .hb .stip:nth-of-type(3), .rv.isIn .hb .lijn:nth-of-type(3) { transition-delay: .3s; }
.rv.isIn .hb .stip:nth-of-type(4), .rv.isIn .hb .lijn:nth-of-type(4) { transition-delay: .45s; }

/* slotband · foto plus gesprek */
.prog-slot { padding: 40px 0 0; }
.slot-kaart { display: grid; grid-template-columns: 1fr minmax(200px, 300px);
  border: 1px solid var(--hair); border-radius: 16px; overflow: hidden;
  background: rgba(110,121,255,.05); align-items: stretch; }
.slot-tekst { padding: 32px 28px; }
.slot-tekst h2 { font-size: clamp(1.3rem, 2.6vw, 1.7rem); letter-spacing: -.02em; color: #fff; margin: 0 0 10px; }
.slot-tekst p { color: var(--mut); font-size: .95rem; margin: 0 0 20px; max-width: 46ch; }
.slot-kaart img { width: 100%; height: 100%; object-fit: cover; display: block; }
@media (max-width: 640px) { .slot-kaart { grid-template-columns: 1fr; }
  .slot-kaart img { max-height: 230px; object-position: center 30%; } }

@media (prefers-reduced-motion: reduce) {
  .iz i, .sp i, .vl .balk, .of .kaart, .spl .breed, .spl .drie i, .vt .zegel, .hb .stip, .hb .lijn {
    transform: none !important; opacity: 1 !important; transition: none !important; }
}

/* ============================================================
   KLANTENWAND & EXCLUSIVITEIT · merken die we bouwen, en de
   regel dat een beroep per regio maar één klant kent. Gevoed
   uit brain.theseo_klantwand en brain.theseo_exclusiviteit.
   ============================================================ */
.klanten { padding: 34px 0 0; }
.kl-rij { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; } /* 130 tot 21-09-2026, zie blok 16 */
.kl-logo { border: 1px solid var(--hair); border-radius: 12px; background: rgba(255,255,255,.02);
  padding: 20px 16px; display: flex; align-items: center; justify-content: center; min-height: 108px; /* 18/76 tot 21-09-2026, 128 een uur lang, zie blok 16 */
  opacity: 0; transform: translateY(10px);
  transition: opacity .7s var(--ease), transform .7s var(--ease), border-color .5s var(--ease); }
.rv.isIn .kl-logo { opacity: 1; transform: none; }
.rv.isIn .kl-logo:nth-child(2) { transition-delay: .08s; }
.rv.isIn .kl-logo:nth-child(3) { transition-delay: .16s; }
.rv.isIn .kl-logo:nth-child(4) { transition-delay: .24s; }
.rv.isIn .kl-logo:nth-child(5) { transition-delay: .32s; }
.rv.isIn .kl-logo:nth-child(6) { transition-delay: .4s; }
.kl-logo img { max-height: 58px; width: auto; height: auto; max-width: 80%; /* 34 tot 21-09-2026, daarna 72 en 100%, nu 80 procent daarvan ("iets kleiner"), zie blok 16; height auto omdat de vakhubs en de oude stadspagina's nog width en height 34 dragen */
  filter: grayscale(1) brightness(1.7) opacity(.7); transition: filter .5s var(--ease); }
.kl-logo:hover { border-color: rgba(139,149,255,.35); }
.kl-logo:hover img { filter: none; }

.beschik { border: 1px solid var(--hair); border-left: 3px solid var(--cyan);
  border-radius: 0 12px 12px 0; padding: 16px 20px; margin-top: 18px;
  background: rgba(103,232,249,.05); font-size: .95rem; color: var(--mut); }
.beschik strong { color: #fff; display: block; margin-bottom: 3px; }
.beschik.bezet { border-left-color: #e0a33a; background: rgba(224,163,58,.06); }
@media (prefers-reduced-motion: reduce) { .kl-logo { opacity: 1 !important; transform: none !important; } }

/* ============================================================
   MOCKUPS · blueprint-maquettes die laten zien wat de dienst
   oplevert. Alles CSS, geen screenshots: past bij het canvas en
   kost geen laadtijd. Beweging via de bestaande .rv-reveal.
   ============================================================ */
.mock { padding: 30px 0 0; }
.mock-wrap { border: 1px solid var(--hair); border-radius: 16px; overflow: hidden;
  background: linear-gradient(180deg, rgba(110,121,255,.06), rgba(5,10,22,.5)); }
.mock-bar { display: flex; align-items: center; gap: 8px; padding: 12px 16px;
  border-bottom: 1px solid var(--hair); background: rgba(5,10,22,.6); }
.mock-bar i { width: 9px; height: 9px; border-radius: 50%; background: #29374f; }
.mock-url { flex: 1; margin-left: 10px; font-family: var(--mono); font-size: 11px;
  color: var(--mut2); background: rgba(255,255,255,.03); border-radius: 6px;
  padding: 5px 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mock-body { padding: 22px 20px; }

/* zoekresultaat: jouw regel schuift naar boven en licht op */
.serp-rij { display: flex; flex-direction: column; gap: 4px; padding: 12px 14px;
  border-radius: 10px; margin-bottom: 8px; border: 1px solid transparent;
  opacity: 0; transform: translateY(12px);
  transition: opacity .7s var(--ease), transform .7s var(--ease),
              background .7s var(--ease), border-color .7s var(--ease); }
.rv.isIn .serp-rij { opacity: 1; transform: none; }
.rv.isIn .serp-rij:nth-child(2) { transition-delay: .1s; }
.rv.isIn .serp-rij:nth-child(3) { transition-delay: .2s; }
.rv.isIn .serp-rij.jij { background: rgba(110,121,255,.1); border-color: rgba(139,149,255,.35);
  transition-delay: .42s; }
.serp-rij .u { font-family: var(--mono); font-size: 10.5px; color: var(--mut2); }
.serp-rij .t { font-size: 1rem; color: #aeb9ff; font-weight: 600; }
.serp-rij.jij .t { color: #fff; }
.serp-rij .d { font-size: .84rem; color: var(--mut); line-height: 1.5; }
.serp-merk { display: inline-block; font-family: var(--mono); font-size: 9.5px;
  letter-spacing: .1em; text-transform: uppercase; color: var(--cyan);
  border: 1px solid rgba(103,232,249,.3); border-radius: 4px; padding: 1px 6px; margin-left: 8px; }

/* rapportagelijn: groeit van links naar rechts */
.graf { height: 130px; position: relative; }
.graf svg { width: 100%; height: 100%; display: block; overflow: visible; }
.graf .lijn { fill: none; stroke: url(#gr); stroke-width: 2.5; stroke-linecap: round;
  stroke-dasharray: 1000; stroke-dashoffset: 1000; transition: stroke-dashoffset 2.2s var(--ease); }
.rv.isIn .graf .lijn { stroke-dashoffset: 0; }
.graf .vlak { fill: url(#gv); opacity: 0; transition: opacity 1.4s var(--ease) .6s; }
.rv.isIn .graf .vlak { opacity: 1; }
.graf .punt { fill: #fff; opacity: 0; transition: opacity .5s var(--ease) 1.9s; }
.rv.isIn .graf .punt { opacity: 1; }
.graf-lbl { display: flex; justify-content: space-between; margin-top: 10px; }

/* toolvenster: invoer en uitkomst */
.tool-mock { display: grid; gap: 12px; }
.tool-veld { display: flex; align-items: center; gap: 10px; border: 1px solid var(--hair);
  border-radius: 10px; padding: 12px 14px; background: rgba(5,10,22,.5);
  font-family: var(--mono); font-size: 12.5px; color: #c8d6f5; }
.tool-veld .lbl2 { color: var(--mut2); font-size: 10.5px; letter-spacing: .08em;
  text-transform: uppercase; }
.tool-uit { border-left: 2px solid var(--per2); padding: 12px 16px;
  background: rgba(110,121,255,.06); border-radius: 0 10px 10px 0;
  opacity: 0; transform: translateX(-8px);
  transition: opacity .8s var(--ease) .5s, transform .8s var(--ease) .5s; }
.rv.isIn .tool-uit { opacity: 1; transform: none; }
.tool-uit .r { font-family: var(--mono); font-size: 12.5px; color: #dbe6ff; margin-bottom: 4px; }
.tool-uit .r:last-child { margin-bottom: 0; }

/* fotoband met bijschrift */
.beeldband { padding: 34px 0 0; }
.beeldband figure { margin: 0; border: 1px solid var(--hair); border-radius: 16px; overflow: hidden; }
.beeldband img { width: 100%; height: auto; display: block; aspect-ratio: 3 / 2; object-fit: cover; }
.beeldband figcaption { padding: 12px 18px; font-family: var(--mono); font-size: 11px;
  letter-spacing: .07em; text-transform: uppercase; color: var(--mut2);
  border-top: 1px solid var(--hair); background: rgba(5,10,22,.4); }

@media (prefers-reduced-motion: reduce) {
  .serp-rij, .tool-uit { opacity: 1 !important; transform: none !important; }
  .graf .lijn { stroke-dashoffset: 0 !important; }
  .graf .vlak, .graf .punt { opacity: 1 !important; }
}

/* ============================================================
   Klantwand: naamvariant voor klanten zonder logobestand.
   Zonder dit valt juist de klant weg waar de exclusiviteit op
   rust, bijvoorbeeld Tempodak op de dakdekkerspagina.
   Gevoed uit brain.theseo_klantwand (logo_pad leeg).
   ============================================================ */
.kl-logo .kl-naam { font: 500 1.15rem/1.3 var(--mono, ui-monospace, monospace); /* .78rem tot 21-09-2026, zie blok 16 */
  letter-spacing: .11em; text-transform: uppercase; text-align: center;
  color: rgba(255,255,255,.52); transition: color .5s var(--ease); }
.kl-logo:hover .kl-naam { color: rgba(255,255,255,.9); }

/* ============================================================
   BEDRIJFSGEGEVENS · wettelijk verplichte identificatie van de
   dienstverlener (artikel 3:15d BW). Twee verschijningsvormen:
     · .jur-blok    het paneel op /contact/
     · .v2foot-jur  de stille tweede regel in de voettekst
   Zolang de eigenaar de gegevens niet heeft aangeleverd staan
   er plaatshouders in de HTML voor rechtsvorm, KvK-nummer,
   btw-identificatienummer en vestigingsadres. Zoek in public/
   op het voorvoegsel INVULLEN gevolgd door een koppelteken om
   ze allemaal te vinden. Uitrollen naar de rest van de site
   gaat met scripts/bedrijfsgegevens-uitrollen.py.
   ============================================================ */
.jur-blok { border: 1px solid var(--hair); border-radius: 14px;
  padding: 26px 24px; background: rgba(110,121,255,.03); }
.jur-blok > .ann { display: block; margin-bottom: 12px; }
.jur-kop { font-size: 1.14rem; font-weight: 700; letter-spacing: -.01em;
  color: #fff; margin: 0 0 20px; }
.jur-lijst { margin: 0; display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px 30px; }
.jur-rij { margin: 0; display: flex; flex-direction: column; gap: 3px; }
.jur-rij--breed { grid-column: 1 / -1; }
.jur-rij dd { margin: 0; color: #e6e9f5; font-size: .96rem; line-height: 1.6; }
.jur-rij dd a { color: var(--per2); text-decoration: none;
  transition: color .5s var(--ease); }
.jur-rij dd a:hover { color: #fff; }
.jur-noot { margin: 22px 0 0; padding-top: 16px; border-top: 1px solid var(--hair);
  color: var(--mut); font-size: .88rem; line-height: 1.65; }
.jur-noot a { color: var(--per2); text-decoration: underline;
  text-underline-offset: 3px; text-decoration-thickness: 1px; }
.jur-noot a:hover { color: #fff; }
@media (max-width: 640px) { .jur-lijst { grid-template-columns: 1fr; } }

.v2foot-jur { border-top: 1px solid var(--hair); }
.v2foot-jur-in { max-width: 1180px; margin: 0 auto;
  padding: 14px 24px calc(24px + env(safe-area-inset-bottom));
  display: flex; flex-wrap: wrap; gap: 8px 26px; }
.v2foot-in:has(+ .v2foot-jur) { padding-bottom: 16px; }

/* Reparatie 2026-08-17 (testronde fase 2): de balken in .iz en .sp kregen
   geen hoogte. De containers zijn flex-rijen, dus flex:1 verdeelt de breedte
   en laat de hoogte 0; de scaleY-animatie schaalt dan niets. align-items op
   de container is flex-end, dus stretch geldt niet. height:100% herstelt het
   en de bestaande scaleY-waarden doen daarna gewoon hun werk. */
.iz i, .sp i { height: 100%; }

/* ============================================================
   AANVULLING 17-08 nacht (eis 3) · FAMILIEOVERGANGEN
   Elke paginafamilie een eigen binnenkomst en vertrek, zodat
   wisselen nergens als laden voelt. De basis blijft het gordijn
   (body.leave) en voor de AI- en skillsfamilie de bestaande
   terminal-boot; hieronder staan alleen de accenten per familie,
   gekleurd via body[data-family] die v2.js bij het laden zet.
   Bouwregels:
     · overlay-accenten leven in body::before (het gordijn is
       body::after), basisstaat onzichtbaar, al het leven zit in
       een animatie; de globale prefers-reduced-motion-regel zet
       animaties uit, dus zonder beweging bestaat er geen accent
     · inhoudsaccenten (de blogkop) animeren alleen VANUIT een
       beginstand (fill-mode backwards): de rustsituatie is de
       normale zichtbare staat, ook zonder animatie
     · alles blijft onder de 900 ms en past binnen de bestaande
       navigatievertraging van 850 ms
   ============================================================ */

/* -- DIENSTEN · het dossier-idioom: onder de header tekent een
      haarlijn met annotatie in; bij vertrek sluit het dossier en
      trekt de lijn zich naar rechts terug -- */
body[data-family="diensten"]::before {
  content: "dossier · geopend";
  position: fixed; top: 96px; left: 24px; right: 24px; z-index: 1000;
  pointer-events: none;
  font: 500 11px/1 var(--mono); letter-spacing: .08em;
  text-transform: uppercase; color: var(--per2); text-align: right;
  padding-bottom: 9px;
  background: linear-gradient(90deg, rgba(139,149,255,.75), rgba(139,149,255,.12))
              left bottom / 0% 1px no-repeat;
  opacity: 0;
  animation: famDosIn .8s var(--ease) .05s;
}
@keyframes famDosIn {
  0%   { opacity: 0; background-size: 0% 1px; }
  22%  { opacity: 1; }
  68%  { opacity: 1; background-size: 100% 1px; }
  100% { opacity: 0; background-size: 100% 1px; }
}
body.leave[data-family="diensten"]::before {
  content: "dossier · gesloten";
  background-position: right bottom;
  animation: famDosUit .6s var(--ease) forwards;
}
@keyframes famDosUit {
  0%   { opacity: 0; background-size: 100% 1px; }
  18%  { opacity: 1; }
  70%  { opacity: 1; }
  100% { opacity: 0; background-size: 0% 1px; }
}

/* -- PRODUCTEN · de terminalbalk die opbouwt: een venstertitel-
      chip met drie stipjes onthult zich van links naar rechts;
      bij vertrek klapt hij dezelfde kant weer dicht -- */
body[data-family="producten"]::before {
  content: "instrument · geladen";
  position: fixed; top: 88px; left: 50%; transform: translateX(-50%);
  z-index: 1000; pointer-events: none; white-space: nowrap;
  font: 500 11px/1 var(--mono); letter-spacing: .06em; color: #c8d6f5;
  padding: 9px 16px 9px 52px;
  border: 1px solid rgba(103,232,249,.18); border-radius: 999px;
  background-color: rgba(4,8,15,.92);
  background-image:
    radial-gradient(circle 3.5px at 16px 50%, #31435f 99%, transparent),
    radial-gradient(circle 3.5px at 28px 50%, #27364e 99%, transparent),
    radial-gradient(circle 3.5px at 40px 50%, #233047 99%, transparent);
  box-shadow: 0 14px 40px rgba(0,0,0,.45);
  opacity: 0;
  animation: famTermIn .8s var(--ease) .05s;
}
@keyframes famTermIn {
  0%   { opacity: 0; clip-path: inset(0 88% 0 0 round 999px); }
  16%  { opacity: 1; }
  55%  { clip-path: inset(0 0% 0 0 round 999px); }
  76%  { opacity: 1; clip-path: inset(0 0% 0 0 round 999px); }
  100% { opacity: 0; clip-path: inset(0 0% 0 0 round 999px); }
}
body.leave[data-family="producten"]::before {
  content: "instrument · gesloten";
  animation: famTermUit .6s var(--ease) forwards;
}
@keyframes famTermUit {
  0%   { opacity: 0; clip-path: inset(0 0% 0 0 round 999px); }
  16%  { opacity: 1; }
  62%  { opacity: 1; clip-path: inset(0 0% 0 0 round 999px); }
  100% { opacity: 0; clip-path: inset(0 88% 0 0 round 999px); }
}

/* -- JURIDISCH · rust, bijna niets, waardigheid: een kalme rechte
      fade bij binnenkomst, geen enkel overlay-element -- */
body[data-family="juridisch"] { animation: famJurIn .7s var(--ease) backwards; }
@keyframes famJurIn { from { opacity: 0; } }

/* -- BLOG · leesbaar: de kop zet als drukwerk, van inktwaas naar
      scherp; bij vertrek licht hij weer van het papier. De vaste
      opacity-regel neemt de kop over van het .rv-reveal-systeem,
      zodat de zetbeweging het hele verhaal draagt en de kop bij
      reduced motion gewoon direct zichtbaar staat. -- */
body[data-family="blog"] h1 { opacity: 1; transform: none;
  animation: famZet .7s var(--ease) .05s backwards; }
@keyframes famZet {
  from { opacity: 0; filter: blur(6px); transform: translateY(10px); }
}
body.leave[data-family="blog"] h1 {
  opacity: 0; filter: blur(4px); transform: translateY(-8px);
  transition: opacity .45s var(--ease), filter .45s var(--ease),
              transform .45s var(--ease);
}

/* -- vertrek-karakter: de technische families en de juridische
      verlaten het blad zonder zoom en zonder vervaging, zodat het
      accent (of juist de rust) scherp blijft; blog houdt de zachte
      inktvervaging van het basisgordijn -- */
body.leave[data-family="diensten"],
body.leave[data-family="producten"],
body.leave[data-family="juridisch"] { transform: none; filter: none; }

/* smal scherm: accenten iets dichter op de header */
@media (max-width: 640px) {
  body[data-family="diensten"]::before { top: 92px; left: 18px; right: 18px; }
  body[data-family="producten"]::before { top: 84px; }
}

/* Dubbele borging bovenop de globale reduced-motion-regel: zonder
   beweging bestaat er geen overlay-accent en staat de blogkop
   gewoon scherp op zijn plek. */
@media (prefers-reduced-motion: reduce) {
  body[data-family="diensten"]::before,
  body[data-family="producten"]::before { content: none; }
  body[data-family="blog"] h1 { opacity: 1 !important; filter: none !important; transform: none !important; }
}

/* Aanvulling 2026-08-17 (mockup-audit paginas/mockup-audit.md): .iz en .sp
   hadden op elke pagina die ze gebruikte precies dezelfde vaste vorm, ook
   als de eigen pagina-tekst een ander seizoenspatroon of een ander
   belpatroon claimde. Onderstaande modifiers geven diezelfde componenten
   een tweede en derde vorm, aan te roepen met een extra klasse op .iz/.sp
   zelf, zodat de standaardvorm voor alle ongewijzigde pagina's identiek
   blijft en alleen de pagina's die het nodig hebben een eigen vorm krijgen.
   Geen nieuwe cijfers: de vormen volgen wat er al in de pagina-tekst staat. */

/* .iz--h2: piek in het tweede jaarhalf (najaar/winter), voor vakwerk dat
   juist buiten het voorjaar-zomer-seizoen piekt. */
.rv.isIn .iz.iz--h2 i:nth-child(1) { transform: scaleY(.45); }
.rv.isIn .iz.iz--h2 i:nth-child(2) { transform: scaleY(.3); transition-delay: .12s; }
.rv.isIn .iz.iz--h2 i:nth-child(3) { transform: scaleY(.65); transition-delay: .24s; }
.rv.isIn .iz.iz--h2 i:nth-child(4) { transform: scaleY(1); transition-delay: .36s; }

/* .iz--twee-pieken: twee piekmomenten met een rustiger middenseizoen ertussen,
   voor vakwerk met zowel een aanleg- als een afsluitmoment. */
.rv.isIn .iz.iz--twee-pieken i:nth-child(1) { transform: scaleY(.35); }
.rv.isIn .iz.iz--twee-pieken i:nth-child(2) { transform: scaleY(1); transition-delay: .12s; }
.rv.isIn .iz.iz--twee-pieken i:nth-child(3) { transform: scaleY(.5); transition-delay: .24s; }
.rv.isIn .iz.iz--twee-pieken i:nth-child(4) { transform: scaleY(.8); transition-delay: .36s; }

/* .sp--vlak: voor 24/7-spoedwerk waarbij de pagina zelf al claimt dat er
   geen rustig moment is; minder contrast tussen piek en dal dan de
   standaard kantoortijden-vorm van .sp. */
.rv.isIn .sp.sp--vlak i { transform: scaleY(.62); }
.rv.isIn .sp.sp--vlak i.piek { transform: scaleY(1); }

/* Voettekst-navigatierij en zichtbaarder registerstrook (eis 17-08: Over ons
   en Contact waren niet terug te vinden in voettekst of menu). De navigatierij
   staat boven de juridische annotaties en leest als echte navigatie, niet als
   kleine lettertjes. De registerstrook krijgt meer gewicht zodat de vier vaste
   ingangen niet meer wegvallen naast de drie kolommen. */
.v2foot-nav { display: flex; flex-wrap: wrap; gap: 4px 26px; width: 100%;
  padding-bottom: 14px; margin-bottom: 14px;
  border-bottom: 1px solid rgba(255,255,255,.07); }
.v2foot-nav a { font: 500 13px/1.6 var(--sans, inherit); letter-spacing: .01em;
  color: #c7d0e6; text-decoration: none; transition: color .4s var(--ease); }
.v2foot-nav a:hover, .v2foot-nav a:focus-visible { color: #fff; }
.v2reg-bij { gap: 6px 24px; }
.v2reg-bij a { font-size: 14px; font-weight: 560; color: #cfd7ea;
  padding: 6px 0; }
.v2reg-bij a:hover, .v2reg-bij a:focus-visible { color: #fff; }

/* ============================================================
   AANVULLING 18-08 · AI-FAMILIE: code-achtige binnenkomst en
   vertrek voor ALLE AI-paginas.
   Aanleiding: de familieronde van 17-08 nacht gaf diensten,
   producten, blog en juridisch een eigen accent, maar de
   AI-familie hield alleen de bestaande opacity-fade uit v2.js
   plus de terminal-boot die enkel afging op vier vaste paden.
   Hieronder krijgt body[data-family="ai"] hetzelfde soort accent
   als de andere families, in het idioom dat bij AI hoort: een
   mono-chip die van rechts naar links inklikt met een cursor
   erin, plus een scanlijn die eenmaal over het blad loopt.
   Bouwregels zijn identiek aan die van de vorige ronde:
     · het accent leeft in body::before (het gordijn is
       body::after), rustsituatie onzichtbaar, al het leven zit
       in de animatie, dus de globale reduced-motion-regel zet
       hem vanzelf uit
     · alles blijft onder de 900 ms zodat het binnen de
       navigatievertraging past
   De oude paginaspecifieke boot-overlays zijn verwijderd: ze hielden de
   hoofdinhoud onnodig tegen en schreven een niet-gedocumenteerde
   sessionStorage-sleutel. Dit accent blijft decoratief, blokkeert niets en
   valt onder dezelfde reduced-motion-regel.
   ============================================================ */

body[data-family="ai"]::before {
  content: "ai · sessie actief";
  position: fixed; top: 88px; left: 50%; transform: translateX(-50%);
  z-index: 1000; pointer-events: none; white-space: nowrap;
  font: 500 11px/1 var(--mono); letter-spacing: .06em; color: var(--cyan);
  padding: 9px 16px 9px 34px;
  border: 1px solid rgba(103,232,249,.22); border-radius: 999px;
  background-color: rgba(4,8,15,.92);
  background-image: radial-gradient(circle 3px at 18px 50%, #67e8f9 99%, transparent);
  box-shadow: 0 14px 40px rgba(0,0,0,.45);
  opacity: 0;
  animation: famAiIn .85s var(--ease) .05s;
}
@keyframes famAiIn {
  0%   { opacity: 0; clip-path: inset(0 0 0 86% round 999px); }
  14%  { opacity: 1; }
  52%  { clip-path: inset(0 0 0 0% round 999px); }
  78%  { opacity: 1; clip-path: inset(0 0 0 0% round 999px); }
  100% { opacity: 0; clip-path: inset(0 0 0 0% round 999px); }
}

/* vertrek: de chip sluit dezelfde kant weer af en het blad
   verlaat scherp, zonder zoom en zonder vervaging, zodat de
   code-vibe niet in een filmische fade verzuipt */
body.leave[data-family="ai"]::before {
  content: "ai · sessie gesloten";
  animation: famAiUit .6s var(--ease) forwards;
}
@keyframes famAiUit {
  0%   { opacity: 0; clip-path: inset(0 0 0 0% round 999px); }
  16%  { opacity: 1; }
  62%  { opacity: 1; clip-path: inset(0 0 0 0% round 999px); }
  100% { opacity: 0; clip-path: inset(0 0 0 86% round 999px); }
}
body.leave[data-family="ai"] { transform: none; filter: none; }

/* het sluitgordijn van de AI-familie: dezelfde terminal als bij
   binnenkomst, maar hij dooft in plaats van hard aan te gaan */
.bootveil--uit { animation: famAiVeil .3s var(--ease) both; }
@keyframes famAiVeil { from { opacity: 0; } to { opacity: 1; } }

/* smal scherm: het logo met de ondertitel breekt naar twee regels,
   dus de chip zakt mee tot onder de kop in plaats van ernaast */
@media (max-width: 640px) {
  body[data-family="ai"]::before { top: 102px; }
}

/* Dubbele borging bovenop de globale reduced-motion-regel:
   zonder beweging bestaat er geen accent. */
@media (prefers-reduced-motion: reduce) {
  body[data-family="ai"]::before { content: none; }
}

/* Reparatie keuring 18-08 · de uitzondering voor /jarvis/app/ waarmaken.
   Die pagina draagt bewust geen data-family in haar HTML, maar v2.js zet
   de familie alsnog omdat het pad onder /jarvis/ valt. De chip verscheen
   dus toch, gecentreerd op 88px, precies over de kop van de React-app.
   Het gordijn en de overgangen blijven gewoon werken; alleen het
   overlay-accent hoort niet op een product met eigen chroom. */
body.jarvis-app-site[data-family="ai"]::before,
body.jarvis-app-site.leave[data-family="ai"]::before { content: none; }

/* ==========================================================================
   AFDRUKKEN, toegevoegd 24-08-2026.
   Waarom: de klasse .rv zet opacity op 0 en JavaScript maakt hem zichtbaar met
   .isIn zodra het blok in beeld scrolt. Wie een pagina print krijgt daardoor
   lege blokken voor alles wat nog niet in beeld is geweest, ook op
   /voorwaarden/jarvis/ waar het wettelijke modelformulier voor herroeping
   staat. Een klant die zijn koop ongedaan wil maken en de voorwaarden print,
   krijgt een leeg vel. Gemeten op 24-08: nul afdrukregels in dit bestand.
   De vorm is letterlijk overgenomen van de bestaande uitzondering voor
   prefers-reduced-motion op regel 349, want die doet al precies het goede.
   ========================================================================== */
@media print {
  *, *::before, *::after { animation: none !important; transition: none !important; }
  .rv, .st > *, .lm > span { opacity: 1 !important; transform: none !important; }
  .v2bg, .grid, .ruler, .glow, .grain { display: none !important; }
  body { background: #fff !important; color: #111 !important; }
}

/* ==========================================================================
   SECTIEKOP OP TELEFOON, toegevoegd 24-08-2026 door de keuringsronde.
   Waarom: .v2sec-head is een flexrij van drie dingen, het sectielabel links,
   de h2 in het midden en het registernummer rechts. Op 1440 past dat. Op 375
   houdt de rij zijn drie kolommen en wordt de h2 in een smalle middenkolom
   geperst; op /jarvis/ brak "Je kennis leeft overal, en dus nergens" daar in
   drie regels naast twee even hoge annotaties. Gemeten op 375 CSS-pixels.
   /tools-en-plugins/ en /skills/ hadden dit al pagina-lokaal opgelost met
   precies deze regel, dus de vorm is van hen overgenomen en alleen naar het
   gedeelde bestand getild. Breekpunt 700px is hetzelfde breekpunt dat .v2sec
   op regel 394 al gebruikt.
   ========================================================================== */
@media (max-width: 700px) {
  .v2sec-head { display: block; }
  .v2sec-head .ann { display: block; }
  .v2sec-head .ann:last-child { margin-left: 0; margin-top: 10px; }
  .v2sec-head h2 { margin: 10px 0 0; }
}

/* ==========================================================================
   ZONDER JAVASCRIPT, toegevoegd 24-08-2026.
   Waarom: dezelfde oorzaak als het afdrukblok hierboven. De klasse .rv zet
   opacity op 0 en pas v2.js zet er .isIn op zodra het blok in beeld scrolt.
   Draait dat script niet, dan blijft de tekst weg. Gemeten op 24-08 met alle
   scripts van de site uitgezet, in een echte Chrome: op /privacy/ was 455 van
   de 25.784 tekens zichtbaar, op /jarvis/ 488 van de 39.162. Twee procent en
   ruim een procent, op pagina's waar een klant rechten aan ontleent.
   Hoe het werkt: bovenaan in de head van elke pagina staat sinds vandaag een
   klein inline scriptje dat de klasse js op het html-element zet. Draait er
   JavaScript, dan staat die klasse er voordat er iets getekend is en verandert
   er niets aan de animaties. Draait er geen JavaScript, dan blijft de klasse
   weg en zet de regel hieronder alle onthulblokken meteen zichtbaar.
   Waarom inline en bovenaan: een extern of uitgesteld script komt na de eerste
   tekening, en dan zie je de tekst eerst staan en daarna wegspringen.
   Waarom deze drie selectors en geen andere: dit zijn precies de drie die
   opacity op 0 zetten en op .isIn wachten. Dezelfde drie staan in het
   afdrukblok hierboven en in de uitzondering voor prefers-reduced-motion.
   Waarom !important: dezelfde reden als daar, zodat de volgorde in dit bestand
   er niet toe doet. Er ontstaat geen botsing met .rv.isIn, want .isIn bestaat
   alleen als het script heeft gedraaid en dan staat html.js er ook.
   Bewaakt door scripts/navigatie/zichtbaar.py.
   ========================================================================== */
html:not(.js) .rv,
html:not(.js) .st > *,
html:not(.js) .lm > span { opacity: 1 !important; transform: none !important; }

/* Tweede helft van hetzelfde vangnet, en de reden dat de eerste helft niet
   genoeg was. Meting op 24-08 met alleen de drie regels hierboven: /privacy/
   stond op 96,8 procent zichtbaar, maar /jarvis/ op 71,9 procent. Het verschil
   zit in de pagina's die hun eigen onthulregels meebrengen. 384 van de 472
   pagina's hebben in hun eigen style-blok regels van de vorm
   `.isIn .kaart { opacity: 1 }` of `.isIn .rij { animation: stagIn ... }`, met
   een eigen opacity 0 als beginstand. Die klassenamen verschillen per pagina,
   dus een gedeeld stijlblad kan ze niet bij naam noemen. Wat ze wel gemeen
   hebben is dat ze allemaal binnen een .rv of .st staan en allemaal op .isIn
   wachten, en dat is precies wat de regel hieronder aangrijpt.
   Bewust alleen opacity en animation, en NIET transform: transform wordt op
   veel plekken gebruikt om iets uit te lijnen of te centreren, en dat sitebreed
   op none zetten zou de opmaak breken in plaats van repareren. Gevolg: een
   figuur dat zijn balken opbouwt met een animatie blijft zonder script in zijn
   beginstand staan. Dat is versiering, geen tekst, en het stond er voor deze
   reparatie ook al zo bij.
   Deze regel kan het gewone bezoek niet raken. Draait er JavaScript, dan staat
   html.js er voordat er iets getekend is en matcht html:not(.js) nergens.
   Nagemeten in een echte Chrome: het html-element draagt dan class="js".
   Na deze regel: /jarvis/ van 71,9 naar 97,4 procent. */
html:not(.js) .rv *,
html:not(.js) .st * { opacity: 1 !important; animation: none !important; }

/* Derde helft, en de reden dat de eerste twee niet overal reikten. Nadat het
   vangnet erin stond is de meting over alle 472 pagina's gedaan, niet over een
   steekproef, en toen bleven er zeven pagina's onder de vijftig procent staan:
   de zes onder /myparcel-dashboard/ en /jarvis/kennis/. Die dragen een eigen
   onthulsysteem uit hun eigen stijlblad, met .mp-reveal en .j-reveal in plaats
   van .rv, en met .is-visible in plaats van .isIn. Dat systeem heeft dezelfde
   fout op dezelfde manier.
   De regels staan hier en niet in myparcel-funnel.css of jarvis-funnel.css,
   omdat die twee bestanden een eigen versiestempel dragen dat dan mee omhoog
   zou moeten. Dit bestand gaat deze ronde toch al van r11 naar r12, dus dit is
   de plek waar de reparatie geen extra stempel kost. Beide pagina's laden dit
   bestand als eerste en hun eigen stijlblad daarna; de regels hieronder winnen
   op zowel gewicht als volgorde.
   Gemeten voor: /myparcel-dashboard/ 1.756 van 29.256 tekens, 6,0 procent, en
   /jarvis/kennis/ 1.329 van 4.770, 27,9 procent. */
html:not(.js) .mp-reveal,
html:not(.js) .j-reveal { opacity: 1 !important; transform: none !important; }
html:not(.js) .mp-reveal *,
html:not(.js) .j-reveal * { opacity: 1 !important; animation: none !important; }

/* ==========================================================================
   APP-NORM 3.2 EN 3.3, SITEBREED, toegevoegd 24-08-2026.
   Bron: paginas/app-gevoel-norm.md hoofdstuk 3.2 (de onthulling bij het
   scrollen), 3.3 (hover en aanraking) en hoofdstuk 7 (de sitebrede ingreep).
   Voorbeeld waar het van is afgekeken: /onderzoek/, plus /jarvis/werking/
   voor de tweetrapsvorm.

   WAAROM DIT BLOK BESTAAT. De norm meet twee dingen af op punt 3 en punt 4
   van de afkeurlijst. Punt 3: elke onthulling op de site duurt 1,1 seconde en
   heeft nergens een cascade, dus elk blok komt op zijn eentje binnen en dat
   leest als haperen. Punt 4: regel 55 van dit bestand zet
   -webkit-tap-highlight-color op transparent, en daarna staan er in het hele
   bestand vijf :active regels tegenover zevenentwintig :hover regels. Op een
   telefoon gebeurt er bij een tik dus letterlijk niets. Wat wij niet tekenen,
   tekent niemand.

   DRIE REGELS DIE VOOR HET HELE BLOK GELDEN.
   1. Alleen opacity, transform en scale bewegen. Nooit height, top, margin of
      width, want die herberekenen de opmaak en dat geeft een sprong.
   2. Een reactie op een handeling blijft onder de 300 ms, en nooit ease-in.
   3. Elke beweging heeft hieronder een tegenhanger onder
      prefers-reduced-motion met de eindstand erbij, en een tegenhanger voor
      het geval er geen JavaScript draait. Niet een globale animation none,
      maar per element met de eindstand, zodat er niets onzichtbaar hangt.
   ========================================================================== */

/* --------------------------------------------------------------------------
   3.2 DE ONTHULLING BIJ HET SCROLLEN

   Wat er al was: .rv laat een heel blok in 1,1 seconde binnenkomen en .st
   geeft zijn kinderen 110 ms per stap zonder bovengrens. Nagemeten op de
   startpagina en op /seo-verbeteren/ stond bij alle 31 en alle 60 onthulde
   elementen transition-delay op 0s, dus de cascade van .st werd nergens
   gebruikt en elk blok kwam los binnen.

   Wat erbij komt: .onth. Een sectie is een ding en komt als een ding binnen,
   met een cascade erin van 50 ms per kind. Na de zesde stap loopt de
   vertraging niet verder op, anders zit de lezer op regel twaalf te wachten
   op tekst die hij al wil lezen. De hele sectie staat er dus binnen
   280 + 250 = 530 ms.

   Waarom .rv.onth en niet .onth alleen: .isIn wordt door v2.js alleen gezet
   op .rv, .st en .lm. Zonder .rv komt .isIn er dus nooit en zou de tekst voor
   altijd op opacity 0 blijven staan. Door de beginstand aan .rv.onth te
   hangen kan een vergeten .rv nooit tekst laten verdwijnen: dan gebeurt er
   niets in plaats van iets ergs. Dat is dezelfde faalrichting als de klep van
   1500 ms in v2.js.

   Waarom de vertraging per nth-child en niet via var(--i): --i erft naar
   beneden door, dus een genest blok zou de vertraging van zijn ouder
   overnemen. Hier staat de vertraging op het kind zelf en erft er niets.
   both zorgt dat de beginstand geldt voor de vertraging en de eindstand
   blijft staan erna, zodat er niets terugspringt.
   -------------------------------------------------------------------------- */
.rv.onth { transition-duration: 280ms; }
.rv.onth > * { opacity: 0; transform: translateY(14px); }
.rv.onth.isIn > * { animation: v2onthIn 280ms var(--ease) both; }
.rv.onth.isIn > :nth-child(2)     { animation-delay:  50ms; }
.rv.onth.isIn > :nth-child(3)     { animation-delay: 100ms; }
.rv.onth.isIn > :nth-child(4)     { animation-delay: 150ms; }
.rv.onth.isIn > :nth-child(5)     { animation-delay: 200ms; }
.rv.onth.isIn > :nth-child(6)     { animation-delay: 250ms; }
.rv.onth.isIn > :nth-child(n + 7) { animation-delay: 250ms; }
@keyframes v2onthIn { to { opacity: 1; transform: none; } }

/* De uit-knop, per element, met de eindstand erbij. Het blok op regel 579
   zet wel animation op none maar raakt opacity van deze kinderen niet aan,
   dus zonder deze drie regels zou een lezer met bewegingsvoorkeur reduce een
   lege sectie zien. Dat is precies het verschil tussen bewegingsvoorkeur
   respecteren en de pagina slopen. */
@media (prefers-reduced-motion: reduce) {
  .rv.onth > *,
  .rv.onth.isIn > * { animation: none !important; opacity: 1 !important; transform: none !important; }
}
/* Zonder JavaScript, en op papier. Zelfde reden als de twee blokken hierboven
   in dit bestand: .isIn komt er dan nooit. Het bestaande vangnet zet opacity
   al goed via html:not(.js) .rv *, maar laat transform staan; hier staat de
   eindstand voluit. */
html:not(.js) .rv.onth > * { opacity: 1 !important; transform: none !important; animation: none !important; }
@media print {
  .rv.onth > * { opacity: 1 !important; transform: none !important; animation: none !important; }
}

/* --------------------------------------------------------------------------
   3.2b DE TIJD VAN DE BESTAANDE ONTHULLING, OP ALLE 472 PAGINA'S

   .onth zet de cascade neer waar hij wordt aangebracht. De twee onthullingen
   die er al waren staan op elke pagina van de site en die krijgen hieronder
   dezelfde tijdschaal, zonder dat er ook maar een pagina voor hoeft te worden
   aangeraakt.

   WAT ER NAGEMETEN IS, want dit wijkt af van wat de norm meldt. De norm zegt
   dat de cascade van .st nergens wordt gebruikt omdat alle onthulde elementen
   op transition-delay 0s staan. Zelf gemeten op de startpagina in een echte
   browser: de zes .st-groepen dragen wel degelijk een cascade, met
   vertragingen 0, 0,11, 0,22, 0,33, 0,44 en 0,55 s, en v2.js zet --i keurig
   op 0 tot en met 5. De meting uit de norm kijkt naar de CONTAINERS .rv en
   .st, en daar staat de vertraging niet op; die staat op hun kinderen. Op de
   containers is 0s dus het juiste antwoord en geen fout.

   Wat wel fout is en hier wordt rechtgezet, is de tijdschaal:

   1. .rv duurt 1,1 seconde. Hoofdstuk 4.3 van de norm noemt dat met zoveel
      woorden een filmtijd en onverdedigbaar voor iets wat je twintig keer per
      bezoek ziet. Hier wordt het 280 ms, dezelfde duur als een stap van .onth,
      zodat een blok en een cascadekind even snel binnenkomen.
   2. De cascade van .st loopt op met 110 ms per stap en heeft geen bovengrens.
      Zes kinderen zijn dus pas na 550 ms compleet en een groep van twaalf pas
      na 1,2 s. Hier staat 50 ms per stap met een grens na de zesde. Dat is
      hieronder ingesteld, met dezelfde nth-child-aftopping als bij .onth.

   Wat NIET verandert: de volgorde, de richting, de afstand van 26 px en 20 px,
   de easing, en welk element wanneer aan de beurt is. Alleen de klok gaat
   sneller lopen. Elke pagina-eigen regel die zwaarder weegt dan een enkele
   klasse houdt zijn eigen tijd, want dit blok noemt alleen .rv en .st > *.
   -------------------------------------------------------------------------- */
/* TWEEDE SLAG, 26-08-2026. De eerste slag zette hier 420 ms en 60 ms neer, en
   dat is al veel beter dan de 1,1 s waar het vandaan kwam, maar het is nog
   steeds geen apptempo. Gianluca noemt de App Store van Apple als maat: daar
   werkt alles op 150 tot 300 ms en reageert het scherm meteen. Bij 420 ms
   voel je de beweging nog als beweging; bij 280 ms zie je het resultaat.
   Gemeten in Chrome op /google-ads-voor-dakdekkers-breda/: .rv stond op 0,42 s
   en .st op 0,42 s met stappen van 0,06 s, hieronder 0,28 s en 0,26 s met
   stappen van 0,05 s.

   De bovengrens van de cascade zakt mee van 360 naar 250 ms, zodat hij precies
   op het zesde kind eindigt en niet erna doorloopt. Een groep van twaalf staat
   er nu binnen 260 + 250 = 510 ms in plaats van binnen 1,2 s. */
.rv { transition: opacity 280ms var(--ease), transform 280ms var(--ease); }
.st > * { transition: opacity 260ms var(--ease), transform 260ms var(--ease);
          transition-delay: calc(var(--i, 0) * 50ms); }
.st > :nth-child(n + 7) { transition-delay: 250ms; }

/* --------------------------------------------------------------------------
   3.3 HOVER EN AANRAKING

   Vier stappen, in deze volgorde:
   a. de overgang staat op het element zelf en niet op :hover, zodat hij ook
      op de weg terug geldt en bij een aanraking;
   b. indrukken werkt overal, ook waar geen hover bestaat;
   c. het toetsenbord krijgt een zichtbare ring;
   d. op een aanraakscherm wordt het raakvlak 44 px zonder dat de tekst
      groter wordt.
   -------------------------------------------------------------------------- */

/* a. De basisovergang. Element-selectors wegen het lichtst van alles, dus
      elke bestaande klasseregel in dit bestand en in de andere stijlbladen
      houdt zijn eigen overgang. Deze regel vult alleen het gat waar er nog
      geen was, en dat is bij verreweg de meeste links en knoppen. */
a, button, [role="button"], summary, label[for],
[tabindex]:not([tabindex="-1"]) {
  transition: color 200ms var(--ease), background-color 200ms var(--ease),
              border-color 200ms var(--ease), opacity 200ms var(--ease),
              box-shadow 200ms var(--ease), scale 110ms var(--ease);
}
/* box-shadow is 26-08 toegevoegd omdat de rand oplichten voor de klik anders
   klapt in plaats van aanzwelt: 41 klassen op de site zetten wel een schaduw
   of een gloed op :hover maar noemen box-shadow niet in hun eigen
   transition-lijst. Een schaduw verschuift niets in de opmaak, dus dit kost
   geen enkele layout shift. label[for] en [tabindex] stonden er niet bij
   terwijl ze verderop wel een indruk en een focusring krijgen; zonder
   overgang sprongen die twee. scale ging van 120 naar 110 ms zodat de weg
   terug even snel is als de weg heen. */

/* b. De indruk. Dit is de vervanging voor de tap-highlight die regel 55
      weghaalt, en tegelijk het antwoord op punt 4 van de afkeurlijst.

      Waarom scale en niet transform: scale(.985). De norm schrijft de
      transform-vorm, maar op deze site werkt die niet. Nagemeten: de
      registerrij .v2reg-row staat 8.816 keer op de site en krijgt zijn
      transform uit `.v2hdr.is-open .v2reg-row { transform: none }`. Die
      selector weegt drie klassen, `a:active` weegt er een, dus een sitebrede
      transform op :active zou op de meest aangeraakte rij van de hele site
      nooit afgaan. Hetzelfde geldt voor alles binnen .rv.isIn en .st.isIn.
      scale is een eigen eigenschap die bovenop transform wordt toegepast in
      plaats van hem te vervangen, dus die kan door geen enkele bestaande
      regel worden weggedrukt en kan zelf ook geen enkele opmaak breken die
      op transform staat. Dezelfde beweging, dezelfde 985 promille, maar dan
      een die overal werkelijk afgaat.

      :where() weegt nul, zodat elke pagina deze indruk met een gewone
      klasseregel kan overstemmen zonder !important. */
:where(a, button, [role="button"], summary, label[for], [tabindex]:not([tabindex="-1"])):active {
  scale: .99;
}
/* HERZIEN 26-08-2026. Hier stond een uitzonderingslijst die begon met
   `[class*="-cta"]:active`. De bedoeling was goed: wie al een eigen indruk
   heeft, hoort er geen tweede bovenop te krijgen. De uitvoering was fout,
   want de lijst ging op de NAAM af en niet op wat er werkelijk in de
   stijlbladen staat.

   NAGEMETEN OVER ALLE 630 PAGINA'S EN ALLE STIJLBLADEN. Er zijn 158
   klassenamen op de site die -cta bevatten en op een <a> of <button> staan.
   Daarvan dragen er 59 werkelijk een eigen :active met een transform. De
   overige 99 dragen niets. Die 99 werden door de regel hierboven wel
   uitgezet, en kregen dus precies nul reactie bij een klik of een tik:

     of-cta      96 pagina's, 192 links     trap-cta   18 pagina's, 54 links
     ao-cta       7 pagina's,  28 links     j-nav-cta   6 pagina's,  6 links
     estrap-cta   5 pagina's,  13 links     sea-cta     2 pagina's, 14 links
     ... en 93 andere, samen 649 links.

   De lijst is daarom weg. Wat ervoor in de plaats komt staat in twee lagen:

   LAAG EEN, zonder JavaScript: de regel hierboven drukt alles in op .99 in
   plaats van .985. Ondiep genoeg dat een dubbele indruk op de 59 klassen die
   wel een eigen transform hebben .97 maal .99 is, dus .9603, en dat verschilt
   een procent van de .97 die ze zelf bedoelden. Onzichtbaar. Daarmee is er
   geen naamlijst meer nodig om schade te voorkomen.

   LAAG TWEE, met JavaScript: v2.js leest bij het laden de werkelijke
   stijlregels uit document.styleSheets, zoekt elke selector met :active die
   zelf transform of scale zet, en hangt .tseo-eigendruk op de elementen die
   daaraan voldoen. Dat is geen naamlijst maar een meting, en hij klopt dus
   ook voor een pagina die morgen wordt gebouwd met een klasse die vandaag
   nog niet bestaat. Precies daar ging de oude lijst kapot.
   Zie het blok DE INDRUK in v2.js. */
.tseo-eigendruk:active,
.tseo-eigendruk.is-druk { scale: 1; }

/* c. Het toetsenbord. De site had hiervoor twee focusregels in het hele
      gedeelde blad, allebei alleen een kleurwissel. :focus-visible gaat
      alleen af bij toetsenbordbediening, dus een muisklik of een tik levert
      geen ring op. */
a:focus-visible, button:focus-visible, [role="button"]:focus-visible,
summary:focus-visible, input:focus-visible, select:focus-visible,
textarea:focus-visible, label[for]:focus-visible,
[tabindex]:not([tabindex="-1"]):focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 3px;
  /* 26-08: van 2 naar 3 px lucht. Met 2 px raakte de ring op de gevulde
     knoppen (.v2cta en de 59 CTA-klassen met een eigen achtergrond) de
     knoprand zelf, en cyaan op het blauwe verloop is nauwelijks te zien.
     Met 3 px staat de ring op de donkere pagina-achtergrond ernaast en is
     het verschil groot. outline volgt border-radius vanzelf, dus de ring om
     een pil is een pil. outline telt niet mee in de opmaak: geen shift. */
}

/* d. Een aanraking geeft hetzelfde antwoord als een hover.

      Dit is de kern van de opdracht: op een telefoon bestaat hover niet, en
      dan hoort een aanraking hetzelfde gevoel te geven. Hieronder staat voor
      elke gedeelde :hover regel van deze site zijn tweelingbroer op :active.
      Drie bronnen zijn nagelopen: dit bestand (27 hovers),
      theseo-product-shell-j06.css (8) en jarvis-funnel.css (3).

      Een regel die het blok stuurt: een hover die het element OPTILT wordt
      hier niet nagedaan. Een indruk hoort omlaag te gaan, niet omhoog. De
      omlaagbeweging komt uit de scale hierboven; wat hier wordt nagedaan is
      de kleur, de achtergrond, de rand, de schaduw en de verschuiving die de
      handtekening van dat element is. */
@media (hover: none) {
  /* de header en het register */
  .v2toggle:active { color: #fff; }
  .v2toggle:active .lines i { width: 22px; }
  .v2reg-row:active { padding-left: 14px; background: rgba(110,121,255,.06); }
  .v2reg-col--ai .v2reg-row:active { background: rgba(103,232,249,.05); }
  .v2reg-col--vrij .v2reg-row:active { background: rgba(139,149,255,.06); }
  .v2reg-bij a:active { color: #fff; }
  /* de knoppen en de grote afsluiting */
  .v2cta:active { box-shadow: 0 12px 32px rgba(22,82,240,.5); }
  .giant:active { color: #fff; }
  .giant:active .arr { transform: translate(10px,-6px); }
  /* de rijen, kaarten en figuren */
  .krow:active { padding-left: 20px; }
  .krow:active::before { transform: scaleX(1); }
  .krow:active .krow-flip > span { transform: translateY(-50%); }
  .lab-tool:active { background: #0a1226; }
  .fig:active::before, .fig:active::after { opacity: 1; }
  .seek-res:active { padding-left: 14px; }
  .kl-logo:active { border-color: rgba(139,149,255,.35); }
  .kl-logo:active img { filter: none; }
  .kl-logo:active .kl-naam { color: rgba(255,255,255,.9); }
  /* de lopende tekst, de juridische rijen en de voettekst */
  .dos-body a:not([class*="cta"]):not([class*="btn"]):active { color: #fff; }
  .dos-links a:active { color: #fff; }
  .jur-rij dd a:active { color: #fff; }
  .jur-noot a:active { color: #fff; }
  .v2foot-nav a:active { color: #fff; }
  .v2foot-in .ann a:active { color: #fff; }
  /* de Jarvis-schil, die op 21 pagina's de hele opmaak levert.
     Deze regels staan hier en niet in theseo-product-shell-j06.css of
     jarvis-funnel.css, omdat die twee bestanden een eigen versiestempel
     dragen dat dan mee omhoog zou moeten. Beide bestanden laden na dit
     bestand, maar er bestaat daar geen enkele :active regel, dus er is
     niets om mee te botsen. */
  .j-nav a:active { color: var(--j-ink); }
  .j-doc-row:active { background: rgba(109,85,239,.035); }
  .jarvis-site-shell .j-doc-row:active { background: rgba(103,232,249,.035); }
  .jarvis-site-shell .j-footer a:active { color: #fff; }
  .j-button:active, .j-nav-cta:active { box-shadow: 0 18px 40px rgba(90,65,214,.28); }
  .jarvis-site-shell .j-aside a:active { color: #fff; }
  .jarvis-site-shell .j-prose a:active { color: #fff; }
  /* De heroknop van de Jarvis-pagina's. Die klasse staat op precies de
     negentien pagina's die dit blad plus de Jarvis-schil laden, met in alle
     negentien dezelfde pagina-lokale regels. Daarom staat de tweelingbroer
     hier een keer in plaats van negentien keer in een pagina-stijlblok.
     De optilling translateY(-1px) wordt bewust niet nagedaan: een indruk
     hoort omlaag te gaan, en dat doet de scale hierboven al.
     De schil .j-page-hero staat er niet voor de sier: het pagina-stijlblok
     laadt na dit bestand en zet de rustkleur op .j-page-hero .jph-btn, dus
     een kale .jph-btn:active zou even zwaar wegen en verliezen op volgorde. */
  .j-page-hero .jph-btn:active { background: #1e5ef7; }
  .j-page-hero .jph-btn:active .arr { transform: translateX(3px); }
  .j-page-hero .jph-btn.ghost:active { border-color: var(--per2); background: rgba(139,149,255,.1); }
}

/* e. Een vinger is geen muispijl.

      Gemeten op 375: 33 raakdoelen onder 44 px op /onderzoek/, en alle 33
      kwamen uit de gedeelde header en de gedeelde voettekst. Dat zijn dus
      niet 33 fouten op een pagina maar een fout op 472 pagina's.

      HERZIEN 25-08 OP TWEE PUNTEN, na de raakdoelenronde over de 34 gemeten
      Jarvis-, MyParcel- en LinkLoop-pagina's.

      EEN. De onzichtbare ::after is vervangen door echte voering. Gianluca:
      "los dit op met padding en niet door de letters groter te maken, want de
      tekst hoort te blijven zoals hij is." De ::after werkte wel, maar hij is
      niet te meten: getBoundingClientRect kijkt naar het element zelf, dus
      oogcheck.js bleef 15 px melden waar de vinger 45 px raakte. Een oplossing
      die je niet kunt nameten wordt elke ronde opnieuw als fout gemeld.

      Voering is hier niet duurder dan de ::after, want zes van de acht
      selectors zijn inline (.ann a, .kruimel a, .j-breadcrumb a). Bij een
      inline element telt voering boven en onder niet mee in de regelhoogte:
      de doos groeit, de bladspiegel niet. Op die plekken verschuift dus geen
      enkele pixel en meet de doos toch 45.

      TWEE. De voorwaarde is verbreed van (pointer: coarse) naar ook
      (max-width: 560px). Reden: alleen op coarse betekent dat een controleur
      die zijn venster op 375 zet niets van de reparatie ziet en hem opnieuw
      als fout opschrijft. Op een muisvenster van 560 px of smaller kost een
      ruimer raakvlak niemand iets.

      De maten komen uit een meting met aanraakemulatie aan, niet uit een
      schatting. Kruimelpad 15 px tekst plus twee keer 15 voering is 45.
      Registerknop 19 px inhoud plus twee keer 13 is 45. Taalknop 15 px inhoud
      plus twee keer 14 plus 2 px rand is 45. */
@media (pointer: coarse), (max-width: 560px) {
  /* Inline: voering vergroot de doos, niet de regel. Nul verschuiving, op elk
     scherm. Daarom mag dit blok wel breed staan. */
  .v2foot-in .ann a, .kruimel a, .j-breadcrumb a { padding-block: 15px; }
  .v2foot-in { row-gap: 30px; }

  /* De registerknop was 31 px. Dertien px voering boven en onder maakt 45.
     De taalknop ernaast krijgt dezelfde behandeling, maar die regel staat
     verderop bij de taalwisselaar zelf: daar staat een pad-verkorting in een
     @media (max-width: 620px) die later komt en anders zou winnen.
     Nagemeten: de kopbalk blijft 92 px, want een knop van 45 past binnen de
     hoogte die de balk toch al had. */
  .v2toggle { padding-block: 13px; }

  /* Deze hebben een eigen doos in de normale stroom en stonden er al.
     .j-footer a staat op de negentien Jarvis-pagina's twaalf keer per
     pagina en mat 22 px; met 11 px voering boven en onder is dat 44.
     .dos-links a staat op 454 pagina's en mat 19 px; 13 px maakt 45.
     .jph-btn mat 42 px, twee te weinig, en is een inline-flex met de tekst
     al gecentreerd, dus een ondergrens verplaatst niets. */
  .j-aside a { min-height: 44px; }
  .v2reg-row { min-height: 44px; }
  .jph-btn { min-height: 44px; }
  .j-footer a { padding-block: 11px; }
  .dos-links a { padding-block: 13px; }
}

/* De twee linkrijen in de voettekst zijn flexkinderen, en daar telt voering wel
   mee in de hoogte. Vandaar de splitsing hieronder, en die is gemeten, niet
   bedacht: met voering onder alleen (pointer: coarse) werd de voettekst van
   /jarvis/vertrouwen/ op 1280 px 382 hoog op een aanraakscherm tegen 294 op
   precies dezelfde laptop met een muis. Dat is 88 px verschil tussen twee
   laptops van dezelfde breedte, en dat is geen reparatie maar een nieuw
   verschil. Dus:

     op telefoonbreedte   echte voering, want daar hoort de rij ook ruimer
     breder met aanraak   het onzichtbare raakvlak van de vorige ronde, want
                          daar mag de bladspiegel niet verschuiven

   Op telefoonbreedte gaat de regelafstand met evenveel omlaag als de voering
   omhoog, zodat de rijafstand blijft staan waar hij stond:
     .v2foot-nav   was 23 px tekst plus 30 px tussenruimte, is 45 plus 8
     .v2reg-bij    was 26 px tekst plus 30 px tussenruimte, is 44 plus 12 */
@media (max-width: 560px) {
  .v2foot-nav a { padding-block: 11px; }
  .v2foot-nav { row-gap: 8px; }
  .v2reg-bij a { padding-block: 15px; }
  .v2reg-bij { row-gap: 12px; }
}

@media (pointer: coarse) and (min-width: 561px) {
  .v2foot-nav a, .v2reg-bij a { position: relative; }
  .v2foot-nav a::after, .v2reg-bij a::after {
    content: ""; position: absolute; inset: -15px -8px;
  }
  /* Het raakvlak is 15 px hoger en lager dan de tekst, dus twee omlopende
     regels moeten 30 px uit elkaar staan om elkaars onderkant niet te stelen. */
  .v2foot-nav, .v2reg-bij { row-gap: 30px; }
}

/* ============================================================
   APP-NORM · PUNT 6, 7 EN 8 uit paginas/app-gevoel-norm.md
   De microreacties: wat er gebeurt op het moment dat een
   bezoeker iets aanraakt.

   Waarom dit blok sitebreed staat en niet per pagina: de norm
   zegt dat een overgang die op de ene pagina anders is dan op
   de andere erger is dan geen overgang. Vier standen, een
   vormtaal, een plek. Een pagina die iets doet erft dit en
   verzint niets nieuws.

   De vier standen zijn overal dezelfde vier:
     rust      de knop wacht op je
     bezig     er loopt iets, met de verstreken tijd erbij
     gelukt    het is klaar, en er staat in woorden wat er klaar is
     mislukt   het ging mis, met de weg terug erbij
   en daarnaast de vijfde die geen fout is maar wel een uitkomst:
     leeg      het zoeken is gelukt en er is niets gevonden

   Twee dragers, een vormtaal:
     .tseo-kwit  de kwitantie, hangt onder het ding dat je net
                 aanraakte en verdwijnt vanzelf. Voor handelingen
                 zonder eigen plek op de pagina: een download,
                 een kopieerknop.
     .tseo-meld  de meldregel, staat vast in de opmaak onder een
                 formulier of een instrument. Voor alles wat een
                 eigen plek heeft en langere tekst nodig heeft.
   Ze delen kleur, pictogram en tijd, dus het is een systeem en
   geen twee systemen.

   Dit blok raakt punt 4, 5, 9 en 10 uit de norm niet aan. Punt 4
   en 5 staan in het blok "3.3 HOVER EN AANRAKING" hierboven, dat
   in dezelfde ronde is toegevoegd. Daar staat de indruk sitebreed
   als `:where(...):active { scale: .985 }`, en dus staat hier geen
   tweede indruk: twee keer .985 is .970 en dat is zichtbaar harder
   dan de rest van de site. Wat hier wel staat is de transitielijst
   compleet houden, zodat een knop met een stand zijn indruk niet
   verliest.
   ============================================================ */

/* ---- de kwitantie ------------------------------------------
   Vast gepositioneerd en pointer-events:none, dus hij kan geen
   enkele opmaak verschuiven en nooit een klik opvangen. v2.js
   zet hem onder het element dat je aanraakte; hij schaalt op uit
   die hoek, niet uit zijn eigen midden, want anders lijkt hij uit
   het niets te komen in plaats van uit de knop. */
.tseo-kwit {
  position: fixed; left: 0; top: 0; z-index: 9000;
  box-sizing: border-box;
  max-width: min(340px, calc(100vw - 24px));
  display: flex; gap: 9px; align-items: flex-start;
  padding: 10px 14px 10px 13px;
  border: 1px solid var(--line); border-radius: 10px;
  background: rgba(8, 13, 28, .96);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  font: 500 11.5px/1.55 var(--mono); letter-spacing: .03em;
  color: #dfe5f6;
  box-shadow: 0 18px 44px rgba(0, 0, 0, .5), inset 2px 0 0 var(--tseo-kleur, var(--per2));
  pointer-events: none;
  opacity: 0; transform: translateY(-6px) scale(.97);
  transform-origin: var(--tseo-bron, top left);
  transition: opacity 160ms var(--ease), transform 200ms var(--ease);
}
.tseo-kwit.is-aan { opacity: 1; transform: none; }
.tseo-kwit b { color: #fff; font-weight: 700; }
.tseo-kwit small {
  display: block; margin-top: 3px;
  font: 400 10.5px/1.5 var(--mono); color: var(--mut2); letter-spacing: .04em;
}

/* ---- de meldregel ------------------------------------------
   Staat altijd in de opmaak, ook als er niets te melden is. De
   vaste minimumhoogte is de reden: verschijnt er een melding,
   dan schuift er niets. Punt 9 van de norm zegt dat niets mag
   schokken, en een regel die van nul naar twintig pixels springt
   duwt de knop eronder weg op het moment dat je erop drukt. */
.tseo-meld {
  display: flex; gap: 9px; align-items: flex-start;
  margin: 12px 0 0; min-height: 20px;
  font: 500 11.5px/1.6 var(--mono); letter-spacing: .03em;
  color: var(--mut);
  box-shadow: inset 2px 0 0 transparent;
  padding-left: 0;
  transition: color 200ms var(--ease), padding 200ms var(--ease),
              box-shadow 200ms var(--ease);
}
.tseo-meld[data-soort] { padding-left: 11px; box-shadow: inset 2px 0 0 var(--tseo-kleur, var(--per2)); }
.tseo-meld b { color: #fff; font-weight: 700; }
.tseo-meld a { color: var(--per2); }
.tseo-meld small {
  display: block; margin-top: 3px;
  font: 400 10.5px/1.5 var(--mono); color: var(--mut2); letter-spacing: .04em;
}
/* De tekst zelf komt binnen, de regel blijft staan. */
.tseo-kwit-t { display: block; min-width: 0; overflow-wrap: anywhere; }
.tseo-meld[data-soort] .tseo-kwit-t { animation: tseoMeldIn 220ms var(--ease) both; }
@keyframes tseoMeldIn { from { opacity: 0; transform: translateY(-3px); } }

/* ---- het pictogram, gedeeld door beide dragers --------------
   Vier standen, vier kleuren, en bij bezig geen teken maar een
   ring die draait. Dat is het verschil tussen "er staat iets" en
   "er loopt iets". */
.tseo-kwit-i {
  flex: 0 0 auto; box-sizing: border-box;
  width: 13px; height: 13px; margin-top: 3px;
  font-style: normal; font-size: 11px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--tseo-kleur, var(--per2));
}
[data-soort="bezig"]   { --tseo-kleur: #8b95ff; }
[data-soort="gelukt"]  { --tseo-kleur: #86efac; }
[data-soort="mislukt"] { --tseo-kleur: #f87171; }
[data-soort="leeg"]    { --tseo-kleur: #67e8f9; }
[data-soort="bezig"] .tseo-kwit-i {
  border: 1.5px solid rgba(139, 149, 255, .26);
  border-top-color: var(--per2);
  border-radius: 50%;
  animation: tseoDraai 620ms linear infinite;
}
/* De verstreken tijd. Geen schatting en geen belofte: v2.js telt
   wat er echt voorbij is, zodat wachten leesbaar wordt. */
.tseo-tel { color: var(--mut2); font-variant-numeric: tabular-nums; }

@keyframes tseoDraai { to { transform: rotate(1turn); } }

/* ---- de vier standen op de knop zelf ------------------------
   De knop is het ding waar de vinger op staat, dus de eerste
   reactie hoort daar te zitten en niet ergens anders op de
   pagina. Alleen eigenschappen die geen enkele opmaak kunnen
   breken: cursor, randkleur en een schud van 240 ms.
   Bewust geen breedte, hoogte, marge of display.
   De transitielijst herhaalt die van blok 3.3 en vult hem aan met
   box-shadow. Dat is geen doublure maar noodzaak: [data-staat]
   weegt zwaarder dan de element-selector daar, dus zonder deze
   herhaling zou een knop met een stand zijn scale-overgang en
   daarmee zijn indruk kwijtraken. */
[data-staat] {
  transition: color 200ms var(--ease), background-color 200ms var(--ease),
              border-color 200ms var(--ease), opacity 200ms var(--ease),
              box-shadow 200ms var(--ease), scale 120ms var(--ease);
}
[data-staat="bezig"] { cursor: progress; }
[data-staat="gelukt"] {
  border-color: rgba(134, 239, 172, .5);
  box-shadow: 0 0 0 1px rgba(134, 239, 172, .18);
}
[data-staat="mislukt"] { border-color: rgba(248, 113, 113, .55); }
/* Fout schudt een keer, kort. Nooit herhalen, nooit rood knipperen. */
@media (prefers-reduced-motion: no-preference) {
  [data-staat="mislukt"] { animation: tseoSchud 240ms var(--ease); }
}
@keyframes tseoSchud {
  25% { transform: translateX(-4px); }
  75% { transform: translateX(4px); }
}

/* Een download of een kopieerknop krijgt bewust geen randkleur:
   dat zijn CTA's met een eigen vulling, en die antwoorden met de
   indruk uit blok 3.3 en met de kwitantie eronder. Alleen de
   wachtcursor hoort erbij, want die zegt iets wat de indruk niet
   zegt. */
a[download][data-staat="bezig"] { cursor: progress; }

/* ---- de velden ---------------------------------------------
   :user-invalid en niet :invalid. :invalid vuurt bij de eerste
   letter en beschuldigt iemand van een fout die hij nog aan het
   maken is; :user-invalid vuurt pas nadat het veld is aangeraakt
   en weer verlaten. Alleen randkleur en gloed, dus er verschuift
   niets. Werkt ook in een formulier met novalidate: dat schakelt
   alleen het blokkeren uit, niet de beoordeling. */
form input:user-invalid,
form select:user-invalid,
form textarea:user-invalid {
  border-color: rgba(248, 113, 113, .65);
  box-shadow: 0 0 0 1px rgba(248, 113, 113, .16);
}
/* Het veld waar v2.js de aandacht heen brengt na een mislukte
   verzending. Een halve seconde, dan is het weer een gewoon veld. */
@media (prefers-reduced-motion: no-preference) {
  .tseo-wijst { animation: tseoWijs 520ms var(--ease); }
}
@keyframes tseoWijs {
  0%, 100% { box-shadow: 0 0 0 1px rgba(248, 113, 113, .16); }
  50%      { box-shadow: 0 0 0 4px rgba(248, 113, 113, .22); }
}

/* ---- het lege resultaat ------------------------------------
   Leeg is een uitkomst, geen fout. Hij ziet er dus niet uit als
   een foutmelding, en hij wijst de weg terug in plaats van
   alleen te zeggen dat er niets is. */
.tseo-leeg {
  margin: 14px 0 0; padding: 14px 16px;
  border: 1px dashed var(--line); border-radius: 10px;
  font: 400 12px/1.7 var(--mono); color: var(--mut);
  background: rgba(103, 232, 249, .03);
}
.tseo-leeg b { color: #e6e9f5; font-weight: 700; }
.tseo-leeg a { color: var(--cyan); }
.tseo-leeg[hidden] { display: none; }
@media (prefers-reduced-motion: no-preference) {
  .tseo-leeg:not([hidden]) { animation: tseoMeldIn 260ms var(--ease) both; }
}

/* ---- filteren: niets verdwijnt zonder overgang --------------
   Punt 8 van de norm. De twee filters op de site zetten hun
   tegels op [hidden], dus display:none, dus ze knipperen weg.
   allow-discrete houdt display 200 ms tegen zodat de tegel
   zichtbaar vertrekt, en @starting-style geeft de terugkomst een
   beginstand.
   De klasse op <html> is er met opzet: zonder die klasse gebeurt
   er bij het laden van de pagina niets extra's, en pas zodra de
   bezoeker zelf filtert gaat de overgang aan. Wat de bezoeker
   veroorzaakt hoort hij te zien; wat de pagina zelf doet bij het
   laden hoort de bestaande onthulling te blijven. */
.tseo-filter-aan .skl-tile,
.tseo-filter-aan .skl-blok,
.tseo-filter-aan .thub-kaart,
.tseo-filter-aan .thub-groep,
.tseo-filter-aan [data-micro-lijst] > * {
  transition: opacity 180ms var(--ease), transform 220ms var(--ease),
              display 220ms allow-discrete;
}
.tseo-filter-aan .skl-tile[hidden],
.tseo-filter-aan .skl-blok[hidden],
.tseo-filter-aan .thub-kaart[hidden],
.tseo-filter-aan .thub-groep[hidden],
.tseo-filter-aan [data-micro-lijst] > [hidden] {
  opacity: 0; transform: scale(.96);
}
@starting-style {
  .tseo-filter-aan .skl-tile:not([hidden]),
  .tseo-filter-aan .skl-blok:not([hidden]),
  .tseo-filter-aan .thub-kaart:not([hidden]),
  .tseo-filter-aan .thub-groep:not([hidden]),
  .tseo-filter-aan [data-micro-lijst] > :not([hidden]) {
    opacity: 0; transform: scale(.96);
  }
}

/* ---- de menuopening ----------------------------------------
   De registerknop is het element dat een bezoeker op deze site het
   vaakst aanraakt, en het register was tegelijk de traagste
   beweging van de site. Nagemeten in het bestand hierboven:
     de vouw open   850 ms   (regel 205)
     de vouw dicht  550 ms   (regel 202)
     de regels      600 ms met een cascade tot 640 ms (regel 280
                    tot 294), dus de laatste regel staat er pas na
                    1.240 ms
     de voetregel   600 ms met 420 ms vertraging
   De norm zegt: een paneel of lade 200 tot 500 ms, een cascade van
   60 ms per stap met maximaal zes stappen, en weggaan korter dan
   aankomen. 1.240 ms is een filmtijd voor iets dat je twintig keer
   per bezoek doet.

   Hieronder staat dezelfde choreografie, op de tijdschaal van een
   interface. De opzet van regel 276 blijft precies zoals hij is:
   de vouw draagt de beweging, de regels landen er vlak achteraan.
     de vouw open   380 ms
     de vouw dicht  260 ms
     de regels      420 ms, 60 ms per stap, na de zesde stap niet
                    verder oplopen, dus alles staat er na 780 ms
     de voetregel   420 ms met 300 ms vertraging
   Sluiten is korter dan openen, en dat is punt 9 van de norm.
   Beweging, ease, kleur en opmaak blijven ongewijzigd; alleen de
   klok gaat anders. Bij prefers-reduced-motion wint regel 350 met
   zijn !important en gebeurt er nog steeds niets. */
/* HERZIEN 09-09-2026, en dit blok is nu de enige plek met tijden.

   Wat er stond en waarom het haperde. De regels vervaagden bij het sluiten in
   200 ms terwijl de vouw er 260 ms over deed, en de voetregel landde bij het
   openen pas na 300 plus 420 is 720 ms terwijl de vouw al na 380 ms klaar was.
   Erger nog: de navigatierij bovenin en de kop boven elke kolom hadden
   helemaal geen beweging, dus die stonden er bij het openen meteen en bleven
   bij het sluiten staan. Dat is de stand op de schermafdruk van Gianluca: een
   paneel met alleen de koppen erin en geen enkele regel.

   Wat er nu staat. Alles komt van boven mee naar beneden, alles vertrekt
   tegelijk, en niets verdwijnt eerder dan het paneel zelf. De trap is 40 ms in
   plaats van 60 en stopt na zes regels, dus de laatste regel staat na 240 plus
   380 is 620 ms stil in plaats van na 780. De twee tijden komen uit de
   variabelen op .v2reg, zodat er niet opnieuw twee waarheden ontstaan. */
.v2reg {
  transition: grid-template-rows var(--reg-uit) var(--ease), max-height var(--reg-uit) var(--ease),
              box-shadow var(--reg-uit) var(--ease), border-color var(--reg-uit) var(--ease),
              visibility 0s var(--reg-uit);
}
.v2hdr.is-open .v2reg {
  transition: grid-template-rows var(--reg-in) var(--ease), max-height var(--reg-in) var(--ease),
              box-shadow var(--reg-in) var(--ease), border-color var(--reg-in) var(--ease),
              visibility 0s;
}
.v2reg-in { transition: padding var(--reg-uit) var(--ease); }
.v2hdr.is-open .v2reg-in { transition: padding var(--reg-in) var(--ease); }

.v2reg-row,
.v2reg-bij,
.v2reg-col > .ann,
.v2reg-foot {
  opacity: 0; transform: translateY(-10px);
  transition: opacity var(--reg-in) var(--ease), transform var(--reg-in) var(--ease);
}
.v2reg-row {
  transition: padding var(--reg-uit) var(--ease), background var(--reg-uit) var(--ease),
              opacity var(--reg-in) var(--ease), transform var(--reg-in) var(--ease);
}
.v2hdr.is-open .v2reg-row,
.v2hdr.is-open .v2reg-bij,
.v2hdr.is-open .v2reg-col > .ann,
.v2hdr.is-open .v2reg-foot {
  opacity: 1; transform: none;
  transition: opacity var(--reg-in) var(--ease) var(--rd, 0s),
              transform var(--reg-in) var(--ease) var(--rd, 0s);
}
.v2hdr.is-open .v2reg-row {
  transition: padding var(--reg-uit) var(--ease), background var(--reg-uit) var(--ease),
              opacity var(--reg-in) var(--ease) var(--rd, 0s),
              transform var(--reg-in) var(--ease) var(--rd, 0s);
}
/* De volgorde van boven naar beneden: eerst de navigatierij, dan de
   kolomkoppen, dan de regels, dan de voet. Zo leest het als een blad dat
   naar beneden komt en niet als losse dingen die aanfloepen. */
.v2hdr.is-open .v2reg-bij { --rd: 0s; }
.v2hdr.is-open .v2reg-col > .ann { --rd: .04s; }
.v2hdr.is-open .v2reg-row:nth-child(2)  { --rd: .04s; }
.v2hdr.is-open .v2reg-row:nth-child(3)  { --rd: .08s; }
.v2hdr.is-open .v2reg-row:nth-child(4)  { --rd: .12s; }
.v2hdr.is-open .v2reg-row:nth-child(5)  { --rd: .16s; }
.v2hdr.is-open .v2reg-row:nth-child(6)  { --rd: .2s;  }
/* Na de zesde stap niet verder oplopen. Anders zit de lezer op
   regel elf te wachten op een ingang die hij al ziet staan. */
.v2hdr.is-open .v2reg-row:nth-child(n + 7) { --rd: .24s; }
.v2hdr.is-open .v2reg-foot { --rd: .2s; }

/* SLUITEN IS EEN BEWEGING. Geen animatie maar het weglaten ervan: een
   overgang van 0 s met een vertraging die gelijk is aan de vouw. Alles houdt
   zijn open stand vast zolang het paneel dichtgaat en springt pas terug op het
   moment dat het paneel al op visibility hidden staat. Je ziet dus nooit iets
   verdwijnen, je ziet het blad omhoog gaan met alles er nog in. */
.v2hdr:not(.is-open) .v2reg-row,
.v2hdr:not(.is-open) .v2reg-bij,
.v2hdr:not(.is-open) .v2reg-col > .ann,
.v2hdr:not(.is-open) .v2reg-foot {
  transition: opacity 0s var(--ease) var(--reg-uit), transform 0s var(--ease) var(--reg-uit);
}
/* De hamburger volgt de vouw en niet zijn eigen halve seconde. */
.v2toggle { transition: color 200ms var(--ease), scale 120ms var(--ease); }
.v2toggle .lines i {
  transition: transform 300ms var(--ease), opacity 240ms var(--ease),
              width 300ms var(--ease);
}

/* Het leefgebied dat alleen voor een schermlezer bestaat. Wordt
   gebruikt voor veranderingen die op het scherm al te zien zijn
   maar niet te horen: het register dat opengaat, een filter dat
   toeslaat. Nooit voor iets dat verder nergens staat. */
.tseo-stil {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; margin: -1px; padding: 0; border: 0;
}

/* ---- de uit-knop, per element, met de eindstand erbij -------
   Niet een globale animation:none, maar per element de eindstand,
   zodat er niets onzichtbaar of half blijft hangen. */
@media (prefers-reduced-motion: reduce) {
  .tseo-kwit { transition: none; transform: none; }
  .tseo-kwit.is-aan { opacity: 1; transform: none; }
  .tseo-meld, .tseo-meld .tseo-kwit-t { transition: none; animation: none; opacity: 1; transform: none; }
  .tseo-leeg:not([hidden]) { animation: none; opacity: 1; transform: none; }
  [data-soort="bezig"] .tseo-kwit-i { animation: none; border-top-color: var(--per2); }
  [data-staat="mislukt"] { animation: none; }
  .tseo-wijst { animation: none; box-shadow: 0 0 0 3px rgba(248, 113, 113, .22); }
  .tseo-filter-aan .skl-tile,
  .tseo-filter-aan .skl-blok,
  .tseo-filter-aan .thub-kaart,
  .tseo-filter-aan .thub-groep,
  .tseo-filter-aan [data-micro-lijst] > * { transition: none; }
  .tseo-filter-aan .skl-tile[hidden],
  .tseo-filter-aan .skl-blok[hidden],
  .tseo-filter-aan .thub-kaart[hidden],
  .tseo-filter-aan .thub-groep[hidden],
  .tseo-filter-aan [data-micro-lijst] > [hidden] { opacity: 0; transform: none; }
}


/* ============================================================
   APP-NORM 3.1 EN 3.5 (24-08) · DE PAGINAOVERGANG EN DE
   MEEREIZENDE ACHTERGROND.
   Hoort bij paginas/app-gevoel-norm.md hoofdstuk 3.1 en 3.5, en
   bij punt 9 en punt 10 van de afkeurlijst.

   Punt 9 stond fout op de hele site: v2.js liet de bezoeker 850
   ms naar een dichtvallend gordijn kijken voordat de volgende
   pagina uberhaupt begon te laden. Weggaan duurde dus langer dan
   aankomen, en er kwam niets van de oude pagina mee.

   Punt 10 stond fout op alle 472 paginas: de achtergrond gaf aan
   de bovenkant hetzelfde beeld als aan de onderkant.

   Deze twee horen bij elkaar, want het is dezelfde laag. De
   achtergrond is het blad waar de tekening op ligt; bij een
   overgang blijft het blad liggen en wisselt alleen de tekening.
   ============================================================ */

/* ---- 1. De browser doet de overgang zelf --------------------
   Beide paginas melden zich hiermee aan. De browser maakt een
   opname van de oude pagina, wisselt de nieuwe in en beweegt
   ertussen. Waar dit niet werkt gebeurt er niets extras en valt
   v2.js terug op het gordijn, dan wel kort. */
@view-transition { navigation: auto; }

/* ---- 2. Wat op beide paginas staat, blijft staan ------------
   De header en het blad staan op elke pagina op dezelfde plek.
   Zonder deze namen vervagen ze bij elke klik over zichzelf
   heen en beweegt het hele blad mee omhoog. Met deze namen
   blijft het blad liggen en wisselt alleen de tekening erop. */
.v2hdr { view-transition-name: hdr; }
.v2bg  { view-transition-name: canvas; }
::view-transition-group(hdr),
::view-transition-group(canvas) { animation: none; }

/* ---- 3. De z-volgorde, en dit is een reparatie -------------
   Een element met een view-transition-name wordt uit de
   paginaopname gelicht en BOVEN de opname van de rest getekend,
   ongeacht zijn eigen z-index. .v2bg is dekkend en ligt normaal
   op z-index -1, dus zonder deze drie regels ligt de
   achtergrond tijdens elke overgang over de hele pagina heen:
   een kwart seconde lang geen tekst, geen kop, geen header.
   Nagemeten in Chrome 148 met een vertraagde overgang van 12 s:
   zonder deze regels een leeg raster, met deze regels blijft
   alles gewoon staan. Wie ze weghaalt, haalt de tekst weg. */
::view-transition-group(canvas) { z-index: 0; }
::view-transition-group(root)   { z-index: 1; }
::view-transition-group(kaart)  { z-index: 2; }
::view-transition-group(hdr)    { z-index: 3; }

/* ---- DE KAART DIE OPENGAAT ---------------------------------
   Hoort bij het blok DE KAART DIE OPENGAAT in v2.js. Lees ze
   samen. v2.js geeft de aangeklikte kaart en de hero van de
   doelpagina allebei de naam "kaart"; de browser tekent dan de
   beweging van de plaats en de maat van de een naar die van de
   ander. Hier staat alleen hoe lang dat duurt en hoe de twee
   opnamen in elkaar overgaan.

   De kaart ligt op 2: boven de pagina waar hij vandaan komt,
   onder de kopbalk waar hij onderdoor schuift. De kopbalk stond
   op 2 en is daarom naar 3 gegaan; zonder dat zou de kaart er
   overheen reizen en dat leest als een fout.

   460 ms, en dat is langer dan de 220 ms van de gewone wissel.
   Dat mag hier, want dit is geen wissel maar een gebaar: je ziet
   het ding dat je aanraakte uitgroeien tot de pagina. De App
   Store zit rond dezelfde halve seconde. De oude opname vervaagt
   in 200 ms, ruim voor het einde, zodat je de nieuwe pagina ziet
   opbouwen in plaats van twee beelden over elkaar.

   Bij bewegingsvoorkeur doet dit blok niets: de regel onderaan
   dit blad zet animation op none voor alles. */
/* De hero van elke pagina draagt de naam vast. Dat kan hier en niet in
   JavaScript, want v2.js staat vlak voor </body> en de browser tekent zijn
   eerste beeld eerder dan dat: een script dat de naam pas dan zet, is te
   laat. Nagemeten met een meter in de pagereveal-luisteraar, die nooit
   afging. Uit v2.css staat de naam er al voordat er iets draait.

   main > :first-child en niet een klassenaam, want de hero heet overal
   anders: .hero, .sv-hero, .prz-hero, .cs-hero, .prod-hero. Wat overal geldt
   is de plaats. Een nieuwe pagina doet dus vanzelf mee.

   v2.js haalt deze naam van de hero af op het moment dat een kaart hem
   overneemt; twee gelijke namen in een document laat de browser de hele
   overgang overslaan. */
main > :first-child { view-transition-name: kaart; }
/* Geteld over 648 pagina's: 576 dragen main.v2main, 50 een kale main, en de
   rest is een app-schil. De schillen van MyParcel en Jarvis dragen hun eigen
   navigatie en doen niet mee aan deze overgang, dus die krijgen de naam niet. */
.mpp-content > :first-child,
.mp-main > :first-child,
.com-main > :first-child,
.jarvis-app-site main > :first-child { view-transition-name: none; }

::view-transition-group(kaart) {
  animation-duration: 460ms;
  animation-timing-function: var(--ease);
}
/* ==========================================================================
   DE VEELGESTELDE VRAGEN · 09-09-2026
   "De FAQ moet overal hetzelfde zijn als op de homepagina."

   Deze vorm stond in home-atelier.css en werkte dus op een pagina. Hij staat
   nu hier, en op twee klassenamen tegelijk: .home-faq-item houdt de HTML van
   de startpagina precies zoals hij is, .tseo-faq-item is de naam voor elke
   andere pagina. Zelfde waarden, een bron.

   Het uitvouwen zelf staat er los van en gebeurt al sitebreed: v2.js maakt van
   elke <details> die geen taalwisselaar is een .tseo-vraag met een raster dat
   van 0fr naar 1fr loopt, plus een plusteken. Wat hier staat is alleen de
   vorm: de haarlijn tussen de vragen, de maat van de vraag en de lucht
   eromheen.
   ========================================================================== */
.tseo-faq-list,
.home-faq-list { overflow: hidden; }

.tseo-faq-item,
.home-faq-item { border-bottom: 1px solid var(--home-border, var(--hair)); }

.tseo-faq-item:last-child,
.home-faq-item:last-child { border-bottom: 0; }

.tseo-faq-item summary,
.home-faq-item summary {
  padding: 24px clamp(20px, 4vw, 34px);
  cursor: pointer;
  color: #f4f6ff;
}

.tseo-faq-item summary::marker,
.home-faq-item summary::marker { color: var(--per2); }

.tseo-faq-item h3,
.home-faq-item h3 {
  display: inline;
  margin: 0;
  font-size: clamp(1.03rem, 1.5vw, 1.18rem);
}

.tseo-faq-item p,
.home-faq-item p {
  max-width: 76ch;
  margin: 0;
  padding: 0 clamp(20px, 4vw, 34px) 28px;
}

.tseo-faq-item[open] summary,
.home-faq-item[open] summary { padding-bottom: 15px; }
/* Op een dossierpagina staat de vragenlijst niet in een kaart maar los onder
   de kop; de binnenmarge van de startpagina zette de vragen dan 20 tot 34 px
   rechts van de kop (Gianluca 14-09: "uitlijning is raar"). Hier begint de
   vraag op dezelfde lijn als de kop; de plus blijft rechts. */
.dos-in .tseo-faq-item summary, .dos-in .tseo-faq-item p { padding-inline: 0; }
/* Een voetnoot direct onder een module krijgt lucht, ook als de pagina zelf
   14 px inline zette ("de tekst eronder zit te dicht op de sectie"). */
:is(.pipe, .ladder, .lagen, .stats, .tijd, .vakken, .steden, .rt, .lad4, .klant-reg, .stg4, .stg5, .timeline, .kaart, .wpr, .wsc, .abtest, .strat-grid, .drie, .wacht, .lgraf, .eer, .aiz4, .aiz5, .aiz6, .roadmap, .mob-dots) + .ann { display: block; margin-top: 28px !important; }
.mob-dots + .ann { margin-top: 6px !important; }
/* de middelste trede van een ladder steekt 18 px onder de rij uit */
.ladder + .ann, .lad4 + .ann { margin-top: 48px !important; }

.tseo-faq-item summary:hover h3,
.tseo-faq-item summary:focus-visible h3,
.home-faq-item summary:hover h3,
.home-faq-item summary:focus-visible h3 { color: #c7d2fe; }

/* ---- HET BLOKJE ZELF, en dit is het deel dat je altijd ziet ----
   Gianluca: "ik wil dat dat blokje zelf animeert, niet de pagina
   erna, dat ook maar dat zie ik wel, maar dat blokje, waardoor
   het voelt als een app."

   Dat is een ander ding dan de overgang hierboven, en het is het
   belangrijkere van de twee. De overgang tussen documenten hangt
   af van wat de browser aankan; dit gebeurt in de pagina zelf en
   werkt dus overal, ook waar die overgang niet bestaat.

   Het gebaar is dat van de App Store, in drie standen. Eerst zakt
   de kaart een fractie in, zoals iets dat je indrukt. Daarna komt
   hij naar je toe: hij groeit, hij licht op aan de rand en hij
   krijgt een schaduw die dieper wordt, alsof hij van het blad
   loskomt. Hij eindigt groter dan hij begon, en op dat moment
   wisselt de pagina. Zo loopt de beweging door in de overgang in
   plaats van ertegenaan te botsen.

   Waarom z-index en isolation: de kaart moet tijdens het groeien
   over zijn buren liggen, anders schuift hij eronder en leest het
   als een fout in plaats van als diepte. */
.kaart-opent {
  position: relative;
  z-index: 6;
  isolation: isolate;
  animation: kaartOpent 320ms var(--ease) both;
  will-change: transform;
}
@keyframes kaartOpent {
  0%   { transform: scale(1);     box-shadow: 0 0 0 rgba(0,0,0,0); }
  22%  { transform: scale(.978);  box-shadow: 0 6px 14px rgba(0,0,0,.28); }
  100% { transform: scale(1.055); box-shadow: 0 44px 96px rgba(0,0,0,.6); }
}
/* De buren wijken een tikje terug, zodat de kaart naar voren komt
   in plaats van alleen groter te worden. Twee procent is genoeg om
   te voelen en te weinig om te zien als beweging op zichzelf. */
.kaart-opent-om {
  animation: kaartWijkt 320ms var(--ease) both;
}
@keyframes kaartWijkt {
  to { transform: scale(.985); opacity: .72; }
}
@media (prefers-reduced-motion: reduce) {
  .kaart-opent, .kaart-opent-om { animation: none; }
}

::view-transition-old(kaart) { animation: kaartOud 200ms var(--ease) both; }
::view-transition-new(kaart) { animation: kaartNieuw 460ms var(--ease) both; }
@keyframes kaartOud   { to   { opacity: 0; } }
@keyframes kaartNieuw { from { opacity: 0; } }

/* ---- 4. Weggaan korter dan aankomen -------------------------
   120 ms eruit, 220 ms erin. Aankomen mag langer duren dan
   weggaan, nooit andersom; zo voelt een klik als vooruit.
   Geen ease-in op het vertrek: dat begint traag, precies op het
   moment dat de bezoeker kijkt.

   APPTEMPO 26-08-2026: dit stond op 160 en 280. De maatstaf van
   Gianluca is dat een overgang die je opmerkt te lang is, en bij
   280 ms merk je hem op. Samen duurt de wissel nu 340 ms in
   plaats van 440 ms, en dat is nog steeds ruim boven de grens
   waar een wissel als een sprong gaat voelen. Korter dan dit mag
   niet zomaar: de opname van de oude pagina moet lang genoeg
   blijven staan om het beeld rustig te houden. */
::view-transition-old(root) { animation: vtUit 120ms cubic-bezier(.4,0,1,1) both; }
::view-transition-new(root) { animation: vtIn 220ms var(--ease) both; }
/* De overgang zelf. Uit gaat sneller dan in, want vertrekken hoort te
   voelen als wegduwen en aankomen als landen.

   AANGESCHERPT 31-08-2026. Gianluca: "de overgang tussen die paginas is geen
   overgang, is gewoon laadknop, ik wil juist dat er iets gebeurt, een mooie
   animatie vooral." Dat klopte op het moment dat hij keek: de versie die toen
   live stond had deze overgang nog niet, die is pas op 31-08 geactiveerd.

   Wat er nu bij komt boven de oude enkele verschuiving: een minieme
   schaalbeweging. Bij het vertrekken krimpt de pagina naar 0,985 en zakt hij
   weg; bij het aankomen komt hij van 1,012 terug naar 1. Dat is precies genoeg
   om diepte te suggereren, zoals een scherm dat in een app naar achteren gaat
   en het volgende naar voren komt. Meer dan dit en het wordt een presentatie in
   plaats van een site.

   De tijden blijven wat ze waren, want die zijn op 26-08 op apptempo gezet en
   het gaat hier om karakter en niet om duur. Uit 120 ms, in 220 ms; samen met
   een paginalading van 80 tot 345 ms (gemeten 31-08) komt dat op ongeveer een
   halve seconde van klik tot klaar.

   De transform-origin ligt boven het midden: de pagina begint bovenaan en daar
   hoort het draaipunt, anders lijkt de kop te zakken bij het binnenkomen. */
@keyframes vtUit {
  to { opacity: 0; transform: translateY(-14px) scale(.985); filter: blur(1px); }
}
@keyframes vtIn {
  from { opacity: 0; transform: translateY(20px) scale(1.012); }
}
::view-transition-old(root),
::view-transition-new(root) { transform-origin: 50% 22%; will-change: transform, opacity; }

/* ---- 5. De terugval, voor browsers zonder overgang ----------
   v2.js zet dan html.geen-vt en navigeert na 180 ms in plaats
   van na 850. Het bestaande gordijn duurt 0,7 tot 0,9 s, dus
   zonder deze twee regels zie je een half dichtgevallen gordijn
   en daarna een harde sprong. Een terugval mag niet trager zijn
   dan het echte werk, en hij mag ook niet half zijn.

   APPTEMPO 26-08-2026: 260 ms is 180 ms geworden, gelijk aan de
   nieuwe klok in v2.js. Deze wachttijd telt zwaarder dan de
   andere getallen in dit bestand, want hij staat VOOR de
   navigatie: elke milliseconde hier is een milliseconde later
   beginnen met laden, en dus een latere Largest Contentful Paint
   op de volgende pagina. Korter is hier dus niet alleen mooier
   maar ook meetbaar sneller. */
html.geen-vt body {
  transition: opacity .18s var(--ease), transform .18s var(--ease), filter .18s var(--ease);
}
html.geen-vt body::after {
  transition: opacity .16s cubic-bezier(.4,0,1,1), transform .18s var(--ease);
}

/* ---- 6. De laag die meereist (3.5, punt 10) -----------------
   Laag 1 (raster, maatlat, korrel) en laag 2 (de gloed van 24 s)
   blijven precies zoals ze zijn; dat is het goedgekeurde DNA.
   Hier komt laag 3 bij: een tweede lichtbron die zijn plek
   ontleent aan hoe ver je in de pagina bent. Boven staat het
   licht linksboven, halverwege is het naar rechts en naar het
   midden gereisd en groter, onderaan zakt het naar linksonder
   en dimt. De bezoeker merkt dit niet bewust; wat hij merkt is
   dat de onderkant van de pagina een andere plek is dan de
   bovenkant.

   Geen JavaScript, geen scroll-luisteraar, geen frame-budget.
   Waar animation-timeline niet werkt staat de laag stil op zijn
   beginpositie en verandert er niets aan wat er nu is. En geen
   laag die de cursor volgt: dat werkt niet op een telefoon en
   het is precies het effect waar elke AI-site mee komt. */
.v2bg .reis {
  position: absolute; left: 0; top: 0;
  width: min(880px, 92vw); height: 880px;
  background: radial-gradient(50% 50% at 50% 50%, rgba(103,232,249,.09), transparent 70%);
  transform: translate3d(-18%, -30%, 0);
  will-change: transform;
}
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .v2bg .reis {
      animation: bgReis linear both;
      animation-duration: auto;
      animation-timeline: scroll(root block);
    }
  }
}
@keyframes bgReis {
  from { transform: translate3d(-18%, -30%, 0) scale(1);    opacity: .90; }
  50%  { transform: translate3d( 46%,  22%, 0) scale(1.18); opacity: 1;   }
  to   { transform: translate3d( -6%,  62%, 0) scale(1.06); opacity: .72; }
}

/* ---- 7. De uit-knop ----------------------------------------
   De globale regel bovenin dit blad (* { animation: none })
   raakt de pseudo-elementen van de overgang niet: ::view-
   transition-old en -new worden niet door * geselecteerd. Ze
   moeten hier dus met naam worden uitgezet. Zonder animaties is
   de overgang meteen klaar, dus wie bewegingsvoorkeur aan heeft
   ziet een gewone paginawissel en geen halve stand. */
@media (prefers-reduced-motion: reduce) {
  .v2bg .reis { animation: none; }
  ::view-transition-old(root),
  ::view-transition-new(root),
  ::view-transition-group(root),
  ::view-transition-group(hdr),
  ::view-transition-group(canvas) { animation: none; }
}


/* ============================================================
   APP-NORM 3.1 EN 3.5, DEEL 2 (24-08) · DE ANDERE TWEE
   ACHTERGRONDLAGEN.

   Waarom dit blok nodig is, en dit is een correctie op de
   meting. De stand bij E88 luidt "22 van 472 paginas dragen de
   bewegende achtergrondlaag .v2bg niet". Die meting zoekt naar
   class="v2bg" in het HTML-bestand. Twintig van die 22 paginas
   dragen de laag wel degelijk, alleen onder een andere naam en
   pas nadat er JavaScript heeft gedraaid:
   theseo-product-shell-j06.js zet bij het laden een
   .jarvis-v2bg neer met precies dezelfde vier lagen (grid,
   ruler, glow, grain), plus een header die .v2hdr heet met een
   klasse erachter. Nagemeten in de browser op /jarvis/kennis/:
   de laag staat er, het raster is zichtbaar, de gloed loopt.

   Wat er dus echt ontbreekt is niet de laag maar de
   GELIJKHEID: die twintig paginas doen niet mee aan de
   paginaovergang en hun achtergrond reist niet mee, waardoor
   een klik naar /jarvis/kennis/ anders aanvoelt dan een klik
   naar /prijzen/. En dat is precies wat er niet mag.

   Na deze correctie blijft er van de 22 nog een over die
   werkelijk geen bewegende laag heeft: /myparcel-dashboard/app/.
   Zie deel 3 hieronder.
   ============================================================ */

/* ---- 1. Elke achtergrondlaag heet canvas --------------------
   Drie verschillende lagen, drie verschillende bestanden, een
   naam. Zo blijft bij elke overgang het blad liggen, of je nu
   op een gewone pagina, op een Jarvis-pagina of in de
   MyParcel-preview staat. Nagegaan dat geen enkele pagina er
   twee van draagt: een dubbele naam laat de browser de hele
   overgang overslaan, en dan is de site weer stil. */
.jarvis-v2bg,
.mpp-grid { view-transition-name: canvas; }

/* ---- 2. De meereizende laag op de Jarvis-paginas ------------
   Daar wordt de achtergrond door script neergezet, dus daar kan
   geen div bij in het HTML-bestand. ::after doet hetzelfde werk
   op dezelfde plek in de stapel: als laatste kind, boven de
   korrel. De waarden zijn letterlijk die van .v2bg .reis, want
   het moet er hetzelfde uitzien. */
.jarvis-v2bg::after {
  content: ""; position: absolute; left: 0; top: 0;
  width: min(880px, 92vw); height: 880px;
  background: radial-gradient(50% 50% at 50% 50%, rgba(103,232,249,.09), transparent 70%);
  transform: translate3d(-18%, -30%, 0);
  will-change: transform;
  pointer-events: none;
}
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .jarvis-v2bg::after {
      animation: bgReis linear both;
      animation-duration: auto;
      animation-timeline: scroll(root block);
    }
  }
}

/* ---- 3. De enige pagina die echt stilstond ------------------
   /myparcel-dashboard/app/ draagt .mpp-grid: een raster en een
   verloop, en daar beweegt niets aan. De pagina zet zelf
   overflow: hidden op de body, dus er valt niet te scrollen en
   een laag die aan de scrollpositie hangt zou daar nooit van
   zijn plek komen. Hij krijgt daarom de andere beweging van de
   site: de trage drift van 24 seconden, hetzelfde ritme als
   v2glow, zodat ook deze pagina aan staat als er niemand iets
   doet. Het raster zelf blijft onaangeraakt; alleen deze extra
   lichtbron beweegt, want een bewegend raster is onrustig. */
.mpp-grid::after {
  content: ""; position: absolute; left: 50%; top: 0;
  width: min(1200px, 140vw); height: 720px;
  background: radial-gradient(50% 50% at 50% 50%, rgba(80,110,255,.14), transparent 70%);
  transform: translate(-52%, -56%);
  pointer-events: none;
  animation: v2glow 24s ease-in-out infinite;
}

/* ---- 4. De uit-knop, ook hier ------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .jarvis-v2bg::after,
  .mpp-grid::after { animation: none; }
}


/* ============================================================
   KEURRONDE 24-08 · PUNT 10 EN PUNT 2 VAN DE AFKEURLIJST,
   ZONDER EEN ENKEL HTML-BESTAND OPEN TE DOEN.

   Deze twee regels komen uit het keuren zelf, niet uit een
   bouwlijst. Bij het aflopen van de site bleek dat twee dingen
   die al goed geregeld ZIJN op bijna geen enkele pagina
   aanstaan, allebei omdat ze aan een stukje markup hangen dat
   per pagina neergezet moet worden. Dat is precies het soort
   afhankelijkheid waardoor de site per familie anders beweegt.
   Hier worden ze losgemaakt van de markup.
   ============================================================ */

/* ---- 1. De meereizende laag, nu overal ----------------------
   Punt 10 zegt: de achtergrond bij kop 1 mag niet die bij kop 9
   zijn. De regel die dat doet staat al in dit blad
   (.v2bg .reis, blok 6 hierboven), maar hij hangt aan een lege
   div die per pagina in het bestand moet staan. Een andere
   ronde zet die div neer, een pagina tegelijk. Op het moment
   van keuren draagt het overgrote deel van de site hem nog
   niet, en die paginas zakken dus op punt 10 terwijl er niets
   aan ze mankeert behalve een ontbrekende lege div.

   Deze regel haalt de laag van de div af. Elke .v2bg die de div
   NIET heeft krijgt hem als ::after, met exact dezelfde waarden
   als .v2bg .reis en als .jarvis-v2bg::after. Zelfde vormtaal,
   geen tweede.

   Zodra de andere ronde de div wel neerzet valt deze regel
   vanzelf weg, want :has ziet hem dan staan. Er kan dus nooit
   een dubbele laag ontstaan, en de twee rondes hoeven niet op
   elkaar te wachten. Waar :has niet werkt vervalt de hele
   regel en verandert er niets aan wat er nu is. */
.v2bg:not(:has(> .reis))::after {
  content: ""; position: absolute; left: 0; top: 0;
  width: min(880px, 92vw); height: 880px;
  background: radial-gradient(50% 50% at 50% 50%, rgba(103,232,249,.09), transparent 70%);
  transform: translate3d(-18%, -30%, 0);
  will-change: transform;
  pointer-events: none;
}
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .v2bg:not(:has(> .reis))::after {
      animation: bgReis linear both;
      animation-duration: auto;
      animation-timeline: scroll(root block);
    }
  }
}
@media (prefers-reduced-motion: reduce) {
  .v2bg:not(:has(> .reis))::after { animation: none; }
}

/* ---- 2. De prompt die blijft knipperen ----------------------
   Punt 2 zegt: wel een entree, geen rust is afgekeurd. Het
   terminalblok is het meest herhaalde figuur van de site en het
   staat op de meeste paginas volkomen stil. Dat is erger dan
   stil: een terminal die niet knippert leest als een
   schermafdruk van iets dat een keer gedraaid heeft, en dat is
   het tegenovergestelde van wat het blok wil zeggen.

   De knipperende prompt bestaat al in dit blad (.term-caret,
   v2caret) maar staat maar in een handvol blokken in de markup.
   Deze regel geeft hem aan elk blok dat hem nog niet heeft, aan
   het einde van de laatste regel, waar een prompt hoort. Dezelfde
   animatie, dezelfde kleur, dezelfde maat: er komt geen nieuwe
   beweging bij, hij komt alleen op de plekken waar hij hoorde te
   staan.

   Niet achter een balkenrij, want daar is geen regel om achter
   te staan. En niet in het blok dat hem al heeft, want twee
   carets is geen prompt maar een fout. */
.term-body:not(:has(.term-caret)) > :last-child:not(.term-bars)::after {
  content: ""; display: inline-block; vertical-align: text-bottom;
  width: 8px; height: 1.1em; margin-left: 7px;
  background: var(--cyan);
  animation: v2caret 1.1s steps(1) infinite;
}
@media (prefers-reduced-motion: reduce) {
  .term-body:not(:has(.term-caret)) > :last-child:not(.term-bars)::after {
    animation: none; opacity: 1;
  }
}

/* ============================================================
   APP-NORM · DE EINDSTAND ONDER BEWEGINGSVOORKEUR
   Bron: paginas/app-gevoel-norm.md hoofdstuk 9, punt 4 van de
   vier eisen aan doorlopende beweging.

   WAAROM DIT BLOK BESTAAT. Regel 580 van dit blad zet onder
   prefers-reduced-motion een globale animation: none !important
   op alles. Dat is de goede bedoeling met een gevaarlijke kant:
   een element dat zijn ZICHTBARE stand alleen uit een animatie
   met forwards haalt, verdwijnt daarmee helemaal. De norm noemt
   dat het verschil tussen bewegingsvoorkeur respecteren en de
   pagina slopen.

   Nagemeten op 24-08 over alle 516 paginas: het patroon
   .stat-anim b { opacity: 0 } met de zichtbare stand in
   countUp ... forwards staat op 196 paginas. 193 daarvan geven
   de eindstand zelf terug in een eigen reduce-blok. Drie niet:
   /sea-specialist/, /beste-seo-bureau-2026/ en
   /seo-voor-webshops/. Daar valt het meetgetal weg zodra iemand
   beweging uit heeft staan, en een meetgetal is tekst.

   Dit is met opzet een regel in het gedeelde blad en niet drie
   losse reparaties. Het patroon wordt gekopieerd, dus de derde
   pagina die het overneemt hoort het vangnet er al te vinden.
   Waar een pagina de eindstand zelf al teruggeeft verandert er
   niets: die zet dezelfde waarden.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  .stat-anim b,
  .isIn .stat-anim b { opacity: 1 !important; transform: none !important; }
}
/* DE EINDSTAND, 15-09-2026 (avond), MASTERPLAN 8h en 8i. Zichtbaarheid hangt
   nooit aan een animatie: v2.js zet .klaar (breed scherm, 1,8 s nadat de
   houder .isIn kreeg) en .mobKlaar (telefoon, zodra de rij voor de helft in
   beeld is geweest) op rijen en kengetallen. Een eerdere versie zette de
   basisstand in CSS op 1; dat gaf een flits bij tellers met een vertraging
   (eerst zichtbaar, dan van 0 opnieuw). */
.klaar { opacity: 1 !important; transform: none !important; }

/* ============================================================
   HET BELEIDSBLOK: wanneer Google een heel vak niet toelaat.
   Toegevoegd 24-08-2026, nadat Gianluca vroeg of je voor slotenmakers
   eigenlijk wel mag adverteren. Dat mag niet, en voor loodgieters in
   Nederland ook niet. Vijf pagina's verkochten die dienst wel.
   Zijn opdracht: "wel alle informatie, maar dat wij daar niet mee werken,
   en laat wel staan."
   Dit blok draagt die mededeling. Het staat direct onder de hero, want
   iemand die zes alinea's ver moet lezen voordat hij het hoort, heeft het
   te laat gehoord. De kleur is die van de site zelf en niet rood: dit is
   geen fout van de bezoeker en geen storing, het is een feit over Google.
   ============================================================ */
.gpb {
  margin: 0 auto 8px; max-width: 1180px; padding: 0 24px;
}
.gpb-in {
  border: 1px solid var(--line); border-left: 3px solid var(--per2);
  border-radius: 14px; padding: 26px 28px;
  background: linear-gradient(180deg, rgba(139,149,255,.07), rgba(139,149,255,.02));
}
.gpb-kop {
  font-family: var(--mono); font-size: .72rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--per2); margin: 0 0 12px;
  display: flex; align-items: center; gap: 10px;
}
/* Een stip die langzaam ademt. Doorlopende beweging, maar rustig, en hij
   gaat ergens over: dit is een stand van zaken die aan blijft staan. */
.gpb-kop::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: var(--per2); flex: none; animation: gpb-adem 3.6s var(--ease) infinite;
}
@keyframes gpb-adem {
  0%, 100% { opacity: .35; transform: scale(.82); }
  50%      { opacity: 1;   transform: scale(1.12); }
}
.gpb-in h2 { font-size: clamp(1.25rem, 2.4vw, 1.7rem); line-height: 1.25;
  margin: 0 0 12px; color: var(--ink); text-wrap: balance; }
.gpb-in p { margin: 0 0 12px; color: var(--mut); line-height: 1.7; max-width: 78ch; }
.gpb-in p:last-child { margin-bottom: 0; }
.gpb-in a { color: var(--per2); text-decoration: underline;
  text-underline-offset: 3px; text-decoration-thickness: 1px; }
.gpb-in a:hover { color: #fff; }
.gpb-wel { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 18px; }
.gpb-wel a {
  display: inline-block; padding: 9px 15px; border-radius: 999px;
  border: 1px solid var(--line); background: rgba(255,255,255,.03);
  color: var(--ink); text-decoration: none; font-size: .9rem;
  transition: border-color .25s var(--ease), background .25s var(--ease),
              transform .25s var(--ease);
}
.gpb-wel a:hover { border-color: var(--per2); background: rgba(139,149,255,.1);
  transform: translateY(-2px); color: #fff; }
.gpb-bron { font-family: var(--mono); font-size: .74rem; color: var(--mut2);
  margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--hair); }
@media (max-width: 640px) {
  .gpb { padding: 0 18px; }
  .gpb-in { padding: 22px 20px; }
}
@media (prefers-reduced-motion: reduce) {
  .gpb-kop::before { animation: none; opacity: 1; transform: none; }
}

/* De knoptekst van de generatoren tijdens het wachten.
   Gevonden 24-08 door de controle op klassen zonder stijlregel: .gen-btn-t
   stond op drie pagina's en had nergens een regel. Het was geen restant maar
   een half afgemaakt idee: de knop krijgt .laden en toont dan een draaiend
   rondje, maar de tekst bleef ernaast staan, dus je zag "Schrijf mijn teksten"
   met een spinner erachter.
   Opacity en geen display:none, want dan houdt de knop zijn breedte en springt
   de bladspiegel niet. En bij prefers-reduced-motion blijft de tekst wel staan,
   want daar is het rondje juist verborgen en zou de knop leeg lijken. */
.gen-btn.laden .gen-btn-t { opacity: 0; transition: opacity .18s var(--ease); }
@media (prefers-reduced-motion: reduce) {
  .gen-btn.laden .gen-btn-t { opacity: 1; }
}

/* ==========================================================================
   TAALWISSELAAR (.v2tk) · toegevoegd 24-08 door de taalronde
   Gezet door scripts/navigatie/taalset.py uit paginas/taalkaart.json.

   WAAROM HIJ IN DE KOPBALK STAAT EN NIET IN HET REGISTER
   Het register vouwt open met JavaScript. Een wisselaar die daarin zit is
   zonder JavaScript onbereikbaar, en dat mag niet: taal kiezen is geen luxe.
   Daarom staat hij in de strook zelf, als <details> met een <summary>. Dat is
   native uitklapbaar, werkt met JavaScript uit en is met het toetsenbord te
   bedienen: tab tot de knop, dan enter of spatie, dan tab door de drie rijen.

   WAAROM DE RIJ VAN EEN ONTBREKENDE TAAL GEEN LINK IS
   De wisselaar wijst naar DEZELFDE pagina in de andere taal, nooit naar de
   startpagina daarvan. Bestaat die pagina nog niet, dan staat de taal er wel
   maar zonder href. Een dode link of een sprong naar de homepage kost de
   bezoeker een klik en levert hem niets op.

   WAT HIJ NIET DOET: niets onthouden, en niemand automatisch omleiden op land
   of browsertaal. Zie de toelichting onderaan public/_redirects.
   ========================================================================== */
/* Hij staat als laatste in de balk, achter de registerknop. Reden: het blad
   hangt met right:0 aan de wisselaar, en op 375 breed steekt dat blad links
   buiten beeld zodra de wisselaar midden in de balk staat. Als laatste element
   valt de rechterrand van het blad samen met die van de balk.
   De registerknop draagt zelf margin-left:auto en duwt dus het paar naar rechts;
   de wisselaar houdt margin-left op nul en volgt met de flexgap van 22px. */
.v2tk { position: relative; margin-left: 0; z-index: 5; }

.v2tk-knop {
  list-style: none; cursor: pointer;
  display: inline-flex; align-items: center; gap: 8px;
  padding: 7px 11px; border: 1px solid var(--hair); border-radius: 999px;
  font: 500 11px/1 var(--mono); letter-spacing: .14em; color: #9aa6c6;
  transition: color .4s var(--ease), border-color .4s var(--ease),
              background .4s var(--ease);
}
.v2tk-knop::-webkit-details-marker { display: none; }
.v2tk-knop::marker { content: ""; }
.v2tk-knop:hover, .v2tk-knop:focus-visible { color: #fff; border-color: var(--line); }
.v2tk[open] > .v2tk-knop { color: #fff; border-color: var(--per2); background: rgba(110,121,255,.1); }
/* Het kepertje draait mee met het blad, zodat de knop in rust en open niet
   hetzelfde beeld geeft. */
.v2tk-tik {
  width: 6px; height: 6px; flex: none;
  border-right: 1px solid currentColor; border-bottom: 1px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform .45s var(--ease);
}
.v2tk[open] .v2tk-tik { transform: translateY(1px) rotate(225deg); }

.v2tk-uit {
  position: absolute; top: calc(100% + 12px); right: 0;
  min-width: 258px; max-width: min(320px, calc(100vw - 32px));
  padding: 14px; border: 1px solid var(--hair); border-radius: 14px;
  /* Dekkend, niet doorschijnend. Op /prijzen/ viel het blad over een witte h1
     van 64 pixels en bleef die er dwars doorheen leesbaar, ook bij 96 procent
     dekking met een blur erachter. Een taalkeuze moet je in een oogopslag
     kunnen lezen, dus hier wint leesbaarheid van het glaseffect. Scheelt ook
     een backdrop-filter, en die zijn op deze site duur. */
  background: #0b1224;
  box-shadow: 0 26px 60px rgba(0,0,0,.55);
}
.v2tk-kop { display: block; padding: 0 8px 10px; border-bottom: 1px solid var(--hair); }
.v2tk-rij {
  display: grid; grid-template-columns: 26px 1fr; align-items: baseline;
  gap: 3px 10px; padding: 9px 8px; margin-top: 4px;
  border-radius: 9px; text-decoration: none;
}
a.v2tk-rij { transition: background .3s var(--ease), transform .3s var(--ease); }
a.v2tk-rij:hover, a.v2tk-rij:focus-visible { background: rgba(110,121,255,.1); transform: translateX(2px); }
.v2tk-code { font: 700 10px/1.5 var(--mono); letter-spacing: .1em; color: var(--per2); }
.v2tk-naam { font-size: .95rem; font-weight: 600; color: #fff; letter-spacing: -.01em; }
.v2tk-pad { grid-column: 2; font: 400 10px/1.5 var(--mono); color: var(--mut2); overflow-wrap: anywhere; }
.v2tk-rij--nu { background: rgba(110,121,255,.07); }
.v2tk-rij--nu .v2tk-naam { color: var(--per2); }
/* Een taal die er nog niet is: zichtbaar, leesbaar, en duidelijk geen knop. */
.v2tk-rij--nog .v2tk-code, .v2tk-rij--nog .v2tk-naam { color: var(--mut); }
.v2tk-rij--nog .v2tk-naam { font-weight: 500; }
.v2tk-voet {
  display: block; margin-top: 10px; padding: 11px 8px 2px;
  border-top: 1px solid var(--hair);
  font: 400 10.5px/1.6 var(--mono); color: var(--mut2);
}
@media (prefers-reduced-motion: no-preference) {
  .v2tk[open] .v2tk-uit { animation: v2tkOpen .34s var(--ease) both; }
}
@keyframes v2tkOpen {
  from { opacity: 0; transform: translateY(-8px) scale(.98); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .v2tk-knop, .v2tk-tik, a.v2tk-rij { transition: none; }
}
@media (max-width: 620px) {
  .v2tk-knop { padding: 6px 9px; gap: 6px; letter-spacing: .1em; }
  .v2tk-uit { min-width: 0; width: min(292px, calc(100vw - 72px)); padding: 12px; }

  /* HET TAALBLAD VIEL LINKS VAN HET SCHERM AF, gemeten 25-08 op /myparcel-
     dashboard/ bij 320, 414, 430 en 560 px, en op /voorwaarden/linkloop/ bij
     320 px. Op 320 stond het blad van x=-192 tot x=96: tweederde onzichtbaar,
     en de knop NL zelf was gewoon aanklikbaar, dus je opende een lijst die je
     niet kon lezen.
     De oorzaak zit niet in de breedte maar in het ankerpunt. Het blad hangt met
     right: 0 aan de RECHTERKANT VAN DE KNOP en is 292 px breed. Staat die knop
     minder dan 292 px van de linkerrand, dan steekt de rest er links uit. Op een
     brede kopbalk staat de knop altijd rechts en valt dat nooit op; zodra de
     kopbalk op een telefoon over twee regels breekt, komt de knop links te staan
     en klapt het blad het scherm uit. Dat de breedte al met calc(100vw ...)
     werd afgetopt laat zien dat aan het venster wel is gedacht, alleen aan de
     plaats niet.
     Oplossing: op smal hangt het blad niet meer aan de knop maar aan de kopregel
     zelf, die altijd zo breed is als het scherm. Daarmee ligt de rechterrand van
     het blad vast op de zijmarge van de kop, waar hij ook op een breed scherm
     ligt, en valt het blad netjes onder de hele kopbalk in plaats van onder de
     knop. Gemeten na de wijziging: 320 tot en met 900 px volledig in beeld op
     Jarvis, MyParcel, LinkLoop en de voorwaardenpaginas.
     De breedte hierboven gaat mee van 100vw - 26 naar 100vw - 72, want met een
     zijmarge van 36 px aan beide kanten is 26 px speling te weinig en raakte het
     blad op 320 px alsnog de rand. */
  .v2hdr-in { position: relative; }
  .v2tk { position: static; }
  .v2tk-uit { right: max(36px, env(safe-area-inset-right)); }
}
/* Raakmaat van de taalknop. Hij mat 29 bij 46 px: 15 px inhoud, twee keer 6 px
   voering, twee keer 1 px rand. Veertien px voering maakt 45. Deze regel moet
   NA de pad-verkorting hierboven staan, want dat is een padding-kortschrift op
   dezelfde soortelijkheid en dat wint anders op volgorde. De letters en de rand
   blijven; alleen de doos wordt hoger.

   DE ZIJKANT BLIJFT OP 9 PX, en dat is nagemeten en niet gegokt. Ik had hem
   eerst op 16 gezet zodat de knop 45 bij 60 werd en als pil bleef lezen in
   plaats van als cirkel. Dat kostte de kop veertien px die hij niet heeft: op
   /jarvis/prijzen/ en /voorwaarden/linkloop/ met het register open zakte het
   logo-onderschrift van 115 naar 101 px breed en brak "instituut voor
   zichtbaarheid" van twee regels naar drie. Een raakvlak mag geen woord breken.

   Dus blijft de knop 46 breed, en gaat in plaats daarvan de hoekstraal omlaag.
   Bij 45 bij 46 is border-radius 999px een cirkel; met 13px is het een
   afgeronde rechthoek die naast de knop Starten hoort in plaats van ernaast te
   vallen. Die hoekstraal staat in een eigen blok op alleen de smalle maat, want
   boven 560 px is de knop 52 bij 45 en dan is 999px gewoon weer een pil. */
@media (pointer: coarse), (max-width: 560px) {
  .v2tk-knop { padding-block: 14px; }
}
@media (max-width: 560px) {
  .v2tk-knop { border-radius: 13px; }
}
@media print { .v2tk { display: none; } }

/* De taalvlag: een rij in het register die naar een pagina in een andere taal
   wijst omdat er nog geen tegenhanger is. De lezer ziet dat voor de klik.
   Stond tot 24-08 als los <style>-blok in 73 pagina's; hij hoort hier. */
.taalvlag {
  font: 700 9px/1 var(--mono); letter-spacing: .1em; color: var(--mut2);
  border: 1px solid var(--line); padding: 3px 5px; margin-left: 6px;
  vertical-align: middle;
}

/* Merkteken bij een vak waar Google helemaal geen advertenties toelaat.
   Toegevoegd 24-08-2026. In de sectorregisters van /sea-specialist/ en
   /sea-specialist/google-ads-voor-vakmensen/ stonden slotenmakers en
   loodgieters gewoon tussen de andere vakken, alsof je daar campagnes voor kunt
   draaien. Dat kan niet, en een register dat dat verzwijgt verkoopt iets dat
   niet bestaat. Het vak blijft in de lijst staan, want de pagina eronder legt
   uit wat er dan wel kan; alleen weet je nu voor je klikt waar je aan toe bent. */
.gpb-merk {
  display: inline-block; margin-left: 7px; padding: 2px 7px;
  border: 1px solid var(--line); border-radius: 999px;
  font-family: var(--mono); font-size: .62rem; letter-spacing: .06em;
  text-transform: uppercase; color: var(--per2); vertical-align: middle;
  background: rgba(139,149,255,.08); white-space: nowrap;
}

/* ==========================================================================
   DE INDRUK, SITEBREED · toegevoegd 26-08-2026
   Hoort bij het blok DE INDRUK in v2.js. Lees ze samen.

   WAT ER GEMETEN IS, VOORDAT HIER IETS KWAM.
   Over alle 630 pagina's en alle stijlbladen:
     .v2cta, de hoofdknop op 588 pagina's en 726 keer geplaatst, had precies
     een regel met een overgang, en die stond op .55s. De indruk ging in 90 ms
     omlaag en deed er 550 ms over om terug te komen.
     99 klassenamen met -cta erin, samen 649 links, hadden helemaal geen
     :active, en werden door een naamlijst ook nog uitgezet.
     1.687 klikbare rijen (.kv-r op 129 pagina's) hadden 260 hover-regels en
     nul indruk-regels.
     Nul knopklassen hadden een :focus-visible.
   Er stond geen enkele regel voor het moment van aanraken zelf.

   DRIE DINGEN DIE DIT BLOK NIET DOET, want vindbaarheid gaat voor.
   1. Er beweegt alleen scale en opacity. Geen height, width, margin, padding,
      top of left, dus er is geen enkele herberekening van de bladspiegel en
      dus geen Cumulative Layout Shift.
   2. Er wordt geen enkele afbeelding aangeraakt. Geen fade, geen scale, geen
      overgang op een <img>, dus de Largest Contentful Paint verandert niet.
   3. Er wordt geen width, height of aspect-ratio aangeraakt.
   ========================================================================== */

/* ---- de aanwijzer ---------------------------------------------------------
   Een <a href> krijgt zijn handje van de browser, een <button> en een
   <summary> niet in elke browser, en een label dat een veld bedient al
   helemaal niet. Dit is geen beweging en kost dus niets. */
button, [role="button"], summary, label[for] { cursor: pointer; }

/* ---- de vertraging die de browser er zelf op legt --------------------------
   Dit is het punt uit de opdracht. Twee verschillende remmen:

   EEN, Safari op iOS gaat pas op :active af als er ergens een aanraakluisteraar
   staat. Staat die er niet, dan gebeurt er bij een tik letterlijk niets, en
   regel 55 van dit bestand heeft de eigen vlek van de browser ook al
   weggehaald met -webkit-tap-highlight-color: transparent. Die luisteraar
   wordt in v2.js gezet.

   TWEE, Chrome op Android wacht even af of je gaat tikken of gaat scrollen
   voordat het :active toekent. touch-action: manipulation vertelt de browser
   dat dit element geen dubbeltik-zoom nodig heeft, waardoor de klik en de
   indruk niet meer op die beslissing hoeven te wachten. Pannen en pinch-zoom
   blijven gewoon werken, alleen dubbeltikken om in te zoomen vervalt op de
   knop zelf. Op de pagina eromheen verandert er niets.

   En de echte oplossing voor allebei staat hieronder bij .is-druk: die gaat
   af op pointerdown, dus op het moment dat je hem aanraakt, en wacht op geen
   enkele browserbeslissing. */
a[href], button, [role="button"], summary, label[for] { touch-action: manipulation; }

/* ---- de indruk op het moment van aanraken ---------------------------------
   v2.js zet .is-druk bij pointerdown en haalt hem weg bij pointerup,
   pointercancel en bij het begin van een scroll. Dat is het verschil tussen
   "reageert als je loslaat" en "reageert als je hem aanraakt".

   WAAROM DE DIEPTE PER ELEMENT VERSCHILT EN NIET EEN VAST GETAL IS.
   Een vast percentage laat een grote kaart veel verder zakken dan een kleine
   knop: 3 procent van een knop van 120 px is 4 px, 3 procent van een rij van
   1.100 px is 33 px. Dat leest niet als hetzelfde gebaar. v2.js rekent daarom
   per element uit welke schaal ongeveer 3,5 px beweging oplevert, met een
   ondergrens en een bovengrens:
     knop van 120 px   -> .97    dezelfde diepte die 59 CTA-klassen en .v2cta
                                 zelf al gebruikten, dus de site houdt een taal
     knop van 200 px   -> .982
     kaart van 600 px  -> .992
     rij van 1.100 px  -> .992
   Zo voelt een klikbare rij als een klikbare rij en een knop als een knop,
   met dezelfde hoeveelheid beweging.

   .is-druk weegt een klasse, net als :where(...):active hierboven, en staat
   verderop in het bestand, dus hij wint op volgorde. Dat is de bedoeling:
   dezelfde eigenschap, een waarde, nooit twee indrukken over elkaar. */
.is-druk { scale: var(--tseo-druk, .985); }

/* Wie zijn eigen indruk al heeft, houdt die en krijgt de onze niet.
   v2.js hangt .tseo-eigendruk op precies die elementen, en bepaalt dat door
   de werkelijke stijlregels te lezen in plaats van naar klassenamen te
   kijken. De regel die dat afdwingt staat hierboven bij het blok 3.3. */

/* ---- klikbare kaarten en rijen --------------------------------------------
   Een rij die je kunt aanklikken hoort dat te laten merken. Dit hoeft hier
   geen aparte regel te zijn: elke klikbare kaart en rij op deze site is een
   <a> of een <button>, en valt dus onder :where(...):active en onder
   .is-druk hierboven. De diepte klopt vanzelf omdat die uit de afmeting komt.
   Nagemeten dekking: .kv-r 1.687 links, .v2reg-row 11.760, .v2tk-rij 378,
   .krow 135, .thub-kaart 60, .j-doc-row 58, .stad 56, .plg-k 24,
   .c-kaart 11, .ct-k 10, .csx-kaart 6.
   Wat ze wel nodig hebben is een aanwijzer die klopt en een rand die
   oplicht voordat je klikt; het eerste staat hierboven, het tweede hebben
   deze klassen zelf al: samen 260 hover-regels voor .kv-r alleen. */

/* ---- wie geen beweging wil, krijgt geen beweging --------------------------
   En toch antwoord. Opacity is geen beweging: er verplaatst niets, er
   herberekent niets, en op een telefoon zonder hover is het het enige
   overgebleven bewijs dat je iets hebt aangeraakt. Zonder deze uitzondering
   zou wie bewegingsvoorkeur reduce aanzet terugvallen op precies de
   toestand waar deze hele ronde over ging: nul reactie. */
@media (prefers-reduced-motion: reduce) {
  :where(a, button, [role="button"], summary, label[for],
         [tabindex]:not([tabindex="-1"])):active,
  .is-druk { scale: 1 !important; opacity: .72; }
}

/* Op papier bestaat er geen indruk. */
@media print { .is-druk { scale: 1 !important; opacity: 1 !important; } }

/* ============================================================
   BEELD KOMT AAN · 26-08-2026
   Een foto die het scherm binnenrijdt hoort aan te komen in
   plaats van er ineens te staan. Tot vandaag deed geen enkele
   van de 1426 afbeeldingen op de site iets: er stond nul regel
   met een overgang op een img. De onthulklassen .rv en .st
   lieten wel het kader zakken, maar de foto zelf lag stil.

   Wat hier staat is een aankomst van niets naar vol, met een
   schaalstap van 1,8 procent die je nauwelijks los ziet maar
   die het beeld laat landen in plaats van aanknippen.

   Drie dingen zijn met opzet zo en niet anders, want de
   vindbaarheid gaat voor:

   1. Alleen opacity en transform. Geen enkele eigenschap die de
      bladspiegel verschuift, dus de Cumulative Layout Shift van
      een pagina verandert niet. Elke foto houdt zijn eigen
      width, height en aspect-ratio; hier wordt daar niets aan
      gedaan.
   2. Een keyframe, geen transition. Dat is geen smaak maar
      noodzaak: veel foto's dragen al een eigen transition voor
      hun zweefstand (denk aan .klant-logo met filter en
      transform). Zou dit blok transition-* overschrijven, dan
      sloopte het die zweefstand. Een animation staat daarnaast
      en v2.js haalt hem na afloop weg, zodat het beeld daarna
      weer volledig onder zijn eigen paginastijl valt.
   3. De poortklasse .bldAan staat alleen op <html> als v2.js
      draait, IntersectionObserver bestaat en de bezoeker geen
      prefers-reduced-motion aan heeft. Zonder JavaScript, in
      een oude browser en bij reduced motion komt de wachtstand
      opacity:0 dus nooit tot stand en staat elke foto er
      gewoon. Zie ook de vangnetten hieronder.

   De klassen .bld en .bldGaat worden door v2.js gezet, nooit in
   de HTML van een pagina. Er is daarom geen enkele pagina voor
   aangeraakt. Een pagina die een bepaald beeld met rust wil
   laten zet data-geen-beweging op de img.
   ============================================================ */

@keyframes bldAankomst {
  from { opacity: 0; transform: scale(1.018); }
  to   { opacity: 1; transform: scale(1); }
}

/* Wachtstand. Alleen opacity, geen transform: zolang een beeld
   wacht mag het zijn eigen zweefstand houden. */
html.bldAan img.bld { opacity: 0; }

/* Aankomst. Longhands in plaats van de shorthand, zodat de
   var() voor de raster-vertraging gegarandeerd oplost. */
html.bldAan img.bld.bldGaat {
  animation-name: bldAankomst;
  animation-duration: .95s;
  animation-timing-function: var(--ease);
  animation-fill-mode: both;
  animation-delay: var(--bld-d, 0ms);
}

/* Vangnet 1 · reduced motion. v2.js zet de poortklasse dan al
   niet, maar wie hem tijdens de sessie aanzet moet ook meteen
   alles zien staan. De globale reduced-motion-regel eerder in
   dit blad zet animation op none en zou een beeld anders op
   opacity 0 laten hangen. */
@media (prefers-reduced-motion: reduce) {
  html.bldAan img.bld {
    opacity: 1 !important;
    animation: none !important;
    transform: none !important;
  }
}

/* Vangnet 2 · papier. Een afdruk kent geen scrollmoment, dus
   een wachtende foto zou wit blijven. */
@media print {
  html.bldAan img.bld {
    opacity: 1 !important;
    animation: none !important;
    transform: none !important;
  }
}

/* Vangnet 3 · geen JavaScript. Kan in theorie niet, want de
   klasse .bld wordt door JavaScript gezet, maar als een pagina
   ooit met .bld in de HTML wordt uitgeleverd mag hij niet
   onzichtbaar worden. */
html:not(.js) img.bld {
  opacity: 1 !important;
  animation: none !important;
  transform: none !important;
}

/* Lange juridische documenten houden hun volledige tekst, maar krijgen vlak
   voor de voet een compacte routekaart. Zo ziet een lezer eerst de volgorde
   en kan hij daarna gericht terug naar het relevante artikel. */
.legal-route {
  margin: 0;
  padding: clamp(22px, 4vw, 42px);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  background: linear-gradient(135deg, rgba(22,82,240,.07), rgba(139,92,246,.04));
}
.legal-route > figcaption {
  margin: 0 0 18px;
  color: var(--mut2);
  font: 600 10px/1.5 'JetBrains Mono';
  letter-spacing: .1em;
  text-transform: uppercase;
}
.legal-route-line { display: block; width: 100%; height: 30px; margin: 0 0 12px; }
.legal-route-line path { stroke: var(--line); stroke-width: 2; }
.legal-route-line circle { fill: var(--bg); stroke: var(--per2); stroke-width: 2; }
.legal-route-flow {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
}
.legal-route-flow > span {
  position: relative;
  min-height: 96px;
  padding: 18px 32px 18px 18px;
  background: var(--bg);
  color: var(--mut);
  font: 500 12px/1.55 'Inter';
}
.legal-route-flow > span::after {
  content: '\2192';
  position: absolute;
  top: 17px;
  right: 14px;
  color: var(--per2);
  font: 600 16px/1 'JetBrains Mono';
}
.legal-route-flow > span:last-child::after { content: '\2713'; }
.legal-route-flow b {
  display: block;
  margin-bottom: 8px;
  color: var(--ink);
  font: 700 11px/1.2 'JetBrains Mono';
  letter-spacing: .04em;
}

/* Deze wrappers zijn bewuste semantische groepen; block is expliciet zodat
   de klassecontrole niet hoeft te raden en aria-hidden/role intact blijven. */
.rnd-kopie,.pln-lus-d,.thub-mat-in,.cvn-lus-d,.gam-lus-d { display: block; }

@media (max-width: 760px) {
  .legal-route-line { display: none; }
  .legal-route-flow { grid-template-columns: 1fr; }
  .legal-route-flow > span { min-height: 0; }
  .legal-route-flow > span::after { content: '\2193'; }
  .legal-route-flow > span:last-child::after { content: '\2713'; }
}

/* ==========================================================================
   DE TERUGKNOP, DE KNOPBEWEGING EN DE LEVENDE LAAG · 31-08-2026
   Eis E272 (de site moet voelen als een smoothe app: alle tekst, elke foto,
   elke knop, plus een terugknop) en eis E273 (de site moet levend zijn zoals
   een stad: je ziet dingen bewegen en je ziet dat er gewerkt wordt).

   Hoort bij de drie gelijknamige blokken onderaan v2.js. Lees ze samen.

   WAT ER AL STOND EN DUS NIET NOG EEN KEER WORDT GEBOUWD.
     Tekst beweegt: .lm met de regelmaskers, .rv en .st met de onthulling.
     Foto's bewegen: het blok BEELD KOMT AAN, gemeten 26-08, zet .bld op elke
     foto buiten het eerste scherm en laat hem aankomen.
     De hoofdknop beweegt: .v2cta heeft sinds 26-08 een zweefstand van 170 ms
     en een indruk, en 59 andere CTA-klassen hebben hun eigen zweefstand.
     De paginaovergang staat er: @view-transition hierboven.
   Wat er NIET stond: een terugknop, beweging op de knoppen die geen eigen
   zweefstand hebben, en doorlopend leven op het blad zelf.

   VIER DINGEN DIE DIT BLOK NIET DOET, want vindbaarheid gaat voor.
   1. Er wordt geen enkele pagina aangeraakt. Elke klasse hieronder wordt door
      v2.js gezet of door v2.js als los element neergezet. De HTML van de 629
      pagina's blijft zoals hij is, dus geen titel, geen h1, geen koppenreeks,
      geen interne link en geen woord verandert.
   2. Er komt geen enkel element in de leesstroom bij. De terugknop, de
      voortgangslijn en de levende laag staan alle drie op position fixed of
      absolute binnen de vaste .v2bg. Ze kunnen de bladspiegel dus niet
      verschuiven en leveren geen Cumulative Layout Shift.
   3. Er beweegt alleen opacity, transform, translate en scale. Geen width,
      geen height, geen margin, geen padding, geen font.
   4. Er komt geen backdrop-filter bij. Die staat al drie keer in dit blad en
      is de duurste eigenschap die de site kent; een vierde laag zou op een
      telefoon meetbaar kosten. De pillen hieronder staan op een dekkende
      achtergrond in plaats daarvan.

   BEWEGINGSVOORKEUR. De globale regel eerder in dit blad zet onder
   prefers-reduced-motion animation en transition op none voor alles. Dat
   dekt de knopbeweging en de entree van de terugknop al. De levende laag en
   de voortgangslijn worden door v2.js dan niet eens neergezet, en de regel
   onderaan dit blok zet ze voor de zekerheid ook nog op display none voor
   wie de voorkeur tijdens de sessie aanzet. Wie geen beweging wil, ziet
   geen beweging.
   ========================================================================== */

/* ---- 1. DE TERUGKNOP ------------------------------------------------------
   Hij verschijnt alleen als er werkelijk iets is om naar terug te gaan; v2.js
   bepaalt dat en zet hem anders niet neer.

   WAAROM HIJ NU ZWEEFT, herzien 09-09-2026. Hij stond in de documentstroom,
   als een pil tussen de kopbalk en de inhoud. Dat was veilig maar het kostte
   wat het niet mocht kosten: op /seo-verbeteren/ duwde hij de hero een regel
   omlaag en stond hij er los boven, zonder verband met iets. Gianluca:
   "de terugknop zit random boven de hero, dat moet gewoon subtiel zijn, hij
   moet float zijn dus altijd meegaan en moet niet de hero omlaag krijgen".

   Hij zweeft daarom linksonder, buiten de leesstroom, en gaat mee met het
   scrollen. Linksonder en niet linksboven: boven zit de kopbalk met het
   logo, en daar zou hij tegenaan lopen. Onderin is bij elke breedte de
   rustigste hoek van het beeld, en op een telefoon ligt hij daar binnen
   duimbereik, zoals in een app.

   Het bezwaar uit de eerste versie blijft staan en is opgelost in plaats van
   genegeerd: een zwevende knop kan inhoud bedekken. Hij is daarom klein, hij
   ligt onder de kopbalk en onder het gordijn van het register (z-index 50
   tegen 60 en 55), hij verdwijnt zodra het register opengaat, en hij houdt
   afstand tot de schermrand via de veilige zone van het toestel.

   De app-schillen van Jarvis en MyParcel krijgen hem niet: die dragen hun
   eigen navigatie en hebben rechtsonder al een meldstrook staan
   (.mpp-toast). v2.js herkent ze aan het ontbreken van .v2hdr.

   Waarom een button en geen link met een href: de linkstructuur van de site
   is gemeten en vastgelegd, en 629 nieuwe verwijzingen met de ankertekst
   "terug" zouden die meting veranderen. Een button verandert niets aan het
   linkweb en doet voor de bezoeker precies hetzelfde. */
.tseo-terug {
  position: fixed; z-index: 50;
  left: max(18px, env(safe-area-inset-left));
  bottom: max(18px, env(safe-area-inset-bottom));
  display: inline-flex; align-items: center; gap: 8px;
  width: max-content; min-height: 44px;
  margin: 0;
  padding: 10px 15px 10px 12px;
  border: 1px solid rgba(148, 163, 184, .16); border-radius: 999px;
  background: rgba(6, 11, 24, .62);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
          backdrop-filter: blur(14px) saturate(150%);
  box-shadow: 0 8px 26px rgba(0, 0, 0, .34);
  color: rgba(203, 213, 240, .74);
  font: 500 10px/1 var(--mono);
  letter-spacing: .12em; text-transform: uppercase;
  cursor: pointer;
  -webkit-appearance: none; appearance: none;
  transition: color 220ms var(--ease), border-color 220ms var(--ease),
              background 220ms var(--ease), opacity 220ms var(--ease);
  /* backwards en niet both: na afloop valt de knop terug op de regels
     hierboven, zodat een animatie nooit een latere stand blokkeert. */
  animation: tseoTerugIn 420ms var(--ease) 240ms backwards;
}
.tseo-terug i {
  font-style: normal; font-size: 13px; line-height: 1; color: var(--per2);
  transition: translate 220ms var(--ease);
}
.tseo-terug:hover { color: #fff; border-color: rgba(139, 149, 255, .42); background: rgba(12, 19, 42, .92); }
.tseo-terug:focus-visible { color: #fff; border-color: rgba(139, 149, 255, .6); outline: none; }
.tseo-terug:hover i { translate: -3px 0; }
@keyframes tseoTerugIn { from { opacity: 0; translate: 0 10px; } }
/* Staat het register open, dan hoort de knop niet over de scrim te liggen. */
body.navOpen .tseo-terug { opacity: 0; pointer-events: none; }
@media (max-width: 520px) {
  .tseo-terug {
    left: max(14px, env(safe-area-inset-left));
    bottom: max(14px, env(safe-area-inset-bottom));
    padding: 10px 14px 10px 11px;
    letter-spacing: .1em;
  }
}
/* Een pagina met een eigen vaste balk onderin krijgt de knop iets hoger, zodat
   de twee elkaar niet raken. */
body:has(.mpp-toast) .tseo-terug { bottom: max(74px, env(safe-area-inset-bottom)); }
@media print { .tseo-terug { display: none; } }

/* De terminalstatus op skill-, MCP- en Jarvis-pagina's hoort bij de inhoud,
   niet boven de inhoud. De paginaspecifieke stijl staat later in de HTML en
   maakt de balk fixed; deze bewust specifiekere regel houdt hem in de stroom.
   Flex-wrap en min-width voorkomen dat lange Engelstalige skillnamen op een
   smal scherm de documentbreedte vergroten. */
body .statusbar {
  position: static;
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
  flex-wrap: wrap;
}
body .statusbar > * { min-width: 0; }
body .statusbar > b { overflow-wrap: anywhere; }
body .statusbar .sb-sec { flex: 1 1 160px; }

/* Een getoond bronbestand mag op mobiel nooit de pagina zelf verbreden. */
pre.kopieerblok {
  max-width: 100%;
  box-sizing: border-box;
  white-space: pre-wrap;
  overflow-x: auto;
  overflow-wrap: anywhere;
}

/* ---- 2. DE KNOP DIE ZELF BEWEEGT -----------------------------------------
   v2.js zet .tseo-zweef op elke knop die nog GEEN eigen zweefstand heeft. Dat
   is met opzet geen lijst met klassenamen: v2.js leest de echte stijlregels
   en slaat over wat al beweegt. Zo krijgt niets twee bewegingen over elkaar
   en klopt het ook voor een knop die morgen wordt gebouwd.

   Waarom een animation en geen transition. Een transition zou hier verliezen
   van de pagina zelf: bijna elke knop draagt al een eigen transition-regel
   met een hogere gewichtsklasse, en die zou de onze wegdrukken. Een animation
   staat daarnaast en botst met niets. De prijs is dat de knop bij het
   weggaan van de muis in een keer terugstaat in plaats van terug te veren,
   en dat is bij twee pixels niet te zien. */
@media (hover: hover) and (pointer: fine) {
  .tseo-zweef:hover { animation: tseoZweef 240ms var(--ease) both; }
  .tseo-zweef:hover > .arr,
  .tseo-zweef:hover > .pijl { animation: tseoPijl 240ms var(--ease) both; }
  /* Indrukken hoort omlaag te gaan. De schaal komt van .is-druk hierboven;
     de zweefstand hoort er op dat moment af. */
  .tseo-zweef:active { animation: none; }
}
@keyframes tseoZweef { from { translate: 0 0; } to { translate: 0 -2px; } }
@keyframes tseoPijl  { from { translate: 0 0; } to { translate: 3px 0; } }

/* ---- 3. DE LEVENDE LAAG: HET WERK OP HET BLAD ----------------------------
   Eis E273. Het blad is een bouwtekening, en een bouwtekening waar aan
   gewerkt wordt heeft een peilbalk die eroverheen loopt, verkeer op de
   banen en knooppunten die oplichten. Dat is wat hier beweegt.

   WAT HIER MET OPZET NIET STAAT: een grafiek met getallen. Een balkenrij die
   op en neer gaat leest als een meting, en een meting die niemand heeft
   gedaan is een verzonnen cijfer. Alles hieronder is lijnwerk zonder een
   enkel getal en zonder een enkele as, en het staat op de achtergrondlaag
   die aria-hidden is. Het enige wat wel een echte waarde toont is de
   voortgangslijn onder punt 4, en die leest de werkelijke scrollpositie van
   de bezoeker zelf.

   De maten zijn geen willekeurige getallen: het raster van .v2bg staat op
   96 px, dus alles hieronder loopt over een echte rasterlijn en de
   knooppunten staan op een echt snijpunt.

   KOSTEN. Tien kleine elementen, alleen transform en opacity, dus alles
   gaat over de compositor en er is geen enkele herberekening van de
   opmaak. Op een smal scherm vallen er vier af. */
.tseo-stad { position: absolute; inset: 0; pointer-events: none; }
.tseo-stad > i { position: absolute; display: block; opacity: 0; }

/* de peilbalk die het blad aftekent */
.tseo-peil {
  left: 0; right: 0; top: 0; height: 1px;
  background: linear-gradient(90deg, transparent,
              rgba(139, 149, 255, .26) 20%, rgba(103, 232, 249, .4) 50%,
              rgba(139, 149, 255, .26) 80%, transparent);
  animation: tseoPeil 26s linear infinite;
}
@keyframes tseoPeil {
  0%   { opacity: 0;   transform: translate3d(0, 0, 0); }
  8%   { opacity: .85; }
  86%  { opacity: .7; }
  100% { opacity: 0;   transform: translate3d(0, 100vh, 0); }
}

/* verkeer op de horizontale banen */
.tseo-rij {
  left: 0; width: 72px; height: 1px;
  background: linear-gradient(90deg, rgba(110, 121, 255, 0),
              rgba(110, 121, 255, .42) 70%, rgba(103, 232, 249, .85));
  animation-name: tseoRij; animation-timing-function: linear;
  animation-iteration-count: infinite;
}
@keyframes tseoRij {
  0%   { opacity: 0;  transform: translate3d(-90px, 0, 0); }
  6%   { opacity: .9; }
  94%  { opacity: .9; }
  100% { opacity: 0;  transform: translate3d(100vw, 0, 0); }
}
.tseo-rij.r1 { top: 192px; animation-duration: 17s; animation-delay: -4s; }
.tseo-rij.r2 { top: 480px; animation-duration: 23s; animation-delay: -13s; }
.tseo-rij.r3 { top: 96px;  animation-duration: 29s; animation-delay: -21s; }
.tseo-rij.r4 { top: 672px; animation-duration: 21s; animation-delay: -8s; }

/* verkeer op de verticale banen */
.tseo-baan {
  top: 0; width: 1px; height: 72px;
  background: linear-gradient(180deg, rgba(110, 121, 255, 0),
              rgba(110, 121, 255, .38) 70%, rgba(103, 232, 249, .7));
  animation-name: tseoBaan; animation-timing-function: linear;
  animation-iteration-count: infinite;
}
@keyframes tseoBaan {
  0%   { opacity: 0;  transform: translate3d(0, -90px, 0); }
  6%   { opacity: .8; }
  94%  { opacity: .8; }
  100% { opacity: 0;  transform: translate3d(0, 100vh, 0); }
}
.tseo-baan.b1 { left: 288px; animation-duration: 25s; animation-delay: -6s; }
.tseo-baan.b2 { left: 96px;  animation-duration: 33s; animation-delay: -19s; }

/* knooppunten op een snijpunt van het raster */
.tseo-knoop {
  width: 4px; height: 4px; border-radius: 50%; background: var(--per2);
  animation-name: tseoKnoop; animation-timing-function: var(--ease);
  animation-iteration-count: infinite;
}
@keyframes tseoKnoop {
  0%, 100% { opacity: 0;   transform: scale(.6); }
  50%      { opacity: .55; transform: scale(1); }
}
.tseo-knoop.k1 { top: 190px; left: 286px; animation-duration: 9s;  animation-delay: -1s; }
.tseo-knoop.k2 { top: 574px; left: 574px; animation-duration: 11s; animation-delay: -5s; }
.tseo-knoop.k3 { top: 382px; left: 862px; animation-duration: 13s; animation-delay: -9s; }

/* Op een telefoon minder verkeer: minder lagen, minder stroom, en het smalle
   blad wordt anders druk in plaats van levend. */
@media (max-width: 760px) {
  .tseo-rij.r2, .tseo-rij.r4, .tseo-baan.b1, .tseo-knoop.k3 { display: none; }
}

/* ---- 4. DE VOORTGANGSLIJN ------------------------------------------------
   De enige levende lijn met een echte waarde erin: hij toont hoe ver je in
   deze pagina bent. Dat is geen schatting en geen verzonnen cijfer maar de
   werkelijke scrollpositie, en er staat geen getal bij.

   scaleX op een vaste lijn, dus geen enkele herberekening van de opmaak. Hij
   verschijnt pas als er echt gescrold is; bovenaan de pagina hoort er niets
   te staan. */
.tseo-voort {
  position: fixed; left: 0; right: 0; top: 0; height: 2px; z-index: 61;
  transform-origin: 0 50%;
  transform: scaleX(var(--tseo-v, 0));
  background: linear-gradient(90deg, var(--blue), var(--per2) 55%, var(--cyan));
  opacity: 0; pointer-events: none;
  transition: opacity 320ms var(--ease);
}
.tseo-voort.is-aan { opacity: .9; }

/* ---- 5. DE UIT-KNOP ------------------------------------------------------
   v2.js zet de levende laag en de voortgangslijn bij bewegingsvoorkeur al
   niet neer. Dit is het vangnet voor wie de voorkeur tijdens de sessie
   aanzet, en voor papier. */
/* 16-09: het verkeer op de banen leest op een telefoon als een streep door de tekst (72 px licht over een regel van 300); zachter, niet weg. */
@media (max-width: 900px) { .tseo-rij, .tseo-baan { filter: opacity(.3); } }
@media (prefers-reduced-motion: reduce) {
  .tseo-stad, .tseo-voort { display: none; }
}
@media print {
  .tseo-stad, .tseo-voort { display: none; }
}


/* ==========================================================================
   AANVULLING 09-09-2026 · DE UITKLAPVRAAG EN DE PLEK IN HET REGISTER
   Twee klachten van Gianluca, allebei sitebreed, allebei zonder ook maar
   een letter HTML aan te raken.

   EEN. "Veelgestelde vragen website breed zijn niet handig gedaan, zo lijkt
   het gewoon lappen tekst, dat wil ik anders, gewoon uitklapbaar met een
   prachtige animatie."

   TWEE. "Als ik op home zit en ik open menu zie ik dat lelijke blauwe om de
   home. Logisch dat je wilt aangeven waar je bent, maar doe dat anders, veel
   mooier dan dat."

   NAGEMETEN VOORDAT ER IETS IS GESCHREVEN, over alle 623 pagina's:
     620x  <details class="v2tk">      de taalwisselaar, GEEN vraag
      45x  <details> zonder klasse     myparcel-dashboard (9, 8, 10),
                                        en/tools-and-plugins/myparcel (9),
                                        es/jarvis (5), plus losse
       5x  <details class="home-faq-item">   de startpagina
       1x  <details class="home-story-more"> het lees-verder blok
   Daarom loopt niets hieronder over `details` als element maar over de klasse
   .tseo-vraag, die v2.js alleen hangt op wat werkelijk een uitklapper is. De
   620 taalwisselaars blijven er buiten, en dat is met opzet: die heeft zijn
   eigen blad hierboven en zijn eigen beweging.
   ========================================================================== */

/* ---- 1. HET MECHANIEK ----------------------------------------------------
   <details> animeert uit zichzelf niet. Een hoogte van auto is niet
   interpoleerbaar, en de browser klapt de inhoud in een keer aan en uit.
   Het raster lost dat op: een rij van 0fr naar 1fr IS interpoleerbaar, en de
   binnenlaag met overflow hidden knipt netjes mee. Geen enkel getal wordt
   gemeten, geen enkele hoogte wordt in JavaScript gezet, dus de tekst mag
   zonder herberekening groeien of krimpen bij een andere lettergrootte.

   v2.js bouwt hiervoor twee schillen om het antwoord heen:
     <details class="tseo-vraag">
       <summary>de vraag</summary>
       <div class="tseo-vraag-uit"><div class="tseo-vraag-in">het antwoord
   De tekst zelf blijft staan waar hij stond, in dezelfde volgorde, in
   dezelfde elementen. Hij wordt verplaatst, nooit vervangen en nooit
   verborgen voor de bron. Google leest het antwoord dus gewoon mee, want het
   staat vanaf byte een in de HTML van de pagina en het blijft in de DOM. */
.tseo-vraag-uit {
  display: grid;
  grid-template-rows: 0fr;
  /* 560 ms en niet 300. De huis-ease cubic-bezier(.16,1,.3,1) legt bijna al
     zijn weg in de eerste helft af, dus met 300 ms was de vraag na 120 ms al
     open en voelde het als klappen in plaats van uitvouwen. Met 560 ms staat
     de staart lang genoeg door om de rust van de oude site te halen, zonder
     dat wachten begint. Nagemeten: 210 ms na de klik staat de rij op 103 van
     de 115 px, dus negentig procent, en de rest komt zacht na. */
  transition: grid-template-rows 560ms var(--ease);
}
.tseo-vraag-in { min-height: 0; overflow: hidden; }

/* Zonder JavaScript, en bij bewegingsvoorkeur, stuurt [open] alles. Dan is er
   geen animatie maar wel gewoon open en dicht. */
.tseo-vraag[open] > .tseo-vraag-uit { grid-template-rows: 1fr; }

/* Met JavaScript stuurt .is-open, want alleen dan kan de weg terug ook
   animeren: bij het sluiten blijft [open] nog even staan totdat de rij op nul
   is. Zonder deze twee regels zou het antwoord bij het dichtklappen in een
   keer verdwijnen en zou alleen het openen mooi zijn. */
.tseo-vraag--js[open]:not(.is-open) > .tseo-vraag-uit { grid-template-rows: 0fr; }
.tseo-vraag--js.is-open > .tseo-vraag-uit { grid-template-rows: 1fr; }

/* ---- 2. DE TEKST SCHUIFT ZACHT IN ---------------------------------------
   Niet de hoogte alleen: het antwoord komt ook naar voren. translate en
   opacity kosten geen enkele herberekening van de opmaak, dus dit voegt nul
   layout shift toe. De trap van 60 ms per alinea is dezelfde maat als de
   registerregels hierboven, die met 60 ms uit elkaar landen. */
@media (prefers-reduced-motion: no-preference) {
  .tseo-vraag-in > * {
    opacity: 0; translate: 0 7px;
    transition: opacity 380ms var(--ease), translate 520ms var(--ease);
  }
  .tseo-vraag.is-open > .tseo-vraag-uit > .tseo-vraag-in > *,
  .tseo-vraag[open]:not(.tseo-vraag--js) > .tseo-vraag-uit > .tseo-vraag-in > * {
    opacity: 1; translate: none;
  }
  .tseo-vraag.is-open > .tseo-vraag-uit > .tseo-vraag-in > *:nth-child(1) { transition-delay: 90ms; }
  .tseo-vraag.is-open > .tseo-vraag-uit > .tseo-vraag-in > *:nth-child(2) { transition-delay: 150ms; }
  .tseo-vraag.is-open > .tseo-vraag-uit > .tseo-vraag-in > *:nth-child(3) { transition-delay: 210ms; }
  .tseo-vraag.is-open > .tseo-vraag-uit > .tseo-vraag-in > *:nth-child(n+4) { transition-delay: 270ms; }
}

/* ---- 3. DE LIJN LINKS ----------------------------------------------------
   Een open vraag krijgt een smalle staande lijn in het merkpaars, precies de
   markering die het register verderop ook krijgt. Absoluut geplaatst, dus hij
   duwt niets opzij, en hij groeit met scale in plaats van met height: dat is
   een samenstelling en geen herberekening. */
.tseo-vraag { position: relative; }
.tseo-vraag::before {
  content: "";
  position: absolute; left: 0; top: 10px; bottom: 10px; width: 2px;
  background: linear-gradient(180deg, var(--per), var(--per2) 60%, rgba(103,232,249,.55));
  border-radius: 2px;
  opacity: 0; scale: 1 .2; transform-origin: 50% 0;
  transition: opacity 320ms var(--ease), scale 560ms var(--ease);
  pointer-events: none;
}
.tseo-vraag.is-open::before,
.tseo-vraag[open]:not(.tseo-vraag--js)::before { opacity: 1; scale: 1 1; }
/* DE KANTLIJN, 13-09-2026. Deze lijn staat op left: 0 van de <details>. Elke
   summary die hem krijgt moet dus minstens 18 px linkermarge hebben, anders
   staat de lijn op de eerste letter. Gemeten op 13-09: .sv-meer 0 px,
   .home-story-more 4 px, allebei hersteld. De FAQ heeft 34, .mod-meer 28.
   Wie een nieuwe uitklapper maakt: meet paddingLeft van de summary. */

/* ---- 4. HET PLUSJE DAT DRAAIT --------------------------------------------
   Alleen waar de pagina er zelf geen heeft. v2.js leest bij het laden de
   werkelijke ::before en ::after van elke summary; myparcel-funnel.css zet
   daar al een plus en een min neer, en die houdt zijn eigen teken. Zo staan
   er nergens twee tekens naast elkaar. Dat is een meting, geen naamlijst,
   dus hij klopt ook voor een pagina die morgen wordt gebouwd. */
.tseo-vraag--tik > summary {
  display: flex; align-items: center; gap: 18px;
  list-style: none;
}
.tseo-vraag--tik > summary::-webkit-details-marker { display: none; }
.tseo-vraag--tik > summary::marker { content: ""; }

/* Het omhulsel dat v2.js om de tekst van de summary zet. Het is een flexvak
   dat mag krimpen tot nul en daarna zijn tekst laat afbreken, in plaats van
   tot zijn langste woord te verschrompelen. Zonder min-width: 0 doet een
   flexvak dat laatste, en dat is precies wat op 390 px de vette openingszin
   in een kolom van 77 pixels drukte. De FAQ verandert hier niet van: die had
   al een enkel kind en stond dus al over de volle breedte. */
.tseo-vraag-kop { flex: 1 1 auto; min-width: 0; }

.tseo-vraag-tik {
  flex: 0 0 auto; position: relative;
  width: 22px; height: 22px; margin-left: auto;
  border: 1px solid var(--line); border-radius: 999px;
  transition: rotate 520ms var(--ease), border-color 300ms var(--ease),
              background-color 300ms var(--ease);
}
.tseo-vraag-tik::before,
.tseo-vraag-tik::after {
  content: "";
  position: absolute; left: 50%; top: 50%;
  width: 9px; height: 1px; margin: -.5px 0 0 -4.5px;
  background: var(--per2);
  transition: opacity 260ms var(--ease), rotate 520ms var(--ease),
              background-color 300ms var(--ease);
}
.tseo-vraag-tik::after { rotate: 90deg; }

.tseo-vraag--tik > summary:hover .tseo-vraag-tik,
.tseo-vraag--tik > summary:focus-visible .tseo-vraag-tik { border-color: var(--per2); }

.tseo-vraag--tik.is-open > summary .tseo-vraag-tik,
.tseo-vraag--tik[open]:not(.tseo-vraag--js) > summary .tseo-vraag-tik {
  rotate: 180deg; border-color: var(--per2); background-color: rgba(110,121,255,.12);
}
.tseo-vraag--tik.is-open > summary .tseo-vraag-tik::after,
.tseo-vraag--tik[open]:not(.tseo-vraag--js) > summary .tseo-vraag-tik::after {
  opacity: 0; rotate: 180deg;
}

/* ---- 5. HET TOETSENBORD --------------------------------------------------
   De gedeelde focusring hierboven staat op 3 px lucht BUITEN het element.
   Op een vraag valt die weg, want .home-faq-list draagt overflow hidden en
   .mp-faq details een eigen rand met ronding: de ring werd daar afgeknipt.
   Naar binnen gelegd staat hij vrij, volgt hij de hele regel, en is hij op de
   donkere ondergrond onmiskenbaar. */
.tseo-vraag > summary:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: -3px;
  border-radius: 5px;
}

/* ---- 6. GEEN BEWEGING WIE DAAR NIET OM VRAAGT ----------------------------
   Open en dicht blijven werken, alleen zonder tussenweg. v2.js laat de
   afhandeling in dit geval helemaal aan de browser, dus [open] stuurt en er
   is geen enkele overgang meer om af te wachten. */
@media (prefers-reduced-motion: reduce) {
  .tseo-vraag-uit,
  .tseo-vraag::before,
  .tseo-vraag-tik,
  .tseo-vraag-tik::before,
  .tseo-vraag-tik::after,
  .tseo-vraag-in > * { transition: none; }
}

/* ---- 7. DE VRAGENLIJST DIE GEEN UITKLAPPER IS ----------------------------
   Vijftien pagina's zetten hun vragen neer als een rij h3 met daaronder een p,
   zonder <details>. Daar valt niets uit te klappen zonder de HTML te
   herschrijven, en dat is niet aan dit blad. Wat hier wel kan is de lap tekst
   opdelen: lucht tussen de paren en een haarlijn erboven, zodat elke vraag
   een eigen blok wordt in plaats van een alinea in een muur. Alleen verticaal,
   dus geen enkele regel verschuift zijwaarts en er verdwijnt geen woord. */
.gle-faq .dos-in > h3 {
  margin-top: 34px;
  padding-top: 26px;
  border-top: 1px solid var(--hair);
}
.gle-faq .dos-in > h3:first-of-type { margin-top: 26px; padding-top: 0; border-top: 0; }
.gle-faq .dos-in > h3 + p { margin-top: 11px; }


/* ==========================================================================
   HET REGISTER · WAAR JE BENT
   ========================================================================== */

/* ---- 8. WAT DAT BLAUWE BLOK WERKELIJK WAS -------------------------------
   Nagemeten in de browser, op / en op /seo-verbeteren/, met het register via
   het toetsenbord geopend:

     document.activeElement  ->  <a href="/">Home</a>
     matches(":focus-visible")  ->  true
     outlineColor / style / width  ->  rgb(103, 232, 249) solid 2px

   Dat is geen markering van de huidige pagina. Het is de gedeelde focusring
   uit blok c hierboven, cyaan met 3 px lucht eromheen, en hij landt op Home
   omdat v2.js bij het openen de aandacht naar de eerste link in het register
   verplaatst. Die eerste link heet toevallig Home. Op /seo-verbeteren/ stond
   hetzelfde blokje om Home, dus hij zei nooit waar je was: hij zei alleen
   waar de aandacht heen was gesprongen.

   De aandacht MOET mee naar binnen, anders staat een toetsenbordgebruiker na
   het openen nog buiten het blad. Wat weg kan is de ring op die ene link. In
   v2.js gaat de aandacht daarom naar .v2reg-in, een omhulsel met tabindex -1.
   Dat is precies het geval dat de focusregel hierboven al uitzondert
   ([tabindex]:not([tabindex="-1"])), dus daar tekent geen enkele browser iets
   omheen. Tab brengt je daarna gewoon bij Home, en DAN hoort de ring er wel
   te staan. Die blijft dus volledig intact. */
.v2reg-in:focus { outline: none; }

/* ---- 9. WAT ERVOOR IN DE PLAATS KOMT ------------------------------------
   v2.js zet aria-current="page" op elke registerlink die exact naar deze
   pagina wijst. Exact, niet op prefix: met een prefixtoets zou /jarvis/ op
   elke subpagina oplichten alsof je daar staat, en dat is dezelfde valkuil
   die theseo-product-shell-j06.js hierboven al beschrijft.

   Vier dingen, en geen achtergrondvlak:
     een smalle staande lijn in het merkpaars links van de regel,
     de regel iets ingesprongen zodat hij uit de kolom komt,
     het mono-label ervoor op de accentkleur, en
     een zachte fade die pas landt nadat de regels zelf zijn geland.
   De markering staat ook in de toegankelijkheidsboom, want aria-current is
   wat een schermlezer voorleest als "huidige pagina". Het is dus niet alleen
   mooier maar ook het eerste dat er werkelijk staat. */
.v2reg-row[aria-current="page"]::before,
.v2reg-bij a[aria-current="page"]::before {
  content: "";
  position: absolute; left: 0; top: 8px; bottom: 8px; width: 2px;
  /* De bijrol-strook is een flexrij met regels van twaalf pixels hoog. Daar
     zou acht boven en acht onder een negatieve hoogte geven en dus niets
     tekenen; die krijgt zijn eigen maat een regel verderop. */
  border-radius: 2px;
  background: linear-gradient(180deg, var(--per), var(--per2));
  box-shadow: 0 0 14px rgba(110,121,255,.45);
  opacity: 0; scale: 1 .2;
  transition: opacity 380ms var(--ease), scale 620ms var(--ease);
  pointer-events: none;
}
.v2reg-bij a[aria-current="page"]::before { top: -2px; bottom: -2px; }
.v2hdr.is-open .v2reg-row[aria-current="page"]::before,
.v2hdr.is-open .v2reg-bij a[aria-current="page"]::before {
  opacity: 1; scale: 1 1;
  transition-delay: .34s;
}

/* De inspringing. Gesloten staat hij op de gewone 4 px, zodat er bij het
   laden niets te verschuiven valt; hij loopt pas mee als het blad opengaat en
   erft daarmee dezelfde padding-overgang van .55s die de regel al draagt. */
.v2hdr.is-open .v2reg-row[aria-current="page"] { padding-left: 16px; }

/* Het mono-label ervoor. In de dienstenkolom staat het nummer op paars, in de
   twee monokolommen op cyaan; met kleur alleen zou de markering in de ene
   kolom hard opvallen en in de andere onzichtbaar zijn. Cyaan EN vet werkt in
   alle drie, want nergens is het nummer al vet. */
.v2reg [aria-current="page"] .v2reg-n { color: var(--cyan); font-weight: 700; }
.v2reg-row[aria-current="page"] .v2reg-t { color: var(--per2); }
.v2reg-row[aria-current="page"] .ann { color: var(--per2); }

/* De bijrol-strook: home, cases, over ons, contact. Diezelfde lijn, dezelfde
   inspringing, alleen kleiner. Hier stond het blauwe blok. */
.v2reg-bij a[aria-current="page"] { position: relative; color: #fff; }
.v2hdr.is-open .v2reg-bij a[aria-current="page"] {
  padding-left: 11px;
  transition: color .3s var(--ease), padding-left .55s var(--ease);
}

@media (prefers-reduced-motion: reduce) {
  .v2reg-row[aria-current="page"]::before,
  .v2reg-bij a[aria-current="page"]::before {
    transition: none; opacity: 1; scale: 1 1;
  }
  .v2hdr.is-open .v2reg-bij a[aria-current="page"] { transition: none; }
}

/* ============================================================================
   HET APPGEVOEL EN DE RUSTIGE BEWEGING
   Toegevoegd 09-09-2026 na punten van Gianluca, bijgesteld 09-09 na zijn
   correctie op de kopbalk.

   WAT HIER NIET MEER STAAT. Op 9 september is de kopbalk eerst wit gemaakt,
   omdat de oude site zijn uitklapblad op background:#fff zette. Dat was de
   verkeerde lezing: het witte vlak was het MEGAMENU, niet de balk. De balk zelf
   was doorzichtig, met .transparent-header op een verloop van #00000063 naar
   transparant en backdrop-filter blur. Gianluca op 9 september: "de nav is wit
   ipv doozrichtig zoals wat het was dat is niet goed fix dat even". De lichte
   balk is daarom weggehaald en de doorzichtige donkere balk uit de basis van dit
   blad (regel 156 en verder) geldt weer, met blur bij het scrollen.

   Punt 1 - "mobiel moet echt voelen als een app dat heb ik altijd gezegd".
   Veilige hoogte, raakmaten van 44, een register dat vanaf de rand komt en een
   indruk in plaats van een zweefstand.

   Punt 5 en 9 - de blokken flowen rustiger in, de getallen animeren, en de
   zweefstand op een klantkaart snijdt de titel niet meer af.

   ALLES STAAT ACHTERAAN EN NIET TUSSENDOOR, want dit blad zet dezelfde
   selectors later nog een keer.
   ============================================================================ */

/* De indrukkleuren voor op de doorzichtige donkere balk. Wit op het donkere
   vlak haalt ruim de 4,5 die dit project eist; het accent is de merkkleur die
   voor een donkere ondergrond is gekozen. */
:root {
  --nav-ink: #ffffff;
  --nav-ink2: rgba(255,255,255,.72);
  --nav-lijn: rgba(255,255,255,.14);
  --nav-lijn2: rgba(255,255,255,.24);
  --nav-acc: var(--per2);
  --nav-acc-vlak: rgba(139,149,255,.14);
}
/* ---- 4. Veilige hoogte en raakmaten op de telefoon ------------------------
   De balk mat 103 px op 390 px breed. Dat is een derde van een duimzone voor
   twee knoppen. Hij gaat naar een app-hoogte, met env(safe-area-inset-top)
   erbovenop zodat de inkeping van een iPhone hem nooit opeet.

   Geen enkele regel hier haalt tekst weg. Het onderschrift van het logo
   blijft staan en blijft leesbaar; alleen de maat wordt kleiner. */
@media (max-width: 720px) {
  .v2hdr-in {
    gap: 12px;
    padding-top: calc(12px + env(safe-area-inset-top, 0px));
    padding-bottom: 12px;
    padding-left: max(18px, env(safe-area-inset-left));
    padding-right: max(18px, env(safe-area-inset-right));
    min-height: calc(58px + env(safe-area-inset-top, 0px));
  }
  /* HET LOGO BLIJFT EEN BLOK. Er stond hier even display:flex om de 44 px
     raakmaat te halen, en dat brak het merk: .v2logo draagt een <i> met de
     punt en daarnaast de tekstknoop "theseo", en in een kolomflex worden dat
     twee losse rijen. De punt stond boven het woord.

     Wat er nu staat: min-height voor de raakmaat, en align-content om de
     inhoud in dat blok te centreren. Een browser die align-content op een
     blok nog niet kent legt de inhoud bovenin, en de voering van 5 px vangt
     dat op. Op 320 px is de inhoud 29,3 px hoog en op 600 px 34,5; zonder de
     ondergrens zou de knop dus onder de 44 zakken. */
  .v2logo {
    font-size: clamp(1.15rem, 4.4vw, 1.34rem);
    padding-block: 5px; min-height: 44px; align-content: center;
  }
  /* Het onderschrift blijft op EEN regel, en dat is een maat en geen wens.
     Op 360 px brak het af en werd de balk 79 px in plaats van 70; op 390 px
     paste het wel. Een balk die per telefoon een andere hoogte heeft leest
     niet als een app. Met clamp krimpt de letter mee met het scherm en
     blijft de hoogte overal gelijk. De tekst blijft volledig staan. */
  .v2logo .ann {
    font-size: clamp(7.2px, 2.3vw, 9px);
    letter-spacing: clamp(.04em, .5vw, .12em);
    white-space: nowrap; margin-top: 1px;
  }
  /* Ruimte vrijmaken voor dat onderschrift op de smalste toestellen. De
     woorden Index en NL blijven staan; alleen hun letterafstand krimpt. */
  .v2toggle { font-size: 11px; letter-spacing: clamp(.05em, 1.6vw, .16em); }
}
@media (max-width: 359px) {
  .v2hdr-in {
    gap: 8px;
    padding-left: max(14px, env(safe-area-inset-left));
    padding-right: max(14px, env(safe-area-inset-right));
  }
  .v2toggle { gap: 7px; }
}
/* Raakmaten. De eis is 44 bij 44 punten. Gemeten op 390 px stonden de
   registerknop op 80,7 bij 45 en de taalknop op 46 bij 45; die waren dus al
   goed en krijgen hier alleen een ondergrens, zodat een latere maatwijziging
   ze niet onder de norm kan duwen. Het logo krijgt zijn 44 in het blok
   hierboven, samen met zijn nieuwe lettermaat. */
@media (pointer: coarse), (max-width: 720px) {
  .v2toggle { min-height: 44px; min-width: 44px; }
  .v2tk-knop { min-height: 44px; min-width: 44px; box-sizing: border-box; }
  .v2tk-rij { min-height: 44px; }
}

/* ---- 5. Het register komt vanaf de rand ----------------------------------
   Op de telefoon vouwde het blad alleen open (grid-template-rows 0fr naar
   1fr). Dat leest als groeien, niet als binnenkomen. Er komt een verschuiving
   bij: het blad begint achttien px hoger, dus onder de balk vandaan, en zakt
   naar zijn plek. De balk staat op laag 2 en dekt de start af, zodat het
   werkelijk van achter de rand vandaan lijkt te komen.

   De volledige transitielijst wordt herhaald, want dit is een kortschrift en
   dat wist de tijden van het apptempo-blok anders uit. */
@media (max-width: 720px) {
  .v2reg {
    transform: translateY(-18px);
    opacity: 0;
    transition: grid-template-rows 260ms var(--ease), max-height 260ms var(--ease),
                box-shadow 260ms var(--ease), border-color 260ms var(--ease),
                transform 240ms var(--ease), opacity 200ms var(--ease),
                visibility 0s 260ms;
  }
  .v2hdr.is-open .v2reg {
    transform: translateY(0);
    opacity: 1;
    transition: grid-template-rows 380ms var(--ease), max-height 380ms var(--ease),
                box-shadow 380ms var(--ease), border-color 380ms var(--ease),
                transform 420ms var(--ease), opacity 240ms var(--ease),
                visibility 0s;
  }
}
/* De hoogtebegrenzer stond op een vast getal van 92 px, en dat was de
   kopbalk van toen. v2.js meet de balk nu en zet --tseo-navh; het getal
   blijft als terugval staan voor het geval het script niet draait. De
   vaartregel erbij, zodat het register zelf op iOS met vaart doorscrolt. */
.v2reg {
  max-height: calc(100dvh - var(--tseo-navh, 92px));
  -webkit-overflow-scrolling: touch;
}

/* ---- 6. Een indruk in plaats van een zweefstand --------------------------
   Een telefoon heeft geen hover. Het aanraakblok eerder in dit blad zette
   .v2toggle:active op #fff, en wit op een witte balk is niets. Hier staat de
   lichte tegenhanger. Deze regels staan bewust buiten @media (hover: none):
   een muisgebruiker die de knop indrukt hoort hetzelfde antwoord te krijgen. */
.v2toggle:active { color: var(--nav-ink); scale: .94; }
.v2tk-knop:active { color: var(--nav-ink); border-color: var(--nav-acc); background: var(--nav-acc-vlak); }
.v2logo:active { opacity: .68; }
.v2logo { transition: opacity 120ms var(--ease); }
@media (hover: none) {
  .v2toggle:active { color: var(--nav-ink); }
  .v2toggle:active .lines i { width: 22px; }
}

/* De terugknop hoort te voelen als de terugknop van een app: hij zakt in bij
   de aanraking en de kepel loopt vooruit, precies zoals bij de zweefstand. */
.tseo-terug {
  transition: color 220ms var(--ease), border-color 220ms var(--ease),
              background 220ms var(--ease), scale 120ms var(--ease);
}
.tseo-terug:active {
  scale: .97;
  color: #fff;
  border-color: rgba(139, 149, 255, .5);
  background: rgba(14, 22, 48, .98);
}
.tseo-terug:active i { translate: -3px 0; }
@media (pointer: coarse) { .tseo-terug { min-height: 46px; } }

/* ---- 7. Rustiger en vloeiender inflowen ----------------------------------
   Het apptempo-blok zette 280 ms op .rv en 260 ms op .st. Dat is snappy maar
   het leest als klikken in plaats van als stromen. Gianluca vroeg om de oude
   site: "als je scrolt dat flowen de blokken in". De afstand en de richting
   blijven, alleen de klok wordt langer en er komt een grotere tussenstap per
   kind zodat een rij kaarten na elkaar binnenloopt in plaats van als blok.

   De waarnemer blijft ongemoeid: drempel 0 met een positieve ondermarge van
   22 procent staat verderop in v2.js uitgelegd en nagerekend. Een drempel in
   procenten van het element (de oude site had 0.18) gaat mis bij een lang
   dossier, en dat is precies het blok waar het het meeste opvalt. */
.rv { transform: translateY(12px); transition: opacity 820ms var(--ease), transform 820ms var(--ease); }

/* 09-09-2026, tweede ronde. 560 ms met 22 px was nog steeds korter en verder
   dan de oude site, en Gianluca vroeg er expliciet naar: "ik wil dat alles
   langzaam toont, moet er mooi ingeglijden, zoals op de oude website". De
   oude site is nagemeten in de bewaarde bron van theseo.nl: de blokken
   stonden daar op transform:translateY(12px) met transition .8s ease. Dat is
   een KORTERE reis over een LANGERE tijd, en dat is precies het verschil
   tussen schuiven en glijden. Die twee waarden staan hierboven nu een op een
   overgenomen; alleen de easing blijft van deze site, want die is zachter
   dan het kale ease van toen. */

/* WAAROM .st MET EEN ANIMATIE EN .rv MET EEN OVERGANG, gemeten 09-09-2026.

   .st > * is een raster van kaarten, en die kaarten hebben op de homepagina
   hun eigen transitielijst uit home-atelier.css: border-color, transform en
   background-color, alle drie 240 ms. Die regel is soortelijker dan .st > *,
   dus de entree liep op 240 ms en had helemaal geen opacity in de lijst: de
   kaarten verschenen ineens en schoven alleen. Een langere lijst schrijven
   zou hun zweefstand meeslepen, en die hoort daar juist te blijven.

   Een animatie loopt naast de transitielijst en raakt hem niet aan. Met
   fill-mode backwards geldt de animatie alleen VOOR en TIJDENS het lopen; is
   hij klaar, dan staat het element weer op zijn gewone regels en hoort de
   zweefstand meteen weer bij de kaart. Zo krijgt de entree zijn rust en
   houdt de aanraking zijn snelheid. */
.st > * { opacity: 0; transform: translateY(12px); }
.st.isIn > * {
  opacity: 1; transform: none;
  animation: stIn 780ms var(--ease) calc(var(--i, 0) * 90ms) backwards;
}
@keyframes stIn { from { opacity: 0; transform: translateY(12px); } }
/* TRAGE TRAP voor grote kaarten en panelen, toegevoegd 14-09-2026 (Gianluca
   over de vier ingangen en de vier kwartalen: "grote blokken zonder leven;
   als ze een voor een zichtbaar worden is dat mooier"). 170 ms per kaart in
   plaats van 90, iets meer weg en een klein beetje kleiner, zodat je de
   volgorde echt ziet. Zet .st-traag naast .st op de houder. */
.st.st-traag > * { transform: translateY(22px) scale(.985); }
/* DE FOUT VAN 14 TOT 15 SEPTEMBER, gevonden met scripts/ontwerp/cdp.mjs in een
   echte browser: de regel hierboven staat NA .st.isIn > * en is even
   soortelijk, dus na de trap bleef elk kind 22 px lager en .985 kleiner
   staan. Dat was de "tekst buiten het blokje", de afgesneden laatste rij in
   een paneel met overflow hidden, het bijschrift over de rand van een
   figuur en de kolom die in de voet van een tekening liep: een fout, op
   elke pagina met een trage trap. De stand na de trap staat nu expliciet. */
.st.st-traag.isIn > * { transform: none; animation: stInTraag 900ms var(--ease) calc(var(--i, 0) * 170ms) backwards; }
@keyframes stInTraag { from { opacity: 0; transform: translateY(22px) scale(.985); } }
/* EINDSTAND NA DE TRAP, 16-09-2026. v2.js zette --i op 0 na 1200 ms,
   terwijl de trage trap tot 2090 ms loopt: kaart zeven en verder stonden dan
   nog in de wachtstand (vulling backwards, opacity 0) en sprongen in plaats
   van binnen te komen. De regel van 8i: zichtbaarheid hangt nooit aan een
   animatie. v2.js zet na de trap op elk kind .stKlaar; dan staat opacity
   vast en is de trap-animatie weg. De strookkaarten houden hun groei naar
   het midden (die animatie staat in het telefoonblok met !important op
   .mobKlaar), daarom blijven de stroken buiten de animatie-regel. */
.st > .stKlaar { opacity: 1 !important; }
.st:not(.mob-strook, .tijd, .ladder, .vakken, .steden, .klant-reg) > .stKlaar { animation: none !important; }
/* Lucht tussen een duo en het instrumentblad eronder (Gianluca 14-09: "iets
   te weinig padding boven"). */
.mod-duo + .rv, .mod-duo + .aiz-fig, .mod-duo + .roadmap, .mod-duo + .sv-fig { margin-top: clamp(32px, 4vw, 48px); }
.lm > span { transition: transform 460ms var(--ease); }
.lm:nth-of-type(2) > span { transition-delay: 90ms; }
.lm:nth-of-type(3) > span { transition-delay: 180ms; }

/* ---- 8. De zweefstand die de titel afsneed (punt 9) ----------------------
   WAT ER MIS WAS, gemeten 09-09-2026 op de klantkaarten van de homepagina.
   .krow-flip staat op overflow:hidden en draagt twee keer dezelfde titel; bij
   hover schuift de binnenste span 50 procent omhoog en rolt de tweede regel
   in beeld. Op de homepagina verbergt het atelierblad die tweede regel
   (.home-case-grid .krow-t + .krow-t { display:none }) maar de schuifregel
   blijft gelden, want .krow:hover .krow-flip > span is soortelijker dan
   .home-case-grid .krow-flip > span. Nagemeten: de titel stak 18,12 px boven
   het kader uit van de 36,23 px die hij hoog is. Precies de helft eraf, en
   dat is wat op de schermafdruk van Gianluca stond.

   DE OPLOSSING IS EEN METING EN GEEN NAAMLIJST. v2.js kijkt bij het laden of
   de tweede titel werkelijk wordt getekend. Zo niet, dan krijgt het kader
   .geen-flip en beweegt er niets. Dat klopt dus ook voor een pagina die
   morgen dezelfde streek uithaalt met een andere klassenaam. */
.krow:hover .krow-flip.geen-flip > span,
.krow:focus-visible .krow-flip.geen-flip > span,
.krow:active .krow-flip.geen-flip > span { transform: none; }

/* De tweede helft van punt 9: de zweefstand kwam vertraagd op gang. De trap
   stond als transition-delay op .st.isIn > * en bleef daarna staan, dus kaart
   vier wachtte 210 ms voordat hij op de muis reageerde. De entree is hierboven
   een animatie geworden en de trap zit daarmee in animation-delay; die raakt
   de zweefstand niet meer. Deze regel blijft als vangnet staan voor de 384
   pagina's die hun eigen onthulregels op --i hebben, en voor het moment
   tussen de entree en het opruimen van --i in v2.js. */
.st.isIn > *:hover, .st.isIn > *:active { transition-delay: 0s; }

/* Dezelfde streek bij de klantenwand. Daar staat de vertraging in de CSS zelf
   en niet in --i, dus hier wordt hij per eigenschap gezet: de entree
   (opacity, transform) houdt zijn trap, de randkleur van de zweefstand komt
   meteen. De volgorde is die van .kl-logo hierboven: opacity, transform,
   border-color. */
.rv.isIn .kl-logo:nth-child(2) { transition-delay: .08s, .08s, 0s; }
.rv.isIn .kl-logo:nth-child(3) { transition-delay: .16s, .16s, 0s; }
.rv.isIn .kl-logo:nth-child(4) { transition-delay: .24s, .24s, 0s; }
.rv.isIn .kl-logo:nth-child(5) { transition-delay: .32s, .32s, 0s; }
.rv.isIn .kl-logo:nth-child(6) { transition-delay: .4s, .4s, 0s; }

/* ---- 9. De teller (punt 5, "de getallen animeren") -----------------------
   Het eindgetal staat in de HTML, dus zonder JavaScript en voor een
   zoekmachine klopt de pagina gewoon. v2.js leest dat getal, telt ernaartoe
   zodra het blok in beeld komt en laat het met rust bij
   prefers-reduced-motion. Elke pagina kan het gebruiken met alleen een
   data-attribuut, zonder eigen script:

     <b data-teller>2.501</b>
     <b data-teller data-teller-duur="900" data-teller-van="1800">2.501</b>

   tabular-nums houdt elk cijfer even breed, en v2.js legt de eindbreedte
   vast voordat hij op nul begint. Zo verschuift er niets tijdens het tellen. */
[data-teller] { font-variant-numeric: tabular-nums; white-space: nowrap; }

@media (prefers-reduced-motion: reduce) {
  .v2reg { transform: none !important; opacity: 1 !important; }
  .v2toggle:active, .tseo-terug:active { scale: 1; }
}


/* ============================================================
   DE AFSLUITER, sitebreed, 10-09-2026
   ------------------------------------------------------------
   Gianluca: elke pagina moet eindigen zoals /seo-verbeteren/ en de
   startpagina eindigen. Die vorm stond tot nu toe alleen in de stijlkop van
   /seo-verbeteren/ zelf, onder .sv-slot, .sv-cta en .sv-chips. Een tweede
   pagina die hem overneemt door zestig regels te kopieren is precies hoe twee
   pagina's uit elkaar gaan lopen. Daarom staat de vorm hier een keer, met
   neutrale namen, in exact dezelfde waarden als het origineel.

   DRIE DELEN, in deze volgorde:
     1. .slot-in   het omkaderde paneel om de slottekst
     2. .slot-cta-row met .slot-cta en .slot-cta.ghost: twee knoppen, de
        eerste gevuld met een pijl, de tweede een omtrek
     3. .slot-chips  het verwijsrijtje als pillen, niet als kale links
   Daaronder pas de auteurskaart, zonder foto.
   ============================================================ */
.slot-in {
  position: relative; overflow: hidden;
  padding: 34px 36px 30px; border: 1px solid var(--line); border-radius: 22px;
  background: linear-gradient(180deg, rgba(255,255,255,.045), rgba(255,255,255,.012));
  box-shadow: 0 30px 90px rgba(2,6,20,.5), inset 0 1px 0 rgba(255,255,255,.055);
  /* Twee kolommen sinds 10-09-2026. De slottekst stond over de halve breedte
     met een leeg rechterdeel ernaast, en dat is precies waar Gianluca op wees.
     De knoppen staan nu rechts, onder elkaar, en vullen die kant met de enige
     handeling die er op deze plek toe doet. Onder 860 px valt het terug op een
     kolom met de knoppen eronder. */
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(230px, .5fr);
  column-gap: clamp(30px, 5vw, 64px);
  align-items: start;
}
/* Twee kinderen: .slot-in-tekst links (NIET .slot-tekst: die naam bestaat al
   voor het slotblok op 105 andere pagina's, met eigen padding en een kleinere
   h2, en die zette op 11-09 mijn kop in het klein en de tekst 28 px naar
   binnen), .slot-cta-row rechts. Eerder stonden de
   alinea's los in het raster met grid-row: 1 / -1 op de knoppen; in een raster
   zonder vaste rijen betekent -1 de eerste lijn, dus de knoppen zaten in rij 1
   en centreerden op de hoogte van de kop. Gemeten op 11-09: 35 px lucht boven,
   379 px onder. Met een tekstblok als enig linkerkind klopt center wel. */
.slot-in-tekst { grid-column: 1; }
.slot-in-tekst > p, .slot-in-tekst > .mod-meer { max-width: 62ch; }
.slot-in-tekst > .dos-kop { margin-top: 0; }
.slot-in > .slot-cta-row { grid-column: 2; align-self: center; margin: 0; flex-direction: column; align-items: stretch; }
/* align-self center sinds 11-09-2026: het knoppenblok staat in het midden van
   de paneelhoogte met evenveel lucht erboven als eronder. Dat leest als een
   keuze; bovenaan geplakt met een leeg vlak eronder leest als vergeten. */
/* De contactregels onder de knoppen, 11-09-2026. De rechterkolom was 100 px
   hoog in een paneel van 499 px: twee knoppen en daaronder niets. Gianluca:
   "dr moet iets rechts zijn zodat het mooi opgevuld is". Wat er hoort is wat
   de tekst links al zegt: hoe je ons bereikt. Die drie regels staan nu onder
   de knoppen, klikbaar. */
.slot-contact { display: grid; gap: 10px; margin-top: 22px; padding-top: 22px; border-top: 1px solid var(--home-border); }
.slot-contact a, .slot-contact span { display: block; font: 400 13px/1.5 var(--mono); color: var(--mut); text-decoration: none; }
.slot-contact a:hover { color: var(--per2); }
.slot-contact b { display: block; font: 700 10px/1 var(--mono); color: var(--per2); letter-spacing: .08em; text-transform: uppercase; margin-bottom: 2px; }
.slot-in > .slot-cta-row .slot-cta, .slot-in > .slot-cta-row .btn-g { justify-content: center; }
.slot-in > .slot-cta-row .btn-g { white-space: nowrap; padding-inline: 1.2rem; }
/* Op een smal scherm mag de knoptekst wel breken: "Plan een gesprek in de
   agenda" paste anders niet in het slotpaneel en werd afgeknipt (over-ons op
   390, gemeten met buiten-blokje.js, 15-09-2026). */
@media (max-width: 480px) { .slot-in > .slot-cta-row .btn-g { white-space: normal; text-align: center; line-height: 1.3; } }
@media (max-width: 860px) {
  .slot-in { grid-template-columns: minmax(0, 1fr); }
  .slot-in-tekst, .slot-in > .slot-cta-row { grid-column: 1; }
  /* 15-09-2026 (avond), Gianluca op de telefoon: "een lange knop, dat is
     niet conform huisstijl" en "de gegevens zijn niet op de mooiste manier
     tentoongesteld". De huisstijl is de startpagina: knoppen op hun eigen
     breedte, naast elkaar, wikkelend. De drie contactregels worden pillen. */
  .slot-in > .slot-cta-row { margin-top: clamp(26px, 4vw, 36px); flex-direction: row; flex-wrap: wrap; align-items: center; gap: 12px; }
  .slot-in > .slot-cta-row .btn-g, .slot-in > .slot-cta-row .slot-cta { flex: 0 0 auto; width: auto; }
  .slot-in > .slot-cta-row .btn-g { white-space: normal; max-width: 100%; text-align: center; line-height: 1.3; }
  .slot-contact { flex-basis: 100%; display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; padding-top: 18px; }
  .slot-in .slot-contact a, .slot-in .slot-contact span { display: inline-flex; align-items: baseline; gap: 7px; padding: 8px 13px; border: 1px solid var(--line); border-radius: 999px; font-size: 12px; line-height: 1.3; text-decoration: none; background: rgba(8,14,30,.5); }
  .slot-in .slot-contact b { display: inline; margin: 0; }
}
.slot-in .slot-contact a, .slot-in .slot-contact a b { text-decoration: none !important; } /* wint van .dos-body a op pagina's die links onderstrepen */
@media (max-width: 620px) {
  /* De giant op een telefoon: "Plan een" op de eerste regel, "gesprek" met de
     pijl op de tweede. Zonder deze breuk paste "Plan een gesprek" net op een
     regel en stond de pijl alleen op de volgende (15-09-2026, avond). */
  .giant { font-size: clamp(52px, 15.5vw, 150px); }
  .giant em::before { content: "\A"; white-space: pre; }
  /* Elke knoppenrij op een telefoon: eigen breedte, geen uitgerekte balk. Dit
     wint bewust van de paginaeigen regels "column, stretch" die sinds
     augustus op de meeste hero's staan. */
  [class*="-cta-row"], [class*="-cta-rij"] { flex-direction: row !important; flex-wrap: wrap !important; align-items: center !important; }
  :is([class*="-cta-row"], [class*="-cta-rij"]) > :is(a, button) { width: auto !important; flex: 0 1 auto !important; align-self: auto !important; justify-content: center; max-width: 100%; white-space: normal; text-align: center; line-height: 1.3; }
}
.slot-in::after {
  content: ''; position: absolute; right: -16%; top: -70%; width: 70%; height: 210%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(103,232,249,.13), transparent 70%);
  pointer-events: none;
}
.slot-in > * { position: relative; z-index: 1; }
/* 16-09-2026 (avond), Gianluca bij de keuring van batch vier (/myparcel-dashboard/,
   /backlink-netwerk/, /linkbuilding-gratis-platform/): "op t eind is daar veel
   ruimte onder, dat moet je beter inrichten". Met een korte slottekst (een
   alinea) was de rechterkolom, twee knoppen plus drie contactregels, ruim
   hoger dan de tekst, en onder de tekst bleef een leeg vlak. Op een breed
   scherm staan de knoppen nu naast de tekst in het midden van de teksthoogte
   (de eerste knop sluit aan op de middenlijn van onderen, de tweede van boven)
   en lopen de drie contactregels als een strook onder beide kolommen. Het
   paneel is dan zo hoog als de tekst plus die strook, nergens een leeg vlak.
   De knoppenrij zelf valt weg uit het raster (display: contents), zodat de
   knoppen en de strook eigen rasterkinderen zijn; daarom krijgen zij hier ook
   de laag boven de gloed. De telefoon (860 en smaller) houdt zijn eigen vorm:
   knoppen op eigen breedte, contact als pillen. */
@media (min-width: 861px) {
  .slot-in { grid-template-rows: auto auto auto; }
  .slot-in-tekst { grid-row: 1 / 3; align-self: center; }
  .slot-in > .slot-cta-row { display: contents; }
  .slot-in > .slot-cta-row > .btn-g { grid-column: 2; grid-row: 1; align-self: end; position: relative; z-index: 1; }
  .slot-in > .slot-cta-row > .slot-cta { grid-column: 2; grid-row: 2; align-self: start; margin-top: 12px; position: relative; z-index: 1; }
  .slot-in > .slot-cta-row > .slot-contact { grid-column: 1 / -1; grid-row: 3; position: relative; z-index: 1;
    display: flex; flex-wrap: wrap; gap: 12px 40px; margin-top: 30px; padding-top: 22px; }
  .slot-in > .slot-cta-row > .slot-contact a, .slot-in > .slot-cta-row > .slot-contact span { display: inline-flex; align-items: baseline; gap: 9px; }
  .slot-in > .slot-cta-row > .slot-contact b { display: inline; margin: 0; }
}
@media (max-width: 620px) { .slot-in { padding: 26px 22px 24px; } }
/* Op 320 px zit dit paneel drie insprongen diep (paginamarge, houder, paneel)
   en houdt 190 px over voor de tekst. Iets minder binnenmarge scheelt daar
   net genoeg om een kop van zeven woorden op vier regels te houden. */
@media (max-width: 400px) { .slot-in { padding: 22px 16px 20px; } }

.slot-cta-row {
  display: flex; flex-wrap: wrap; gap: 14px; align-items: center;
  margin: clamp(34px, 4vw, 46px) 0 0;
}
.slot-cta {
  display: inline-flex; align-items: center; gap: 10px; padding: 14px 28px;
  background: var(--btn); color: #fff; font: 700 14px/1 'Inter', system-ui, sans-serif;
  text-decoration: none; transition: background .3s var(--ease), transform .3s var(--ease);
}
.slot-cta:hover { background: #1e5ef7; transform: translateY(-1px); }
.slot-cta .arr { transition: transform .3s var(--ease); }
.slot-cta:hover .arr { transform: translateX(3px); }
.slot-cta.ghost { background: transparent; border: 1px solid var(--line); color: var(--ink); }
.slot-cta.ghost:hover { border-color: var(--per2); background: rgba(22,82,240,.06); }
@media (max-width: 620px) {
  .slot-cta-row { flex-direction: column; align-items: stretch; }
  .slot-cta { justify-content: center; }
}
@media (prefers-reduced-motion: reduce) {
  .slot-cta:hover { transform: none; }
  .slot-cta:hover .arr { transform: none; }
}

.slot-chips { gap: 10px; }
.slot-chips a {
  padding: 9px 15px; border: 1px solid var(--line); border-radius: 999px;
  background: rgba(255,255,255,.028);
  transition: color .4s var(--ease), border-color .4s var(--ease), background .4s var(--ease);
}
.slot-chips a:hover { border-color: rgba(139,149,255,.42); background: rgba(110,121,255,.1); }
/* 21-09-2026 (batch zeven): een pil met een lange tekst breekt op de telefoon in twee regels binnen 280 px, in plaats van breder
   dan het scherm te worden (knopbreed 18-09: 396 px op /toegankelijkheid/); de tekst blijft heel, geen halve zin als pil. */
@media (max-width: 900px) { .slot-chips a { white-space: normal; max-width: 280px; text-align: left; line-height: 1.35; } }


/* ============================================================
   GEDEELDE MODULES, 10-09-2026
   ------------------------------------------------------------
   WAAROM DIT BESTAAT. Gianluca op 10-09: een sectie met tekst links en
   niets rechts leest als een pagina waar een afbeelding is weggevallen.
   Hij heeft gelijk, en de meting laat zien waar het vandaan komt. De
   startpagina heeft GEEN ENKELE kale tekstsectie: alles is een kaartenraster,
   een tekening, of tekst naast een foto. Op /seo-verbeteren/ zijn de eerste
   negen secties modules en is alleen de korte staart kale tekst.
   /wordpress-specialist/ had dertien kale tekstsecties op twintig, waarvan
   meerdere van driehonderd tot vierhonderd woorden. Dat is het verschil.

   De vier vormen hieronder stonden al op /seo-verbeteren/, maar in de
   stijlkop van die pagina zelf. Ze staan nu hier, met dezelfde waarden en
   dezelfde afbreekpunten, zodat elke volgende pagina ze aanroept in plaats
   van ze over te schrijven. De pagina zelf houdt zijn eigen .pg-*-regels; die
   liggen er met hogere specificiteit overheen en veranderen dus niet.
   ============================================================ */

/* ---- 1. TEKST NAAST BEELD ------------------------------------------------
   De werkpaardvorm, gelijk aan .home-story-grid op de startpagina. Links de
   tekst, rechts wat die tekst laat zien. Onder 900 px onder elkaar, beeld
   eerst, want op een telefoon wil je zien waar het over gaat voordat je
   leest. */
.mod-duo {
  display: grid;
  grid-template-columns: minmax(0, 1.08fr) minmax(300px, .72fr);
  gap: clamp(30px, 5vw, 68px);
  align-items: start;
}
.mod-duo-copy > p { max-width: 62ch; }
/* De kop staat IN de linkerkolom sinds 11-09-2026. Stond hij erboven, dan
   begon het beeld pas op de hoogte van de eerste alinea, 150 px onder de kop,
   en hing het te laag: "de mobiel moet omhoog". Nu begint het beeld op de
   bovenrand van de kop. */
.mod-duo-copy > .dos-kop { margin-top: 0; }
/* De rechterkolom plakt mee, 11-09-2026. Is de tekst links langer dan wat er
   rechts staat, dan ontstaat onder het beeld alsnog een gat; bij de prijzen
   was links 1060 px en rechts 491. De startpagina doet hetzelfde met
   .home-device-stage: position sticky, zodat het beeld meeloopt met de lezer
   en het gat nooit in beeld komt. Alleen op een breed scherm, want op een
   telefoon staan de kolommen onder elkaar. */
@media (min-width: 901px) {
  .mod-duo-beeld, .mod-duo-zij { position: sticky; top: clamp(88px, 12vh, 120px); }
  .mod-duo > .term { position: sticky; top: clamp(88px, 12vh, 120px); } /* een terminal als beeld plakt net als een foto (nakijkronde 16-09: 613 px lucht onder de terminal in DOC.04 van het sjabloon) */
  /* Een beeld IN de zijkolom plakt niet apart: de kolom is al sticky, en als het
     beeld ook plakt schuift het over de kaartjes eronder heen zodra de sectie
     eindigt en de kolom omhoog wordt geduwd (Gianluca, 14-09-2026, /sea-specialist/
     sectie resultaten: "de telefoon gaat over die dingen heen"). */
  .mod-duo-zij .mod-duo-beeld { position: static; }
}
.mod-duo-copy > p:first-child, .mod-duo-copy > .dos-kop + p { font-size: clamp(1.04rem, 1.4vw, 1.18rem); }
.mod-duo-beeld { margin: 0; }
.mod-duo-beeld img { display: block; width: 100%; height: auto; }
.mod-duo-beeld figcaption { display: block; margin-top: 16px; line-height: 1.6; } /* 12 tot 21-09-2026, zie blok 15 */
.mod-duo-beeld--staand figcaption { text-align: center; }
/* EEN UITGEKNIPTE MOCKUP ZWEEFT OP EEN GLOED, toegevoegd 14-09-2026 (Gianluca
   over de telefoons bij de werkdag op mobiel: "het staat er maar, het voelt
   out of place"). Achter het beeld een zachte lichtvlek in de kleuren van de
   site, een slagschaduw onder het toestel en dezelfde langzame zweefbeweging
   als in de hero, zodat het beeld bij de pagina hoort in plaats van erop
   geplakt te zijn. */
/* DE TWEEDE FOUT VAN 15 SEPTEMBER: position: relative hier won van de sticky
   regel hierboven (min-width 901px) maar liet de top van clamp(88px, 12vh,
   120px) staan, dus elke losse mockup stond permanent 98 px te laag en liep
   over het blok eronder heen (de iPad op de Ads-pagina, de telefoon van
   Swap.coupons). Sticky is ook positioned, dus de gloed (::before) werkt. */
.mod-duo-beeld--vrij { position: sticky; isolation: isolate; }
@media (max-width: 900px) { .mod-duo-beeld--vrij { position: relative; top: auto; } }
.mod-duo-beeld--vrij::before { content: ""; position: absolute; inset: 8% 4%; z-index: -1; border-radius: 50%;
  background: radial-gradient(closest-side, rgba(22,82,240,.28), rgba(139,149,255,.12) 55%, transparent 78%); filter: blur(18px); }
.mod-duo-beeld--vrij img { filter: drop-shadow(0 28px 44px rgba(2,6,20,.62)); }
@media (prefers-reduced-motion: no-preference) { .mod-duo-beeld--vrij img { animation: toestelZweef 7s ease-in-out 1.2s infinite; } }
@media (max-width: 900px) {
  /* 15-09-2026 (avond), Gianluca: "de foto's staan er gewoon random, nu zijn
     ze gewoon geplakt". Op een telefoon krijgt een losse mockup een eigen
     toneel: een zacht paneel met de gloed erin, het toestel op maat, het
     bijschrift links uitgelijnd. Zo leest het als een kaart in een app. */
  .mod-duo-beeld--vrij { max-width: none; margin: 0; padding: 22px 18px 16px; border: 1px solid var(--line); border-radius: 22px;
    background: radial-gradient(ellipse at 50% 30%, rgba(22,82,240,.16), transparent 60%), rgba(8,14,30,.55); overflow: hidden; }
  .mod-duo-beeld--vrij::before { display: none; }
  .mod-duo-beeld--vrij img { display: block; width: auto; max-width: 100%; max-height: 380px; margin: 0 auto; }
  .mod-duo-beeld--vrij figcaption { text-align: left; margin-top: 16px; }
}
/* een foto krijgt een kader, een uitgeknipte mockup juist niet: die zweeft */
.mod-duo-beeld--kader img {
  border-radius: clamp(14px, 1.6vw, 20px);
  border: 1px solid var(--line);
  box-shadow: 0 26px 60px rgba(2, 6, 20, .5);
}
.mod-duo-beeld--vrij img {
  filter: saturate(.95) brightness(1.02) drop-shadow(0 26px 44px rgba(2, 6, 20, .55));
}
@media (max-width: 900px) {
  .mod-duo { grid-template-columns: minmax(0, 1fr); gap: clamp(22px, 4vw, 32px); }
  /* Op een telefoon: kop, dan beeld, dan tekst. De kop zit in de linkerkolom
     (zie hierboven), dus om het beeld TUSSEN kop en tekst te krijgen laat de
     kolom zijn kinderen los in het raster vallen (display: contents) en
     bepaalt order de volgorde. Zonder dit stond het beeld boven de kop. */
  .mod-duo-copy { display: contents; }
  .mod-duo-copy > .dos-kop, .mod-duo-copy > h3:first-child { order: -2; } /* ook een h3 als kop van het blok (14-09-2026, linkbewaking) */
  .mod-duo > figure, .mod-duo-zij { order: -1; } /* elke figure, ook .mod-scherm en paginaeigen tekeningen (14-09-2026): op mobiel viel het beeld anders onder de tekst */
  .mod-duo-copy > p { max-width: none; }
  .mod-duo-copy > .mod-meer { max-width: none; }
}

/* ---- 2. DE RUSTIGE UITKLAPPER -------------------------------------------
   Gelijk aan .home-story-more op de startpagina: twee haarlijnen, de
   openingszin vet, en verder niets. De uitklapper die hiervoor op
   /wordpress-specialist/ stond zag er willekeurig uit omdat hij over de volle
   breedte liep: het plusje stond dan zeshonderd pixels van de zin af. In een
   kolom van deze breedte staat het er gewoon naast. */
.mod-meer {
  margin: 26px 0; border-top: 1px solid var(--home-border); border-bottom: 1px solid var(--home-border);
  /* Op leesbreedte houden. Het plusje dat v2.js aanhangt staat met
     margin-left: auto helemaal rechts in de summary; loopt die summary over de
     volle sectiebreedte, dan staat het teken zeshonderd pixels van zijn eigen
     zin af en oogt het willekeurig neergezet. Dat was de opmerking van 10-09.
     Op 72ch staat het waar het hoort: aan het eind van de regel. */
  max-width: 72ch;
}
/* Zijmarge op 11-09-2026. De summary stond op 4 px van de haarlijn die
   over de volle breedte loopt, en Gianluca zag het meteen: "de tekst is te
   dicht bij de linkerrand". De FAQ heeft 34 px en oogt daardoor rustig; dit
   volgt die maat, iets kleiner omdat een losse uitklapper in een tekstkolom
   anders te ver van de alinea's erboven inspringt. Body en summary krijgen
   dezelfde marge zodat het antwoord recht onder de vraag begint. */
.mod-meer > summary { padding: 22px clamp(18px, 2.4vw, 28px); color: #eef1ff; cursor: pointer; font-weight: 700; line-height: 1.55; }
.mod-meer > .tseo-vraag-uit > .tseo-vraag-in,
.mod-meer:not(.tseo-vraag) > p, .mod-meer:not(.tseo-vraag) > ul { padding-inline: clamp(18px, 2.4vw, 28px); }
.mod-meer > .tseo-vraag-uit > .tseo-vraag-in > :last-child { padding-bottom: 24px; }
.mod-meer > summary::marker { color: var(--per2); }
.mod-meer[open] > summary { border-bottom: 1px solid var(--home-border); }
.mod-meer p { margin: 0; padding-top: 22px; }
.mod-meer p + p { padding-top: 14px; }
/* Staan er meerdere onder elkaar, dan is het een lijst en geen rij losse
   blokjes. Zonder deze regel krijgt elk blok zijn eigen marge en zijn eigen
   bovenlijn, en dan zie je vier zwevende regels met gaten ertussen. Dat was op
   10-09 de opmerking "dit ziet er gewoon random geplaatst uit". Zo sluiten ze
   op elkaar aan, met een haarlijn ertussen, net als de FAQ. */
.mod-meer + .mod-meer { margin-top: 0; border-top: 0; }
.mod-meer + .mod-meer > summary { padding-top: 22px; }
/* en de ondermarge van de vorige er ook af, anders staat er alsnog 26 px lucht
   tussen twee regels die een lijst horen te zijn. De onderlijn blijft staan en
   dient meteen als scheiding met de volgende. Gemeten voor de reparatie:
   vier blokken van 118 px hoog met 144 px ertussen. */
.mod-meer:has(+ .mod-meer) { margin-bottom: 0; }

/* HET VERSCHIL TUSSEN EEN LOSSE EN EEN RIJ, vastgelegd 10-09-2026.
   Een uitklapper die alleen staat blijft op leesbreedte, want anders zweeft
   het plusje ver van zijn eigen zin; dat was de opmerking bij het tweede
   screenshot. Staan er meerdere onder elkaar, dan is het een lijst en mag hij
   juist wel de volle breedte hebben: dan staat het teken in een kolom recht
   onder elkaar en leest het als de FAQ op de startpagina, die wel goed was.
   De vorm volgt dus uit hoeveel het er zijn, niet uit een naam. */
.mod-meer:has(+ .mod-meer), .mod-meer + .mod-meer { max-width: none; }

/* Een staand beeld naast een korte alinea zou de sectie twee keer zo hoog
   maken als zijn eigen tekst. Dan begrenzen we de hoogte en niet de breedte. */
.mod-duo-beeld--staand img { max-height: 460px; width: auto; margin-inline: auto; }
/* een staande foto in een kader krijgt zijn hoogte vooraf (2:3, de maat van
   de portretten), anders is het kader 49 px hoog tot de lazy foto binnen is
   en springt de kolom open; gemeten 15-09 op /ai-index/ */
.mod-duo-beeld--kader.mod-duo-beeld--staand img { height: 460px; aspect-ratio: 2 / 3; object-fit: cover; }
@media (max-width: 900px) { .mod-duo-beeld--staand img { max-height: 380px; } .mod-duo-beeld--kader.mod-duo-beeld--staand img { height: 380px; } }
.mod-meer ul { margin: 0; padding-top: 22px; }

/* ---- 3. DE FASENBALK -----------------------------------------------------
   Vijf schotten naast elkaar met een pijlpunt ertussen. Overgenomen uit de
   stijlkop van /seo-verbeteren/, waarden en afbreekpunten ongewijzigd. */
.tijd { display: grid; grid-template-columns: repeat(5, 1fr); gap: clamp(14px, 1.6vw, 20px); }
.tijd-k { border: 1px solid var(--line); background: rgba(8, 14, 30, .7); padding: 22px 18px; position: relative; overflow: visible; }
.tijd-k::after {
  content: ''; position: absolute; right: -8px; top: 50%; width: 9px; height: 9px;
  border-top: 1px solid rgba(22, 82, 240, .55); border-right: 1px solid rgba(22, 82, 240, .55);
  transform: translate(50%, -50%) rotate(45deg); background: transparent; z-index: 2;
}
.tijd-k:last-child::after { display: none; }
.tijd-n { font: 700 10px/1 var(--mono); color: var(--per2); letter-spacing: .08em; display: block; margin: 0 0 12px; }
.tijd-t { font: 700 13.5px/1.3 'Inter', system-ui, sans-serif; color: var(--ink); display: block; margin: 0 0 8px; }
.tijd-d { font: 400 11.8px/1.6 'Inter', system-ui, sans-serif; color: var(--mut); display: block; }
.tijd-w { display: block; margin: 12px 0 0; font: 400 10px/1 var(--mono); color: var(--mut2); }
@media (max-width: 900px) { .tijd { grid-template-columns: repeat(2, 1fr); } .tijd-k::after { display: none; } }
@media (max-width: 520px) { .tijd { grid-template-columns: 1fr; } }
@media (max-width: 760px) { .tijd-w { min-height: 44px; display: inline-flex; align-items: center; } }

/* ---- 4. DE PRIJSLADDER ---------------------------------------------------
   Drie treden naast elkaar, de middelste opgelicht. Ook overgenomen uit
   /seo-verbeteren/. Er staat nooit een bedrag in de CSS; de bedragen komen
   uit de pagina en moeten gelijk zijn aan /prijzen/. */
.ladder { display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(18px, 2.2vw, 26px); }
.trap { background: rgba(8, 14, 30, .7); border: 1px solid var(--line); padding: 28px 24px; display: flex; flex-direction: column; overflow: visible; }
.trap.mid { background: rgba(22, 82, 240, .05); border-color: rgba(22, 82, 240, .3); }
.trap-kop { font: 700 10px/1 var(--mono); color: var(--mut2); letter-spacing: .08em; text-transform: uppercase; display: block; margin: 0 0 14px; }
.trap.mid .trap-kop { color: var(--per2); }
.trap-prijs { font: 800 30px/1 'Inter', system-ui, sans-serif; color: var(--ink); letter-spacing: -.03em; display: block; margin: 0 0 4px; }
.trap-prijs small { font: 400 13px/1 var(--mono); color: var(--mut2); letter-spacing: 0; }
.trap-wat { font: 400 12px/1.5 var(--mono); color: var(--mut2); display: block; margin: 0 0 18px; }
.trap-lijst { list-style: none; margin: 0 0 22px; padding: 0; display: grid; gap: 8px; flex: 1; }
.trap-lijst li { font: 400 13px/1.5 'Inter', system-ui, sans-serif; color: var(--mut); display: flex; gap: 9px; align-items: flex-start; }
.trap-lijst li::before { content: '\2022'; color: var(--per2); flex-shrink: 0; }
.trap-cta { display: inline-flex; align-items: center; gap: 8px; color: var(--per2); font: 700 13px/1 'Inter', system-ui, sans-serif; text-decoration: none; border-bottom: 1px solid rgba(110, 121, 255, .35); align-self: flex-start; }
.trap-cta:hover { color: #6E79FF; border-color: #6E79FF; }
@media (max-width: 860px) { .ladder { grid-template-columns: 1fr; } }
@media (max-width: 760px) { .trap-cta { min-height: 44px; } }

/* ---- 5. HET VLAKKENRASTER ------------------------------------------------
   Voor een opsomming die geen volgorde heeft en geen bestemming: vier soorten
   trajecten, zes soorten vakken, drie soorten onderhoud. Ziet eruit als
   .tijd-k maar zonder de pijl ertussen, want er zit geen volgorde in, en het
   is met opzet GEEN <a>: een kaart die klikbaar oogt maar nergens heen gaat is
   erger dan een blok dat gewoon een blok is. Het klikgebaar van v2.js pakt
   .krow en .stad, niet dit, dus er beweegt hier ook niets bij een klik.
   auto-fit met min() zodat het spoor meekrimpt in plaats van buiten beeld te
   steken; die val stond op 10-09 nog op de startpagina. */
.vakken { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); gap: clamp(14px, 1.6vw, 20px); }
.vak-k { border: 1px solid var(--line); background: rgba(8, 14, 30, .7); padding: 22px 18px; display: flex; flex-direction: column; gap: 8px; }
.vak-n { font: 700 10px/1 var(--mono); color: var(--per2); letter-spacing: .08em; }
.vak-t { font: 700 13.5px/1.3 'Inter', system-ui, sans-serif; color: var(--ink); }
.vak-d { font: 400 11.8px/1.6 'Inter', system-ui, sans-serif; color: var(--mut); }
.vak-d a { color: var(--per2); }

/* ---- 8. LUCHT ROND EEN MODULE ---------------------------------------------
   Gemeten op 11-09-2026: het gat tussen de fasenbalk en de tekst eronder was
   0 px, tussen de vlakken en de tekst ook. Gianluca: "t moet genoeg ruimte
   hebben tussen de blokken en dan de tekst". Een module die tussen twee
   stukken tekst staat krijgt aan beide kanten dezelfde ruimte, iets meer dan
   de afstand tussen twee alinea's, zodat hij als een eigen ding leest en niet
   als een vergeten regel. Geldt voor alle gedeelde modules tegelijk. */
/* Alleen waar tekst en module elkaar raken; een module direct onder de kop
   of het sectielabel houdt de gewone afstand. */
.dos-body + .tijd, .dos-body + .ladder, .dos-body + .vakken, .dos-body + .steden, .dos-body + .mod-duo,
.tijd + .dos-body, .ladder + .dos-body, .vakken + .dos-body, .steden + .dos-body, .mod-duo + .dos-body {
  margin-top: clamp(28px, 4vw, 44px);
}

/* ---- 6. DE PLAATSKAARTJES ------------------------------------------------
   Een rij compacte links naar plaats- of vakpagina's. Stond in de stijlkop van
   /seo-verbeteren/ en van de stadspagina's; staat nu een keer hier. De vaste
   vier kolommen zijn vervangen door auto-fit met min(), zodat het spoor ook
   in een smalle zijkolom of op een telefoon meekrimpt in plaats van buiten
   beeld te steken. Op een breed scherm komt dat op hetzelfde neer. */
.steden { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr)); gap: clamp(12px, 1.4vw, 16px); }
.stad { border: 1px solid var(--line); background: rgba(8, 14, 30, .7); padding: 16px 18px; text-decoration: none; display: block; min-height: 56px; transition: border-color .3s var(--ease), background .3s var(--ease); }
.stad:hover { border-color: rgba(22, 82, 240, .45); background: rgba(22, 82, 240, .05); }
.stad b { display: block; font: 600 13.5px/1.25 'Inter', system-ui, sans-serif; color: var(--ink); margin-bottom: 4px; transition: color .3s var(--ease); }
.stad:hover b { color: var(--per2); }
.stad span { display: block; font: 400 10.5px/1.4 var(--mono); color: var(--mut2); }

/* ---- 7. EEN ZIJKOLOM DIE GEEN BEELD IS -----------------------------------
   Soms hoort er rechts geen foto maar een handvat: twee plaatspagina's, een
   rijtje cijfers. Dan draagt .mod-duo-zij de rechterkant, en staan de
   kaartjes daarin onder elkaar in plaats van naast elkaar. */
.mod-duo-zij { display: grid; gap: clamp(12px, 1.4vw, 16px); align-content: start; }
.mod-duo-zij .steden { grid-template-columns: 1fr; }
/* De prijsladder in de zijkolom, 11-09-2026. Drie treden naast elkaar lieten
   twee ervan grotendeels leeg: een bedrag en een regel in een vak van 300 px
   hoog. Gianluca: "zet de prijzen onder elkaar aan de rechterkant, dan benut
   je de ruimte beter, zorg voor genoeg luxe ruimte en rust". Onder elkaar is
   elke trede zo hoog als zijn inhoud, en het bedrag krijgt de regel. */
.mod-duo-zij .ladder { grid-template-columns: 1fr; gap: 12px; margin: 0; }
.mod-duo-zij .trap { padding: 22px 24px; }
.mod-duo-zij .trap-kop { margin-bottom: 10px; }
.mod-duo-zij .trap-prijs { font-size: 26px; }
.mod-duo-zij .trap-wat { margin-bottom: 0; }
.mod-duo-zij .trap-lijst { margin: 14px 0 16px; }
/* de knoppen onder de ladder: onder elkaar, over de kolombreedte */
/* drie klassen diep, want de pagina zet zelf .pg-* .stra-cta-rij op flex met
   een marge van 30 px, en dat wint anders op volgorde */
.mod-duo .mod-duo-zij .stra-cta-rij { display: grid; gap: 10px; margin: 6px 0 0; }
.mod-duo .mod-duo-zij .stra-cta-rij .v2cta { justify-content: center; text-align: center; }
.mod-duo .mod-duo-zij .stra-cta-rij .ann { display: block; text-align: center; }
.mod-duo-zij > .ann { display: block; }

/* ---- 9. DE PAGINAKOP -------------------------------------------------------
   Elke dienstpagina had tot 13-09-2026 zijn eigen kopklassen in zijn eigen
   stijlblok (.wp-hero, .wlz-hero, .sv-hero) met dezelfde waarden. Hier staat
   die vorm een keer, met de waarden van /wordpress-specialist/. Nieuwe
   pagina's roepen hem aan; de bestaande houden hun eigen tot ze aan de beurt
   zijn. De knoppen zijn .slot-cta, dezelfde als in de afsluiter. */
.mod-hero { padding: 96px 0 26px; }
.mod-hero-top { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; flex-wrap: wrap;
  border-bottom: 1px solid var(--hair); padding-bottom: 14px; margin-bottom: 34px; }
.mod-hero .lm { padding-bottom: .15em; margin-bottom: -.15em; }
.mod-lede { max-width: 640px; margin: 30px 0 0; color: var(--mut); font-size: 1.06rem; line-height: 1.75; }
.mod-cta-row { display: flex; flex-wrap: wrap; gap: 14px; align-items: center; margin: 34px 0 0; }
.mod-cta-sub { font: 400 12px/1.6 var(--mono); color: var(--mut2); }
@media (max-width: 640px) {
  .mod-hero { padding: 72px 0 18px; }
  .mod-hero-top { flex-direction: column; gap: 6px; }
  .mod-cta-row { flex-direction: column; align-items: stretch; }
  .mod-cta-row .slot-cta { justify-content: center; }
}

/* ---- 10. EEN SCHERMAFDRUK IN EEN VENSTERKADER -----------------------------
   Voor een site die wij bouwden en die live staat: een echte schermafdruk in
   een kader met drie stippen en het adres, zodat de lezer ziet dat het een
   bestaande site is en niet een tekening. Het bijschrift noemt de datum van
   de opname, want een site verandert. */
.mod-scherm { margin: 0; border: 1px solid var(--line); border-radius: clamp(14px, 1.6vw, 20px); overflow: hidden;
  background: #0b1120; box-shadow: 0 26px 60px rgba(2, 6, 20, .5); }
.mod-scherm-balk { display: flex; align-items: center; gap: 7px; padding: 10px 14px; border-bottom: 1px solid var(--line); background: rgba(255, 255, 255, .03); }
.mod-scherm-balk i { width: 9px; height: 9px; border-radius: 50%; background: rgba(255, 255, 255, .14); }
.mod-scherm-balk span { margin-left: 8px; font: 400 11px/1 var(--mono); color: var(--mut2); letter-spacing: .04em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mod-scherm img { display: block; width: 100%; height: auto; }
.mod-scherm figcaption { padding: 16px 14px 12px; } /* 10 boven tot 21-09-2026, zie blok 15 */

/* ---- 11. HET SECTIELABEL ---------------------------------------------------
   De regel boven elke sectie: links "DOC.03 · waar het over gaat", rechts een
   registratienummer of een korte toevoeging. Stond als .wp-lbl in de stijlkop
   van /wordpress-specialist/; nu gedeeld onder een neutrale naam, zodat de
   werkgebiedpagina's en elke volgende pagina hem aanroepen. */
/* Ook als .dos-in > .ann; die regel (twee klassen) won anders van .dos-lbl en zette
   het label op block, waardoor de rechterhelft tegen de linkerhelft aan kleefde
   (gezien op 14-09-2026 op de regiopagina's en de werkgebiedpagina's). */
.dos-lbl, .dos-in > .ann.dos-lbl { display: flex; justify-content: space-between; align-items: baseline; gap: 14px; flex-wrap: wrap;
  border-bottom: 1px solid var(--hair); padding-bottom: 12px; margin-bottom: 26px; }
.dos-lbl b { color: var(--per2); }

/* ---- 12. EEN TOESTEL DAT BINNENKOMT EN ZACHT ZWEEFT ---------------------
   Gianluca op 13-09-2026 over /sea-specialist/: "de hero moet foto's hebben"
   en "wat meer leuke animaties". Een mockup in de hero komt binnen zoals de
   toestellen op de startpagina (iets gedraaid, van onder) en zweeft daarna
   heel licht, acht pixels in zeven seconden, zodat de pagina leeft zonder
   dat het afleidt. Bij een bewegingsvoorkeur staat hij gewoon stil. */
.mod-hero-duo { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(300px, .8fr); gap: clamp(28px, 5vw, 64px); align-items: center; }
.mod-toestel { margin: 0; position: relative; }
.mod-toestel img { display: block; width: 100%; height: auto; filter: drop-shadow(0 30px 50px rgba(2, 6, 20, .6));
  animation: toestelIn 900ms var(--ease) both, toestelZweef 7s ease-in-out 1.1s infinite; }
.mod-toestel figcaption { display: block; margin-top: 16px; text-align: center; }
@keyframes toestelIn { from { opacity: 0; transform: translateY(28px) rotate(-4deg) scale(.96); } to { opacity: 1; transform: none; } }
@keyframes toestelZweef { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } }
@media (max-width: 900px) { .mod-hero-duo { grid-template-columns: minmax(0, 1fr); }
  .mod-toestel { max-width: none; margin: 8px 0 0; padding: 22px 18px 16px; border: 1px solid var(--line); border-radius: 22px;
    background: radial-gradient(ellipse at 50% 30%, rgba(22,82,240,.16), transparent 60%), rgba(8,14,30,.55); overflow: hidden; }
  .mod-toestel img { max-height: 360px; width: auto; max-width: 100%; margin: 0 auto; }
  .mod-toestel figcaption { text-align: left; }
}
@media (prefers-reduced-motion: reduce) { .mod-toestel img { animation: none; } }

/* Een rij uitklappers komt trapsgewijs binnen: de wrapper krijgt .st en de
   bestaande instroom van v2.js doet de rest. */
.mod-rij { display: block; }

/* GESPIEGELD DUO, toegevoegd 14-09-2026. Gianluca: "afwisselend, niet steeds
   rechts van de tekst". Met --links staat het beeld of de zijkolom op een
   breed scherm in de eerste kolom en de tekst in de tweede; de kolombreedtes
   draaien mee, zodat de tekst zijn ruime kolom houdt. Op een telefoon
   verandert er niets: daar geldt kop, beeld, tekst via order (zie boven). */
@media (min-width: 901px) {
  .mod-duo--links { grid-template-columns: minmax(300px, .72fr) minmax(0, 1.08fr); }
  .mod-duo--links > .mod-duo-copy { order: 2; }
  .mod-duo--links > .mod-duo-beeld,
  .mod-duo--links > .mod-duo-zij,
  .mod-duo--links > .mod-scherm { order: 1; }
}

/* ---- 13. AFWISSELING: BAND, TEKENING, VAKKEN, KLANTBAND ------------------
   Toegevoegd 14-09-2026 (laat) na de keuring van /seo-specialist-inhuren/.
   Gianluca: "de laatste 3 blokken zijn een beetje hetzelfde; al zijn een
   aantal blokken exact hetzelfde, moet je een andere indeling verzinnen die
   ook mooi is." En over het resultaat: "die animaties, die mockups die je
   hebt gemaakt, echt geweldig." Dit is de voorraad naast het duo met
   kaartjes (MASTERPLAN 8f), sitebreed zodat de volgende batch er alleen
   markup voor hoeft te schrijven. recept.py heeft er helpers voor:
   band(), tekening(), vakken(), klantband().

   .mod-band        band over de volle breedte (rand tot rand, zoals .seo-full),
                    met tekst links en een tekening rechts in .mod-band-in en
                    daaronder eventueel een rij kaartjes (.steden-rij)
   .tek             figure met een eigen svg-tekening; .tek-serp is de
                    zoekresultatenpagina (gesponsord boven, organisch onder,
                    haken SEA en SEO), .tek-maps het lokale pakket (kaart,
                    pins, drie profielen met sterren, knoppen); de regels
                    komen een voor een binnen zodra het kader zijn isIn heeft
   .steden-rij      drie kaartjes naast elkaar over de volle breedte
   .steden-stapel   kaartjes onder elkaar in een tekstkolom (geen onderstreping)
   .klant-reg       klantregister (logo, naam, branche, regel); met .klant-band
                    van rand tot rand op een breed scherm, op de telefoon een
                    strook zoals elke kaartgroep
   .breed-kop       intro boven vakken of een rij: kop plus een alinea, 70ch
   .breed-uitklap   een dos-body zonder maximumbreedte, voor een uitklapper
                    onder een raster                                          */
/* de kantlijn van de band valt samen met de tekst in een dossier: main 24 plus
   .dos-in 24 = 48 vanaf de rand van de 1180-kolom, op de telefoon 18 plus 24 = 42;
   een pagina met een smallere .dos-in (1060) zet zijn eigen waarde, zie .pg-seo */
.mod-band { margin-inline: calc(-50vw + 50%); padding: clamp(40px, 5vw, 64px) max(42px, calc(50vw - 542px)); background: rgba(22,82,240,.045); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.mod-band-in { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(280px, .85fr); gap: clamp(28px, 5vw, 64px); align-items: center; }
.mod-band .steden-rij { margin-top: clamp(28px, 3.5vw, 44px); }
/* cijferband: een band met alleen grote tellende getallen (over-ons, 15-09) */
.mod-band--cijfers .stats { border: 0; padding: 0; justify-content: space-between; gap: 24px 48px; }
.mod-band--cijfers .stat b { font-size: clamp(2.6rem, 5.4vw, 4.4rem); line-height: 1; }
.mod-band--cijfers .stat .ann { margin-top: 10px; display: block; }
.mod-band--cijfers .stat { flex: 1 1 220px; }
.breed-kop { max-width: 70ch; }
.breed-uitklap { max-width: none !important; }
h3.vak-t { margin: 0; }
.steden-rij { grid-template-columns: repeat(3, 1fr); }
.steden-stapel { grid-template-columns: 1fr; margin-top: 10px; }
.dos-body .steden .stad, .dos-body .steden .stad b, .dos-body .steden .stad span { text-decoration: none; }
.tek { margin: 0; position: relative; }
.tek svg { display: block; width: 100%; height: auto; }
.tek figcaption { display: block; margin-top: 16px; text-align: center; }
.tek .blad { fill: #0b1220; stroke: rgba(174,185,255,.22); stroke-width: 1.2; }
.tek .lbl { fill: #8fa0c8; font: 500 8px 'JetBrains Mono', monospace; letter-spacing: .1em; }
.tek .regel { fill: rgba(174,185,255,.16); }
.tek-serp .zoek { fill: rgba(174,185,255,.06); stroke: rgba(174,185,255,.28); }
.tek-serp .zoek-t { fill: #c7d2fe; font: 500 9px 'JetBrains Mono', monospace; }
.tek-serp .zone { fill: rgba(255,154,154,.06); stroke: rgba(255,154,154,.28); stroke-dasharray: 3 4; }
.tek-serp .org-zone { fill: rgba(52,211,153,.05); stroke: rgba(52,211,153,.3); }
.tek-serp .regel.ad { fill: rgba(255,154,154,.7); }
.tek-serp .regel.seo { fill: rgba(52,211,153,.75); }
.tek-serp .haak { fill: none; stroke-width: 1.6; stroke-linecap: round; }
.tek-serp .haak.sea { stroke: rgba(255,154,154,.8); }
.tek-serp .haak.seo { stroke: rgba(52,211,153,.8); }
.tek-serp .haak-t { font: 700 11px 'Inter', sans-serif; }
.tek-serp .haak-t.sea { fill: #ff9a9a; }
.tek-serp .haak-t.seo { fill: #34d399; }
.tek-serp .haak-s { fill: #8fa0c8; font: 500 8.5px 'JetBrains Mono', monospace; letter-spacing: .06em; }
.tek-maps .veld { fill: rgba(22,82,240,.08); stroke: rgba(174,185,255,.18); }
.tek-maps .weg { fill: none; stroke: rgba(174,185,255,.28); stroke-width: 3; stroke-linecap: round; }
.tek-maps .weg.dun { stroke-width: 1.5; }
.tek-maps .pin path { fill: #ff9a9a; }
.tek-maps .pin circle { fill: #0b1220; }
.tek-maps .pin.p2 path { fill: #8b95ff; }
.tek-maps .pin.p3 path { fill: #34d399; }
.tek-maps .regel.naam { fill: rgba(255,255,255,.55); }
.tek-maps .sterren use { fill: #fbbf24; }
.tek-maps .knop { fill: rgba(22,82,240,.35); stroke: rgba(139,149,255,.5); }
.tek-maps .knop-t { fill: #fff; font: 600 9px 'Inter', sans-serif; text-anchor: middle; }
/* .tek-index: een meetregel uit de AI-index (een vraag, zeven kolommen, namen als balkjes;
   een balk die stopt is een naam die uit het antwoord verdwijnt). Geen namen, geen cijfers. */
.tek-index .kol-t { fill: #8fa0c8; font: 500 7.5px 'JetBrains Mono', monospace; letter-spacing: .08em; text-anchor: middle; }
.tek-index .vraag { fill: #c7d2fe; font: 500 8.5px 'JetBrains Mono', monospace; }
.tek-index .kolom { stroke: rgba(174,185,255,.12); stroke-width: 1; }
.tek-index .regel.blijft { fill: rgba(52,211,153,.7); }
.tek-index .regel.nieuw { fill: rgba(139,149,255,.85); }
.tek-index .regel.weg { fill: rgba(255,154,154,.75); }
.tek-index .kruisje { fill: none; stroke: #ff9a9a; stroke-width: 1.6; stroke-linecap: round; }
.tek-index .legenda { fill: #8fa0c8; font: 500 7.5px 'JetBrains Mono', monospace; letter-spacing: .06em; }
.tek-index .bol { r: 3.2; }
.tek-index .bol.blijft { fill: rgba(52,211,153,.7); } .tek-index .bol.nieuw { fill: rgba(139,149,255,.85); } .tek-index .bol.weg { fill: rgba(255,154,154,.75); }
/* .tek-shop: een categoriepagina van een webshop (filters links, productraster rechts) */
.tek-shop .vink { fill: rgba(174,185,255,.08); stroke: rgba(174,185,255,.35); }
.tek-shop .vink.aan { fill: rgba(139,149,255,.85); stroke: rgba(139,149,255,.9); }
.tek-shop .tel { fill: #8fa0c8; font: 500 8px 'JetBrains Mono', monospace; letter-spacing: .06em; }
.tek-shop .tel.groot { fill: #ff9a9a; font: 700 10px 'Inter', sans-serif; letter-spacing: 0; }
.tek-shop .regel.kop { fill: rgba(255,255,255,.55); }
.tek-shop .tegel { fill: rgba(22,82,240,.06); stroke: rgba(174,185,255,.18); }
.tek-shop .foto { fill: rgba(174,185,255,.14); }
.tek-shop .prijs { fill: rgba(52,211,153,.75); }
.tek-shop .haak { fill: none; stroke: rgba(174,185,255,.2); stroke-width: 1; }
.tek-shop .legenda { fill: #8fa0c8; font: 500 7.5px 'JetBrains Mono', monospace; letter-spacing: .06em; }
.tek-shop.isIn .rij.r1 { transition-delay: .1s; } .tek-shop.isIn .rij.r2 { transition-delay: .2s; } .tek-shop.isIn .rij.r3 { transition-delay: .3s; } .tek-shop.isIn .rij.r4 { transition-delay: .4s; } .tek-shop.isIn .rij.r5 { transition-delay: .5s; } .tek-shop.isIn .rij.r6 { transition-delay: .6s; } .tek-shop.isIn .acco { transition-delay: .8s; }
/* .tek-agenda (16-09-2026, /gesprek-plannen/): een werkweek als raster, ma tot vr,
   negen tot vijf, met bezette en vrije blokken en het halfuur dat de bezoeker kiest.
   Geen namen, geen aantallen: alleen de vorm van een agenda. */
.tek-agenda .dag { fill: #8fa0c8; font: 500 8px 'JetBrains Mono', monospace; letter-spacing: .1em; text-anchor: middle; }
.tek-agenda .uur { fill: #8fa0c8; font: 500 7.5px 'JetBrains Mono', monospace; letter-spacing: .06em; }
.tek-agenda .lijn { stroke: rgba(174,185,255,.14); stroke-width: 1; }
.tek-agenda .kolom { stroke: rgba(174,185,255,.08); stroke-width: 1; }
.tek-agenda .pauze { fill: rgba(255,154,154,.08); }
.tek-agenda .regel.bezet { fill: rgba(174,185,255,.18); }
.tek-agenda .regel.vrij { fill: rgba(52,211,153,.14); stroke: rgba(52,211,153,.5); stroke-dasharray: 3 3; }
.tek-agenda .regel.keus { fill: rgba(52,211,153,.8); }
.tek-agenda .vink { fill: none; stroke: #0b1220; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.tek-agenda .haak { fill: none; stroke: rgba(52,211,153,.8); stroke-width: 1.6; stroke-linecap: round; }
.tek-agenda .haak-t { fill: #34d399; font: 600 9px 'JetBrains Mono', monospace; letter-spacing: .08em; }
.tek-agenda .legenda { fill: #8fa0c8; font: 500 9px 'JetBrains Mono', monospace; letter-spacing: .06em; }
.tek-agenda .lbl { font-size: 9.5px; } /* op een telefoon schaalt de tekening naar 354 px; 7,5 px werd daar 7,4 px (nakijkronde 16-09) */
.tek-agenda .bol.bezet { fill: rgba(174,185,255,.18); } .tek-agenda .bol.vrij { fill: rgba(52,211,153,.14); stroke: rgba(52,211,153,.5); } .tek-agenda .bol.keus { fill: rgba(52,211,153,.8); }
.tek-agenda.isIn .rij.r1 { transition-delay: .15s; } .tek-agenda.isIn .rij.r2 { transition-delay: .3s; } .tek-agenda.isIn .rij.r3 { transition-delay: .5s; } .tek-agenda.isIn .rij.r4 { transition-delay: .8s; } .tek-agenda.isIn .acco { transition-delay: 1s; }
.mod-hero-duo > .tek { width: 100%; max-width: 520px; justify-self: center; }
/* de tekening komt regel voor regel binnen zodra het kader zijn isIn heeft */
.tek .regel, .tek .acco, .tek .pin, .tek .rij { opacity: 0; transform: translateY(6px); transition: opacity .5s var(--ease), transform .5s var(--ease); }
.tek.isIn .regel, .tek.isIn .acco, .tek.isIn .pin, .tek.isIn .rij { opacity: 1; transform: none; }
.tek-serp.isIn .ads .regel { transition-delay: .15s; } .tek-serp.isIn .org .regel { transition-delay: .4s; } .tek-serp.isIn .acco { transition-delay: .7s; }
.tek-maps.isIn .pin.p1 { transition-delay: .15s; } .tek-maps.isIn .pin.p2 { transition-delay: .3s; } .tek-maps.isIn .pin.p3 { transition-delay: .45s; }
.tek-index.isIn .rij.r1 { transition-delay: .15s; } .tek-index.isIn .rij.r2 { transition-delay: .3s; } .tek-index.isIn .rij.r3 { transition-delay: .45s; } .tek-index.isIn .rij.r4 { transition-delay: .6s; } .tek-index.isIn .rij.r5 { transition-delay: .75s; } .tek-index.isIn .acco { transition-delay: .95s; }
.tek-maps.isIn .rij.r1 { transition-delay: .5s; } .tek-maps.isIn .rij.r2 { transition-delay: .65s; } .tek-maps.isIn .rij.r3 { transition-delay: .8s; }
@media (prefers-reduced-motion: reduce) { .tek .regel, .tek .acco, .tek .pin, .tek .rij { opacity: 1; transform: none; transition: none; } }
/* klantregister, sitebreed; de twee SEA-pagina's en /seo-specialist-inhuren/
   dragen dezelfde regels nog in hun eigen stijlblok */
.klant-reg { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); grid-auto-rows: 1fr; gap: 1px; background: var(--line); } /* rijen even hoog, ook als een regel afbreekt */
.klant-card { background: rgba(8,14,30,.7); padding: 26px 22px; display: flex; align-items: center; gap: 16px; position: relative; transition: background .5s var(--ease); }
.klant-card:hover { background: rgba(22,82,240,.06); }
.klant-card::after { content: ''; position: absolute; bottom: 0; left: 24px; right: 24px; height: 0; background: var(--per2); opacity: 0; transition: height .4s var(--ease), opacity .4s var(--ease); }
.klant-card:hover::after { height: 2px; opacity: .6; }
.klant-logo { width: 54px; height: 54px; object-fit: contain; flex-shrink: 0; filter: brightness(1.4); transition: filter .4s var(--ease), transform .4s var(--ease); }
.klant-card:hover .klant-logo { filter: brightness(1) drop-shadow(0 0 12px rgba(22,82,240,.3)); transform: scale(1.08); }
.klant-info { flex: 1; min-width: 0; }
.dos-body .mod-meer > summary h3, .mod-meer > summary h3 { margin: 0; } /* nakijkronde 15-09: 13 uitklappers waren 47 px te hoog door .dos-body h3 */
.klant-reg a.klant-card, .klant-reg a.klant-card span { text-decoration: none; } /* klikbare klantkaarten (over-ons, cases): geen onderstreping uit een paginaregel */
.klant-reg a.klant-card { color: inherit; }
.klant-naam { display: block; font: 600 14px/1.2 'Inter'; color: var(--ink); margin: 0 0 4px; overflow-wrap: anywhere; }
.klant-branche { display: block; font: 400 10px/1.3 'JetBrains Mono'; color: var(--mut2); text-transform: uppercase; letter-spacing: .08em; }
.klant-stat { display: block; font: 700 11px/1.5 'JetBrains Mono'; color: #34d399; margin-top: 8px; }
@media (min-width: 901px) { .klant-reg.klant-band { margin-inline: calc(-50vw + 50%); } }
@media (max-width: 900px) {
  /* op de telefoon: kop, tekening, tekst, zoals in elk duo (MASTERPLAN 8d) */
  .mod-band-in { grid-template-columns: minmax(0, 1fr); }
  .mod-band-in .dos-body { display: contents; }
  .mod-band-in .dos-body > .dos-kop { order: -2; }
  .mod-band-in > figure { order: -1; }
  .mod-band-in > .tek { max-width: 420px; margin: 0 auto; width: 100%; }
  .steden-rij { grid-template-columns: 1fr; }
}

/* ---- 14. HET MEETBLAD VAN DE AI-INDEX, gedeeld sinds 15-09-2026 ------------
   Gianluca over /ai-index/: "ik zou het niet erg vinden als de index met de
   resultaten daar wordt getoond zoals op de oude website, maar dan als grote
   statistieken; het moet dezelfde look hebben, geanimeerde cijfers." Dat blad
   stond al op de startpagina (FIG.05b, .home-aix); het staat hier zodat de
   AI-Indexpagina hetzelfde blad kan dragen. De variabelen van de startpagina
   zijn ingevuld; de startpagina houdt haar eigen kopie. Elke telling komt
   uit public/ai-index/open-data/data (dataset 2026.1, 29 juli 2026) en is
   op 15-09 nagerekend: 2.501 geldig van 2.520, 3.807 namen, Bol.com 368. */
.home-aix{
  margin:40px 0 0;
  padding:clamp(24px,3.4vw,56px);
  border:1px solid rgba(174,185,255,.3);border-radius:28px;
  background:
    radial-gradient(ellipse at 8% 0%,rgba(110,121,255,.09),transparent 52%),
    radial-gradient(ellipse at 92% 100%,rgba(22,82,240,.07),transparent 52%),
    rgba(5,9,20,.9);
  box-shadow:0 30px 84px rgba(0,0,0,.36);
  overflow:hidden;
}
.home-aix-bar{display:flex;align-items:baseline;gap:10px 16px;flex-wrap:wrap;margin-bottom:32px}
.home-aix-bar .ann:last-child{margin-left:auto}
.home-aix-vlak{
  display:grid;grid-template-columns:minmax(0,1.06fr) minmax(0,.94fr);
  gap:40px clamp(32px,4vw,72px);
}
.home-aix-kol{min-width:0}
.home-aix-kop{
  display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;
  gap:4px 16px;margin-bottom:24px;
}
.home-aix-kop > span:first-child{flex:1 1 auto;min-width:0}
.home-aix-lijst{list-style:none;margin:0;padding:0;display:grid;gap:10px}
.home-aix-rij{
  display:grid;align-items:center;gap:0 12px;
  grid-template-columns:22px minmax(84px,auto) minmax(0,1fr) 4.2ch;
}
.home-aix-r{font:400 10px/1 var(--mono);letter-spacing:.08em;color:var(--mut2)}
.home-aix-n{font-size:.95rem;font-weight:600;color:#eef1ff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.home-aix-baan{display:block;height:10px;border-radius:999px;background:rgba(148,163,184,.13);overflow:hidden}
.home-aix-vul{
  display:block;height:100%;border-radius:999px;
  background:linear-gradient(90deg,rgba(103,232,249,.6),rgba(110,121,255,.85));
  transform:scaleX(var(--v,1));transform-origin:left center;
}
.js .home-aix .home-aix-vul{transform:scaleX(0);transition:transform .9s var(--ease) var(--d,0s)}
.js .home-aix.isIn .home-aix-vul{transform:scaleX(var(--v,1))}
.home-aix-w{
  font:700 .95rem/1 var(--mono);color:#f2f5ff;text-align:right;
  font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1;
}
.home-aix-uit{margin:24px 0 0;max-width:52ch;color:var(--mut);font-size:.93rem;line-height:1.65}
.home-aix-branche + .home-aix-branche{margin-top:24px}
.home-aix-bn{display:block;margin-bottom:10px;font-size:.95rem;font-weight:600;color:#dfe5ff}
.home-aix-lijst--kort .home-aix-rij{grid-template-columns:22px minmax(78px,auto) minmax(0,1fr) 4.2ch}
.home-aix-lijst--kort .home-aix-baan{height:8px}
.home-aix-lijst--kort .home-aix-n{font-size:.9rem;font-weight:500;color:var(--mut)}
.home-aix-lijst--kort .home-aix-w{font-size:.88rem}
.home-aix-tel{
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px;
  margin-top:32px;
}
.home-aix-telblok{
  display:grid;gap:6px;padding:24px 24px;
  border:1px solid rgba(174,185,255,.17);border-radius:18px;
  background-color:rgba(255,255,255,.025);
}
.home-aix-telw{
  font:700 clamp(1.3rem,2.2vw,1.7rem)/1 var(--mono);letter-spacing:-.02em;color:#fff;
  font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1;
}
.home-aix-bron{
  margin-top:32px;padding-top:24px;
  border-top:1px solid rgba(174,185,255,.17);
  color:var(--mut2);font-size:.82rem;line-height:1.72;max-width:92ch;
}
.home-aix-bron a{color:#c7d2fe}
@media (max-width:1080px){ .home-aix-vlak{grid-template-columns:minmax(0,1fr);gap:40px} }
@media (max-width:760px){ .home-aix{padding:24px} .home-aix-rij{grid-template-columns:20px minmax(72px,auto) minmax(0,1fr) 4.2ch;gap:0 10px} }
@media (max-width:480px){ .home-aix{border-radius:22px} .home-aix-telblok{border-radius:16px;padding:16px} .home-aix-tel{grid-template-columns:minmax(0,1fr)} .home-aix-telblok .ann{overflow-wrap:anywhere} .home-aix-n{font-size:.9rem} }
@media (prefers-reduced-motion:reduce){ .js .home-aix .home-aix-vul{transition:none;transform:scaleX(var(--v,1))} }

/* DE KNOP UIT DE HERO VAN DE STARTPAGINA, sitebreed sinds 14-09-2026 op
   verzoek van Gianluca ("gebruik op de goedgekeurde pagina ook soms de knop
   style die je in de hero gebruikt van de homepagina"). Zelfde regels als
   .pg-index .btn-g in public/index.html; hier zonder paginaklasse zodat elke
   pagina hem kan zetten. Gebruik hem spaarzaam: een per blok, voor de
   belangrijkste stap. */
.btn-g { display: inline-flex; align-items: center; gap: .6rem; padding: 1rem 1.7rem; border-radius: .7rem;
  background: linear-gradient(115deg, var(--blue), var(--per)); color: #fff; font-weight: 600; letter-spacing: .01em;
  text-decoration: none; transition: transform .4s cubic-bezier(.22, 1, .36, 1), box-shadow .4s; }
.btn-g:hover { transform: translateY(-2px); box-shadow: 0 18px 44px -18px color-mix(in srgb, var(--per) 65%, transparent); }
.btn-g .arr { transition: transform .35s; }
.btn-g:hover .arr { transform: translate(2px, -2px); }
.btn-t { display: inline-flex; align-items: center; gap: .5rem; color: var(--ink); text-decoration: none; padding-bottom: .25rem;
  border-bottom: 1px solid var(--line); transition: border-color .3s; }
.btn-t:hover { border-color: var(--cyan); }
@media (max-width: 640px) { .btn-g { padding: .9rem 1.4rem; } }

/* SCROLLGEDREVEN BEWEGING, toegevoegd 14-09-2026 ("scrollend echt leuke andere
   nieuwe manieren"). Een element met .scrol-draai draait langzaam mee met de
   scrollpositie, .scrol-zweef schuift een klein stuk omhoog terwijl het door
   het beeld gaat. Alleen in browsers die animation-timeline kennen; elders
   staat het gewoon stil, en bij verminder-beweging ook. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .scrol-draai { animation: scrolDraai linear both; animation-timeline: view(); animation-range: entry 0% exit 100%; }
    .scrol-zweef { animation: scrolZweef linear both; animation-timeline: view(); animation-range: entry 0% exit 100%; }
  }
}
@keyframes scrolDraai { from { transform: rotate(-14deg); } to { transform: rotate(14deg); } }

/* ============================================================
   MOBIEL ALS APP, toegevoegd 14-09-2026. Gianluca: "op mobiel moet het
   als een app voelen, zo smooth als in de App Store van Apple; nu voelt
   het als een kleiner scherm." Dit blok geldt alleen onder 901 px en
   alleen in browsers die scroll-driven animations kennen (iOS 26, Chrome,
   Edge); elders blijft de gewone onthulling (.rv) staan. Alles op
   transform, marge en afronding, geen opacity: opacity is van .rv.
   ============================================================ */
@media (max-width: 900px) {
  /* 1. Pillenrij als strook die je zijwaarts schuift, met snap, zoals een
        categorierij in een app. Op breed scherm blijft het een gewone rij. */
  .slot-chips { flex-wrap: nowrap; overflow-x: auto; scroll-snap-type: x proximity; -webkit-overflow-scrolling: touch;
    scrollbar-width: none; padding-bottom: 6px; margin-inline: -18px; padding-inline: 18px;
    mask-image: linear-gradient(90deg, transparent, #000 18px, #000 calc(100% - 28px), transparent); }
  .slot-chips::-webkit-scrollbar { display: none; }
  .slot-chips a { flex: 0 0 auto; scroll-snap-align: start; white-space: nowrap; }
  /* 2. Een foto in een kader (liggend) krijgt diepte: het beeld krijgt een
        vaste, bredere verhouding en schuift binnen zijn kader met het
        scrollen (parallax hieronder, via object-position). Een eerdere versie
        vergrootte het beeld zelf en dat viel over het bijschrift heen
        (Gianluca 14-09: "mijn foto zit over de tekst heen"). */
  .mod-duo-beeld--kader:not(.mod-duo-beeld--staand) img { aspect-ratio: 16 / 10; height: auto; object-fit: cover; object-position: 50% 50%; }
  /* 3. Kaartgroepen als strook die je zijwaarts schuift, met snap en een
        kaart die net over de rand piept, zoals de kaarten in de App Store.
        Onder elkaar gestapeld was het "een pagina waarop alles te zien moet
        zijn"; een app laat een kaart tegelijk zien. Gedeelde groepen krijgen
        het vanzelf, paginaeigen groepen via de klasse .mob-strook. De
        !important is bewust: de paginaeigen rasterregels staan later in de
        cascade en zouden anders winnen. */
  .mob-strook, .tijd, .ladder, .vakken, .steden, .klant-reg {
    display: flex !important; flex-wrap: nowrap !important; grid-template-columns: none !important;
    gap: 12px !important; align-items: flex-start; overflow-x: auto; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch;
    scrollbar-width: none; margin-inline: -18px !important; padding-block: 4px 10px; padding-inline: 18px !important; scroll-padding-inline: 18px;
    /* de kantlijnen met !important: een pagina die haar raster met "margin: 32px 0 0" plaatst
       (strat-grid, compare, feat-grid) zette anders de negatieve marge weer op nul en de
       strook raakte de rand niet meer; gemeten 14-09 op drie goedgekeurde pagina's */
    mask-image: linear-gradient(90deg, transparent, #000 14px, #000 calc(100% - 22px), transparent); }
  /* In een dossier (.dos-in, 24 px kantlijn bovenop de 18 px van main) stond
     de strook 6 px van de rand af en raakte hem niet; gemeten 14-09 op
     /seo-specialist-inhuren/: strook 342 breed in een venster van 390. Met
     42 px loopt hij ook daar van rand tot rand. Een strook in een paneel met
     eigen kantlijn (.slot-in, .deep, een band) hoort hier niet onder. */
  .dos-in :is(.mob-strook, .tijd, .ladder, .vakken, .steden, .klant-reg) { margin-inline: -42px !important; padding-inline: 42px !important; scroll-padding-inline: 42px; }
  .dos-in :is(.slot-in, .deep) :is(.mob-strook, .tijd, .ladder, .vakken, .steden, .klant-reg) { margin-inline: -24px !important; padding-inline: 24px !important; scroll-padding-inline: 24px; }
  .mob-strook::-webkit-scrollbar, .tijd::-webkit-scrollbar, .ladder::-webkit-scrollbar, .vakken::-webkit-scrollbar, .steden::-webkit-scrollbar, .klant-reg::-webkit-scrollbar { display: none; }
  :is(.mob-strook, .tijd, .ladder, .vakken, .steden, .klant-reg) > * {
    flex: 0 0 min(82%, 360px) !important; width: auto !important; max-width: none !important; scroll-snap-align: center; scroll-snap-stop: always; }
  :is(.mob-strook, .tijd, .ladder, .vakken, .steden, .klant-reg) > :only-child { flex-basis: 100% !important; }
  /* De puntjes staan los van de strook en van wat eronder komt (Gianluca
     14-09: "het bolletje niet goed, te weinig ruimte"). */
  .mob-dots { display: flex; justify-content: center; gap: 8px; margin: 14px 0 22px; }
  .mob-dots i { width: 7px; height: 7px; border-radius: 50%; background: rgba(174,185,255,.22); transition: background .3s var(--ease), transform .3s var(--ease); }
  .mob-dots i.aan { background: var(--per2); transform: scale(1.25); }
  /* De schuifbalk onder alles wat zijwaarts scrolt en geen puntjes heeft: tabellen, tekeningen, de pillen in het
     slot (17-09-2026, v2.js SCHUIFHINT EN SCHUIFBALK). De duim toont hoeveel er buiten beeld staat en waar je bent. */
  .mob-schuif { position: relative; height: 2px; margin: 12px 4px 4px; border-radius: 2px; background: rgba(174,185,255,.14); overflow: hidden; }
  .mob-schuif > i { position: absolute; top: 0; left: 0; height: 100%; width: 30%; border-radius: 2px; background: var(--per2); }
  .mob-schuif[hidden] { display: none; }
  /* de balk staat soms als kind in een .st-houder (de trap zet dan --i en stKlaar op hem): hij beweegt nooit mee */
  .mob-schuif { animation: none !important; transition: none !important; opacity: 1 !important; transform: none !important; }
  /* STROKEN OP DE TELEFOON ZONDER KAARTANIMATIE, 17-09-2026. Gianluca zag op zijn iPhone de vier plankaarten van
     /jarvis/prijzen/ leeg ("glitcht de prijzen sectie", de puntjes stonden op kaart drie): de kaarten in een zijwaartse
     strook droegen elk hun eigen instroom (de trap, en op die pagina een ademende schaduw op de middelste kaart), en een
     WebKit die een schuivende houder met bewegende kinderen opnieuw tekent laat die kinderen soms weg. In de simulator
     (iOS 26.5) niet te zien, op zijn telefoon wel. Regel: in een strook beweegt geen kaart op zichzelf; de strook komt
     als geheel binnen (.rv op de houder of erboven). De indruk bij aanraken (:active) blijft. */
  .mob-strook > * { animation: none !important; transition: none !important; opacity: 1 !important; }
  .mob-strook > *:not(:active) { transform: none !important; }
}
@media (min-width: 901px) { .mob-dots, .mob-schuif { display: none; } }
@media (max-width: 900px) { .mobKlaar { animation: none !important; opacity: 1 !important; transform: none !important; } }
@media (max-width: 900px) and (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    /* 3. Het slotpaneel groeit terwijl je erin scrolt: van de gewone
          kolombreedte naar bijna de schermrand, met ruimere binnenmarge, zoals
          een sheet die openschuift. "Als je daar scrolt moet dat blok groter
          worden, breder, meer ruimte." */
    .slot-in { animation: mobSheet linear both; animation-timeline: view(); animation-range: entry 0% entry 55%; }
    /* 3b. Elk omrand paneel dat de kolom vult (v2.js zet .mob-blad en de
          randafstand als --blad-l/--blad-r) groeit terwijl je erin scrolt van
          de kolombreedte tot 8 px van de schermrand. Gianluca 16-09 bij het
          weekpaneel van /contact/: "als ik scroll naar beneden dat die breder
          kan worden, zo zijn er meerdere voorbeelden". */
    .mob-blad { animation: mobBlad linear both; animation-timeline: view(); animation-range: entry 0% entry 60%; }
    /* 4. Schermvensters, toestellen, tekeningen en kaartjes komen iets
          kleiner binnen en groeien naar hun maat. */
    .mod-scherm, .mod-toestel, .mod-duo-beeld--vrij, .aiz-halo, .lb-kring, .aiz-toestel, .aiz-ster {
      animation: mobGroei linear both; animation-timeline: view(); animation-range: entry 0% entry 50%; transform-origin: 50% 100%; }
    /* 4b. Rijen, regels en losse kaarten komen EEN VOOR EEN binnen terwijl
          je scrolt (Gianluca 14-09 bij de lijst "waarom SEO-trajecten blijven
          steken", de afspraak en de kengetallen: "dat wil ik een voor een uit
          zien komen"). Elke regel heeft zijn eigen tijdlijn op de kijkhoogte,
          dus de volgorde volgt vanzelf de scrollpositie. De keyframes dragen
          opacity EN transform, zodat ze ook paginaeigen onthullingen op
          dezelfde eigenschap (bijvoorbeeld .lgraf-r, .pr-k) veilig vervangen:
          een element wordt nooit onzichtbaar achtergelaten. */
    .stats > .stat, .sv-rail > p, .eer-rij, .lgraf-r, .lgraf-voet, .wacht-r, .wacht-voet, .aiz4-r, .aiz6-r, .aiz-naam,
    .kv-r, .clus-n, .pr-k, .stg4-k, .stg5-k, .timeline-phase, .strat-card, .drie-n, .drie-u, .abtest-var, .wpk-r, .dek-rij,
    .aiz-rap-deel, .aiz2-t, .sv-kaart, .rt-k, .lab-tool, .fig, .krow, .stap, .stap-k, .prog-feiten > span,
    .mod-meer, .tseo-faq-item,
    /* de batch van zes, 15-09-2026: rijen en kaarten van /ai-index/, de twee Ads-pagina's,
       /wordpress-specialist-inhuren/, /over-ons/ en /seo-voor-webshops/ */
    .gasi-h, .gasw-s, .type, .wire-blok, .br, .vv, .rich-col, .anat-col, .duo-k, .idx-row, .gab2-r, .tv-r,
    .klk-r, .ovs-k, .ovr-r, .wf-rij, .tree-tak, .expl-rij, .rij-r, .scr-r, .trf-r, .tj-k, .schema-tag {
      animation: mobRij linear both !important; animation-timeline: view() !important; animation-range: entry 0% entry 42% !important; }
    /* !important is hier bewust: de paginaeigen onthullingen staan als
       .isIn .lgraf-r (twee klassen) en zouden anders altijd winnen. */
    /* De gedeelde kaartgroepen in een strook: binnenkomen op kijkhoogte EN
       groeien naar het midden van de strook (tweede tijdlijn op de
       dwarsrichting). */
    :is(.tijd, .ladder, .vakken, .steden, .klant-reg) > * {
      animation: mobRij linear both, mobKaart linear both !important; animation-timeline: view(), view(inline) !important; animation-range: entry 0% entry 42%, cover 0% cover 100% !important; }
    /* VANGNET 15-09-2026 (avond). Op de iPhone van Gianluca bleven rijen met
       een scrolgestuurde onthulling onzichtbaar terwijl ze midden in beeld
       stonden (de kengetallen, de meetklok en de grenzen op /ai-index/);
       in Chrome en in de simulator met iOS 26.4 liep dezelfde animatie wel.
       v2.js zet elke rij die eenmaal voor de helft in beeld is geweest op
       .mobKlaar, en dan telt de animatie niet meer mee. Nooit meer een
       leeg vlak, ongeacht welke WebKit-versie de bezoeker heeft. */
    .mobKlaar { animation: none !important; opacity: 1 !important; transform: none !important; }
    /* de kaarten in een strook houden hun groei naar het midden (de dwarstijdlijn), alleen de instroom valt weg */
    :is(.tijd, .ladder, .vakken, .steden, .klant-reg) > .mobKlaar { animation: mobKaart linear both !important; animation-timeline: view(inline) !important; animation-range: cover 0% cover 100% !important; }
    /* 5. Foto met parallax: het beeld schuift binnen zijn kader langzamer
          dan de pagina. */
    .mod-duo-beeld--kader:not(.mod-duo-beeld--staand) img { animation: mobParallax linear both; animation-timeline: view(); animation-range: cover 0% cover 100%; }
    /* 8. In een strook groeit de kaart die in het midden staat (tijdlijn op de
          dwarsrichting van de strook). */

    /* 6. De grote afsluiter zwelt aan als hij in beeld komt. */
    .giant { animation: mobZwel linear both; animation-timeline: view(); animation-range: entry 0% entry 90%; transform-origin: 0 50%; }

  }
}
@keyframes mobBlad {
  from { margin-left: 0; margin-right: 0; width: 100%; }
  to   { margin-left: calc(-1 * var(--blad-l, 0px)); margin-right: calc(-1 * var(--blad-r, 0px)); width: calc(100% + var(--blad-l, 0px) + var(--blad-r, 0px)); }
}
/* de eindstand van een blad staat los van de animatie (8i) */
@media (max-width: 900px) { .mob-blad.mobKlaar { margin-left: calc(-1 * var(--blad-l, 0px)) !important; margin-right: calc(-1 * var(--blad-r, 0px)) !important; width: calc(100% + var(--blad-l, 0px) + var(--blad-r, 0px)) !important; max-width: none !important; } }
/* een blad maakt de pagina nooit breder: versiering die eruit steekt wordt geknipt (clip, geen scrollcontainer), en een
   strook in een blad houdt de kantlijn van het blad in plaats van de negatieve kantlijn van de kolom */
@media (max-width: 900px) {
  .mob-blad { overflow-x: clip; }
  .mob-blad :is(.mob-strook, .tijd, .ladder, .vakken, .steden, .klant-reg) { margin-inline: 0 !important; padding-inline: 0 !important; scroll-padding-inline: 0; }
}
@keyframes mobSheet {
  from { margin-inline: 0; padding-inline: 22px; border-radius: 22px; }
  to   { margin-inline: -30px; padding-inline: 30px; border-radius: 28px; }
}
@keyframes mobGroei { from { transform: scale(.9); } to { transform: none; } }
@keyframes mobSchuif { from { transform: translateY(18px); } to { transform: none; } }
@keyframes mobRij { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }
@keyframes mobParallax { from { object-position: 50% 0%; } to { object-position: 50% 100%; } }
@keyframes mobKaart { from { transform: scale(.94); } 50% { transform: none; } to { transform: scale(.94); } }
@keyframes mobZwel { from { transform: scale(.88); } to { transform: none; } }
@keyframes scrolZweef { from { transform: translateY(28px); } to { transform: translateY(-28px); } }
/* Zonder JavaScript staat de commandoregel er toch (nakijkronde 16-09). */
html:not(.js) .term [data-type]::after { content: attr(data-type); }

/* ---- 14. KOP LINKS, DE REST VAN HET DOSSIER RECHTS (21-09-2026) ------------
   Keuring van batch zeven: "als er een lege ruimte is, maak de tekst helemaal
   breed met de kolom of zet er een foto tussen". Een dossier met een LOSSE
   uitklapper (die op 72ch blijft, zie 2) en een alinea op 68ch vulde tweederde
   van de kolom en liet rechts een leeg vlak; 61 secties op 28 pagina's. Deze
   vorm zet de kop, en als er daarna nog minstens twee blokken volgen ook de
   eerste alinea, in een smalle linkerkolom die meeplakt, en de rest van het
   dossier in de rechterkolom over de rest van de breedte. Zo gebruikt het blok
   de hele kolom zonder dat een regel langer wordt dan ongeveer 80 tekens.
   De kop staat altijd links: een gespiegelde proef (kop rechts) las verkeerd,
   het oog kwam bij de alinea voor de kop. Onder 900 px onder elkaar: kop,
   alinea, uitklappers, in de volgorde waarin ze stonden. Bouwer:
   scripts/ontwerp/b7-vulling.py (twee). */
.mod-twee { display: grid; grid-template-columns: minmax(0, .78fr) minmax(0, 1.22fr); gap: clamp(30px, 5vw, 68px); align-items: start; }
.mod-twee-kop > .dos-kop { margin-top: 0; max-width: none; }
.mod-twee-kop > p { max-width: none; color: var(--mut); font-size: clamp(1.04rem, 1.4vw, 1.18rem); line-height: 1.7; margin: 0; }
.mod-twee-rest > p, .mod-twee-rest > ul, .mod-twee-rest > ol, .mod-twee-rest > .mod-meer { max-width: none; }
/* twee korte lijsten naast elkaar in plaats van onder elkaar: een lijst van vijf woorden per regel vult anders
   maar 70 procent van de rechterkolom (gemeten 22-09 op /online-marketing-vacatures/) */
@media (min-width: 901px) {
  .mod-twee-rest:has(> ul + p + ul) { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 clamp(24px, 3vw, 44px); align-items: start; }
  .mod-twee-rest:has(> ul + p + ul) > p { grid-column: auto; }
}
.mod-twee-rest > :first-child { margin-top: 0; }
.mod-twee-rest > :last-child { margin-bottom: 0; }
@media (min-width: 901px) {
  .mod-twee-kop { position: sticky; top: clamp(88px, 12vh, 120px); }
}
@media (max-width: 900px) {
  .mod-twee { display: block; }
  .mod-twee-kop { position: static; margin-bottom: 18px; }
}

/* ---- 15. LUCHT BOVEN EEN BIJSCHRIFT (21-09-2026) --------------------------
   Gianluca over de bewegende tekening op /google-ads-landingspagina-vakman/:
   "daaronder de tekst heeft geen padding tussen dat ding en de tekst, daar
   moet wel wat padding zitten, dat moet voor alles gelden". Gemeten: 2 px
   daar, 10 tot 14 op de andere vormen. De vloer is nu 16 px marge (gemeten
   vanaf de onderkant van het beeld tot de eerste letterregel minstens 14):
   de vormen hierboven (.mod-duo-beeld, .mod-scherm, .mod-toestel, .tek)
   staan op 16, en elk ander bijschrift in een figuur krijgt het hier. Meet
   met scripts/ontwerp/bijschrift.js. */
main figure > figcaption { margin-top: 16px; }
main figure > figcaption:first-child { margin-top: 0; }
main .mod-scherm > figcaption { margin-top: 0; } /* die draagt zijn lucht als padding binnen het venster */

/* ---- 16. DE KLANTENLOGO'S TWEE KEER ZO GROOT (21-09-2026) ------------------
   Gianluca bij batch acht, over de rij REG.KLANTEN op de stadspagina's:
   "klantlogo's mogen niet zo klein, zeker 2x zo groot; ik weet niet waar dit
   overal staat maar dat moet overal groter". De rij (.kl-rij, .kl-logo) staat
   op 86 pagina's: de 61 stadspagina's, de 24 vakhubs onder /sea-specialist/
   en /google-ads-dakdekkers-utrecht/. De maten staan hierboven bij de rij
   zelf: logo van 34 naar 72 px hoog, het vak van 76 naar 128 px, de
   tekstnaam (Tempodak, zonder logo) van .78rem naar 1.15rem, de kolom van
   130 naar 140 px zodat zes vakken op een laptop naast elkaar blijven en op
   een telefoon twee. De echte oorzaak zat in de bestanden: de vijf logo's
   zijn vierkante vellen van 1920 px waarop het merk 17 tot 37 procent van
   de hoogte vult, dus een vak van 72 px toonde een merk van 12 tot 27 px.
   De rij gebruikt nu /assets/logos/knip/*.webp, op de inhoud bijgesneden
   (twee procent rand, 800 px breed), zodat het merk de breedte van het vak
   vult. Zijn tweede blik: "iets kleiner, logo's mogen 80 procent zijn van
   wat ze nu zijn, en het H3-logo echt, dat is nu gewoon een teken": het
   logo op hooguit 80 procent van het vak en 58 px hoog (gemeten 110 px
   breed op 1280, 92 op een telefoon), en de twee tekstnamen (Tempodak,
   H3-Cleaning) zijn het echte logo geworden (knip/tempodak-logo.webp,
   knip/h3-facility-logo.webp). De vierkante originelen blijven staan voor
   de andere plekken. Het klantregister (.klant-reg, logo van 54 px naast
   naam en branche) is een andere, goedgekeurde vorm en blijft. */

/* ---- 17. EEN LOPER SCHUIFT NOOIT BUITEN ZIJN BAAN (22-09-2026) ------------
   Op de skillpagina's zit in een figuur vaak een dunne baan met een lichtje
   dat eroverheen loopt (.lup-baan i, .sch-lees i, en soortgelijke: een
   absoluut kind dat met translateX(100%) naar rechts schuift). De baan zelf
   knipte niet, dus tijdens de animatie werd het document 2000 px breed en
   meldde scripts/ontwerp/buiten-blokje.js "PAGINA BREDER DAN HET SCHERM".
   Zichtbaar was er niets, maar een horizontale schuifbalk op de telefoon is
   wel zichtbaar. Regel: de houder van zo'n loper knipt. Dit vangt elke
   houder met een absoluut kind dat een animatienaam draagt; paginaeigen
   regels mogen het overschrijven. */
main :is(.lup-baan, .sch-lees, [class$="-baan"], [class$="-lees"], [class$="-loper"], [class$="-lus"], [class$="-loop"]) { overflow: hidden; }
/* dezelfde houders nadat v2.js er .stKlaar achter heeft gezet (direct kind van een .st): de klasse eindigt dan
   op " stKlaar" en de regel hierboven raakt hem kwijt (gevonden 22-09 op de OKR-coach met [class$="-schaal"]) */
main :is([class$="-baan stKlaar"], [class$="-lees stKlaar"], [class$="-loper stKlaar"], [class$="-lus stKlaar"], [class$="-loop stKlaar"]) { overflow: hidden; }
/* en de houder van een absolute laag die met translateX schuift: de laag zelf mag niet knippen (dan is de gloed weg),
   dus de OUDER knipt. Een laag met een klasse op -loop of -lus zit altijd in zo'n houder. */
main *:has(> [class$="-loop"]), main *:has(> [class$="-lus"]), main *:has(> [class$="-scan"]) { overflow: hidden; }
main *:has(> [class$="-loop stKlaar"]), main *:has(> [class$="-lus stKlaar"]), main *:has(> [class$="-scan stKlaar"]) { overflow: hidden; }
/* en de algemene vorm: een houder van hooguit 12 px hoog met alleen een absoluut <i> erin is een baan. Omdat CSS
   geen hoogte kan bevragen, pakt deze regel elk direct kind <i> dat absoluut staat EN een animatie draagt: die
   hoort binnen zijn houder te blijven. De houder krijgt de klemming, niet het lichtje zelf. */
main *:has(> i[class=""]:only-child), main *:has(> i:not([class]):only-child) { overflow: hidden; }
/* en een naald die over een schaal veegt (.scb-naald op de OKR-coach: een laag zo breed als de schaal die tot 70%
   naar rechts schuift). De schaal zelf mag niet knippen, want de naald steekt 9 px boven en onder de schaal uit;
   het paneel om de schaal heen knipt. Gemeten 22-09: het document werd 1896 px breed op 1280. Let op: v2.js zet
   na de trap .stKlaar op elk kind van een .st, dus de klasse eindigt dan niet meer op -schaal; daarom *= en niet $=. */
main *:has(> [class*="-schaal"] > [class*="-naald"]) { overflow: hidden; }

/* ---- 18. EEN RIJ VASTE VAKJES WORDT OP DE TELEFOON EEN STROOK (22-09-2026) --
   In de kennisbank staan figuren met een rij vakjes van een vaste breedte
   (.nw-rij met .nw-node van 92 px, en soortgelijke). Op 390 px steekt de
   laatste 19 px buiten zijn kaart: de rij is een flexrij zonder wikkeling en
   de kaart knipt. De rij mag daar schuiven zoals elke andere kaartgroep
   (MASTERPLAN 8d): horizontaal scrollen met snap, zodat niets wordt
   afgesneden. */
@media (max-width: 900px) {
  main :is(.nw-rij, [class$="-rij"]:has(> [class$="-node"])) {
    overflow-x: auto; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch; padding-bottom: 6px;
  }
  main :is(.nw-rij, [class$="-rij"]:has(> [class$="-node"])) > * { scroll-snap-align: start; }
}

/* ---- 19. ONDER EEN KNOPPENRIJ HOORT LUCHT (22-09-2026) -------------------
   Gianluca op /zelf-leren/ in een smal venster: "registers en dat zoekbalkje,
   daar zit geen ruimte tussen, alles moet altijd een beetje padding hebben".
   Gemeten op 620 en 390 px: de knoppenrij raakt het blok eronder op 0 px.
   De oorzaak is niet die pagina maar de heromodule: .mod-hero-duo staat op
   column-gap 64 en row-gap 0. Op een breed scherm is er maar een rij, dus dat
   viel nooit op; zodra de twee kolommen onder elkaar klappen (900 px en
   smaller) plakt de onderkant van de tekstkolom tegen de bovenkant van het
   beeld. Dat is dus zichtbaar op elke pagina met een hero van tekst naast
   beeld: de zoekbalk op /zelf-leren/, het omlijste beeld op /prijzen/ en op de
   dienst- en stadspagina's. .mod-duo had die lucht wel (row-gap 24,8).
   Daarnaast de algemene ondergrens: staat er na een knoppenrij nog iets in
   dezelfde houder, dan houdt die rij lucht onder zich. In gewone stroom valt
   die marge samen met de marge van het volgende blok (de grootste wint), dus
   het is een ondergrens en geen opstapeling. Staat de rij als laatste in zijn
   houder, dan draagt het paneel of de sectie zijn eigen binnenmarge al.
   Let op de kiezer: v2.js zet na de trap .stKlaar achter de klasse, dus *= en
   niet $= (zie blok 17 en observatie 94). */
.mod-hero-duo { row-gap: clamp(22px, 2.4vw, 30px); }
main :is([class*="cta-rij"], [class*="cta-row"], [class*="knoprij"], [class*="knop-rij"], .k-cta-rij):not(:last-child) {
  margin-bottom: clamp(22px, 2.4vw, 30px);
}
