/* ── Tokens ───────────────────────────────────── */
/* Donker "werkplaats"-palet. De drie accenttokens houden bewust hun oude
   namen: content.js bouwt klassen als 'card-' + accent uit content/*.json,
   dus hernoemen raakt drie bestandstypen zonder dat er iets mooier van wordt.
   --steel bevat daarom een teal en geen staalblauw. */
:root {
  --bg:          #14181A;
  --surface:     #1E2427;
  --surface-2:   #1A2023;   /* navbalk — iets lichter dan de pagina */
  --border:      #2E3639;
  --border-soft: #3A4448;
  --ink:         #F2F0E9;
  --muted:       #B6C0C2;
  /* Niet verlagen: onder #ABB4B6 haalt de eyebrow bij geen enkele wasdekking
     de AA-norm, ook niet bij 86% waar de video praktisch onzichtbaar wordt.
     Zie tools/check_contrast.py en tests/tokens.test.js. */
  --label:       #BAC4C6;
  --wood:        #D08A3C;   /* Graveren */
  --steel:       #2E9CB8;   /* Industriële Tagplaten */
  --graphite:    #6E8A94;   /* 3D Printen */
  --radius:      4px;
  /* Vertelt de browser dat de pagina donker is, voor de UA-gerenderde
     onderdelen die CSS niet rechtstreeks kan kleuren: scrollbars, de native
     <select>-popup (dienst-dropdown in het contactformulier) en Chrome's
     autofill-opvulling. Blijven anders licht tegen de #14181A-pagina. */
  color-scheme: dark;
}

/* ── Reset ────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; background: var(--bg); }
body {
  background: var(--bg);
  color: var(--ink);
  font-family: 'Libre Franklin', sans-serif;
  font-weight: 400;
  line-height: 1.7;
  min-height: 100vh;
}
img, svg { display: block; max-width: 100%; }
a { color: var(--steel); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ── Typografie ───────────────────────────────── */
h1, h2, h3 { font-family: 'Bricolage Grotesque', sans-serif; line-height: 1.15; color: var(--ink); }
h1 { font-weight: 800; font-size: clamp(2rem, 5vw, 2.75rem); letter-spacing: -0.015em; }
h2 { font-weight: 700; font-size: clamp(1.3rem, 3vw, 1.75rem); letter-spacing: -0.01em; }
h3 { font-weight: 700; font-size: 1.05rem; letter-spacing: -0.005em; }
p  { color: var(--muted); font-weight: 400; }

.eyebrow {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--label);
}
.label {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.65rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--label);
}

/* ── Knoppen ──────────────────────────────────── */
.btn {
  display: inline-block;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  border-radius: var(--radius);
  padding: 12px 26px;
  cursor: pointer;
  transition: opacity 0.2s, transform 0.2s;
  text-decoration: none;
  border: none;
}
.btn:hover { opacity: 0.85; transform: translateY(-1px); text-decoration: none; }
.btn-primary { background: var(--ink); color: var(--bg); }
.btn-ghost   { background: transparent; color: var(--ink); border: 1px solid var(--border-soft); }
.btn-ghost:hover { border-color: var(--ink); }
.btn-block   { width: 100%; padding: 14px; text-align: center; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }

/* ── Kaarten ──────────────────────────────────── */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 4px solid var(--border-soft);
  border-radius: var(--radius);
  padding: 24px 22px;
  transition: transform 0.2s;
}
.card:hover { transform: translateY(-2px); }
.card h3 { margin-bottom: 10px; }
.card-wood     { border-top-color: var(--wood); }
.card-steel    { border-top-color: var(--steel); }
.card-graphite { border-top-color: var(--graphite); }

