/* Layoutprimitieven */
.wrap {
  width: min(100% - (var(--pad-x) * 2), var(--container));
  margin-inline: auto;
}

.wrap--narrow {
  width: min(100% - (var(--pad-x) * 2), var(--measure));
  margin-inline: auto;
}

.wrap--full {
  width: 100%;
  padding-inline: var(--pad-x);
}

/* ============================================================
   Navigatie: header + schermvullende menu-overlay
   Naar het model van lebarnhotel.com/fr (devtools-metingen van Mees,
   augustus 2026; zie preview/lebarn-landing.png en -menu.png). Geen
   menubalk met losse links meer in de header: alleen een hamburger met
   label (links), het V-monogram (gecentreerd) en een paar vaste links (rechts).
   Klikken op de hamburger opent een schermvullende overlay met de
   volledige navigatie. Geen library, vanilla JS in js/main.js. ============= */
/* --nav-link-color volgt exact dezelfde drie toestanden als color hierboven
   en hieronder. De burger en .nav__secondary gebruiken dit om hun
   hover-/focuskleur via color-mix op de juiste basiskleur te baseren: wit
   boven de hero, koningsblauw daarna. currentColor kan dat niet, omdat dit
   niet gebruikt mag worden om de eigen color-property te berekenen. */
.site-header {
  position: fixed;
  z-index: 50;
  inset-inline: 0;
  top: 0;
  background-color: var(--ivoor);
  border: 0;
  color: var(--blauw);
  --nav-link-color: var(--blauw);
  /* De opacity-transitie hoort hier in de basisregel, niet alleen in
     html.lightbox-locked .site-header hieronder: anders heeft het
     terugkomen van de header na het sluiten van de modal geen eigen
     transitie (de meer specifieke regel zou "transition" dan volledig
     overschrijven in plaats van aanvullen). Dit is een van de twee
     bewuste uitzonderingen op "hero en header blijven visueel bevroren". */
  transition: color var(--motion-nav-color) var(--ease-nav),
    background-color var(--motion-nav-color) var(--ease-nav),
    opacity var(--motion-gallery-modal) var(--ease-house);
}

/* Boven de hero is alles exact wit en blijft de balk transparant. Zodra de
   IntersectionObserver .is-scrolled zet, wordt dit volledig omkeerbaar
   koningsblauw op ivoor. De scrim hoort bij de header en blijft daardoor
   boven iedere fotoslide identiek. */
.site-header--hero {
  background-color: transparent;
  color: var(--wit);
  --nav-link-color: var(--wit);
}

.site-header--hero::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: 0 0 auto;
  height: 140px;
  pointer-events: none;
  background: linear-gradient(to bottom, rgba(26, 59, 170, .25), rgba(26, 59, 170, 0));
  opacity: 1;
  transition: opacity var(--motion-nav-color) var(--ease-nav);
}

.site-header--hero.is-scrolled {
  background-color: var(--ivoor);
  color: var(--blauw);
  --nav-link-color: var(--blauw);
}

.site-header--hero.is-scrolled::before {
  opacity: 0;
}

/* Zonder intro-state (geen JS of reduced motion) is de navigatie direct
   zichtbaar. Alleen intro-pending brengt
   haar naar het startpunt; intro-running speelt de exacte 3000/500ms-cue. */
.site-header--hero .nav {
  opacity: 1;
  transform: translateY(0);
}

.intro-pending .site-header--hero .nav {
  opacity: 0;
  transform: translateY(-8px);
}

.intro-running .site-header--hero .nav {
  animation: nav-intro-in var(--motion-nav-duration) ease var(--motion-nav-start) both;
}

.intro-skipping .site-header--hero .nav {
  animation-delay: 0ms;
  animation-duration: var(--motion-intro-skip);
}

@keyframes nav-intro-in {
  from {
    opacity: 0;
    transform: translateY(-8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* .nav-overlay staat hieronder in de brondocument-volgorde als sibling van
   .nav (allebei rechtstreeks in <header>), niet meer als kind ervan - zie de
   toelichting bij .site-header--hero .nav hierboven. z-index: 1 houdt .nav
   zichtbaar en klikbaar boven de overlay (die zelf z-index: 0 heeft), zoals
   bedoeld: "de header blijft zichtbaar bovenop de overlay". position:
   relative is hier al nodig voor het absoluut gecentreerde .nav__logo
   erbinnen en draagt de z-index tegelijk. */
.nav {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--space-4);
  min-height: var(--header-h);
}

/* 17px tussen icoon en label: letterlijke devtools-meting van
   lebarnhotel.com, eenmalig gebruikt, vandaar geen apart token. */
.nav__burger {
  grid-column: 1;
  display: inline-flex;
  gap: 17px;
  align-items: center;
  justify-self: start;
  border: 0;
  background: transparent;
  color: inherit;
  cursor: pointer;
  transition: color var(--motion-nav-link-hover) var(--ease-lebarn);
}

.nav__burger-icon {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  width: var(--nav-burger-w);
  height: var(--nav-burger-h);
}

.nav__burger-line {
  display: block;
  width: 100%;
  height: 3px;
  flex: 0 0 3px;
  border-radius: 0;
  background: currentColor;
  transition: transform var(--motion-burger) var(--ease-lebarn);
}

/* De middelste lijn is het kenmerkende detail: exact 62% breed, altijd
   gecentreerd. scaleX (geen kleinere width) zodat hij mee-animeert naar
   scaleX(0) in de open toestand. */
.nav__burger-line:nth-child(2) {
  transform: scaleX(.62);
  transform-origin: center;
}

.nav.is-open .nav__burger-line:first-child {
  transform: translateY(8.5px) rotate(45deg);
}

.nav.is-open .nav__burger-line:last-child {
  transform: translateY(-8.5px) rotate(-45deg);
}

.nav.is-open .nav__burger-line:nth-child(2) {
  transform: scaleX(0);
}

/* Kruislingse crossfade tussen "Menu" en "Sluiten": beide teksten staan
   permanent in de DOM en overlappen in dezelfde grid-cel, zodat er geen
   directe omwisseling of layoutsprong is. De knop draagt zijn eigen
   aria-label (js/main.js) - de teksten hier zijn puur visueel. Het label
   draagt de iets zwaardere Newsreader-actierol; de bestaande crossfade
   blijft ongewijzigd. */
.nav__burger-label {
  position: relative;
  display: grid;
  font-family: var(--font-action);
  font-size: 1.0625rem;
  font-weight: var(--font-action-weight);
  letter-spacing: .1em;
  text-transform: uppercase;
  white-space: nowrap;
  transition: letter-spacing var(--motion-nav-link-hover) var(--ease-lebarn);
}

.nav__burger-label-text {
  grid-area: 1 / 1;
  opacity: 1;
  transition: opacity var(--motion-burger-label-fade) ease-in-out;
}

.nav__burger-label-text--close {
  opacity: 0;
}

.nav.is-open .nav__burger-label-text--menu {
  opacity: 0;
}

.nav.is-open .nav__burger-label-text--close {
  opacity: 1;
}

/* Het label reserveert altijd de breedte van "SLUITEN" (de langste van de
   twee gestapelde teksten hierboven), ook terwijl "MENU" zichtbaar is.
   Onder ~430px viewportbreedte duwt dat het label tegen het absoluut
   gecentreerde .nav__logo aan (gemeten met puppeteer, zie CLAUDE.md-
   verificatiestatus). Icoon-only lost dat op zonder de kruisvervaging of de
   aria-label-tekst aan te tasten: de knop blijft bedienbaar en aangekondigd. */
@media (max-width: 460px) {
  .nav__burger-label {
    display: none;
  }
}

/* Absoluut gecentreerd op de nav, los van de grid: bij ongelijke inhoud
   links en rechts (bijvoorbeeld .nav__secondary die hieronder pas vanaf
   768px verschijnt) zou centreren via de grid-kolom het logo laten
   meedrijven met welke kant het meest gevuld is. Absoluut positioneren
   houdt het logo op elk formaat exact in het midden.
   Correctie: het volledige wordmark (voorheen hier via .nav__logo::before
   als CSS-mask op logo_white.svg) is vervangen door alleen het V-teken, zie
   .nav__v-mark hieronder. .nav__logo is nu enkel nog de gecentreerde
   linkwrapper; de maat komt van .nav__v-mark zelf. logo_white.svg blijft
   als bestand bestaan, alleen niet meer hier gebruikt. */
.nav__logo {
  position: absolute;
  top: 50%;
  left: 50%;
  display: inline-flex;
  transform: translate(-50%, -50%);
  border-bottom: 0;
}

/* ── Fasen van de header op de homepage ──────────────────────────────
   Tot 2 september 2026 kwam het navigatiemonogram tijdens de intro in beeld,
   dus terwijl het hero-woordmerk Villa Sophialaan er nog stond. Gemeten:
   op 1440x900 stonden woordmerk en V allebei op dekking 1 bij scrollY 0, 20
   en 40. Dat hoort omgekeerd: eerst gaat het woordmerk weg, pas daarna komt
   het monogram.

   js/main.js zet één attribuut op de header, afgeleid uit één scrollbereke-
   ning: data-fase is "rust", "uitfaden" of "v-zichtbaar". Zie de toelichting
   bij de fasemachine daar voor de drempels en het tijdslot.

   Eén regel, en bewust alleen de VERBERGENDE. In fase 3 geldt gewoon de rest
   van de cascade, zodat `.tijdlijn-kaal .nav__logo { opacity: 0 }` (0,2,0)
   binnen de tijdlijnpin niet hoeft te vechten met een zwaardere toonregel.

   visibility en niet alleen opacity: een onzichtbare link die wel focus
   krijgt is een lege plek in de tabvolgorde en een spookitem voor een
   schermlezer. visibility: hidden houdt wél de layoutbox, zodat de meting van
   de vrije ruimte tot het monogram in tools/_nav2.js blijft kloppen.

   Gescopet op html.js: zonder JavaScript is er geen fasemachine en houdt de
   CSS-default een gewoon zichtbaar monogram. Omdat html.js in de inline
   bootstrap van index.html wordt gezet, vóór de eerste paint, is er geen
   flits van een zichtbare V bij het laden.

   De transitie staat in deze regel en dus alleen op de weg NAAR verborgen:
   bij het terugscrollen fadet het monogram in --motion-nav-color weg voordat
   het woordmerk terugkomt. De weg eruit is hard, want de schrijfbeweging van
   de V is zelf de entree; een fade eroverheen zou daarmee concurreren.
   Bestaand token, er is er geen bijgekomen. */
html.js .site-header--hero:not([data-fase="v-zichtbaar"]) .nav__logo {
  visibility: hidden;
  opacity: 0;
  transition: opacity var(--motion-nav-color) var(--ease-lebarn),
    visibility 0s linear var(--motion-nav-color);
}

/* Exact dezelfde vorm, maskerhartlijnen, schrijfkeyframes en timings als de
   hero-V. Alleen de responsive context en de 40px navigatiemaat verschillen. */
.nav__v-mark {
  display: block;
  width: auto;
  height: var(--nav-v-mark-h);
  aspect-ratio: 1;
  overflow: visible;
  color: inherit;
  shape-rendering: geometricPrecision;
  transition: color var(--motion-nav-color) var(--ease-nav);
}

/* Zonder JS en na de eenmalige schrijfbeweging blijft het monogram compleet. */
.nav__v-mark .v-stroke {
  stroke-dashoffset: 0;
}

.nav.is-v-writing .nav__v-mark .v-stroke--1 {
  animation: v-draw var(--motion-v-stroke1-duration) var(--ease-v-stroke-1)
    var(--motion-v-stroke1-start) both;
}

.nav.is-v-writing .nav__v-mark .v-stroke--2 {
  animation: v-draw var(--motion-v-stroke2-duration) var(--ease-v-stroke-2)
    var(--motion-v-stroke2-start) both;
}

.intro-skipping .nav.is-v-writing .nav__v-mark .v-stroke {
  animation-delay: 0ms;
  animation-duration: var(--motion-intro-skip);
}

/* grid-column: 3, expliciet: .nav__logo staat op position: absolute en
   doet dus niet meer mee aan de auto-plaatsing van de grid-kolommen.
   Zonder deze regel valt .nav__secondary terug in de nu lege middelste
   kolom (naast .nav__burger) in plaats van rechts, en overlapt hij het
   gecentreerde logo. */
.nav__secondary {
  grid-column: 3;
  display: flex;
  gap: var(--space-6);
  align-items: center;
  justify-self: end;
  color: inherit;
  transition: color var(--motion-nav-color) var(--ease-nav);
}

.nav__secondary > a {
  display: none;
}

@media (min-width: 768px) {
  .nav__secondary > a {
    display: inline;
  }
}

/* Galerij verschijnt pas vanaf 900px. Met drie links loopt de rechterkolom
   tussen 768 en 900px tegen het absoluut gecentreerde V-monogram aan:
   gemeten overlapt hij 31px bij 768px en 16,6px bij 800px, en bij 850px
   blijft er nog maar 6,8px over. De gap van .nav__secondary verkleinen zou
   het ook oplossen, maar die gap is gedeeld met de rest van de header; een
   eigen breakpoint voor deze ene link laat Ruimtes, Contact en de taalknop
   ongemoeid. Onder 900px is er niets verloren: het burgermenu draagt de
   galerijlink al.

   nth-child(2) en niet first-child: sinds 27 augustus 2026 luidt de
   volgorde Ruimtes, Galerij, Contact. Op first-child zou deze regel nu de
   ruimtelink verbergen, en dat is juist de belangrijkste van de drie.

   Sinds 14 september 2026 staat Bedrijven als vierde link rechts en is de
   taalknop NL verdwenen, beide op verzoek van de eigenaar. Gemeten vrije
   ruimte tot het V-monogram zonder NL: Ruimtes, Contact en Bedrijven 57px
   bij 768; met Galerij erbij 30px bij 900 en 53px bij 950. Galerij
   verschijnt daarom vanaf 950px; de andere drie vanaf 768px. Galerij staat
   altijd in het burgermenu. */
@media (max-width: 949px) {
  .nav__secondary > a:nth-child(2) {
    display: none;
  }
}

/* Locatie staat sinds 15 september 2026 in de snelkoppelingen, op verzoek van
   de eigenaar in de volgorde Ruimtes, Galerij, Bedrijven, Locatie, Contact.
   Locatie is dus nth-child(4); Galerij blijft nth-child(2). Met vijf links
   gemeten vrije ruimte tot het V-monogram: -37px bij 950, 32px bij 1100, 55px
   bij 1150 en 166px bij 1440. Locatie verschijnt daarom vanaf 1150px, met
   dezelfde marge als Galerij bij 950px; kleiner staat hij in het burgermenu. */
@media (max-width: 1149px) {
  .nav__secondary > a:nth-child(4) {
    display: none;
  }
}

/* Menu is de canonieke hoverbeweging voor alle headercontrols: 20% zachtere
   currentColor en 0,2px extra tracking op dezelfde 500ms-huistiming. Alleen
   directe snelkoppelingen staan hier. */
.nav__secondary > a {
  border-bottom: 0;
  color: inherit;
  font-family: var(--font-action);
  font-size: .8125rem;
  font-weight: var(--font-action-weight);
  letter-spacing: .08em;
  text-transform: uppercase;
  transition: color var(--motion-nav-link-hover) var(--ease-lebarn),
    letter-spacing var(--motion-nav-link-hover) var(--ease-lebarn);
}

.nav__secondary > a:focus-visible {
  color: color-mix(in srgb, var(--nav-link-color) 80%, transparent);
  letter-spacing: 1.6px;
}

@media (hover: hover) and (pointer: fine) {
  .nav__burger:hover {
    color: color-mix(in srgb, var(--nav-link-color) 80%, transparent);
  }

  .nav__burger:hover .nav__burger-label {
    letter-spacing: 3px;
  }

  .nav__secondary > a:hover {
    color: color-mix(in srgb, var(--nav-link-color) 80%, transparent);
    letter-spacing: 1.6px;
  }
}

/* Header en menu gebruiken kleur, letterspatiëring en het bestaande streepje
   als focusfeedback, zonder een rechthoekige omheining rond het element. */
.site-header--hero:not(.is-scrolled) .nav :focus-visible,
.site-header.is-over-dark .nav :focus-visible,
.nav.is-open :focus-visible {
  outline: 0;
}

.nav.is-open {
  color: var(--wit);
  --nav-link-color: var(--wit);
}

.nav__burger:focus-visible {
  color: color-mix(in srgb, var(--nav-link-color) 80%, transparent);
}

.nav__burger:focus-visible .nav__burger-label {
  letter-spacing: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .intro-pending .site-header--hero .nav,
  .intro-running .site-header--hero .nav {
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
  }
}

/* --- Menu-overlay ---
   Eén rechte, verticale navigatielijst. Alle zes links hebben exact
   dezelfde typografie en maat. De huidige pagina houdt haar streep vast;
   bij andere links verschijnt hij alleen als hover-/focusfeedback. */
.nav-overlay {
  position: fixed;
  inset: 0;
  z-index: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  padding: calc(var(--header-h) + var(--space-8)) var(--pad-x) var(--space-12);
  background: var(--surface-blueprint);
  opacity: 0;
  visibility: hidden;
  overflow-x: hidden;
  overflow-y: auto;
  /* Het paneel schuift en zoomt niet, het verschijnt met een vlakke fade -
     0,4s op --ease-lebarn, letterlijke devtools-meting van
     lebarnhotel.com. De gerichte reduced-motion-regel verderop verwijdert
     de itemverplaatsing maar behoudt een vlakke opacityfade van 200ms. */
  transition: opacity var(--motion-menu-fade) var(--ease-lebarn),
    visibility var(--motion-menu-fade) var(--ease-lebarn);
}

@media (min-width: 768px) {
  .nav-overlay {
    padding-left: 22vw;
  }
}

.nav-overlay.is-open {
  opacity: 1;
  visibility: visible;
}

.nav-overlay__menu {
  display: flex;
  flex-direction: column;
  gap: var(--nav-overlay-gap);
}

/* Items komen gestaffeld binnen (0,5s, 40ms stagger, bovenste eerst) en
   verdwijnen bij sluiten gelijktijdig zonder stagger (0,3s) - de gesloten
   toestand hieronder is meteen ook de sluit-transitie, de is-open regel
   erna geldt alleen bij het openen. */
.nav-overlay__menu a {
  position: relative;
  display: inline-block;
  padding-left: calc(var(--nav-active-mark) + 1.75rem);
  border-bottom: 0;
  color: var(--ivoor);
  font-family: var(--font-action);
  font-size: var(--nav-overlay-item);
  font-weight: var(--font-action-weight);
  line-height: var(--nav-overlay-leading);
  letter-spacing: var(--nav-overlay-tracking);
  text-transform: uppercase;
  opacity: 0;
  transform: translateY(12px);
  transition: opacity var(--motion-menu-item-out) var(--ease-lebarn),
    transform var(--motion-menu-item-out) var(--ease-lebarn);
}

.nav-overlay.is-open .nav-overlay__menu a {
  opacity: 1;
  transform: translateY(0);
  transition: opacity var(--motion-menu-item-in) var(--ease-lebarn),
    transform var(--motion-menu-item-in) var(--ease-lebarn);
}

.nav-overlay.is-open .nav-overlay__menu a:nth-child(1) { transition-delay: 0ms; }
.nav-overlay.is-open .nav-overlay__menu a:nth-child(2) { transition-delay: var(--motion-menu-item-stagger); }
.nav-overlay.is-open .nav-overlay__menu a:nth-child(3) { transition-delay: calc(var(--motion-menu-item-stagger) * 2); }
.nav-overlay.is-open .nav-overlay__menu a:nth-child(4) { transition-delay: calc(var(--motion-menu-item-stagger) * 3); }
.nav-overlay.is-open .nav-overlay__menu a:nth-child(5) { transition-delay: calc(var(--motion-menu-item-stagger) * 4); }
.nav-overlay.is-open .nav-overlay__menu a:nth-child(6) { transition-delay: calc(var(--motion-menu-item-stagger) * 5); }

/* De streep ligt permanent in de DOM maar is voor niet-actieve links in
   rust naar de tekst toe ingeklapt. De huidige pagina blijft gemarkeerd;
   andere links schrijven hem van de tekst naar links in op hover/focus. */
.nav-overlay__menu a::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: var(--nav-active-mark);
  height: 3px;
  background: var(--wit);
  transform: translateY(-50%) scaleX(0);
  transform-origin: right center;
  transition: transform var(--motion-menu-streak) ease-in-out;
}

.nav-overlay__menu a[aria-current="page"]::before,
.nav-overlay__menu a:focus-visible::before {
  transform: translateY(-50%) scaleX(1);
}

@media (hover: hover) and (pointer: fine) {
  .nav-overlay__menu a:hover::before {
    transform: translateY(-50%) scaleX(1);
  }
}

/* Geef de zwaarste, langste menulink ook rond de 360px-browsergrens genoeg
   afrondingsmarge in Safari. Alleen de ruimte na het streepje wordt hier
   compacter; de kleinere 20px tekst blijft voor de echt smalle maat eronder. */
@media (max-width: 380px) {
  .nav-overlay__menu a {
    padding-left: calc(var(--nav-active-mark) + .5rem);
  }
}

@media (max-width: 359px) {
  .nav-overlay__menu a {
    font-size: 1.25rem;
  }
}

/* Gerichte uitzondering op de globale vangnetregel bovenaan style.css
   (die alle transitie- en animatieduur naar 0,01ms klemt): hier draagt
   beweging betekenis, dus behouden we een bewuste, trage kruisvervaging in
   plaats van een instant snap. Geen translateY, geen stagger. */
@media (prefers-reduced-motion: reduce) {
  .nav-overlay {
    transition-duration: 200ms !important;
  }

  .nav-overlay__menu a {
    transition-duration: 200ms !important;
    transition-delay: 0s !important;
    transform: none !important;
  }

  .nav-overlay__menu a::before {
    transition-duration: 0.01ms !important;
  }
}

/* js/main.js zet deze class zolang de overlay open staat, zodat de pagina
   eronder niet meescrolt. */
html.nav-locked,
html.nav-locked body {
  overflow: hidden;
}

/* js/gallery.js zet deze class zolang de lightbox open staat. Losse class
   van nav-locked hierboven: de twee overlays kunnen niet gelijktijdig open
   staan, maar hoeven elkaars scroll-lock niet te delen. */
html.lightbox-locked,
html.lightbox-locked body {
  overflow: hidden;
}

/* Header helemaal weg zolang de lightbox open staat: anders staat hij
   nog steeds boven de modal (z-index: 50 tegen 100 van .lightbox is al
   genoeg om hem eronder te houden, maar opacity/pointer-events maken hem
   ook echt onbereikbaar). Zelfde 400ms als de modal zelf, via de
   opacity-transitie op de .site-header-basisregel hierboven. */
html.lightbox-locked .site-header {
  opacity: 0;
  pointer-events: none;
}

/* Buttons */
.button {
  display: inline-flex;
  min-height: 43px;
  align-items: center;
  justify-content: center;
  padding: 13px 30px;
  border-radius: 0;
  font-family: var(--font-body);
  font-size: var(--meta);
  font-weight: 500;
  letter-spacing: 0.1em;
  line-height: 1;
  text-transform: uppercase;
  transition: border-color 180ms ease, color 180ms ease;
}

.button--primary {
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--on-accent);
}

@media (hover: hover) and (pointer: fine) {
  .button--primary:hover {
    border-color: var(--accent-hover);
    background: var(--accent-hover);
    color: var(--on-accent);
  }
}

/* .button--primary is --accent op --accent zodra hij op de CTA-band staat:
   dezelfde rgb(4, 23, 144) als de sectie eronder, dus letterlijk onzichtbaar
   op een randje na. Alleen de witte tekst was zichtbaar, waardoor de
   belangrijkste conversieknop van de homepage als losse tekst las. Op een
   accentvlak keert de knop daarom om: ivoor vlak, accentkleur tekst. De
   modifier is los bruikbaar voor toekomstige donkere banden. */
.cta-section .button--primary,
.button--on-accent {
  border-color: var(--ivoor);
  background: var(--ivoor);
  color: var(--accent);
}

@media (hover: hover) and (pointer: fine) {
  .cta-section .button--primary:hover,
  .button--on-accent:hover {
    border-color: var(--wit);
    background: var(--wit);
    color: var(--accent-hover);
  }
}

.button--secondary {
  border: 1px solid var(--accent);
  background: transparent;
  color: var(--accent);
}

@media (hover: hover) and (pointer: fine) {
  .button--secondary:hover {
    background: var(--accent);
    color: var(--on-accent);
  }
}

/* Shared labels and rules */
/* Eyebrow, label, specificatie en jaartal-onderschrift delen één stijl:
   Newsreader op de opsz 8-instantie, 12px, gewicht 500, kapitalen, 0.16em
   tracking. Dat is bewust geen tweede lettertype: de opsz-as is er precies
   voor gemaakt om een contrastrijke schreefletter op kleine maat overeind te
   houden, en de metingen in CLAUDE.md laten zien dat zij dat doet. Alles wat
   om een monospace of schreefloze "labelletter" zou vragen, gebruikt dit. */
.eyebrow {
  display: block;
  margin-bottom: 16px;
  color: var(--text-muted);
  font-family: var(--font-body);
  font-size: var(--meta);
  font-weight: 500;
  font-variation-settings: "opsz" 8;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.tag {
  position: absolute;
  top: 12px;
  right: 12px;
  color: var(--text-muted);
  font-family: var(--font-body);
  font-size: var(--meta);
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}


/* Footer: compact hoofdblok met gegevens, formulier en navigatie. */
.site-footer {
  background: var(--surface);
}

.footer-contact {
  scroll-margin-top: calc(var(--header-h) + var(--space-4));
}

.footer-contact:focus {
  outline: 0;
}

.footer-contact__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 36px;
  padding-block: clamp(56px, 5vw, 72px);
}

.footer-contact__intro,
.footer-contact__form,
.site-footer__nav {
  min-width: 0;
}

/* Desktop: gegevens in 1-3, formulier in 5-9 en navigatie in 11-12. */
@media (min-width: 900px) {
  .footer-contact__inner {
    grid-template-columns: repeat(12, minmax(0, 1fr));
    column-gap: var(--space-gutter);
    align-items: start;
  }

  .footer-contact__intro { grid-column: 1 / 4; }
  .footer-contact__form  { grid-column: 5 / 10; }
  .site-footer__nav      { grid-column: 11 / 13; }
}

@media (max-width: 759px) {
  .footer-contact__inner {
    padding-block: 44px;
  }
}

.site-footer__logo {
  display: inline-block;
  border-bottom: 0;
  color: var(--accent);
  font-family: var(--font-display);
  font-size: var(--h3);
  font-weight: 400;
  line-height: 1;
}

.site-footer .footer-contact__gegevens {
  margin: var(--space-5) 0 0;
  color: var(--text);
  font-family: var(--font-body);
  font-size: 1rem;
  font-style: normal;
  font-weight: 400;
  font-variation-settings: "opsz" 16;
  line-height: 1.55;
}

.site-footer .footer-contact__gegevens a {
  color: var(--accent);
  border-bottom: 0;
}

.footer-contact__mail {
  font-family: var(--font-action);
  font-size: 1rem;
  font-weight: var(--font-action-weight);
  letter-spacing: var(--font-action-tracking);
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  min-height: 44px;
}

.footer-contact__mail svg {
  width: 1.25rem;
  height: 1.25rem;
  flex: none;
}

.footer-contact__mail:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 4px;
}

/* Het formulier blijft een zeskolomsraster zodat Naam en E-mail op desktop
   naast elkaar staan. Onder 600px volgt ook dit onderdeel één kolom. */
.f-form {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 10px 20px;
  align-items: start;
}

.f-form[hidden] {
  display: none;
}

.f-field--half {
  grid-column: span 3;
}

.f-field--vol,
.f-form__status,
.f-form__actie {
  grid-column: 1 / -1;
}

@media (max-width: 599px) {
  .f-form {
    grid-template-columns: minmax(0, 1fr);
    gap: 12px;
  }

  .f-field--half {
    grid-column: 1 / -1;
  }
}

.f-field {
  position: relative;
  min-width: 0;
}

.f-field > input,
.f-field > select,
.f-field > textarea {
  display: block;
  width: 100%;
  min-height: 44px;
  margin: 0;
  padding: 17px 0 5px;
  border: 0;
  border-bottom: var(--rule-w) solid var(--rule-strong);
  border-radius: 0;
  background: transparent;
  color: var(--text);
  font-family: var(--font-body);
  font-size: 1rem;
  font-weight: 400;
  font-variation-settings: "opsz" 16;
  letter-spacing: 0;
  line-height: 1.2;
  transition: border-color var(--dur-fast) var(--ease-out);
}

.f-field > select {
  appearance: none;
  padding-right: 1.5rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%2369665F' stroke-width='1.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 2px center;
  background-size: 12px 8px;
  cursor: pointer;
}

.f-field > textarea {
  min-height: 5rem;
  max-height: 200px;
  resize: vertical;
  line-height: 1.25;
}

@supports (field-sizing: content) {
  .f-field > textarea {
    field-sizing: content;
  }
}

.f-field > label {
  position: absolute;
  top: 17px;
  left: 0;
  color: var(--text-muted);
  font-family: var(--font-body);
  font-size: 1rem;
  font-variation-settings: "opsz" 16;
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.2;
  pointer-events: none;
  transform-origin: left top;
  transition: transform var(--dur-fast) var(--ease-lebarn),
    font-size var(--dur-fast) var(--ease-lebarn),
    letter-spacing var(--dur-fast) var(--ease-lebarn);
}

.f-field > input:focus + label,
.f-field > input:not(:placeholder-shown) + label,
.f-field > textarea:focus + label,
.f-field > textarea:not(:placeholder-shown) + label,
.f-field--select > select + label {
  transform: translateY(-0.875rem);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
}

.f-field--select:not(:focus-within):has(select option[value=""]:checked) > select + label {
  transform: none;
  font-size: 1rem;
  letter-spacing: 0;
}

@media (hover: hover) and (pointer: fine) {
  .f-field > input:hover,
  .f-field > select:hover,
  .f-field > textarea:hover {
    border-bottom-color: var(--text-muted);
  }
}

.f-field > input:focus,
.f-field > select:focus,
.f-field > textarea:focus {
  border-bottom-color: var(--blauw);
  border-bottom-width: 2px;
  outline: 0;
  box-shadow: none;
}

.f-field > [aria-invalid="true"] {
  border-bottom-color: var(--error);
  border-bottom-width: 2px;
}

.footer-contact .field-error {
  display: block;
  margin-top: var(--space-2);
  color: var(--error);
  font-family: var(--font-body);
  font-size: var(--small);
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.4;
  text-transform: none;
}

.footer-contact .field-error[hidden] {
  display: none;
}

.site-footer .f-form__status {
  margin: 0;
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--small);
  font-weight: 400;
  line-height: 1.5;
}

.f-form__actie {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
}

.site-footer .f-pot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  margin: 0;
  overflow: hidden;
}

.footer-contact__success h2 {
  margin: 0 0 var(--space-3);
  font-size: clamp(1.5rem, 2.2vw, 1.875rem);
}

.site-footer .footer-contact__success p {
  margin: 0 0 var(--space-6);
  max-width: var(--measure);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.6;
}

/* De hoofdnavigatie gebruikt de bestaande V-link, maar in de compacte
   labelmaat en zonder extra verticale tussenregels. */
.site-footer__nav-list {
  --v-mark-h: 0.9rem;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

.site-footer__nav-list .v-mark-link {
  font-size: 1rem;
  line-height: 1.9;
}

.site-footer__nav-list .v-mark-link[aria-current="page"] {
  color: var(--accent);
}

.site-footer__nav-list .v-mark-link[aria-current="page"] .v-mark__pen path {
  stroke-dashoffset: 0;
}

.site-footer__nav-list .v-mark-link[aria-current="page"] .v-mark__rule {
  stroke-dashoffset: 1;
}

@media (min-width: 600px) and (max-width: 899px) {
  .site-footer__nav-list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--space-gutter);
  }
}

/* Onderregel: exact 56px op desktop, met alleen bestaande werkende links. */
.site-footer__base {
  height: 56px;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-gutter);
  align-items: center;
  justify-content: space-between;
  border-top: 0;
}

.site-footer__base p {
  margin: 0;
  color: var(--text-muted);
  font-family: var(--font-body);
  font-size: var(--meta);
  font-weight: 400;
  letter-spacing: var(--tracking-meta);
  line-height: 1.4;
}