/* ── Materiaalband ────────────────────────────── */
/* De band zit binnen .site-nav zodat hij met de sticky balk meebeweegt. Stond
   voorheen eronder en was daardoor na één scrollbeweging uit beeld. Staat nu
   op alle pagina's; eerder alleen op de homepage.
   position:relative + z-index:1 omdat .site-nav::before (de blurlaag, zie
   hieronder) een gepositioneerde laag met z-index:0 is: zonder eigen z-index
   zou deze niet-gepositioneerde band daaronder wegvallen. */
.material-band {
  position: relative;
  z-index: 1;
  height: 4px;
  background: linear-gradient(90deg,
    var(--wood)     0%,  var(--wood)     12%,
    var(--steel)   45%,  var(--steel)    62%,
    var(--graphite) 90%, var(--graphite) 100%);
}

/* ── Scheidingslijn ───────────────────────────── */
.divider {
  height: 1px;
  background: var(--border);
  border: none;
  margin: 0;
}

/* ── Navigatie ────────────────────────────────── */
.site-nav {
  position: sticky;
  top: 0;
  z-index: 100;
  border-bottom: 1px solid var(--border);
  /* Geen backdrop-filter hier: filter/backdrop-filter/transform/perspective
     maken een element de containing block voor position:fixed nakomelingen.
     Stond die blur op .site-nav zelf, dan zou de mobiele full-screen overlay
     (.nav-links, position:fixed) geclipt worden tot de hoogte van de balk
     in plaats van het volledige scherm te vullen. De blur staat daarom op
     ::before, een losse laag die niets bevat en dus geen containing block
     hoeft te worden voor iets dat erin moet passen. */
}
/* Achtergrond + blur van de balk, als eigen laag. inset:0 + z-index:0 zodat
   hij precies .site-nav vult en onder .nav-inner / .material-band blijft
   liggen (die hebben allebei hun eigen, hogere z-index — zie hieronder). */
.site-nav::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  /* Fallback voor engines zonder color-mix-ondersteuning: dezelfde kleur als
     de color-mix hieronder (rgb 26,32,35 == --surface-2, op 88% dekking).
     Zelfde patroon als .lightbox verderop in dit bestand — het token draagt
     zelf geen alfakanaal, dus color-mix met transparent geeft de nav zijn
     dekking terwijl --surface-2 wél de bronkleur blijft. */
  background: rgba(26, 32, 35, 0.88);
  background: color-mix(in srgb, var(--surface-2) 88%, transparent);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}
.nav-inner {
  /* position:relative + z-index:1 om boven .site-nav::before (z-index:0) te
     blijven staan — anders verdwijnen de lockup en de navlinks achter de
     achtergrond/blurlaag van de balk. */
  position: relative;
  z-index: 1;
  max-width: 1100px;
  margin: 0 auto;
  padding: 0 32px;
  /* 60px -> 90px: de balk is 50% hoger. */
  height: 90px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.nav-logo-link { display: flex; align-items: center; text-decoration: none; }
.nav-logo-link:hover { opacity: 0.8; text-decoration: none; }
/* De officiële horizontale lockup (merk + woordmerk + tagline) uit de brand
   kit, transparante versie: lichte inkt op transparant, dus hij staat
   rechtstreeks op de donkere balkachtergrond. Vervangt het losse merk-icoon
   plus de live tekst "GRAVELUX" — de lockup bevat het woordmerk zelf al.
   Verhouding 552:169, dus 78px hoog is ongeveer 255px breed.

   78px past binnen de balk van 90px omdat het SVG zelf marge meebrengt: van
   die 78px is maar 62px daadwerkelijk inkt, met ruim 8px leeg boven en onder.
   De balk hoeft dus niet mee te groeien om de tagline leesbaar te maken. */
.nav-lockup { height: 78px; width: auto; display: block; }

/* Hamburgerknop. Alleen zichtbaar onder 767px; hierboven is er ruimte voor
   de vijf links zelf. 44x44px omdat dat de kleinste comfortabel te raken
   taptarget is — de drie streepjes zijn kleiner, het raakvlak niet. */
.nav-burger {
  display: none;
  width: 44px;
  height: 44px;
  padding: 0;
  background: none;
  border: none;
  cursor: pointer;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
}
.nav-burger span {
  display: block;
  width: 22px;
  height: 2px;
  background: var(--ink);
  transition: transform 0.25s, opacity 0.2s;
}
/* Open: de bovenste en onderste streep draaien naar een kruis, de middelste
   verdwijnt. 7px is de afstand tussen de strepen (2px hoog + 5px gap). */
body.nav-open .nav-burger span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
body.nav-open .nav-burger span:nth-child(2) { opacity: 0; }
body.nav-open .nav-burger span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

.nav-links { display: flex; gap: 40px; list-style: none; }
.nav-links a {
  font-family: 'Libre Franklin', sans-serif;
  font-size: 1.05rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--muted);
  text-decoration: none;
  transition: color 0.2s;
}
.nav-links a:hover,
.nav-links a.active { color: var(--ink); }