.site-footer__legal {
  display: flex;
  flex-wrap: wrap;
  gap: 0 0.625rem;
  align-items: center;
  color: var(--text-muted);
  font-family: var(--font-body);
  font-size: var(--meta);
  letter-spacing: var(--tracking-meta);
  line-height: 1.4;
}

.site-footer__legal a {
  font-family: var(--font-action);
  font-size: .9375rem;
  font-weight: var(--font-action-weight);
  letter-spacing: var(--font-action-tracking);
  border-bottom: 0;
  color: inherit;
  transition: color var(--dur-fast) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .site-footer__legal a:hover {
    color: var(--accent);
  }
}

@media (max-width: 599px) {
  .site-footer__base {
    height: auto;
    min-height: 56px;
    flex-direction: column;
    align-items: flex-start;
    padding-block: 14px;
  }
}

/* Eén reveal per kolom; geen veld-voor-veld laadpatroon. */
.js .footer-contact .reveal {
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 320ms var(--ease-lebarn),
    transform 320ms var(--ease-lebarn);
  transition-delay: calc(var(--i, 0) * var(--stagger));
}

.js .footer-contact .reveal.is-revealed {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .js .footer-contact .reveal {
    transform: none;
    transition: opacity 200ms ease;
    transition-delay: 0ms;
  }

  .f-field > label {
    transition: none;
  }
}

/* ============================================================
   Galerij: collage met scroll- en muisparallax, lightbox, cursorknop
   Nabouw van zorge9.estate. Waardes en opbouw letterlijk uit
   docs/research/04 Teardown Zorge 9 gallery.md (nabouwrecept), aanpassingen
   per docs/prompts/04-galerij.md. Hier in components.css, niet pages.css:
   dit is een zelfstandig herbruikbaar onderdeel met eigen markup, styling
   en gedrag (js/gallery.js) - vergelijkbaar met de nav en de knoppen
   hierboven - en geen paginaspecifieke lay-out zoals de portfolio- en
   verhuurpagina's in pages.css (die overigens een andere, ongerelateerde
   klasse "gallery-section" gebruiken).
   GSAP + ScrollTrigger (CDN, alleen op index.html) sturen de
   scroll-parallax, de muis-parallax per beeld en de cursorknop aan; de
   lightbox zelf is een pure CSS clip-path-transitie. Bij
   prefers-reduced-motion staat de collage stil en opent de lightbox met
   een opacity-fade in plaats van de clip-path-onthulling, zie de losse
   mediaquery's verderop in dit blok. ============================ */
/* Tijdens de magnetische terugveer schrijft gallery.js iedere frame de
   exacte positie. Deze override is bedoeld om te voorkomen dat de globale
   native smooth-scroll daar nog een tweede vertraging overheen legt.

   LET OP: op de homepage doet hij niets, en dat is uitgezocht in plaats van
   vermoed. ScrollTrigger zet bij het laden een INLINE stijl op <html>
   ("scroll-behavior: smooth"), en een style-attribuut wint van iedere
   selector, hoe specifiek ook. Gemeten met CSS.getMatchedStylesForNode:
   beide regels matchen, allebei actief, en de computed waarde blijft toch
   smooth. Op ruimtes.html en de andere pagina's zonder GSAP staat er geen
   inline stijl en werkt de override wel, maar daar is geen galerij.

   Daarom leunt de frameschrijver hier niet op: setScroll() in
   js/gallery.js geeft altijd expliciet behavior: 'instant' mee, en dat is
   ook de enige vorm die tegen dit inline-attribuut bestand is. De regel
   blijft staan omdat hij de bedoeling correct uitdrukt en weer gaat werken
   zodra GSAP dat attribuut niet meer zet; schrijf hem niet weg als
   werkende bescherming. */
html.is-gallery-snapping {
  scroll-behavior: auto;
}

/* Alleen zolang gallery.js de scroll werkelijk vasthoudt (capturing en
   held). Op touch is dit de enige manier om preventDefault op touchmove te
   laten aankomen: zonder touch-action klinkt de browser zich na het eerste
   niet-geannuleerde event vast op scrollen en negeert hij de rest van het
   gebaar. Permanent laten staan mag juist niet, want dan kan een bezoeker
   de sectie niet meer voorbij scrollen zodra het JavaScript faalt. */
.gallery.is-scroll-captured {
  touch-action: none;
}

.gallery {
  position: relative;
  height: 100svh;
  contain: strict;
  /* Puur zwart, niet --surface-dark: de galerij en de lightbox eronder
     zijn de enige plek op de site die --zwart gebruikt, zie de
     toelichting bij dat token in style.css. */
  background: var(--zwart);
}

.gallery__inner {
  position: relative;
  width: 100%;
  height: 100%;
}

/* De beeldlaag staat op volle sterkte. Eerder stond hier .6 en daarna
   .75, om de collage te dempen tegen de zwarte ondergrond, maar dat werkt
   averechts: de collage bedekt maar ongeveer 45 procent van het venster
   (zes losse beelden met ruimte ertussen, zo is dit ontwerp bedoeld), dus
   alles wat de foto's dempt vergroot alleen het aandeel zwart. Het dempen
   gebeurt nu uitsluitend daar waar het nodig is, namelijk achter de titel,
   via de sluier bij .gallery__title hieronder. Gemeten op 1440x900 ging de
   gemiddelde luminantie van het venster daarmee van 27 naar 45 van 255,
   terwijl titel, telling en knop alle drie ruim boven WCAG AA blijven. */
.gallery__bg {
  position: absolute;
  inset: 0;
  z-index: 2;
  overflow: hidden;
  opacity: 1;
  pointer-events: none;
}

.gallery__parallax {
  position: relative;
  width: 100%;
  height: 100%;
}

/* Een expliciet podium vervangt de vroegere flex-wrap. De oude rij kon bij
   vrijwel gelijke desktopbreedtes anders afbreken, waardoor beeld 5 en 6
   onder de 100svh-sectie verdwenen en flex-items elkaar visueel raakten.
   Deze coördinaten reconstrueren de twee echte rijen van Zorge 9, maar de
   kolom schaalt ook op de viewporthoogte. Daardoor blijft de volledige
   zwevende compositie op ieder gangbaar landschapsscherm leesbaar. */
.gallery__row {
  position: absolute;
  top: 50%;
  left: 50%;
  width: calc(var(--gallery-col) * 12);
  height: calc(var(--gallery-col) * 7.5);
  transform: translate(-50%, -50%);
}

.gi {
  position: absolute;
  margin: 0;
}

/* Elk beeld is een echt <button>, geen div met een klik-listener: zonder
   muis blijft elke foto zo apart focusbaar en bedienbaar (Enter/Spatie
   openen de lightbox op die foto), zie js/gallery.js. */
.gi__btn {
  position: relative;
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  pointer-events: auto;
}

.gi__btn picture,
.gi__btn img {
  display: block;
  width: 100%;
  height: auto;
}

/* Cursor verdwijnt pas zodra de muisvolgende cursorknop (.gallery__cursor,
   verderop) daadwerkelijk actief is - dezelfde voorwaarde als de
   muis-parallax, door js/gallery.js bewaakt met class "is-cursor-active". */
@media (min-width: 980px) and (hover: hover) {
  .gallery.is-cursor-active .gi__btn,
  .gallery.is-cursor-active .gallery__link {
    cursor: none;
  }
}

/* Desktop: twaalf kolommen breed en maximaal 7,5 kolommen hoog. Bij
   1440x900 is een kolom exact 120px, dezelfde geometrie als de referentie.
   Op een breder maar lager scherm wint 13,3333svh, zodat de onderste rij
   niet meer wordt afgesneden. */
.gallery {
  --gallery-col: min(8.3333vw, 13.3333svh);
}

/* Alle zes tegels liggen sinds 21 augustus 2026 volledig binnen de rij van
   12 bij 7,5 kolommen. Daarvoor stak .gi--1 een hele kolom links buiten de
   rij, .gi--2 twee kolommen boven en .gi--3 een halve rechts; op 1440px valt
   de rij precies samen met de viewport, dus dat was letterlijk buiten beeld.
   De hoogte per tegel volgt uit de verhouding in TILES (tools/build-images.js):
   breedte gedeeld door 1,5, behalve tegel 2 die 1,91 is. Wie hier schuift,
   controleert opnieuw dat elke tegel binnen [0,12] en [0,7.5] blijft en dat
   de rechthoeken elkaar niet raken.

     tegel  x            y            b x h (kol)
     1      0,4 - 3,2    0,5 - 2,37   2,8 x 1,87
     2      4,2 - 7,8    0,25 - 2,13  3,6 x 1,88
     3      8,9 - 11,3   0,6 - 2,2    2,4 x 1,6
     4      3,8 - 8,2    2,6 - 5,53   4,4 x 2,93   <- midden, het balkon
     5      0,4 - 3,2    5,1 - 6,97   2,8 x 1,87
     6      8,9 - 11,5   5,3 - 7,03   2,6 x 1,73
*/
.gi--1 { left: calc(var(--gallery-col) * .4); top: calc(var(--gallery-col) * .5); width: calc(var(--gallery-col) * 2.8); }
.gi--2 { left: calc(var(--gallery-col) * 4.2); top: calc(var(--gallery-col) * .25); width: calc(var(--gallery-col) * 3.6); }
.gi--3 { left: calc(var(--gallery-col) * 8.9); top: calc(var(--gallery-col) * .6); width: calc(var(--gallery-col) * 2.4); }
.gi--4 { left: calc(var(--gallery-col) * 3.8); top: calc(var(--gallery-col) * 2.6); width: calc(var(--gallery-col) * 4.4); }
.gi--5 { left: calc(var(--gallery-col) * .4); top: calc(var(--gallery-col) * 5.1); width: calc(var(--gallery-col) * 2.8); }
.gi--6 { left: calc(var(--gallery-col) * 8.9); top: calc(var(--gallery-col) * 5.3); width: calc(var(--gallery-col) * 2.6); }

@media (max-width: 979px) {
  .gallery { --gallery-col: min(25vw, 12.5svh); }

  .gallery__row {
    width: calc(var(--gallery-col) * 4);
    height: calc(var(--gallery-col) * 7.6);
  }

  /* Zelfde regel als op desktop: alles binnen de rij, hier 4 bij 7,6
     kolommen. Tegel 3 blijft verborgen, anders wordt het op deze breedte
     te vol. */
  .gi--3 { display: none; }
  .gi--1 { left: calc(var(--gallery-col) * .15); top: calc(var(--gallery-col) * .52); width: calc(var(--gallery-col) * 2); }
  .gi--2 { left: calc(var(--gallery-col) * 1.6); top: calc(var(--gallery-col) * 2.12); width: calc(var(--gallery-col) * 2.3); }
  .gi--4 { left: calc(var(--gallery-col) * .5); top: calc(var(--gallery-col) * 3.62); width: calc(var(--gallery-col) * 2.6); }
  .gi--5 { left: calc(var(--gallery-col) * .2); top: calc(var(--gallery-col) * 5.72); width: calc(var(--gallery-col) * 2); }
  .gi--6 { left: calc(var(--gallery-col) * 2.3); top: calc(var(--gallery-col) * 5.82); width: calc(var(--gallery-col) * 1.6); }
}

/* Titel: GALERIJ in --font-display, gecentreerd over de collage.
   pointer-events: none op de laag zelf, weer aan op .gallery__link erin:
   alleen die knop is klikbaar, de tekst eromheen laat kliks door naar de
   foto's eronder. */
.gallery__title {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 var(--space-6);
  text-align: center;
  pointer-events: none;
}

/* Contrastsluier onder de titel. Met .gallery__bg op .75 komen de foto's
   sterker door, en dat is ook de bedoeling, maar gemeten haalde de titel
   daardoor nog maar 4,10:1 tegen wat er werkelijk onder ligt: 46 procent
   van de pixels in dat tekstblok zat onder de 4,5:1 die WCAG AA vraagt,
   met uitschieters naar 1,30:1 op de lichte partijen van de gevel.
   Een zachte, naar buiten uitlopende sluier lost dat op zonder de collage
   als geheel te dempen: in het midden, waar de tekst staat, is hij het
   sterkst, aan de randen verdwijnt hij volledig. Dit is de enige plek waar
   de foto's nog gedempt worden, want .gallery__bg staat inmiddels op 1.
   De waarden .6 en .42 zijn de zachtste stand die gemeten nog ruim boven
   WCAG AA blijft. Gemeten op 1440x900, 1366x768, 1280x800, 390x844 en
   320x568 is de laagste van de drie tekstelementen 6,6:1; er is dus geen
   aparte mobiele stand nodig. Ga niet verder omlaag zonder h2, telling en
   Bekijk-knop op al die breedtes opnieuw te meten: de smalste marge zit
   niet altijd op dezelfde breedte, want het hangt af van welk deel van
   welke foto er toevallig achter de tekst valt. */
.gallery__title::before {
  content: "";
  position: absolute;
  inset: 0;
  /* GEEN z-index: -1. .gallery heeft contain: strict en is dus een eigen
     stacking context; een negatieve z-index legt de sluier daar onder
     .gallery__bg en dan doet hij niets. Zonder z-index paint hij boven de
     foto's (want .gallery__title komt later in de DOM) en onder de eigen
     tekstkinderen, wat precies de bedoeling is. */
  pointer-events: none;
  background: radial-gradient(
    ellipse 46% 34% at 50% 50%,
    rgba(0, 0, 0, .6) 0%,
    rgba(0, 0, 0, .42) 55%,
    rgba(0, 0, 0, 0) 100%
  );
}

/* position: relative op de twee tekstkinderen is hier geen opmaak maar
   een correctie op de schildervolgorde. De sluier hierboven is een
   absoluut gepositioneerde ::before zonder z-index, en die valt in stap 8
   van de CSS-schildervolgorde (gepositioneerde nakomelingen), terwijl
   gewone niet-gepositioneerde tekstkinderen al in stap 4 tot 7 klaar
   zijn. De sluier lag daardoor OVER de titel in plaats van eronder: het
   helderste pixel in "GALERIJ" mat 61 op een schaal van 255, waar wit
   (#FAF9F6) er 250 hoort te zijn. Door deze twee kinderen ook te
   positioneren komen ze in dezelfde stap als de sluier, en omdat ze later
   in de DOM staan winnen ze. Haal dit niet weg zonder opnieuw te meten. */
.gallery__title-line {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  white-space: nowrap;
}

.gallery__title h2 {
  margin: 0;
  color: var(--text-on-dark);
  text-transform: uppercase;
  /* Expliciet losgekoppeld van de h2-schaal die sinds 21 augustus 2026
     site-breed geldt (--tracking-title en --leading-title): de galerij is
     visueel bevroren en beide waarden verschoven de kop aantoonbaar, samen
     goed voor duizenden afwijkende pixels in de regressievergelijking.
     Laat dit staan zolang de galerij bevroren is. */
  letter-spacing: normal;
  line-height: var(--leading-tight);
}

/* Niet --text-on-dark-muted zoals .meta elders: die grijstint (#9C9A94)
   staat hier over wisselende fotografie in plaats van over een egale
   ondergrond, en haalde afhankelijk van wat er toevallig achter lag 2,6
   tot 3,3:1. Wit is de enige kleur die op alle gemeten breedtes boven AA
   blijft. De hiërarchie ten opzichte van de h2 wordt hier toch al gedragen
   door de grootte (--meta), de kapitalen en de tracking, niet door de
   kleur. */
.gallery__title .meta {
  margin: .15em 0 0;
  color: var(--text-on-dark);
}

@media (min-width: 980px) {
  .gallery__title {
    justify-content: flex-start;
    padding-inline: 0;
    text-align: left;
  }

  .gallery__title-line {
    margin-left: 25vw;
  }
}

/* Volvlaksbediening zoals bij Zorge 9: op de vrije zwarte ruimte is de
   sectie klikbaar, terwijl de zes echte fotoknoppen erboven hun eigen
   startpositie in de modal behouden. De bediening zelf heeft bewust geen
   zichtbare vorm; die taak ligt uitsluitend bij de cursor hieronder. */
.gallery__link {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  color: transparent;
  pointer-events: auto;
}

/* Cursorknop: puur decoratief (aria-hidden, pointer-events: none), volgt
   de muis via gsap.quickTo in js/gallery.js. Als sibling van .gallery in
   plaats van erin geplaatst (zie index.html): .gallery heeft
   contain: strict, wat een position: fixed-nakomeling zou binden aan de
   sectie in plaats van aan de viewport - dezelfde valkuil als bij
   .nav-overlay, zie de toelichting daar. Alleen zichtbaar vanaf 980px met
   een echte hover-muis, dezelfde voorwaarde als de muis-parallax. */
.gallery__cursor {
  font-weight: var(--font-action-weight);
  position: fixed;
  top: 0;
  left: 0;
  z-index: 40;
  display: none;
  width: 7.5rem;
  height: 7.5rem;
  border: 0;
  border-radius: 0;
  background: rgba(0, 0, 0, .32);
  color: var(--text-on-dark);
  font-family: var(--font-action);
  font-size: var(--meta);
  letter-spacing: var(--tracking-meta);
  text-transform: uppercase;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--motion-language-popover) var(--ease-lebarn);
}

@media (min-width: 980px) and (hover: hover) {
  .gallery__cursor {
    display: block;
  }
}

.gallery__cursor.is-visible {
  opacity: 1;
}

.gallery__cursor-outline {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}

.gallery__cursor-outline rect {
  fill: none;
  stroke: rgba(255, 255, 255, .82);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset var(--motion-gallery-cursor-outline) var(--ease-house);
}

.gallery__cursor.is-visible .gallery__cursor-outline rect {
  stroke-dashoffset: 0;
}

.gallery__cursor-label {
  position: absolute;
  top: .875rem;
  left: .875rem;
  line-height: 1;
}

.gallery__cursor-chevron {
  position: absolute;
  right: .875rem;
  bottom: .875rem;
  width: .5rem;
  height: .875rem;
}

@media (prefers-reduced-motion: reduce) {
  .gallery__cursor {
    display: none !important;
  }
}

/* Header boven de donkere galerij: dezelfde reversibele class-aanpak als
   .site-header--hero.is-scrolled hierboven, maar met een eigen
   waarnemer op .gallery in js/main.js (dode band van 20px). Moet
   zwaarder wegen dan .site-header--hero.is-scrolled zonder !important:
   beide selectors hebben twee klassen op hetzelfde element, dus gelijke
   specificiteit - deze regel wint omdat ze later in het bestand staat. */
.site-header.is-over-dark {
  background-color: transparent;
  color: var(--wit);
  --nav-link-color: var(--wit);
}

/* --- Lightbox: verticale scrollmodal ---
   Niet langer een carousel met prev/next/count: de hele collectie staat
   onder elkaar op ware grootte, de bezoeker scrolt. De items zelf komen
   uit tools/build-gallery.js, gevoed door data/photos.json; voeg hier
   dus geen foto met de hand toe. Elke foto heeft een uitleg die bij een
   klik onder haar openvouwt (.lightbox__info), en onderin staat een
   bronbalk die meewisselt met de foto die in beeld is
   (.lightbox__credit).
   Openen en sluiten van de modal zelf zijn een opacity-fade van
   --motion-gallery-modal (400ms) op --ease-house, zowel op
   .lightbox__bg als op .lightbox__scroller zelf. .lightbox regelt alleen
   visibility, naar het model van .nav-overlay hierboven: zichtbaar is de
   knip op t=0 bij openen, pas verborgen na de volledige transitieduur
   bij sluiten. Als sibling van <main> en <footer> in index.html
   geplaatst (niet genest), zie de toelichting bij .gallery__cursor
   hierboven.
   Reduced motion heeft hier bewust GEEN eigen blok: alle beweging in dit
   onderdeel loopt via transitions, dus het globale vangnet bovenaan dit
   bestand (transition-duration: .01ms !important) zet de modalfade, het
   openvouwen van de uitleg en de tekstwissel in de bronbalk in een keer
   stil. js/gallery.js kijkt daarnaast zelf naar prefers-reduced-motion
   en wisselt de brontekst dan zonder wachttijd. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  visibility: hidden;
  transition: visibility var(--motion-gallery-modal) linear;
  /* Losstaand van de responsieve --gallery-col hierboven: .lightbox is een
     sibling van <main>, niet van .gallery, en erft dat token dus niet. De
     beeldkolom blijft exact 960px op 1440px (8,3333vw * 8 = 66,667vw). */
  --lightbox-col: 8.3333vw;
}

.lightbox.is-open {
  visibility: visible;
}

.lightbox__bg {
  position: absolute;
  inset: 0;
  background: var(--zwart);
  opacity: 0;
  transition: opacity var(--motion-gallery-modal) var(--ease-house);
}

.lightbox.is-open .lightbox__bg {
  opacity: 1;
}

.lightbox__scroller {
  position: relative;
  z-index: 1;
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  opacity: 0;
  transition: opacity var(--motion-gallery-modal) var(--ease-house);
}

.lightbox.is-open .lightbox__scroller {
  opacity: 1;
}

@media (min-width: 980px) {
  .lightbox__scroller {
    display: flex;
    align-items: flex-start;
  }
}

/* Volledig scherm hoog en sticky: de titel en de sluitknop blijven
   zichtbaar terwijl .lightbox__list ernaast/eronder doorscrolt.
   pointer-events: none op de laag zelf, weer aan op de kinderen - alleen
   knop en titel zijn klikbaar, de rest laat de scroll/klik gewoon door. */
.lightbox__head {
  position: sticky;
  top: 0;
  height: 100svh;
  display: flex;
  flex-direction: column;
  /* Zelfde inspringing als .lightbox__close hierboven, zodat de titel en de
     sluitknop op een lijn staan. Zonder dit raakt de G van Galerij de
     schermrand. Padding verschuift .lightbox__close niet: het containing
     block van een absoluut gepositioneerd kind is de padding box, en die
     begint aan de binnenkant van de rand, niet achter de padding. */
  padding-left: 1.875rem;
  pointer-events: none;
}

.lightbox__head > * {
  pointer-events: auto;
}

@media (min-width: 980px) {
  .lightbox__head {
    flex: 1 1 auto;
  }
}

/* Onder de tweekolomsomslag stapelt de modal: de titel hoort dan BOVEN de
   beeldkolom te staan, niet als sticky laag eronder. Bleef hij sticky en
   100svh hoog, dan schoof de fotokolom er overheen en lagen de titel en de
   sluitknop bovenop de eerste foto, allebei onleesbaar. */
@media (max-width: 979px) {
  .lightbox__head {
    position: relative;
    height: auto;
    padding-top: 7.5rem;
    padding-bottom: 2.5rem;
  }

  /* Een collagefoto kan de modal direct halverwege de collectie openen.
     De sluitknop moet dan in beeld blijven; rechtsboven botst hij niet met
     het HCL-jaartal linksboven op historische foto's. */
  .lightbox .lightbox__close {
    position: fixed;
    top: 1rem;
    right: 1rem;
    left: auto;
    z-index: 2;
    background: rgba(0, 0, 0, .58);
  }
}

.lightbox__close {
  position: absolute;
  top: 1.875rem;
  left: 1.875rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3.75rem;
  height: 3.75rem;
  border: 1px solid var(--text-on-dark-muted);
  border-radius: 0;
  background: none;
  color: var(--text-on-dark);
  transition: border-color 180ms ease;
}

.lightbox__close svg {
  width: 1.25rem;
  height: 1.25rem;
}

@media (hover: hover) and (pointer: fine) {
  .lightbox__close:hover {
    border-color: var(--text-on-dark);
  }
}

.lightbox__title {
  /* Zie .gallery__title h2: bevroren, dus expliciet los van --tracking-title. */
  letter-spacing: normal;
  margin: auto 0;
  margin-right: 3.75rem;
  padding-bottom: 1.875rem;
  border-bottom: 1px solid rgba(255, 255, 255, .2);
  color: var(--wit);
  font-family: var(--font-display);
  font-size: 2.25rem;
  font-weight: 700;
  line-height: 1;
}

.lightbox__list {
  margin: 0 0 0 auto;
  padding: 0;
  width: calc(var(--lightbox-col) * 8);
}

@media (max-width: 979px) {
  .lightbox__list {
    margin-left: 0;
    width: 100%;
  }
}

/* Tussenkop per set (Het pand nu, Uit het archief). Staat in de kolom
   zelf en niet in de sticky kopregel: de sets volgen elkaar op tijdens
   het scrollen, dus de kop hoort op de plek waar de set begint. */
.lightbox__set {
  margin: 0 0 var(--space-8);
  padding-top: var(--space-16);
}

.lightbox__list > .lightbox__set:first-child {
  padding-top: 0;
}

.lightbox__set-title {
  margin: 0 0 var(--space-2);
  color: var(--text-on-dark);
  font-family: var(--font-display);
  font-size: var(--h3);
  font-weight: 700;
  line-height: 1.1;
}

.lightbox__set-intro {
  margin: 0;
  max-width: 44ch;
  color: var(--text-on-dark-muted);
  font-size: var(--small);
  line-height: 1.6;
}

/* Een foto plus haar uitleg. De figure vervangt de losse knop die hier
   eerder stond: de knop is nu .lightbox__shot erbinnen, zodat de
   figcaption met de uitleg buiten het klikbare vlak valt en de tekst
   gewoon te selecteren is. */
.lightbox__item {
  margin: 0 0 var(--motion-gallery-item-gap);
}

.lightbox__item:last-child {
  margin-bottom: 0;
}

/* De onscherpe placeholder van 20px komt als --lb-blur inline uit
   tools/build-gallery.js mee. Hij vult het vlak zolang het echte beeld
   nog laadt, zodat er tijdens het scrollen geen zwarte gaten vallen.
   Onder het beeld en niet erachter: het echte beeld is dekkend en dekt
   hem daarna volledig af. */
.lightbox__shot {
  position: relative;
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: var(--lb-blur, none) center / cover no-repeat;
  text-align: left;
}

/* De datering van HCL-beelden komt uit data/photos.json en blijft altijd
   zichtbaar, ook zolang de uitgebreidere uitleg nog dicht staat. Het is
   echte tekst in de knop (met aria-describedby), geen CSS-content. */
.lightbox__shot-year {
  position: absolute;
  top: var(--space-4);
  left: var(--space-4);
  z-index: 1;
  display: inline-flex;
  padding: .4rem .55rem .35rem;
  background: rgba(0, 0, 0, .72);
  color: var(--wit);
  font-family: var(--font-body);
  font-size: var(--meta);
  font-variation-settings: "opsz" 8;
  font-weight: 600;
  letter-spacing: .1em;
  line-height: 1;
  text-transform: uppercase;
  pointer-events: none;
}

.lightbox__shot picture,
.lightbox__shot img {
  display: block;
  width: 100%;
  height: auto;
}

/* Uitleg bij de foto, als disclosure onder het beeld. De dichte
   toestand is bewust gescopet op html.js: zonder JavaScript is er niets
   dat de knop kan bedienen, dus staat de uitleg daar gewoon open en
   blijft de koppeling tussen foto en bron zichtbaar. Zie ook de
   toelichting in js/gallery.js.
   grid-template-rows animeert van 0fr naar 1fr in plaats van max-height
   naar een geraden waarde: die hoeft niet geschat te worden en klopt
   dus ook bij een lange uitleg op een smal scherm. */
.lightbox__info {
  padding: var(--space-6) 0 var(--space-8);
  color: var(--text-on-dark);
}

.js .lightbox__info {
  display: grid;
  grid-template-rows: 0fr;
  padding-block: 0;
  opacity: 0;
  overflow: hidden;
  transition: grid-template-rows var(--motion-gallery-info) var(--ease-letter),
    padding-block var(--motion-gallery-info) var(--ease-letter),
    opacity var(--motion-gallery-info) var(--ease-letter);
}

.js .lightbox__info > * {
  min-height: 0;
}

.js .lightbox__item.is-open .lightbox__info {
  grid-template-rows: 1fr;
  padding-block: var(--space-6) var(--space-8);
  opacity: 1;
}

/* Wrapper om alles wat in de disclosure zit: grid-template-rows kan maar
   een rij animeren, dus de inhoud moet in een enkel kind zitten. */
.lightbox__info-inner {
  min-height: 0;
  max-width: 62ch;
}

.lightbox__info-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3);
  margin: 0 0 var(--space-3);
}

.lightbox__info-title {
  color: var(--text-on-dark);
  font-family: var(--font-display);
  font-size: var(--h3);
  font-weight: 700;
  line-height: 1.2;
}

.lightbox__info-year {
  color: var(--text-on-dark-muted);
  font-size: var(--meta);
  font-variation-settings: "opsz" 8;
  font-weight: 500;
  letter-spacing: .12em;
  text-transform: uppercase;
}

.lightbox__info-text {
  margin: 0;
  color: var(--text-on-dark);
  font-size: var(--body);
  line-height: 1.6;
}

.lightbox__info-credit {
  margin: var(--space-4) 0 0;
  color: var(--text-on-dark-muted);
  font-size: var(--small);
}

/* --- Bronbalk ---
   Vaste regel onderin die de bron toont van de foto die op dat moment in
   beeld staat, gevuld door de IntersectionObserver in js/gallery.js.
   position: fixed werkt hier omdat geen enkele voorouder een transform
   draagt; hij zit desondanks binnen .lightbox__scroller zodat hij in de
   a11y-boom van role="dialog" valt en de link binnen de focus-trap
   blijft.
   In rust staat de balk op opacity 0 en pointer-events: none, zodat hij
   nooit over een foto ligt te wachten voordat er iets te melden valt. */
.lightbox__credit {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2) var(--space-6);
  padding: var(--space-6) 1.875rem;
  background: linear-gradient(to top, rgba(0, 0, 0, .85) 0%, rgba(0, 0, 0, .6) 55%, rgba(0, 0, 0, 0) 100%);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--motion-credit-in) var(--ease-house);
}

.lightbox__credit.is-visible {
  opacity: 1;
}

/* Alleen de link vangt kliks; de rest van de balk laat ze door naar de
   foto eronder, zodat de balk het klikvlak van de laatste foto niet
   afsnijdt. */
.lightbox__credit.is-visible .lightbox__credit-link {
  pointer-events: auto;
}

/* Tekstwissel: uit in --motion-credit-out, weer in over de gewone
   --motion-credit-in van de balk zelf. js/gallery.js wacht dezelfde
   180ms voordat het de tekst vervangt. */
.lightbox__credit-text {
  margin: 0;
  color: var(--text-on-dark);
  font-size: var(--small);
  letter-spacing: .01em;
  transition: opacity var(--motion-credit-in) var(--ease-house);
}

.lightbox__credit.is-swapping .lightbox__credit-text {
  opacity: 0;
  transition-duration: var(--motion-credit-out);
}

.lightbox__credit-link {
  font-family: var(--font-action);
  color: var(--text-on-dark-muted);
  font-size: var(--meta);
  font-weight: var(--font-action-weight);
  letter-spacing: .1em;
  text-transform: uppercase;
  text-underline-offset: .3em;
  transition: color var(--motion-nav-link-hover) var(--ease-lebarn);
}

@media (hover: hover) and (pointer: fine) {
  .lightbox__credit-link:hover {
    color: var(--text-on-dark);
  }
}

/* Onder de tweekolomsomslag scrolt de kopregel mee en is de kolom vol
   breed; de balk krijgt dan dezelfde inspringing als de rest. */
@media (max-width: 979px) {
  .lightbox__credit {
    padding-inline: var(--space-6);
  }
}

/* Tweede toestand van de bestaande cursorknop (.gallery__cursor
   hierboven): "Uitleg" boven een dichte foto, "Sluit" boven de foto
   waarvan de uitleg openstaat, gezet door js/gallery.js. Vorm, grootte
   en zichtbaarheidsvoorwaarde blijven identiek aan de collage. */
.gallery__cursor.is-lightbox {
  font-weight: var(--font-action-weight);
  font-size: var(--meta);
  font-family: var(--font-action);
  letter-spacing: .08em;
  text-transform: uppercase;
}

@media (min-width: 980px) and (hover: hover) {
  .lightbox.is-cursor-active .lightbox__shot {
    cursor: none;
  }
}