/* ── Footer ───────────────────────────────────── */
.site-footer {
  background: var(--surface);
  border-top: 1px solid var(--border);
  padding: 24px;
}
.footer-inner {
  max-width: 1100px;
  margin: 0 auto;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
}
.footer-copy { font-family: 'Libre Franklin', sans-serif; font-size: 0.75rem; color: var(--muted); }
.footer-copy a { color: var(--muted); }
.footer-copy a:hover { color: var(--ink); text-decoration: none; }
.footer-links { display: flex; gap: 24px; }
.footer-links a {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  text-decoration: none;
}
.footer-links a:hover { color: var(--ink); }
.footer-links a.active { color: var(--ink); }

/* ── Container ────────────────────────────────── */
.container { max-width: 1100px; margin: 0 auto; padding: 0 24px; }

/* ── Rasterlay-outs ───────────────────────────── */
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }

/* ── Afbeeldings-placeholder ──────────────────── */
.img-placeholder {
  aspect-ratio: 4 / 3;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--label);
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* ── Hero (home) ──────────────────────────────── */
.hero-section { padding: 88px 24px 72px; text-align: center; }
.hero-inner { max-width: 640px; margin: 0 auto; }
.hero-inner .eyebrow { margin-bottom: 24px; }
.hero-inner h1 { margin-bottom: 16px; }
.hero-desc { font-size: 1rem; margin: 0 auto 36px; max-width: 480px; }
.hero-ctas { display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; }

/* ── Diensten-kaarten (home) ──────────────────── */
.services-section { padding: 56px 24px 80px; }

/* ── Paginakop (diensten) ─────────────────────── */
.page-header { padding: 64px 24px 48px; text-align: center; }

/* ── Paginakop met intro (portfolio) ──────────── */
.page-header-intro { padding: 64px 24px 0; text-align: center; }
.page-header-intro .eyebrow { margin-bottom: 16px; }
.page-header-intro h1 { margin-bottom: 16px; }
.page-header-intro p { max-width: 440px; margin: 0 auto; }