/* ============================================================
   V-merkteken in links: het streepje schrijft zichzelf om naar het logo
   Naar het model van lebarnhotel.com, waar een streepje voor een label bij
   hover opgaat in het OO-merk. Hier gebeurt hetzelfde met het echte
   Villa Sophialaan-logo: hetzelfde gevulde letterpad als .hero__v-shape en
   .nav__v-mark, onthuld door dezelfde pen van 19 breed met ronde verbinding.
   Alleen de timing verschilt, zie de --motion-v-mark-tokens in style.css.

   Drie dingen zijn hier bewust anders dan in de intro:

   1. EEN doorlopend pad in plaats van twee losse halen. Twee curves die
      allebei op snelheid nul eindigen en beginnen laten de pen op de punt
      van de V stilstaan; bij 330ms leest dat als een hapering. Doorgerekend
      gaf de oude opzet daar een snelheidssprong van 1,91 keer. Met een
      doorlopend pad plus stroke-linejoin: round is die sprong per definitie
      weg.
   2. Vlakke uiteinden in plaats van ronde. Een ronde cap tekent op tijdstip
      nul al een volle stip van 19 eenheden, wat als een plopje voelt. De
      uiteinden van het pad zijn daarom 6 en 4,5 eenheden verlengd tot buiten
      de letter, zodat de vlakke cap nergens zichtbaar is. Dekking tegen het
      kale letterpad gecontroleerd: nul ontbrekende pixels op 93.854.
   3. pathLength="1" in plaats van een via JS gemeten --len. De .v-stroke in
      de intro heeft getTotalLength() nodig; hier normaliseert het attribuut
      de lengte naar 0 tot 1, dus dit onderdeel werkt volledig zonder JS.

   Streepje en logo delen een viewBox met vaste hoogte en meebewegende
   breedte. Het label kan daardoor geen halve pixel verspringen.

   Uitgaan gaat sneller dan ingaan (224ms tegen 330ms) en wist in omgekeerde
   volgorde, want wie wegbeweegt heeft zijn aandacht al verlegd. Er staan
   transitions en geen keyframes: een transition vertrekt altijd vanaf de
   waarde die op dat moment op het scherm staat, dus wie halverwege wegbeweegt
   ziet het logo terugtrekken vanaf precies dat punt in plaats van eerst naar
   vol te springen.

   Het clippad staat een keer per pagina in de gedeelde defs bovenin de body
   (#vs-v-glyph-clip). Puur CSS kan ook, met
   clip-path: view-box path("<het letterpad>") op .v-mark__pen, wat in Chrome
   pixelgelijk uitkomt; de clipPath is gekozen omdat dit dezelfde vorm van
   hergebruik is als de bestaande maskers en breder wordt ondersteund. ====== */
.v-mark-link {
  display: inline-flex;
  align-items: center;
  gap: var(--v-mark-gap);
  /* De basisregel voor <a> in style.css geeft iedere link een onderrand.
     Hier is het merkteken zelf de affordance, dus die rand gaat eruit,
     naar hetzelfde patroon als .nav__secondary en .nav-overlay__menu a. */
  border-bottom: 0;
  text-decoration: none;
  color: var(--text);
  font-family: var(--font-action);
  font-size: 1rem;
  font-weight: var(--font-action-weight);
  font-optical-sizing: auto;
  text-transform: none;
  letter-spacing: var(--font-action-tracking);
  line-height: 1.4;
  min-height: 44px;
  /* Nooit transition: all. transform staat op een eigen, kortere duur: een
     indruk hoort direct te reageren, een kleurwissel mag rustiger. */
  transition: color var(--motion-v-mark-color) var(--ease-out),
    transform var(--dur-press) var(--ease-out);
}

/* De actievorm werkt ook op echte buttons. Zonder deze reset hielden de
   verzendknoppen een native grijze achtergrond, een 2px outset-rand en een
   te kleine hitarea, terwijl dezelfde class op links wel goed tekende. */
button.v-mark-link {
  width: fit-content;
  min-height: 44px;
  padding: 0;
  border: 0;
  appearance: none;
  background: transparent;
  cursor: pointer;
  text-align: left;
}

button.v-mark-link[aria-disabled="true"],
button.v-mark-link:disabled {
  cursor: wait;
  opacity: .62;
}

/* .is-drawn is de blijvende variant: het logo staat vast geschreven en het
   label draagt de accentkleur. Bedoeld voor de link naar de huidige pagina
   of voor een enkele nadrukkelijke actie, vergelijkbaar met de permanente
   streep bij aria-current in de menu-overlay. */
.v-mark-link:hover,
.v-mark-link:focus-visible,
.v-mark-link.is-drawn {
  color: var(--accent);
}

/* Ontbrak: de belangrijkste link van de body gaf geen indrukfeedback. */
.v-mark-link:active {
  transform: scale(0.97);
}

.v-mark-link:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 4px;
}

/* Op donkere vlakken heeft --accent te weinig contrast tegen --blauw
   (1,4:1). Goud haalt daar 4,9:1. Het merkteken erft currentColor en
   hoeft dus niets extra's. */
.on-dark .v-mark-link,
.surface-blueprint .v-mark-link {
  color: var(--text-on-dark);
}

.on-dark .v-mark-link:hover,
.on-dark .v-mark-link:focus-visible,
.on-dark .v-mark-link.is-drawn,
.surface-blueprint .v-mark-link:hover,
.surface-blueprint .v-mark-link:focus-visible,
.surface-blueprint .v-mark-link.is-drawn {
  color: var(--goud);
}

/* De twee acties in de blauwe band op de homepage. Staan hier en niet bij
   de rest van .cta-band in style.css, omdat ze in bronvolgorde achter het
   .on-dark-blok hierboven moeten staan om te winnen; components.css laadt
   na style.css. Zelfde plek en zelfde reden als de tijdlijnuitzondering
   verderop in dit bestand.

   .on-dark staat bewust in de selector en niet alleen de eigen class. Een
   losse .cta-band__primary is (0,1,0) en verliest van .on-dark .v-mark-link
   (0,2,0); bronvolgorde breekt alleen een gelijke stand, geen verschil in
   specificiteit. Gemeten toen dat wel zo stond: beide labels kwamen op
   rgb(250, 249, 246) uit en het kleurverschil tussen primair en secundair
   was er niet.

   De Newsreader-actierol gebruikt compacte tracking; kleur en
   volgorde houden de primaire en secundaire actie uit elkaar. */
.on-dark .cta-band__primary {
  color: var(--goud);
  font-size: 1rem;
}

/* Rust en hover staan allebei op goud. De affordance van dit component is
   het merkteken dat zichzelf schrijft en niet de kleurwissel; dat staat al
   zo op de blauwe tijdlijnpanelen. Zou de primaire actie bij hover naar de
   gewone .on-dark-hoverkleur gaan, dan werd hij daar identiek aan de
   secundaire actie in hover en viel de hiërarchie precies op het verkeerde
   moment weg. */
.on-dark .cta-band__primary:hover,
.on-dark .cta-band__primary:focus-visible {
  color: var(--goud);
}

/* Ivoor op 72 procent haalt 6,70:1 op --accent en zit dus ruim boven de
   4,5:1 die 12px vraagt. color-mix zodat er geen vierde ivoortint in de
   tokens komt. Bij hover naar vol ivoor: een brightening, zodat de
   secundaire actie reageert zonder de kleur van de primaire te lenen. */
.on-dark .cta-band__secondary {
  color: color-mix(in srgb, var(--text-on-dark) 72%, transparent);
  font-size: 1rem;
}

.on-dark .cta-band__secondary:hover,
.on-dark .cta-band__secondary:focus-visible {
  color: var(--text-on-dark);
}

.v-mark {
  flex: none;
  height: var(--v-mark-h);
  width: auto;
  display: block;
  overflow: visible;
  shape-rendering: geometricPrecision;
}

.v-mark__pen path {
  fill: none;
  stroke: currentColor;
  stroke-width: 19;
  stroke-linecap: butt;
  stroke-linejoin: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset var(--motion-v-mark-erase) var(--ease-v-mark);
}

.v-mark__rule {
  fill: none;
  stroke: currentColor;
  stroke-width: var(--v-mark-rule-weight);
  stroke-linecap: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 0;
  transition: stroke-dashoffset var(--motion-v-mark-rule-in) var(--ease-v-mark)
    var(--motion-v-mark-rule-in-start);
}

.v-mark-link:focus-visible .v-mark__pen path,
.v-mark-link.is-drawn .v-mark__pen path {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset var(--motion-v-mark-draw) var(--ease-v-mark)
    var(--motion-v-mark-draw-start);
}

.v-mark-link:focus-visible .v-mark__rule,
.v-mark-link.is-drawn .v-mark__rule {
  stroke-dashoffset: 1;
  transition: stroke-dashoffset var(--motion-v-mark-rule-out) var(--ease-v-mark);
}

/* Hover apart gescopet achter een fine-pointerquery, naar hetzelfde patroon
   als .nav-overlay__menu a hierboven: op touch blijft een aanraking anders
   als hover hangen. Toetsenbordgebruikers krijgen dezelfde bevestiging via
   :focus-visible hierboven. */
@media (hover: hover) and (pointer: fine) {
  .v-mark-link:hover .v-mark__pen path {
    stroke-dashoffset: 0;
    transition: stroke-dashoffset var(--motion-v-mark-draw) var(--ease-v-mark)
      var(--motion-v-mark-draw-start);
  }

  .v-mark-link:hover .v-mark__rule {
    stroke-dashoffset: 1;
    transition: stroke-dashoffset var(--motion-v-mark-rule-out) var(--ease-v-mark);
  }
}

/* Reduced motion: geen schrijfbeweging, wel de toestandswissel. Het streepje
   en het logo verwisselen direct van plaats, zodat de bevestiging blijft maar
   de beweging verdwijnt. De globale vangnetregel bovenaan style.css klemt
   alle duur al op 0,01ms; dit blok legt vast dat dat hier de bedoelde uitkomst
   is en niet per ongeluk een halve animatie oplevert. */
@media (prefers-reduced-motion: reduce) {
  .v-mark__pen path,
  .v-mark__rule {
    transition-duration: .01ms !important;
    transition-delay: 0ms !important;
  }
}

/* ============================================================
   Zwevend beeld (.floating-media)

   Eén component voor de ruimtebanden en voor de omgevingsfoto's in de
   locatiesectie, zodat die twee als één site lezen in plaats van als twee
   losse ideeën. Drie dingen tegelijk en met niets meer:

   1. Schaduw, altijd aan. Twee lagen, want één laag leest als een sticker
      en twee lagen lezen als hoogte. De kleur is de inkt van de site met
      alpha (--text, #1A1A18) en geen zuiver zwart: zwart op ivoor wordt vuil.
   2. Parallax bij het scrollen, aangestuurd door js/floating-media.js.
   3. Optillen bij hover, alleen op een echte muis.

   Waarom er een aparte __frame binnen __media zit. De parallax verschuift
   het beeld BINNEN zijn kader, dus dat kader moet clippen. Een element met
   overflow: hidden clipt echter ook het pseudo-element waarmee de zwaardere
   hoverschaduw wordt gekruisfade, en dan is die schaduw onzichtbaar. De
   buitenste laag draagt daarom de schaduwen zonder te clippen, en de
   binnenste laag clipt zonder schaduw. De transform gaat, precies zoals
   voorgeschreven, uitsluitend naar de <picture> en nooit naar de band of
   naar de <img>. ============================================ */
.floating-media {
  position: relative;
  box-shadow:
    0 18px 40px -18px rgba(26, 26, 24, .28),
    0 2px 6px -2px rgba(26, 26, 24, .16);
}

/* Zwaardere schaduw, vooraf klaargelegd op opacity 0. Alleen die opacity
   beweegt bij hover: box-shadow animeren kost een paint per frame. */
.floating-media::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: 0;
  pointer-events: none;
  box-shadow:
    0 30px 60px -20px rgba(26, 26, 24, .34),
    0 4px 10px -3px rgba(26, 26, 24, .2);
  transition: opacity 220ms var(--ease-lebarn);
}

.floating-media__frame {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: var(--surface-alt);
}

.floating-media__frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* De parallax verschuift het beeld tot 28px binnen dit kader. Zonder extra
   hoogte zou daardoor aan één kant een strook achtergrond zichtbaar worden. */
.floating-media__frame > picture {
  display: block;
  width: 100%;
  height: calc(100% + 56px);
  margin-top: -28px;
  will-change: transform;
}

/* Plaatshouder zolang de fotografie er nog niet is. Zelfde kader, zelfde
   verhouding en zelfde schaduw als de echte foto, zodat het beeld er later
   in schuift zonder dat de layout verspringt. Bewust een regel tekst en
   geen lijntekening of icoon: dan is meteen duidelijk wat er hoort te komen. */
.floating-media__placeholder {
  margin: 0;
  padding: 0 var(--space-6);
  max-width: none;
  color: var(--text-muted);
  font-family: var(--font-body);
  font-size: var(--meta);
  font-weight: 500;
  font-variation-settings: "opsz" 8;
  letter-spacing: 0.16em;
  line-height: 1.6;
  text-align: center;
  text-transform: uppercase;
}

@media (hover: hover) and (pointer: fine) {
  .floating-media {
    transition: transform 220ms var(--ease-lebarn);
  }

  .room-band:hover .floating-media,
  .floating-media:hover {
    transform: translateY(-6px);
  }

  .room-band:hover .floating-media::after,
  .floating-media:hover::after {
    opacity: 1;
  }
}

/* Geen parallax en geen optillen. De schaduw blijft: het beeld zweeft dan
   stil, en dat is nog steeds het effect. */
@media (prefers-reduced-motion: reduce) {
  .floating-media__frame > picture {
    transform: none !important;
    will-change: auto;
  }

  .room-band:hover .floating-media,
  .floating-media:hover {
    transform: none;
  }
}

/* ============================================================
   Tijdlijn (index.html, tussen de galerij en de locatie)

   Sinds 28 augustus 2026 HORIZONTAAL: het toneel zet zich vast op
   position: sticky en verticaal scrollen wordt zijwaartse beweging van de
   track. De verticale as met punten op schaal die hier stond is vervangen.

   De schaal is niet verdwenen, hij is verhuisd naar de rail onderin. De
   panelen zijn allemaal even breed (een paneel van zeventig jaar breed zou
   onleesbaar zijn), maar de rail loopt wel op ware schaal van 1871 tot 2026.
   Sinds 8 september 2026 zijn er twaalf onderbouwde panelen. De afstand
   tussen de jaartallen bepaalt de snelheid van het merkteken.

   NERGENS preventDefault op wheel of touchmove. De galerij hierboven mag de
   scroll overnemen omdat daar een elastiek wordt gesimuleerd; de tijdlijn
   heeft dat niet nodig en mag het dus niet doen. Scrollbalk, spatiebalk,
   Page Down, Home en End blijven werken.
   ============================================================ */
.tijdlijn {
  position: relative;
  background: var(--surface);
}

/* Statische sectiekop als basis: zo staat hij zonder JavaScript, onder
   900px en bij beperkte beweging gewoon boven het eerste paneel. Op desktop
   ligt hij sinds 14 september 2026 bovenin het sticky toneel, over het
   openingspaneel, en vervaagt hij op de horizontale voortgang. */
.tijdlijn__aanloop {
  padding-block: clamp(2rem, 4vh, 3rem) clamp(1rem, 2vh, 1.5rem);
  background: var(--surface);
}

.tijdlijn__aanloop h2 {
  margin: 0;
  color: var(--blauw);
  font-size: clamp(4rem, 13vw, 13rem);
  line-height: 1;
  letter-spacing: -.045em;
  text-align: center;
}

.tijdlijn__pin {
  position: relative;
}

.tijdlijn__stage {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
  background: var(--ivoor);
  transition: background-color var(--motion-tijdlijn-grond) var(--ease-lebarn);
}

/* De grondkleur van het toneel staat verderop, binnen .js-tijdlijn. Zij hoort
   daar en niet hier: het degradatieblok onderaan zet .tijdlijn__stage terug op
   --surface, en dat blok komt later in het bestand. Op deze plek zou een
   [data-grond]-regel dezelfde specificiteit hebben en dus verliezen, waardoor
   het toneel permanent ivoor blijft. Dat was aantoonbaar het geval. */

.tijdlijn__track {
  display: flex;
  height: 100%;
  will-change: transform;
}

.tijdlijn__paneel {
  position: relative;
  flex: 0 0 100vw;
  height: 100%;
  overflow: hidden;
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  align-content: center;
  gap: 0 var(--space-gutter);
  padding: clamp(4rem, 9vh, 7.5rem) var(--pad-x) clamp(6rem, 14vh, 9.5rem);
}

/* Het reuzejaartal loopt met een kleine marge de rand van het paneel uit,
   aan de kant van het BEELD en niet aan de kant van de tekst: een cijfer van
   34vh achter lopende tekst leest slecht, ook toon op toon. */