/* ── Responsiviteit ───────────────────────────── */
@media (max-width: 1023px) {
  .grid-3 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 767px) {
  /* Drie kolommen, niet flex: de lege derde kolom is wat de lockup optisch
     in het midden houdt. Met space-between zou hij naast de burger hangen
     en meeschuiven zodra de knop van breedte verandert. */
  .nav-inner {
    height: 76px;
    padding: 0 8px;
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
  }
  .nav-burger { display: flex; grid-column: 1; justify-self: start; }
  .nav-logo-link { grid-column: 2; justify-self: center; }
  .nav-lockup { height: 52px; }

  /* De burger, de lockup en de band moeten boven de overlay blijven staan;
     de overlay is een kind van .site-nav, dus dit speelt zich binnen één
     stacking context af en is met z-index onderling te regelen. */
  .nav-burger,
  .nav-logo-link,
  .material-band { position: relative; z-index: 2; }

  /* Het volledige scherm. position:fixed haalt de lijst uit het raster, dus
     de derde kolom blijft leeg en de lockup blijft gecentreerd. */
  .nav-links {
    position: fixed;
    inset: 0;
    z-index: 1;
    background: var(--bg);
    flex-direction: column;
    align-items: center;
    justify-content: center;
    /* safe center: in liggende stand (bijv. iPhone SE, 667×375) is er te
       weinig hoogte voor alle vijf links en zou plain 'center' de eerste link
       achter de balk duwen (negatieve top). 'safe center' voorkomt dat door bij
       overflow uit te wijken naar start-uitlijning. Oudere engines die de
       'safe'-keyword niet kennen, negeren deze hele declaratie (een ongeldige
       waarde wordt genegeerd, niet toegepast als 'center') en houden dus de
       gewone 'center' erboven aan — vandaar dat justify-content twee keer
       voorkomt, dit is geen fout. */
    justify-content: safe center;
    gap: 4px;
    /* Ruimte voor de balk zelf, zodat de bovenste link er niet achter valt. */
    padding: 80px 24px 24px;
    /* Zonder overflow-y blijft dit fixed;inset:0-paneel visueel binnen de
       viewport maar is de inhoud die eronder uitsteekt nergens bereikbaar:
       window.scrollTo werkt niet door de scroll-lock op body, en scrollTop op
       een element met overflow:visible (het standaardgedrag) doet niets. Bij
       vijf links (~464px) tegen een liggend scherm van ~288px hoogte was de
       laatste link ("Contact") zo onbereikbaar. overscroll-behavior: contain
       voorkomt dat het doorscrollen van dit paneel de achterliggende (gelockte)
       pagina laat meebewegen. */
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  /* De 'gesloten' (verborgen) toestand geldt alleen als nav.js ook echt
     geladen én uitgevoerd is (zie de .js-klasse die nav.js als allereerste
     actie op <html> zet, vóór zijn eigen early-return). Faalt nav.js om wat
     voor reden dan ook, dan blijft de .js-klasse weg: opacity/visibility
     vallen dan terug op hun standaardwaarden (1/visible), de burger blijft
     wel inert, maar de links zelf zijn zonder JS gewoon zichtbaar en
     aantikbaar in plaats van onbereikbaar achter een dode knop. Dit trof
     vooral privacybeleid.html, de enige pagina die verder geen JS nodig
     heeft en dus vóór deze fix zonder nav.js een onbruikbare mobiele nav had. */
  html.js .nav-links {
    /* visibility (niet alleen opacity) zodat de links in gesloten toestand
       niet aan te tikken en niet te tabben zijn. */
    opacity: 0;
    visibility: hidden;
    /* visibility krijgt hier een duur van 0s ipv een echte duur: visibility is
       een discrete eigenschap en flipt bij een duur pas halverwege (na ~125ms
       van de 0.25s), niet meteen. nav.js zet body.nav-open en focust de eerste
       link in dezelfde synchrone beurt — was visibility dan nog hidden, dan
       kan die link geen focus krijgen (een element in een verborgen subboom
       is niet focusbaar) en blijft document.activeElement op BODY staan. De
       delay van 0.25s hier (in gesloten toestand) zorgt dat visibility pas ná
       de opacity-fade omslaat bij het sluiten, zodat de links niet midden in
       de fade verdwijnen; de open-toestand hieronder zet die delay juist op 0
       zodat visibility bij het openen instant meegaat met transitionstart. */
    transition: opacity 0.25s, visibility 0s 0.25s;
  }
  body.nav-open .nav-links {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.25s, visibility 0s 0s;
  }
  .nav-links a {
    display: block;
    font-size: 1.5rem;
    padding: 14px 24px;
  }
  .grid-3, .grid-2 { grid-template-columns: 1fr; }
  .hero-ctas { flex-direction: column; align-items: center; }
  .hero-ctas .btn { width: 100%; text-align: center; }
  .footer-inner { flex-direction: column; text-align: center; }
  .footer-links { justify-content: center; }
}

/* Staat bewust ná het mobiele blok hierboven, niet ervoor (en niet
   samengevoegd met de burger-transitie, die geen zo'n conflict heeft): de
   selectors hier hebben dezelfde specificiteit als .nav-links, html.js
   .nav-links en body.nav-open .nav-links daarboven, dus bij een gelijke stand
   wint de laatste in de bronvolgorde. Stond dit blok vóór het mobiele blok,
   dan zou de mobiele transitie hierboven 'm overschrijven en had
   reduced-motion op mobiel geen enkel effect. html.js .nav-links staat hier
   met naam genoemd om dezelfde reden als body.nav-open .nav-links: zonder
   die regel zou de transition van de (specifiekere) .js-gate hierboven
   blijven winnen en had reduced-motion geen effect zodra nav.js draait. */
@media (prefers-reduced-motion: reduce) {
  .nav-links,
  html.js .nav-links,
  body.nav-open .nav-links,
  .nav-burger span { transition: none; }
}

/* ── Diensten ─────────────────────────────────── */
.service-section { padding: 72px 0; border-bottom: 1px solid var(--border); }
.service-section:last-child { border-bottom: none; }
.service-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 48px; align-items: start; margin-top: 8px; }
.service-copy h2 { margin-bottom: 20px; }
.service-copy p { margin-bottom: 16px; }
.service-copy p:last-of-type { margin-bottom: 0; }
.service-cta { margin-top: 28px; }
.tag-list { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 20px; }
.tag {
  font-family: 'IBM Plex Mono', monospace; font-size: 0.65rem; font-weight: 400;
  letter-spacing: 0.08em; text-transform: uppercase;
  border: 1px solid var(--border-soft); color: var(--muted);
  border-radius: var(--radius); padding: 4px 10px;
}
.tag-wood     { border-color: #D08A3C55; }
.tag-steel    { border-color: #2E9CB855; }
.tag-graphite { border-color: #6E8A9455; }
@media (max-width: 767px) {
  .service-grid { grid-template-columns: 1fr; gap: 28px; }
}

/* ── Materialenband (home) ───────────────────── */
.materials-banner-section { padding: 0 0 80px; overflow: hidden; }
.materials-banner-section .eyebrow { text-align: center; margin-bottom: 24px; }
.materials-marquee {
  position: relative;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 64px, #000 calc(100% - 64px), transparent);
          mask-image: linear-gradient(90deg, transparent, #000 64px, #000 calc(100% - 64px), transparent);
}
.materials-track {
  display: flex;
  gap: 12px;
  width: max-content;
  animation: materials-scroll 32s linear infinite;
}
#materials-track-copy { position: absolute; top: 0; left: 100%; }
.materials-marquee:hover .materials-track,
.materials-marquee:focus-within .materials-track { animation-play-state: paused; }
@keyframes materials-scroll { to { transform: translateX(-100%); } }
@media (prefers-reduced-motion: reduce) { .materials-track { animation: none; } }

.tag-link { cursor: pointer; }
.tag-link:hover { text-decoration: none; transform: translateY(-1px); }

/* ── Materiaalcatalogus-groepen ───────────────── */
.materials-group { margin-bottom: 56px; }
.materials-group:last-child { margin-bottom: 0; }
.materials-group-title { margin-bottom: 20px; }

/* ── Kleurstalen (PLA/PETG) ───────────────────── */
.color-swatches { display: flex; gap: 6px; margin-top: 12px; }
.swatch {
  display: inline-block;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 1px solid var(--border-soft);
}

/* ── Materiaalcatalogus-kaarten ───────────────── */
#materials-grid .card { display: flex; flex-direction: column; }
.card-link {
  display: inline-block;
  margin-top: auto;
  padding-top: 16px;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.card-link:hover { color: var(--ink); }

/* ── Ankerdoelen: ruimte voor sticky nav ──────── */
/* De sticky kop is .nav-inner (90px) + .material-band (4px) = 94px. Zonder
   deze marge landt het ankerdoel onder de balk.

   De mobiele override staat hier direct onder en NIET in het algemene
   mobiele blok verderop in dit bestand. Dat is bewust: beide regels hebben
   dezelfde selector en dezelfde specificiteit, dus wint de laatste in de
   bronvolgorde. Stond de override eerder in het bestand, dan zou de 94px
   hieronder hem overschrijven en had de media query geen enkel effect. */
.service-section[id],
#materials-grid .card[id] { scroll-margin-top: 94px; }

/* Op mobiel is .nav-inner 76px in plaats van 90px: 76 + 4 = 80px. */
@media (max-width: 767px) {
  .service-section[id],
  #materials-grid .card[id] { scroll-margin-top: 80px; }
}

/* ── Portfolio ────────────────────────────────── */
.portfolio-grid-section { padding: 48px 24px 80px; }

/* Masonry: tiny fixed rows + per-tile row-span set by content.js, so every
   photo keeps its own aspect ratio. GRID_ROW_UNIT/GRID_GAP in content.js
   must match the 8px/16px here. */
.portfolio-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 8px;
  gap: 16px;
  margin-top: 48px;
}

/* The grid item's height is set by its row-span; the inner figure is left
   at its natural height because that is what content.js measures. Do not
   give .portfolio-item a height, and do not hide its overflow — if a span
   is ever miscomputed the overlap must be visible, not silently clipped. */
.portfolio-item { margin: 0; }

.portfolio-item-inner {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0;
}

.portfolio-item.is-hero { grid-column: span 2; }

.portfolio-photo {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  transition: border-color 0.2s ease, transform 0.2s ease;
}

.portfolio-item:hover .portfolio-photo,
.portfolio-item:focus-within .portfolio-photo {
  border-color: var(--border-soft);
  transform: translateY(-3px);
}

.portfolio-caption-desc { font-size: 0.85rem; color: var(--muted); }

@media (max-width: 1023px) {
  .portfolio-grid { grid-template-columns: repeat(2, 1fr); }
  /* A double-width hero would be the entire row on two columns. */
  .portfolio-item.is-hero { grid-column: span 1; }
}

@media (max-width: 767px) {
  .portfolio-grid { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  .portfolio-photo { transition: none; }
  .portfolio-item:hover .portfolio-photo,
  .portfolio-item:focus-within .portfolio-photo { transform: none; }
}

/* ── Content-secties (contact/privacy) ────────── */
.content-section { padding: 64px 24px 96px; }
.content-narrow { max-width: 560px; margin: 0 auto; }
.content-wide    { max-width: 720px; margin: 0 auto; }

/* ── Formulier ────────────────────────────────── */
.form-intro .eyebrow { margin-bottom: 16px; }
.form-intro h1 { margin-bottom: 12px; }
.form-intro p { margin-bottom: 40px; }
.contact-form { display: flex; flex-direction: column; gap: 20px; }
.form-group { display: flex; flex-direction: column; gap: 6px; }
.form-label {
  font-family: 'IBM Plex Mono', monospace; font-size: 0.65rem; font-weight: 500;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--label);
}
.form-control {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px 16px;
  font-family: 'Libre Franklin', sans-serif;
  font-size: 0.9rem;
  font-weight: 400;
  color: var(--ink);
  width: 100%;
  transition: border-color 0.2s;
  outline: none;
  -webkit-appearance: none;
}
.form-control:focus { border-color: var(--ink); }
.form-control::placeholder { color: var(--label); }
textarea.form-control { min-height: 140px; resize: vertical; }
select.form-control { cursor: pointer; }
select.form-control option { background: var(--surface); color: var(--ink); }
.form-status {
  font-family: 'Libre Franklin', sans-serif; font-size: 0.85rem;
  padding: 12px 16px; border-radius: var(--radius); display: none;
}
.form-status.visible { display: block; }
/* Contrast gemeten: success-tekst 8.62:1, error-tekst 8.17:1 op hun achtergrond. */
.form-status.success { background: #1B2A20; color: #8FD3A8; border: 1px solid #2F4A38; }
.form-status.error   { background: #2C1C18; color: #F0A594; border: 1px solid #4A2C24; }

/* ── Juridisch (privacybeleid) ────────────────── */
.legal-intro .eyebrow { margin-bottom: 16px; }
.legal-intro h1 { margin-bottom: 40px; }
.legal-meta { margin-bottom: 12px; font-size: 0.8rem; }
.legal-divider { margin: 24px 0; }
.legal-block { margin-bottom: 32px; }
.legal-block:last-child { margin-bottom: 0; }
.legal-block h2 { margin-bottom: 16px; font-size: 1.1rem; }
.legal-block p { margin-bottom: 16px; }
.legal-block p:last-child { margin-bottom: 0; }
.legal-block ul { margin-bottom: 16px; padding-left: 20px; color: var(--muted); line-height: 2; }

/* ── Lichtbak ─────────────────────────────────── */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px;
  /* Fallback voor engines zonder color-mix-ondersteuning: dezelfde donkere
     kleur (rgb 10,13,14) als de color-mix hieronder, met een net iets hogere
     dekking (0.96 tegen 0.92) — geen exacte overlap, maar beide vallen ruim
     binnen wat als "ondoorzichtige donkere overlay" leest. De overlay moet
     donkerder zijn dan de pagina zelf, anders leest hij niet meer als
     overlay. Bewust een letterlijke waarde in plaats van --ink: --ink is
     sinds de donkere palet-omkering bijna wit en zou hier het omgekeerde
     effect geven; --bg (#14181A) komt dicht in de buurt maar is niet zo
     donker als het ontwerp vraagt (#0A0D0E). */
  background: rgba(10, 13, 14, 0.96);
  background: color-mix(in srgb, rgba(10, 13, 14, 1) 92%, transparent);
}

.lightbox[hidden] { display: none; }

.lightbox-figure {
  margin: 0;
  max-width: min(1100px, 100%);
  max-height: 100%;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.lightbox-image {
  max-width: 100%;
  max-height: calc(100vh - 140px);
  object-fit: contain;
  border-radius: var(--radius);
}

.lightbox-caption {
  /* Ligt op de donkere overlay, niet op de pagina zelf, dus volgt --ink
     (licht) in plaats van --bg (donker). */
  color: var(--ink);
  font-size: 0.85rem;
  text-align: center;
  font-family: 'IBM Plex Mono', monospace;
  letter-spacing: 0.04em;
}

.lightbox button {
  position: absolute;
  background: none;
  border: none;
  /* Ligt op de donkere overlay, niet op de pagina zelf, dus volgt --ink
     (licht) in plaats van --bg (donker). */
  color: var(--ink);
  font-size: 2rem;
  line-height: 1;
  cursor: pointer;
  padding: 12px 16px;
  opacity: 0.7;
  transition: opacity 0.2s ease;
}

.lightbox button:hover,
.lightbox button:focus-visible { opacity: 1; }

.lightbox-close { top: 12px; right: 16px; }
.lightbox-prev { left: 8px; top: 50%; transform: translateY(-50%); }
.lightbox-next { right: 8px; top: 50%; transform: translateY(-50%); }

.portfolio-item[tabindex]:focus-visible {
  outline: 2px solid var(--wood);
  outline-offset: 3px;
}

@media (max-width: 767px) {
  .lightbox { padding: 16px; }
  .lightbox-prev { left: 0; }
  .lightbox-next { right: 0; }
}

.portfolio-video {
  width: 100%;
  height: auto;
  /* Letterbox-/posterfill achter de <video>, niet de tekst erop — moet dus
     donker blijven en --ink (licht) volgen niet. Volgde stilzwijgend het oude
     palet toen --ink nog bijna zwart was; nu onbereikbaar (geen item in
     content/projects.json zet een "video"-key) maar buildVideoNode in
     content.js bestaat nog, dus dit moet kloppen voordat iemand hem gebruikt. */
  background: var(--bg);
}

/* The video tile is not clickable-to-enlarge, so it should not lift on
   hover the way the photo tiles do. */
.portfolio-item.is-video:hover .portfolio-photo,
.portfolio-item.is-video:focus-within .portfolio-photo { transform: none; }

/* ── Dienstfoto's (diensten) ──────────────────── */
/* Vervangt de grijze .img-placeholder zodra content.js een echte foto inlaadt.
   Geen vaste beeldverhouding: de foto houdt zijn eigen vorm, net als in het
   portfolio-raster. */
.service-photo-frame {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface);
  line-height: 0;
}

.service-photo {
  display: block;
  width: 100%;
  height: auto;
}

/* ── Hero-achtergrondvideo (home) ─────────────── */
/* De clip is 850x236 en wordt dus opgeschaald; daarom ligt er een dekkende
   was overheen. Die maakt de zachtheid onzichtbaar en houdt de lichte
   koptekst leesbaar tegen het bewegende beeld eronder.
   `isolation: isolate` sluit de negatieve z-index op binnen deze sectie, zodat
   de video nooit achter de nav of de pagina-achtergrond verdwijnt. */
.hero-section {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

.hero-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: -2;
  /* De clip is een wit kaartje onder een laser. Een beetje extra contrast en
     verzadiging laat de laserpunt en de vonken oplichten in plaats van
     wegvallen. Gemeten: het hele filterpad (saturate -> contrast -> was) over
     alle 225 frames geeft tot op drie decimalen hetzelfde worstcasecontrast
     als alleen contrast() modelleren (ink 8.751, muted 5.373, label 5.607) —
     de kritieke pixels zijn bijna neutrale vonken, waar saturate() geen effect
     heeft. saturate() draagt dus geen AA-risico en wijzigen aan deze waarde is
     puur esthetisch. Zie tools/check_contrast.py. */
  filter: saturate(1.25) contrast(1.1);
  /* 40px naar rechts. De schaal van 1.15 gaat vóór de verschuiving: de
     beeldverhouding van de clip (850x236) ligt dicht bij die van de hero, dus
     `cover` laat vrijwel niets overlopen om in te schuiven. Zonder die extra
     15% zou een verschuiving van 40px een lege strook aan de linkerkant
     openleggen. De sectie heeft overflow:hidden, dus de rest valt weg. */
  transform: translateX(40px) scale(1.15);
}

/* De was bepaalt of de koptekst leesbaar is. 80% is niet willekeurig: bij 70%
   zakt de bodytekst naar 3,71:1 en de eyebrow naar 3,87:1, allebei onder de
   AA-norm van 4,5:1. 76% is de laagste waarde die alles haalt; 80% geeft marge.
   Meet opnieuw met tools/check_contrast.py voordat je hieraan draait. */
.hero-section::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: rgba(16, 20, 22, 0.80);
}

/* Op smalle schermen vervalt de verschuiving. 1.15 schaal levert daar maar
   ~28px overloop per kant op, dus 40px opschuiven zou links een lege strook
   openleggen. Op die breedte is 40px bovendien een veel groter deel van het
   beeld dan op desktop. */
@media (max-width: 767px) {
  .hero-video { transform: scale(1.15); }
}

/* Bewegende achtergrond uit bij een reduced-motion-voorkeur; de poster blijft
   staan via de was, dus de sectie blijft er hetzelfde uitzien. */
@media (prefers-reduced-motion: reduce) {
  .hero-video { display: none; }
}