.tijdlijn__jaar {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--font-display);
  font-variation-settings: "opsz" 72;
  font-weight: 600;
  font-size: min(34vh, 26vw);
  line-height: .8;
  letter-spacing: -.03em;
  font-variant-numeric: tabular-nums;
  pointer-events: none;
  user-select: none;
  white-space: nowrap;
}

.tijdlijn__paneel--links  .tijdlijn__jaar { right: clamp(-28px, -2vw, 0px); }
.tijdlijn__paneel--rechts .tijdlijn__jaar { left:  clamp(-28px, -2vw, 0px); }

.tijdlijn__beeld {
  position: relative;
  z-index: 2;
  align-self: center;
}

.tijdlijn__paneel--links  .tijdlijn__beeld { grid-column: 7 / 13; }
.tijdlijn__paneel--rechts .tijdlijn__beeld { grid-column: 1 / 7; }

/* Zolang er geen fotografie is: een vlak met dezelfde verhouding en dezelfde
   schaduw, met de id van de foto die daar hoort te komen. Zo verschuift de
   opmaak niet zodra het beeld er is. Dezelfde afspraak als bij de
   ruimtebanden, zie .floating-media__placeholder. */
.tijdlijn__plaats {
  aspect-ratio: 4 / 3;
  width: 100%;
  display: grid;
  place-items: center;
  text-align: center;
  padding: var(--space-6);
  box-shadow:
    0 22px 48px -24px rgba(26, 26, 24, .42),
    0 3px 8px -3px rgba(26, 26, 24, .22);
  will-change: transform;
}

.tijdlijn__plaats span {
  font-family: var(--font-body);
  font-variation-settings: "opsz" 8;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .15em;
  text-transform: uppercase;
  line-height: 1.7;
}

.tijdlijn__tekst {
  position: relative;
  z-index: 2;
  align-self: center;
  display: grid;
  gap: var(--space-4);
  max-width: 70ch;
}

.tijdlijn__paneel--links  .tijdlijn__tekst { grid-column: 1 / 6; }
.tijdlijn__paneel--rechts .tijdlijn__tekst { grid-column: 8 / 13; }

.tijdlijn__label {
  font-family: var(--font-body);
  font-variation-settings: "opsz" 8;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
}

.tijdlijn__kop {
  margin: 0;
  font-family: var(--font-display);
  font-variation-settings: "opsz" 32;
  font-weight: 600;
  font-size: clamp(1.4rem, 2.4vw, 2rem);
  line-height: 1.12;
  letter-spacing: -.015em;
}

.tijdlijn__tekst p {
  margin: 0;
  font-size: 1rem;
  max-width: 70ch;
}

/* ---------- De drie gronden ----------

   Gemeten op 28 augustus 2026 tegen de WERKELIJKE tokens van dit project.
   Het ontwerpprototype rekende met een lichter goud (#C7A34A); dit project
   heeft --goud: #C49A20 en dat verschuift twee waarden:

   - Blauw op goud haalt 3,55:1 in plaats van 3,90. Genoeg voor grote tekst
     (AA-large eist 3,0) en dus voor de kop en het reuzejaartal, maar het is
     krapper dan het prototype aannam. Verklein de kop hier niet.
   - "inkt op 62 procent" voor het label op goud haalt maar 3,24:1 en zakt
     dus onder de 4,5 die een label van 12px nodig heeft. Het label staat
     daarom in vol --text (6,63:1).
   - Goud als label op blauw haalt 3,55:1 en zakt eveneens onder 4,5. Het
     label staat daarom in --ivoor (8,23:1) en niet in goud. Een lichtere
     goudtint verzinnen mag niet: twee tinten van dezelfde kleur naast
     elkaar bestaat niet in dit project.

   Dat label en lopende tekst op blauw en goud dezelfde kleur hebben is geen
   omissie: de hiërarchie komt hier van grootte, kapitalen en tracking,
   precies zoals bij de telling naast de galerijtitel. */
.tijdlijn__paneel[data-grond="ivoor"] {
  background: var(--ivoor);
  color: var(--text);
}

.tijdlijn__paneel[data-grond="ivoor"] .tijdlijn__jaar {
  color: color-mix(in srgb, var(--blauw) 14%, var(--ivoor));
}

.tijdlijn__paneel[data-grond="ivoor"] .tijdlijn__kop   { color: var(--blauw); }
.tijdlijn__paneel[data-grond="ivoor"] .tijdlijn__label { color: var(--text-muted); }

.tijdlijn__paneel[data-grond="ivoor"] .tijdlijn__plaats {
  background: var(--surface-alt);
  color: var(--text-muted);
  border: 1px solid var(--rule);
}

.tijdlijn__paneel[data-grond="blauw"] {
  background: var(--blauw);
  color: var(--ivoor);
}

.tijdlijn__paneel[data-grond="blauw"] .tijdlijn__jaar {
  color: color-mix(in srgb, #FFFFFF 16%, var(--blauw));
}

.tijdlijn__paneel[data-grond="blauw"] .tijdlijn__kop   { color: var(--ivoor); }
.tijdlijn__paneel[data-grond="blauw"] .tijdlijn__label { color: var(--ivoor); }
.tijdlijn__paneel[data-grond="blauw"] .tijdlijn__tekst p { color: var(--text-on-dark); }

.tijdlijn__paneel[data-grond="blauw"] .tijdlijn__plaats {
  background: color-mix(in srgb, #000000 14%, var(--blauw));
  color: var(--ivoor);
  border: 1px solid rgba(245, 240, 232, .28);
}

.tijdlijn__paneel[data-grond="goud"] {
  background: var(--goud);
  color: var(--text);
}

.tijdlijn__paneel[data-grond="goud"] .tijdlijn__jaar {
  color: color-mix(in srgb, var(--text) 13%, var(--goud));
}

.tijdlijn__paneel[data-grond="goud"] .tijdlijn__kop   { color: var(--text); }
.tijdlijn__paneel[data-grond="goud"] .tijdlijn__label { color: var(--text); }
.tijdlijn__paneel[data-grond="goud"] .tijdlijn__tekst p { color: var(--text); }

.tijdlijn__paneel[data-grond="goud"] .tijdlijn__plaats {
  background: color-mix(in srgb, var(--text) 17%, var(--goud));
  color: var(--text);
  border: 1px solid rgba(26, 26, 24, .28);
}

/* ---------- Twee bijzondere panelen ---------- */

.tijdlijn__paneel--opening {
  place-content: center;
  text-align: center;
}

.tijdlijn__paneel--opening .tijdlijn__tekst,
.tijdlijn__paneel--leeg .tijdlijn__tekst {
  grid-column: 3 / 11;
  justify-items: center;
  text-align: center;
  max-width: none;
}

.tijdlijn__paneel--opening .tijdlijn__kop {
  font-variation-settings: "opsz" 72;
  font-size: clamp(2rem, 4.4vw, 3.4rem);
  letter-spacing: -.022em;
}

/* Op deze twee gecentreerde panelen zou de 38ch van de lopende tekst een
   smalle koker midden op een breed vlak opleveren. */
.tijdlijn__paneel--opening .tijdlijn__tekst p,
.tijdlijn__paneel--leeg .tijdlijn__tekst p {
  max-width: var(--measure);
}

/* De .v-mark-link per paneel is exact het bestaande component. Op de blauwe
   panelen staat .on-dark op de tekstkolom, en die geeft de rusttoestand
   --text-on-dark (8,86:1 op --blauw) plus een gouden focusring.

   Eén ding wijkt af, en dat is gemeten. .on-dark zet de HOVER-kleur op goud,
   en dat klopt op --accent (#041790), waar goud 4,9:1 haalt. De panelen hier
   staan op --blauw (#1A3BAA), dat lichter is, en daar zakt goud naar 3,55:1.
   Dat is te weinig voor een label van deze grootte. De kleur blijft op blauw
   daarom staan bij hover; de affordance van dit component is het merkteken
   dat zichzelf schrijft, niet de kleurwissel. Een lichtere goudtint verzinnen
   mag niet: twee tinten van dezelfde kleur bestaat niet in dit project. */
.tijdlijn__paneel[data-grond="blauw"] .v-mark-link:hover,
.tijdlijn__paneel[data-grond="blauw"] .v-mark-link:focus-visible,
.tijdlijn__paneel[data-grond="blauw"] .v-mark-link.is-drawn {
  color: var(--text-on-dark);
}

/* ---------- Eén omtrek-V per paneel ----------

   De plek staat reproduceerbaar in data-v-plek op het merkteken zelf. Het
   teken ligt achter tekst en beeld, zodat een fotohoek het als een echte
   laag kan afsnijden zonder de inhoud minder leesbaar te maken. */
.tijdlijn__paneel-v {
  position: absolute;
  z-index: 1;
  height: min(30vh, 26vw);
  color: var(--blauw);
  opacity: .16;
  pointer-events: none;
  will-change: transform;
}

.tijdlijn__paneel-v svg {
  display: block;
  width: auto;
  height: 100%;
  overflow: visible;
}

.tijdlijn__paneel-v path {
  fill: none;
  stroke: currentColor;
  stroke-width: 2px;
  vector-effect: non-scaling-stroke;
}

.tijdlijn__paneel-v[data-v-plek="linksboven"]  { left: 8%;  top: 10%; }
.tijdlijn__paneel-v[data-v-plek="rechtsboven"] { left: 66%; top: 6%; }
.tijdlijn__paneel-v[data-v-plek="middenrechts"] { left: 70%; top: 34%; }
.tijdlijn__paneel-v[data-v-plek="rechtsonder"] { left: 62%; top: 58%; }
.tijdlijn__paneel-v[data-v-plek="middenonder"] { left: 40%; top: 66%; }
.tijdlijn__paneel-v[data-v-plek="linksonder"]  { left: 6%;  top: 60%; }
.tijdlijn__paneel-v[data-v-plek="middenlinks"] { left: 4%;  top: 30%; }
.tijdlijn__paneel-v[data-v-plek="hoogmidden"]  { left: 42%; top: 4%; }

.tijdlijn__paneel[data-grond="blauw"] .tijdlijn__paneel-v {
  color: var(--ivoor);
  opacity: .22;
}

.tijdlijn__paneel[data-grond="goud"] .tijdlijn__paneel-v {
  color: var(--text);
  opacity: .18;
}

/* Op de gestapelde smalle layout raken juist deze twee lage plekken de
   tekstkolom. De andere zes merktekens blijven als rustige verbindingslaag
   staan; de DOM houdt nog steeds exact één V per paneel. */
@media (max-width: 900px) {
  .tijdlijn__paneel-v[data-v-plek="middenonder"],
  .tijdlijn__paneel-v[data-v-plek="rechtsonder"] {
    display: none;
  }
}

/* ---------- De rail, met de meelopende V ---------- */
.tijdlijn__rail {
  position: absolute;
  left: var(--pad-x);
  right: var(--pad-x);
  bottom: clamp(1.875rem, 5vh, 3.375rem);
  height: 44px;
  z-index: 5;
}

.tijdlijn__rail-lijn {
  position: absolute;
  left: 0;
  right: 0;
  top: 30px;
  height: 3px;
  background: var(--blauw);
  opacity: .9;
  transition: background-color var(--motion-tijdlijn-grond) var(--ease-lebarn);
}

.tijdlijn__tik {
  position: absolute;
  top: 22px;
  width: 3px;
  height: 19px;
  padding: 0;
  border: 0;
  background: var(--blauw);
  transform: translateX(-1px);
  cursor: pointer;
  transition:
    background-color var(--motion-tijdlijn-grond) var(--ease-lebarn),
    height var(--motion-tijdlijn-tik) var(--ease-lebarn),
    top var(--motion-tijdlijn-tik) var(--ease-lebarn);
}

.tijdlijn__tik:hover,
.tijdlijn__tik:focus-visible { height: 27px; top: 18px; }

.tijdlijn__rail-jaar {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  white-space: nowrap;
  font-family: var(--font-body);
  font-variation-settings: "opsz" 8;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .14em;
  font-variant-numeric: tabular-nums;
  opacity: .72;
  pointer-events: none;
  transition: opacity var(--motion-tijdlijn-tik) var(--ease-lebarn);
}

/* De aanwijzer is het echte huisstijlteken, gevuld, niet het vroegere
   getekende pijltje. */
.tijdlijn__rail-v {
  position: absolute;
  top: 15px;
  width: 22px;
  margin-left: -11px;
  pointer-events: none;
  will-change: transform;
}

.tijdlijn__rail-v svg { display: block; width: 100%; height: auto; overflow: visible; }

.tijdlijn__rail-v path {
  fill: var(--blauw);
  transition: fill var(--motion-tijdlijn-grond) var(--ease-lebarn);
}

.tijdlijn__rail-v-jaar {
  position: absolute;
  bottom: 26px;
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  font-family: var(--font-body);
  font-variation-settings: "opsz" 8;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .14em;
  font-variant-numeric: tabular-nums;
  color: var(--blauw);
}

.tijdlijn__stage[data-grond="blauw"] .tijdlijn__rail-lijn,
.tijdlijn__stage[data-grond="blauw"] .tijdlijn__tik        { background: var(--ivoor); }
.tijdlijn__stage[data-grond="blauw"] .tijdlijn__rail-v path { fill: var(--ivoor); }
.tijdlijn__stage[data-grond="blauw"] .tijdlijn__rail-jaar,
.tijdlijn__stage[data-grond="blauw"] .tijdlijn__rail-v-jaar { color: var(--ivoor); }

.tijdlijn__stage[data-grond="goud"] .tijdlijn__rail-lijn,
.tijdlijn__stage[data-grond="goud"] .tijdlijn__tik         { background: var(--text); }
.tijdlijn__stage[data-grond="goud"] .tijdlijn__rail-v path  { fill: var(--text); }
.tijdlijn__stage[data-grond="goud"] .tijdlijn__rail-jaar,
.tijdlijn__stage[data-grond="goud"] .tijdlijn__rail-v-jaar  { color: var(--text); }

.tijdlijn__stage[data-grond="ivoor"] .tijdlijn__rail-jaar,
.tijdlijn__stage[data-grond="ivoor"] .tijdlijn__rail-v-jaar { color: var(--blauw); }

/* ---------- Kale header zolang het toneel vastzit ----------

   Alleen de drie burgerstreepjes blijven staan; het label Menu, het
   V-monogram en de drie snelkoppelingen gaan weg, en de balk zelf wordt
   doorzichtig. Dat geeft de panelen de volle schermhoogte terug.

   De knop zelf blijft bestaan en blijft bedienbaar, dus het menu is nog
   gewoon te openen; alleen zijn zichtbare tekst verdwijnt. De streepjes
   erven hun kleur van de grond van het actieve paneel, want een blauwe
   header op een blauw paneel is onzichtbaar.

   Dit is de enige toegestane wijziging aan de bevroren header buiten de
   volgorde van de snelkoppelingen, en de eigenaar heeft er op 28 augustus
   2026 om gevraagd. Hij is volledig additief en geldt alleen zolang
   html.tijdlijn-kaal staat, dus buiten de tijdlijn is de header ongemoeid. */
.tijdlijn-kaal .site-header {
  background-color: transparent;
  transition: color var(--motion-nav-color) var(--ease-nav);
}

.tijdlijn-kaal .site-header::before { opacity: 0; }

.tijdlijn-kaal .nav__burger-label,
.tijdlijn-kaal .nav__logo,
.tijdlijn-kaal .nav__secondary {
  opacity: 0;
  pointer-events: none;
}

.tijdlijn-kaal .nav__burger-label,
.tijdlijn-kaal .nav__logo,
.tijdlijn-kaal .nav__secondary,
.tijdlijn-kaal .site-header {
  transition-duration: var(--motion-nav-color);
}

.tijdlijn-kaal[data-tijdlijn-grond="ivoor"] .site-header { color: var(--blauw); }
.tijdlijn-kaal[data-tijdlijn-grond="blauw"] .site-header { color: var(--ivoor); }
.tijdlijn-kaal[data-tijdlijn-grond="goud"]  .site-header { color: var(--text); }

/* Het menu zelf blijft volwaardig: zodra de overlay open is komt de header
   terug, anders zou de sluitknop naamloos zijn. */
.tijdlijn-kaal.nav-locked .nav__burger-label { opacity: 1; }

/* ---------- Degradatie ----------

   Onder 900px en bij reduced motion bestaat de hele constructie niet: geen
   pin, geen horizontale beweging, geen rail en geen grote V. De panelen
   staan onder elkaar en houden hun eigen grond, dus je krijgt een reeks
   kleurvlakken. Dat is een prima mobiele tijdlijn.

   Dit is tegelijk de no-JS-toestand. De sticky doet zonder JavaScript
   niets, want dan krijgt .tijdlijn__pin geen hoogte, maar het toneel zou
   dan wel op 100svh met overflow: hidden staan en de panelen afsnijden.
   Daarom hangt de horizontale opmaak aan html.js-tijdlijn, die het script
   pas zet wanneer het de constructie daadwerkelijk opbouwt. */
.tijdlijn__stage {
  position: static;
  height: auto;
  overflow: visible;
  background: var(--surface);
}

.tijdlijn__track { display: block; }

.tijdlijn__paneel {
  flex: none;
  width: 100%;
  height: auto;
  min-height: 78svh;
}

.tijdlijn__jaar {
  position: static;
  transform: none;
  grid-column: 1 / 13;
  font-size: min(18vh, 30vw);
  margin-bottom: var(--space-2);
}

.tijdlijn__paneel--links .tijdlijn__beeld,
.tijdlijn__paneel--rechts .tijdlijn__beeld { grid-column: 1 / 13; margin-bottom: var(--space-8); }

.tijdlijn__paneel--links .tijdlijn__tekst,
.tijdlijn__paneel--rechts .tijdlijn__tekst,
.tijdlijn__paneel--opening .tijdlijn__tekst,
.tijdlijn__paneel--leeg .tijdlijn__tekst {
  grid-column: 1 / 13;
  max-width: none;
  justify-items: start;
  text-align: left;
}

.tijdlijn__rail { display: none; }

/* De horizontale opmaak komt er pas bij wanneer het script hem opbouwt. */
@media (min-width: 901px) {
  .js-tijdlijn .tijdlijn__stage {
    position: sticky;
    top: 0;
    height: 100svh;
    overflow: hidden;
    background: var(--ivoor);
  }

  .js-tijdlijn .tijdlijn__track { display: flex; }

  /* De kop ligt over het openingspaneel, gecentreerd in dezelfde kolom als
     de paneeltekst, en onder de rail (die komt later in de stage en heeft
     z-index 5). --tijdlijn-voortgang zet js/tijdlijn.js op de pin: de
     horizontale voortgang 0..1. Bij 0,12 is de kop weg; hij verschuift
     daarbij hoogstens 24px omhoog. Geen transitie: de waarde volgt de
     scroll, een transitie erbovenop geeft naloop. De CSS-default zonder
     JavaScript is opacity 1. */
  .js-tijdlijn .tijdlijn__aanloop {
    position: absolute;
    inset: 0 0 auto;
    z-index: 3;
    background: transparent;
    padding: calc(var(--header-h) + clamp(.5rem, 2vh, 1.5rem)) 0 0;
    pointer-events: none;
    opacity: clamp(0, calc(1 - var(--tijdlijn-voortgang, 0) / 0.12), 1);
    transform: translate3d(0, calc(min(var(--tijdlijn-voortgang, 0) / 0.12, 1) * -24px), 0);
    transition: none;
    will-change: transform, opacity;
  }

  .js-tijdlijn .tijdlijn__aanloop h2 {
    font-size: var(--page-title);
    line-height: 1;
  }

  .js-tijdlijn .tijdlijn__aanloop.is-weg { pointer-events: none; }

  /* Ruimte bovenin het openingspaneel voor de kop erboven, zodat het
     reuzejaartal 1871–2026 er nooit tegenaan loopt: headerhoogte, de
     bovenmarge van de kop, de kop zelf en een tussenruimte. Twee klassen
     op het paneel, want .js-tijdlijn .tijdlijn__paneel zet verderop (ook
     in het blok voor lage schermen) een eigen padding-top op dezelfde
     specificiteit. */
  .js-tijdlijn .tijdlijn__paneel.tijdlijn__paneel--opening {
    padding-top: calc(var(--header-h) + clamp(.5rem, 2vh, 1.5rem) + var(--page-title) + clamp(1.5rem, 5vh, 3.5rem));
  }

  .js-tijdlijn .tijdlijn__aanloop .container { width: 100%; }

  .js-tijdlijn .tijdlijn__paneel {
    flex: 0 0 100vw;
    width: auto;
    height: 100%;
    min-height: 0;
  }

  /* Het jaartal is het hoofdonderwerp van een paneel en moet dus LEESBAAR
     zijn. Achter de foto gecentreerd wás het dat niet: op 1440x900 mat het
     750x245px tegen een foto van 652x489px, allebei verticaal gecentreerd,
     zodat er links 76px en rechts 21px uitstak en de rest erachter verdween.
     Het ontwerpprototype had exact hetzelfde probleem.

     Groter maken alleen loste het niet op: dan liep het cijfer juist achter
     de TEKSTkolom, en tekst over een cijfer van deze maat leest nog slechter
     dan een half verstopt cijfer.

     Het jaartal staat daarom nu op een eigen rij bóven de inhoud, in de
     ruimte die vrijkwam door de kale header. Het bleedt met een klein
     negatief marge de rand van het paneel uit, aan de kant van het beeld,
     en overlapt niets meer. Zet het niet terug op absolute centrering. */
  .js-tijdlijn .tijdlijn__paneel {
    grid-template-rows: auto auto;
    row-gap: clamp(1rem, 3vh, 2.25rem);
    padding-top: clamp(2rem, 5vh, 4rem);
  }

  .js-tijdlijn .tijdlijn__jaar {
    position: relative;
    top: auto;
    transform: none;
    grid-row: 1;
    grid-column: 1 / 13;
    font-size: min(32vh, 26vw);
    line-height: .78;
    margin-bottom: 0;
  }

  .js-tijdlijn .tijdlijn__paneel--links .tijdlijn__jaar {
    text-align: right;
    margin-right: -.045em;
  }

  .js-tijdlijn .tijdlijn__paneel--rechts .tijdlijn__jaar {
    text-align: left;
    margin-left: -.045em;
  }

  .js-tijdlijn .tijdlijn__beeld,
  .js-tijdlijn .tijdlijn__tekst { grid-row: 2; }

  .js-tijdlijn .tijdlijn__paneel--links  .tijdlijn__beeld { grid-column: 8 / 13; margin-bottom: 0; }
  .js-tijdlijn .tijdlijn__paneel--rechts .tijdlijn__beeld { grid-column: 1 / 6;  margin-bottom: 0; }
  .js-tijdlijn .tijdlijn__paneel--links  .tijdlijn__tekst { grid-column: 1 / 7;  max-width: 70ch; }
  .js-tijdlijn .tijdlijn__paneel--rechts .tijdlijn__tekst { grid-column: 7 / 13; max-width: 70ch; }

  .js-tijdlijn .tijdlijn__paneel--opening .tijdlijn__tekst,
  .js-tijdlijn .tijdlijn__paneel--leeg .tijdlijn__tekst {
    grid-column: 3 / 11;
    justify-items: center;
    text-align: center;
  }

  .js-tijdlijn .tijdlijn__rail { display: block; }

  /* Het toneel neemt de grond over van het paneel dat het midden van het
     scherm vult. Puur een vangnet: de panelen dragen hun eigen achtergrond,
     maar zonder dit flitst een subpixelnaad tussen twee panelen even in de
     verkeerde kleur op. */
  .js-tijdlijn .tijdlijn__stage[data-grond="blauw"] { background: var(--blauw); }
  .js-tijdlijn .tijdlijn__stage[data-grond="goud"]  { background: var(--goud); }
}

@media (prefers-reduced-motion: reduce) {
  .tijdlijn__stage,
  .tijdlijn__rail-lijn,
  .tijdlijn__tik,
  .tijdlijn__rail-v path { transition: none; }

  .tijdlijn__paneel-v {
    transform: none !important;
    will-change: auto;
  }
}

/* Tijdlijnbeelden en onderzoeksverantwoording, opdracht 10.
   Eigen opnamedatum blijft zichtbaar; de foto's houden hun hele uitsnede.
   Familiewapens zijn klein, in kleur en uitsluitend bij hun familienaam. */
.tijdlijn__beeld {
  margin: 0;
  min-width: 0;
}

.tijdlijn__beeld picture {
  display: block;
}

.tijdlijn__beeld img {
  display: block;
  width: 100%;
  height: auto;
  max-height: 42svh;
  object-fit: contain;
  object-position: center;
  filter: none;
}

.tijdlijn__beeld figcaption,
.tijdlijn__wapen figcaption {
  font-family: var(--font-body);
  font-variation-settings: "opsz" 12;
  font-size: 0.8125rem;
  line-height: 1.45;
  color: inherit;
}

.tijdlijn__beeld figcaption {
  margin-top: var(--space-3);
  max-width: 70ch;
}

.tijdlijn__wapens {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
}

.tijdlijn__wapen {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: var(--space-3);
  flex: 1;
  margin: 0;
  min-width: 0;
}

.tijdlijn__wapen img {
  width: 4.5rem;
  height: 4rem;
  object-fit: contain;
  filter: none;
}

.tijdlijn__tekst { min-width: 0; }
.tijdlijn__tekst > .v-mark-link { justify-self: start; }

.tijdlijn__paneel--opening .tijdlijn__kop {
  font-size: clamp(3rem, 9vw, 9rem);
  line-height: 1;
  white-space: nowrap;
  letter-spacing: -.04em;
}

.tijdlijn__paneel--opening > .v-mark-link {
  position: absolute;
  right: var(--pad-x);
  bottom: clamp(6rem, 14vh, 9rem);
  z-index: 2;
  font-size: .9375rem;
}

@media (max-width: 900px), (prefers-reduced-motion: reduce) {
  .tijdlijn__aanloop { padding-top: calc(var(--header-h) + 2rem); }
  .tijdlijn__paneel--opening > .v-mark-link {
    position: static;
    grid-column: 1 / 13;
    justify-self: end;
    margin-top: 2rem;
  }
}

/* Op goud haalt het gewone linkblauw geen 4,5:1 bij een klein label. */
.tijdlijn__paneel[data-grond="goud"] .v-mark-link,
.tijdlijn__paneel[data-grond="goud"] .v-mark-link:hover,
.tijdlijn__paneel[data-grond="goud"] .v-mark-link:focus-visible {
  color: var(--text);
}

.tijdlijn__verantwoording { padding-block: var(--space-8); }

/* Extra raakruimte rond de bestaande maatstreep, zonder de ware schaal
   of de positie van het streepje te wijzigen. */
.tijdlijn__tik::before {
  content: "";
  position: absolute;
  inset: -12px -6px;
}

.tijdlijn__tik:focus-visible { outline: 2px solid currentColor; outline-offset: 4px; }
.tijdlijn__stage[data-grond="blauw"] .tijdlijn__tik { color: var(--ivoor); }
.tijdlijn__stage[data-grond="goud"] .tijdlijn__tik { color: var(--text); }
.tijdlijn__stage[data-grond="ivoor"] .tijdlijn__tik { color: var(--blauw); }

@media (max-width: 900px) {
  .tijdlijn__beeld { grid-row: 2; }
  .tijdlijn__tekst { grid-row: 3; }
  .tijdlijn__paneel--opening .tijdlijn__tekst { grid-row: auto; }
  .tijdlijn__wapens { flex-wrap: wrap; }
  .tijdlijn__wapen { flex-basis: 100%; }
  .tijdlijn__paneel-v { height: max(4.5rem, min(30vh, 26vw)); }
}

/* Een lage desktop houdt de alinea leesbaar door het jaartal ruimte te
   laten afstaan. */
@media (min-width: 901px) and (max-height: 800px) {
  .js-tijdlijn .tijdlijn__jaar { font-size: min(22vh, 26vw); }
  .js-tijdlijn .tijdlijn__paneel { padding-top: var(--space-6); row-gap: var(--space-4); }
  .tijdlijn__tekst { gap: var(--space-3); }
  .tijdlijn__beeld img { max-height: 34svh; }
}

.onderzoek__hoofdstuk { scroll-margin-top: calc(var(--header-h) + var(--space-6)); }
.onderzoek__proza { max-width: 70ch; }
.onderzoek__proza p,
.onderzoek__hoofdstuk > .container > p { max-width: 70ch; margin-block: 0 var(--space-6); }
.onderzoek__hoofdstuk h3 { margin-block: var(--space-8) var(--space-4); }
.onderzoek__hoofdstuk ul,
.onderzoek__hoofdstuk ol { max-width: 70ch; padding-left: var(--space-6); margin-bottom: var(--space-8); }
.onderzoek__hoofdstuk li + li { margin-top: var(--space-4); }
.onderzoek .bronnen__table { font-size: 1rem; table-layout: fixed; }
.onderzoek .bronnen__table td:nth-child(2) { white-space: normal; }
.onderzoek .onderzoek__tabel--codes th:first-child { width: 4.5rem; }
.onderzoek .bronnen__table th,
.onderzoek .bronnen__table td { overflow-wrap: anywhere; }
.onderzoek .bronnen__table caption { text-align: left; padding-bottom: var(--space-4); color: var(--text); }
.onderzoek .bronnen__table-wrap:focus-visible { outline: 2px solid var(--blauw); outline-offset: 4px; }
.onderzoek__identifier { overflow-wrap: anywhere; font-size: 0.875rem; }
.onderzoek__perioden { margin: 0; }
.onderzoek__perioden > div { padding-block: var(--space-4); border-bottom: 1px solid var(--rule); scroll-margin-top: calc(var(--header-h) + var(--space-6)); }
.onderzoek__perioden dt { font-weight: 600; }
.onderzoek__perioden dd { margin: var(--space-2) 0 0; max-width: 70ch; }
.onderzoek tr[id], .onderzoek p[id] { scroll-margin-top: calc(var(--header-h) + var(--space-6)); }

/* ============================================================
   Locatie (index.html, onderaan boven de footer)

   Links een accordeon met drie manieren om er te komen, rechts een grote
   OpenStreetMap-embed. Het luchtbeeld en de drie omgevingsfoto's die hier
   stonden zijn op 27 augustus 2026 vervallen; de sectie is tekst plus
   kaart en verder niets. ======================= */
.locatie {
  padding-block: var(--space-section);
  background: var(--surface);
}

.locatie__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-block);
  margin-top: var(--space-16);
}

/* Onder 900px staat de accordeon boven de kaart, allebei over de volle
   breedte. Daarboven vijf kolommen accordeon, kolom 6 leeg als tussenruimte
   en zes kolommen kaart. */
@media (min-width: 900px) {
  .locatie__grid {
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: var(--space-gutter);
    align-items: start;
  }

  .locatie__routes { grid-column: 1 / span 5; }
  .locatie__kaart  { grid-column: 7 / span 6; }
}

/* Het adres stond hier onderaan de sectie herhaald. Weg op 28 augustus 2026:
   de footer draagt het adres al in een <address>, op iedere pagina, en twee
   keer hetzelfde adres op een scherm is geen nadruk maar ruis. */

/* ---------- Accordeon ---------- */

/* Haarlijn onder elke regel, ook onder de laatste, zodat de rij als lijst
   afsluit tegen de kaart ernaast. */
.loc-item {
  border-bottom: 1px solid var(--rule);
}

.loc-item:first-child {
  border-top: 1px solid var(--rule);
}

.loc-item__heading {
  margin: 0;
}

/* De hele regel is de knop, niet alleen het label: een klikdoel van 88px
   hoog over de volle breedte. Klik opent, hover doet alleen het merkteken
   (zie .loc-item__mark hieronder). */
.loc-item__trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-6);
  width: 100%;
  min-height: 88px;
  padding: var(--space-4) 0;
  border: 0;
  background: none;
  color: var(--blauw);
  cursor: pointer;
  text-align: left;
}

.loc-item__label {
  font-family: var(--font-action);
  font-size: 1.375rem;
  font-weight: var(--font-action-weight);
  line-height: 1.2;
  letter-spacing: var(--font-action-tracking);
}

.loc-item__mark {
  flex: none;
  display: block;
  color: var(--blauw);
}

/* Het paneel animeert op grid-template-rows van 0fr naar 1fr. De binnenste
   laag moet daarvoor overflow: hidden en min-height: 0 hebben, anders duwt
   de inhoud de rij open ongeacht de fractie.

   De DICHTE toestand is gescopet op html.js, net als .lightbox__info: de
   CSS-default is open, zodat zonder JavaScript alle drie de panelen gewoon
   leesbaar zijn. Het script klapt ze bij het laden dicht, niet andersom. */
.loc-item__panel {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows var(--motion-loc-panel-open) var(--ease-lebarn);
}

.loc-item__panel[hidden] {
  display: none;
}

.loc-item__panel-inner {
  overflow: hidden;
  min-height: 0;
}

.js .loc-item__panel {
  grid-template-rows: 0fr;
  transition-duration: var(--motion-loc-panel-close);
}

.js .loc-item__panel.is-open {
  grid-template-rows: 1fr;
  transition-duration: var(--motion-loc-panel-open);
}

.loc-item__panel-inner > p {
  margin: 0 0 var(--space-6);
  max-width: var(--measure);
}

.loc-item__panel-inner > :last-child {
  margin-bottom: var(--space-8);
}

/* ---------- Merkteken: plus die wegveegt naar het V-teken ----------

   Rust is een plus: de bestaande horizontale .v-mark__rule plus een tweede,
   verticale streep met exact dezelfde stroke-width, linecap en kleur. Bij
   hover of focus gebeuren drie dingen NA elkaar, niet tegelijk:

     1. de verticale streep krimpt        0 tot 120ms
     2. de horizontale streep trekt terug 120 tot 260ms
     3. het V-teken schrijft zichzelf     200 tot 530ms

   Terug is bewust korter, zoals overal in dit project: V wissen 0-224ms,
   horizontale streep terug 123-305ms, verticale streep terug 305-425ms.

   Alle waarden komen uit de bestaande --motion-v-mark-*-tokens; alleen
   --motion-loc-mark-cross is nieuw, voor de verticale streep. De delays
   zijn daaruit berekend met calc() in plaats van los ingevoerd.

   scaleY en niet height: een geanimeerde height op een SVG-pad triggert
   layout per frame en de streep zou vanaf de bovenkant inklappen in plaats
   van vanuit het midden. */
.loc-mark__cross {
  fill: none;
  stroke: currentColor;
  stroke-width: var(--v-mark-rule-weight);
  stroke-linecap: round;
  transform-box: fill-box;
  transform-origin: center;
  transform: scaleY(1);
  transition: transform var(--motion-loc-mark-cross) var(--ease-v-mark)
    calc(var(--motion-v-mark-rule-in-start) + var(--motion-v-mark-rule-in));
}

.loc-item__mark .v-mark__rule {
  transition: stroke-dashoffset var(--motion-v-mark-rule-in) var(--ease-v-mark)
    var(--motion-v-mark-rule-in-start);
}

.loc-item__mark .v-mark__pen path {
  transition: stroke-dashoffset var(--motion-v-mark-erase) var(--ease-v-mark);
}

/* Gemeenschappelijke eindtoestand van hover, focus en open: de plus is weg
   en het V-teken staat er. Bij een open regel blijft hij staan, ook als de
   muis er weer af gaat; hover voegt daar niets aan toe. */
.loc-item__trigger:focus-visible .loc-mark__cross,
.loc-item__trigger[aria-expanded="true"] .loc-mark__cross {
  transform: scaleY(0);
  transition-delay: 0ms;
  transition-duration: var(--motion-loc-mark-cross);
}

.loc-item__trigger:focus-visible .v-mark__rule,
.loc-item__trigger[aria-expanded="true"] .v-mark__rule {
  stroke-dashoffset: 1;
  transition: stroke-dashoffset var(--motion-v-mark-rule-out) var(--ease-v-mark)
    var(--motion-loc-mark-cross);
}

.loc-item__trigger:focus-visible .v-mark__pen path,
.loc-item__trigger[aria-expanded="true"] .v-mark__pen path {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset var(--motion-v-mark-draw) var(--ease-v-mark)
    calc(var(--motion-loc-mark-cross) + var(--motion-v-mark-draw-start));
}

/* Hover apart achter een fine-pointerquery, zoals overal in dit project: op
   touch blijft een aanraking anders als hover hangen en zou de plus na het
   sluiten een V blijven. */
@media (hover: hover) and (pointer: fine) {
  .loc-item__trigger:hover .loc-mark__cross {
    transform: scaleY(0);
    transition-delay: 0ms;
    transition-duration: var(--motion-loc-mark-cross);
  }

  .loc-item__trigger:hover .v-mark__rule {
    stroke-dashoffset: 1;
    transition: stroke-dashoffset var(--motion-v-mark-rule-out) var(--ease-v-mark)
      var(--motion-loc-mark-cross);
  }

  .loc-item__trigger:hover .v-mark__pen path {
    stroke-dashoffset: 0;
    transition: stroke-dashoffset var(--motion-v-mark-draw) var(--ease-v-mark)
      calc(var(--motion-loc-mark-cross) + var(--motion-v-mark-draw-start));
  }
}

/* ---------- Kaart ---------- */

/* aspect-ratio en geen vaste hoogte, zodat de kaart meeschaalt. Op 1440px
   is de kolom 6 van 12 met de gutter eraf: circa 600px breed, dus 4/3,4
   levert een kaart van circa 570px hoog en dat valt binnen de gevraagde 520
   tot 640px. --surface-alt eronder voorkomt een wit gat tijdens het laden. */
.locatie__kaart {
  display: grid;
  gap: var(--space-4);
}

.loc-map {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  max-height: 60svh;
  border: 0;
  background: var(--surface-alt);
}

@media (min-width: 900px) {
  .loc-map {
    aspect-ratio: 4 / 3.4;
    max-height: none;
  }
}

/* De link "Bekijk in Google Maps" onder de kaart is op 28 augustus 2026 weer
   verwijderd. De embed heeft zelf al twee knoppen naar Google Maps in het
   infokaartje bij de marker, dus een derde ingang eronder was dubbelop.
   De plaatshouder hieronder houdt wel een link: die staat er juist wanneer
   er geen kaart is. */

/* Plaatshouder zolang de bezoeker de kaart niet heeft aangezet (sinds 14
   september 2026, zie js/consent.js). Draagt ook .loc-map en heeft dus exact
   de maat van de iframe: bij het laden verspringt de sectie niet. Verving de
   terugvalkaart voor een ontbrekende sleutel, die dezelfde vorm had.
   overflow: hidden is een vangnet en geen opmaak: de inhoud is op 320px
   uitgemeten om binnen de 4:3-verhouding te passen. */
.loc-map--toestemming {
  display: grid;
  align-content: center;
  justify-items: start;
  gap: var(--space-4);
  padding: clamp(1rem, 4vw, 3.5rem);
  border: 1px solid var(--rule);
  overflow: hidden;
}

.loc-map__toestemming-kop {
  margin: 0;
  color: var(--blauw);
  font-family: var(--font-display);
  font-size: var(--h3);
  font-variation-settings: "opsz" 24;
  font-weight: 600;
  line-height: 1.3;
}

.loc-map__toestemming-tekst {
  margin: 0;
  max-width: var(--measure);
  color: var(--text-muted);
}

.loc-map__toestemming-acties {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-8);
}

/* Een gewone tekstlink: zonder eigen padding, anders hangt de onderrand van
   de basis-<a> los onder de tekst. */
.loc-map__toestemming-link {
  line-height: 1.5;
}

/* Vierde keer de [hidden]-val: display: inline-flex van .v-mark-link wint van
   de UA-regel. Zonder deze regel staat de knop ook zonder JavaScript in beeld,
   terwijl hij dan niets doet. Geldt ook voor de knop op cookies.html. */
.loc-map__toestemming-acties .v-mark-link[hidden],
.v-mark-link[data-consent-open][hidden] {
  display: none;
}

/* Regel van 12px onder de knop. De links erin houden de gewone linkopmaak. */
.loc-map__toestemming-regel {
  margin: 0;
  /* --measure is 62ch en ch rekent met de eigen 12px: dan breekt de regel
     al rond 380px. Zelfde leesbreedte als de uitleg erboven. */
  max-width: 40rem;
  color: var(--text-muted);
  font-size: var(--meta);
  line-height: 1.5;
}

/* Op een smalle kaart (4:3 van 288px is 216px hoog) is er geen ruimte voor
   een kop boven de uitleg; de uitleg zegt al waar het vlak voor is. */
@media (max-width: 599px) {
  .loc-map__toestemming-kop {
    display: none;
  }

  .loc-map--toestemming {
    gap: var(--space-2);
    padding-block: var(--space-3);
  }

  .loc-map__toestemming-tekst {
    font-size: var(--small);
    line-height: 1.45;
  }
}

/* Reduced motion: er schrijft niets. Dicht is een plus, open is een V, en de
   wissel is een korte opacityovergang. Het paneel klapt zonder
   hoogte-animatie open; de globale vangnetregel bovenaan style.css klemt de
   duur al, dit blok legt vast dat dat hier de bedoelde uitkomst is. */
@media (prefers-reduced-motion: reduce) {
  .loc-item__panel,
  .js .loc-item__panel,
  .js .loc-item__panel.is-open {
    transition: none;
  }

  .loc-mark__cross,
  .loc-item__mark .v-mark__rule,
  .loc-item__mark .v-mark__pen path {
    transition: opacity var(--motion-loc-mark-cross) linear !important;
  }
}

/* ============================================================
   Privacyregel onder de verzendknop (14 september 2026)

   Eén regel van 12px na de knop, dus ook na de knop in de tabvolgorde.
   Een gewone tekstlink en geen .v-mark-link: die vorm is voor acties. Bewust
   geen aanvinkvakje; een verwijzing is genoeg en een vinkje verhoogt alleen
   de drempel. .site-footer ervoor zodat de regel op elke pagina gelijk wint
   van losse paragraafregels in de footer.
   ============================================================ */
.site-footer .f-form__privacy {
  grid-column: 1 / -1;
  margin: calc(var(--space-3) * -1) 0 0;
  max-width: none;
  color: var(--text-muted);
  font-family: var(--font-body);
  font-size: var(--meta);
  font-weight: 400;
  letter-spacing: 0.01em;
  line-height: 1.5;
}

/* ============================================================
   Cookievoorkeuren (js/consent.js, 14 september 2026)

   Geen schermvullende overlay en geen blokkade: een blok rechtsonder van
   hooguit 420px, op mobiel over de volle breedte met de gewone zijmarge.
   Ivoor met de tweelaagse inktschaduw van .floating-media. Sinds 15
   september 2026 op verzoek van de eigenaar met ronde hoeken
   (--form-radius-panel, dezelfde ronding als de aanvraagkaart) en een
   schakelaar in iOS-vorm. Onder de header (50), het menu en de lightbox
   (100), boven de galerijcursor (40).

   De twee acties zijn identiek van vorm, maat, gewicht en kleur: allebei een
   .v-mark-link met dezelfde korte viewBox. Een grote accepteerknop naast een
   grijze weigerlink bestaat hier niet.
   ============================================================ */
.consent {
  position: fixed;
  right: var(--pad-x);
  bottom: calc(var(--pad-x) + env(safe-area-inset-bottom, 0px));
  z-index: 45;
  width: min(420px, calc(100% - var(--pad-x) * 2));
  padding: var(--space-6) var(--space-6) var(--space-4);
  border: 1px solid var(--rule);
  border-radius: var(--form-radius-panel);
  background: var(--ivoor);
  color: var(--text);
  box-shadow:
    0 18px 40px -18px rgba(26, 26, 24, .28),
    0 2px 6px -2px rgba(26, 26, 24, .16);
  opacity: 0;
  transform: translateY(12px);
  transition:
    opacity 320ms var(--ease-lebarn),
    transform 320ms var(--ease-lebarn);
}

.consent.is-zichtbaar {
  opacity: 1;
  transform: none;
}

.consent[hidden] {
  display: none;
}

.consent__kop {
  margin: 0 44px var(--space-2) 0;
  font-size: 1.375rem;
  font-variation-settings: "opsz" 24;
  line-height: 1.2;
  letter-spacing: -0.01em;
}

.consent__uitleg {
  margin: 0 0 var(--space-4);
  color: var(--text);
  font-size: .9375rem;
  line-height: 1.5;
}

/* Instellingenregel zoals op iOS: label links, schakelaar rechts. */
.consent__keuze {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  min-height: 44px;
}

/* Een echte checkbox (role="switch") in de vorm van de iOS 26-schakelaar:
   een capsulebaan met een langgerekte witte knop. Aan is koningsblauw.
   Zoals op iOS reageert hij al bij indrukken: de knop rekt 6px uit naar het
   midden, en schiet bij loslaten door naar zijn kant. De overgang is
   kritisch gedempt en keert bij een snelle tweede tik om vanaf de stand
   waarin hij op dat moment is. Het raakvlak is de hele regel van 44px. */
.consent__schakelaar {
  --baan-b: 3.875rem;
  --baan-h: 1.75rem;
  --knop-b: 2.375rem;
  --knop-h: 1.5rem;
  --knop-rand: .125rem;
  --knop-rek: .375rem;
  flex: none;
  position: relative;
  order: 2;
  width: var(--baan-b);
  height: var(--baan-h);
  margin: 0;
  border: 0;
  border-radius: 999px;
  background: color-mix(in srgb, var(--text) 16%, var(--ivoor));
  appearance: none;
  cursor: pointer;
  transition: background-color var(--motion-schakelaar) var(--ease-schakelaar);
}

.consent__schakelaar::before {
  content: "";
  position: absolute;
  top: var(--knop-rand);
  left: var(--knop-rand);
  width: var(--knop-b);
  height: var(--knop-h);
  border-radius: 999px;
  background: var(--wit);
  box-shadow:
    0 2px 6px rgba(26, 26, 24, .18),
    0 1px 1px rgba(26, 26, 24, .08);
  transition:
    transform var(--motion-schakelaar) var(--ease-schakelaar),
    width var(--motion-schakelaar) var(--ease-schakelaar);
}

.consent__schakelaar:checked {
  background: var(--blauw);
}

.consent__schakelaar:checked::before {
  transform: translateX(calc(var(--baan-b) - var(--knop-b) - var(--knop-rand) * 2));
}

.consent__schakelaar:active::before {
  width: calc(var(--knop-b) + var(--knop-rek));
}

.consent__schakelaar:checked:active::before {
  transform: translateX(calc(var(--baan-b) - var(--knop-b) - var(--knop-rek) - var(--knop-rand) * 2));
}

/* De globale button:active-schaal geldt niet voor input, maar een eventuele
   indrukschaal hoort bij deze vorm niet: de knop rekt, de baan staat stil. */
.consent__schakelaar:active {
  transform: none;
}

@media (prefers-contrast: more) {
  .consent__schakelaar:not(:checked) {
    box-shadow: inset 0 0 0 1px var(--rule-strong);
  }
}

/* De hele regel is raakvlak: het label loopt tot 44px hoog door tot de
   schakelaar. In de DOM staat de checkbox vóór het label (tabvolgorde
   ongewijzigd); order zet het label visueel links, zoals op iOS. */
.consent__label {
  order: 1;
  flex: 1;
  padding-block: 11px;
  font-family: var(--font-body);
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.375;
  cursor: pointer;
}

.consent__vast {
  margin: 0 0 var(--space-2);
  color: var(--text-muted);
  font-size: var(--meta);
  line-height: 1.5;
}

.consent__acties {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-8);
}

.consent__sluit {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}

.consent__sluit svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: square;
}

@media (hover: hover) and (pointer: fine) {
  .consent__sluit:hover {
    color: var(--text);
  }
}

.consent__meer {
  margin: var(--space-2) 0 0;
  font-size: var(--meta);
  line-height: 1.5;
}

/* Ruimte onder de footer zolang het venster open is, zodat het formulier en
   de onderregel altijd boven het venster te scrollen zijn. De hoogte meet
   consent.js; de footer houdt zijn eigen achtergrond. */
.consent-open .site-footer::after {
  content: "";
  display: block;
  height: var(--consent-ruimte, 0px);
}

@media (max-width: 599px) {
  .consent {
    left: var(--pad-x);
    width: auto;
    padding: var(--space-6) var(--space-6) var(--space-3);
  }
}

/* Reduced motion: het venster staat er meteen, zonder fade of verschuiving.
   De schakelaar verspringt zonder overgang; zijn knop houdt wel zijn plek. */
@media (prefers-reduced-motion: reduce) {
  .consent {
    transform: none;
    transition: none;
  }

  .consent__schakelaar,
  .consent__schakelaar::before {
    transition: none;
  }

  .consent__schakelaar:active::before {
    width: var(--knop-b);
  }

  .consent__schakelaar:checked:active::before {
    transform: translateX(calc(var(--baan-b) - var(--knop-b) - var(--knop-rand) * 2));
  }
}
