/* ==========================================================================
   Jazetti - site.css
   Hand-written, no build step, no preprocessor, no nesting: what ships is
   what is written here.

   Design principle: the continuous line. Business-critical software keeps
   running, so a hairline runs down the page with a node at each section. With
   JavaScript it tracks the reader (assets/js/site.js); without it, or with
   reduced motion, it is drawn in its final state.

   Owner: the design track (DESIGN-BRIEF.md section 10). One block near the end
   is FUNCTIONAL and belongs to Claude Code - it is marked as such.

   Degradation (DESIGN-BRIEF.md section 5), and where each state lives in this
   file:
     1  JavaScript disabled  - every "live" rule is scoped under .spine-live or
                               .engine-live, classes only site.js adds. Without
                               them the defaults are the final, static state.
     2  Reduced motion       - the @media (prefers-reduced-motion: reduce) block.
                               It removes motion and restores the final state of
                               the line; it hides nothing.
     3  Older browser        - layout never depends on clamp(), min(), flex gap
                               or logical properties. Where a modern feature is
                               used, a flat value precedes it or an @supports
                               block replaces it.
     4  Font blocked         - the display face has a metric-matched local
                               fallback, so headlines keep their line breaks.
   ========================================================================== */

/* --- Fonts ---------------------------------------------------------------- */
/* "Jazetti Display" is a Modified Version of Source Serif 4 (Adobe, SIL Open
   Font License 1.1): the Display optical size at weight 600, as one static
   subset of Latin + Latin-1 Supplement + 12 punctuation marks.
   It is RENAMED, and that is a licence requirement rather than branding: Adobe
   reserves the font name "Source", and OFL condition 3 forbids a Modified
   Version - which a subset is - from using a Reserved Font Name as its name.
   Adobe's copyright notice and the licence are kept inside the file (name
   records 0, 13, 14) and beside it as assets/fonts/OFL.txt.
   Measurements and the argument: docs/design/type-specimen.html. */
@font-face {
  font-family: "Jazetti Display";
  src: url("/assets/fonts/jazetti-display-600.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

/* The fallback is a design deliverable, not a leftover. Georgia Bold ships with
   every Windows and macOS installation. These values were computed from the
   fonts' own tables, not tuned by eye: size-adjust matches the average advance
   over the site's real headlines (0.4191 em against Georgia Bold's 0.5214), and
   the overrides are the web font's ascent (1.036) and descent (0.335) divided
   by that size-adjust, because size-adjust scales them too. Measured result in
   Chrome: the same number of lines as the web font in 28 of 30 headline/width
   cases. Browsers that ignore size-adjust (Safari before 17) get plain Georgia,
   which is state 4 on an older browser and still reads as a deliberate serif. */
@font-face {
  font-family: "Jazetti Display Fallback";
  src: local("Georgia Bold"), local("Georgia-Bold");
  font-weight: 600;
  font-style: normal;
  size-adjust: 80.4%;
  ascent-override: 128.9%;
  descent-override: 41.7%;
  line-gap-override: 0%;
}

/* --- Tokens --------------------------------------------------------------- */
/* The contract from DESIGN-BRIEF.md section 1: names and meanings unchanged.
   Added: --font-display, the oxide pair, and the spine geometry. */
:root {
  /* Colour - marine for trust, brass for craft */
  --ink:          #0F2233;   /* marine: body text and dark bands */
  --ink-deep:     #0A1826;   /* deeper marine: footer */
  --paper:        #F4F2ED;   /* warm paper: the page ground */
  --paper-pure:   #FFFFFF;   /* input fields */
  --steel:        #59646E;   /* secondary text */
  --line:         #DCD7CC;   /* hairlines on light */
  --line-dark:    #2A3D50;   /* hairlines on marine */
  --brass:        #8A6520;   /* brass on light: text, rules */
  --brass-light:  #C9A45C;   /* brass on marine */
  --brass-tint:   #F0E7D6;   /* quiet accent field */
  --mute:         #B6C2CC;   /* text on marine */
  /* Oxide: a blocking state. Analogous to brass rather than fighting it, and
     green is absent on purpose - it belongs to ExcelSpecialist.
     Contrast: oxide on paper 7.4:1, oxide-light on marine 6.4:1. */
  --oxide:        #8C2F20;
  --oxide-light:  #E0907C;
  --oxide-tint:   #F3E3DF;

  /* Type */
  --font-display: "Jazetti Display", "Jazetti Display Fallback", Georgia,
                  "Times New Roman", serif;
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
               "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas,
               "Liberation Mono", monospace;

  --step--1: clamp(0.80rem, 0.78rem + 0.10vw, 0.86rem);
  --step-0:  clamp(1.00rem, 0.96rem + 0.18vw, 1.10rem);
  --step-1:  clamp(1.20rem, 1.12rem + 0.36vw, 1.42rem);
  --step-2:  clamp(1.50rem, 1.34rem + 0.72vw, 2.00rem);
  --step-3:  clamp(1.95rem, 1.62rem + 1.55vw, 3.00rem);
  --step-4:  clamp(2.40rem, 1.80rem + 2.80vw, 4.25rem);

  /* Rhythm */
  --gap-xs: 0.5rem;
  --gap-s:  1rem;
  --gap-m:  1.75rem;
  --gap-l:  3rem;
  --gap-xl: 5.5rem;

  --measure: 62ch;
  --shell:   71.25rem;       /* 1140px */
  --gutter:  1.25rem;
  --spine:   2.75rem;        /* text indent right of the spine, wide screens */
  --node:    7px;
}

/* State 3: a browser without clamp() gets the upper-middle of each step as a
   flat value. Custom properties that hold an unsupported function would
   otherwise become "invalid at computed-value time" and fall back to inherit,
   which collapses the type scale - so the tokens are replaced wholesale. */
@supports not (width: clamp(1px, 1px, 1px)) {
  :root {
    --step--1: 0.84rem;
    --step-0:  1.05rem;
    --step-1:  1.32rem;
    --step-2:  1.80rem;
    --step-3:  2.50rem;
    --step-4:  3.25rem;
  }
}

/* --- Reset ---------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 1.05rem;
  font-size: var(--step-0);
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
img, svg { max-width: 100%; height: auto; display: block; }
h1, h2, h3 { margin: 0; font-weight: 600; line-height: 1.14; }
h3 { letter-spacing: -0.015em; }
p  { margin: 0 0 var(--gap-s); max-width: var(--measure); }
ul { margin: 0; padding: 0; list-style: none; }
a  { color: inherit; text-decoration-thickness: 1px; text-underline-offset: 0.18em; }
[hidden] { display: none !important; }

/* The display face: page and section titles, and nothing that is read at length.
   h1/h2 inside a list (the 404 page) stay sans - they are list items that happen
   to be headings, not titles. */
h1, h2 {
  font-family: var(--font-display);
  letter-spacing: -0.005em;
  line-height: 1.1;
  font-kerning: normal;
}
.services h2 { font-family: var(--font-sans); letter-spacing: -0.015em; line-height: 1.14; }

/* --- Accessibility -------------------------------------------------------- */
:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 3px;
}
.band--dark :focus-visible, .masthead :focus-visible, .foot :focus-visible { outline-color: var(--brass-light); }
.skip {
  position: absolute; left: -9999px; top: 0;
  background: var(--ink); color: var(--paper);
  padding: 0.7rem 1rem; z-index: 100;
}
.skip:focus { left: 1rem; top: 1rem; outline: 2px solid var(--brass-light); }

/* --- Shell ---------------------------------------------------------------- */
/* No min() and no margin-inline: two margins and one media query give the same
   result in every browser that can run this site. */
.shell {
  max-width: var(--shell);
  margin-left: var(--gutter);
  margin-right: var(--gutter);
}
@media (min-width: 73.75rem) {
  .shell { margin-left: auto; margin-right: auto; }
}

/* --- Bands ---------------------------------------------------------------- */
/* The vertical padding lives on the SHELL, not on the band. That is what makes
   the line continuous: each shell spans its band's full height, bands are
   adjacent, so the hairline on one shell meets the next without a gap.
   --pad-top is read by the node so it sits level with the section's eyebrow. */
.band { position: relative; }
.band > .shell { position: relative; --pad-top: 0rem; }
.band--pad   > .shell { --pad-top: var(--gap-xl); padding-top: var(--gap-xl); padding-bottom: var(--gap-xl); }
.band--pad-s > .shell { --pad-top: var(--gap-l);  padding-top: var(--gap-l);  padding-bottom: var(--gap-l); }
.band--dark  { background: var(--ink); color: var(--paper); }
.band--tint  { background: var(--brass-tint); }

@media (max-width: 40rem) {
  .band--pad > .shell { --pad-top: var(--gap-l); padding-top: var(--gap-l); padding-bottom: var(--gap-l); }
}

/* --- The spine ------------------------------------------------------------ */
/* One 1px line per shell, drawn with ::after, and one node per section, drawn
   with ::before.
   The line is a two-stop gradient: the filled part above --fill, the plain
   hairline below it. --fill defaults to 1 - the final state - and only site.js
   changes it, so with scripting off or reduced motion the line is complete and
   every node is registered. That is states 1 and 2 by construction, not by an
   extra rule. */
.band > .shell {
  --fill: 1;
  --spine-ground: var(--line);
  --spine-ink: var(--brass);
  --spine-x: calc(-1 * var(--gutter) / 2);
}
.band--dark > .shell {
  --spine-ground: var(--line-dark);
  --spine-ink: var(--brass-light);
}
.band > .shell::after {
  content: "";
  position: absolute;
  top: 0; bottom: 0;
  left: var(--spine-x);
  width: 1px;
  background: var(--spine-ground);
  background: linear-gradient(to bottom,
                var(--spine-ink) 0,
                var(--spine-ink) calc(var(--fill) * 100%),
                var(--spine-ground) calc(var(--fill) * 100%),
                var(--spine-ground) 100%);
  pointer-events: none;
}
.band > .shell::before {
  content: "";
  position: absolute;
  top: calc(var(--pad-top) + 0.5rem);
  left: calc(var(--spine-x) - (var(--node) - 1px) / 2);
  width: var(--node); height: var(--node);
  background: var(--spine-ink);
  z-index: 1;
  pointer-events: none;
}
/* The hero's node sits level with its eyebrow, which the hero padding pushes
   down further than a normal band. */
.hero > .shell { --pad-top: 3.5rem; }

@media (max-width: 61.99rem) {
  .band > .shell { --node: 5px; }
}
@media (min-width: 62rem) {
  .band > .shell { --spine-x: 0px; padding-left: var(--spine); }
}

/* Live: site.js adds .spine-live to <html>, sets --fill on each shell as the
   reader moves, and adds .is-passed to a band once the reading line has crossed
   its node. A node not yet reached is hollow - the same square, outlined. */
.spine-live .band:not(.is-passed) > .shell::before {
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--spine-ground);
}
.spine-live .band--dark:not(.is-passed) > .shell::before { background: var(--ink); }
.spine-live .band--tint:not(.is-passed) > .shell::before { background: var(--brass-tint); }

/* --- Labels (monospace) --------------------------------------------------- */
.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--steel);
  margin: 0 0 var(--gap-s);
  display: block;
}
.band--dark .eyebrow { color: var(--brass-light); }

/* --- Masthead ------------------------------------------------------------- */
.masthead {
  background: var(--ink);
  color: var(--paper);
  padding-top: 1.1rem;
  padding-bottom: 1.1rem;
  border-bottom: 1px solid var(--line-dark);
}
.masthead__row {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap;
  margin-bottom: -0.5rem;          /* flex gap without gap: see the children */
}
.masthead__row > * { margin-bottom: 0.5rem; }
.wordmark {
  font-family: var(--font-mono);
  font-size: 1.05rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  text-decoration: none;
  display: inline-flex; align-items: center;
  margin-right: var(--gap-m);
}
.wordmark::after {
  content: ""; width: 7px; height: 7px; background: var(--brass-light);
  display: inline-block; margin-left: 0.55rem; transform: translateY(1px);
}
/* The logo lockup in place of the text wordmark (DESIGN-BRIEF.md 12.3, 12.5).
   One colour - --paper on the marine masthead - baked into the file, because
   an <img> cannot inherit currentColor. The brass square was the text
   wordmark's accent and goes with it: the mark carries the identity now.
   Height in rem so it follows the reader's text size; the width follows the
   file's aspect ratio. The wordmark's thin capitals set the floor: below about
   1.75rem they stop reading as letters. */
.wordmark--logo::after { content: none; }
.wordmark--logo img { height: 2.25rem; width: auto; }
@media (max-width: 40rem) {
  .wordmark--logo img { height: 1.875rem; }
}
.masthead__right { display: flex; align-items: center; flex-wrap: wrap; }
.nav { display: flex; flex-wrap: wrap; margin-right: var(--gap-m); }
.nav a {
  text-decoration: none;
  font-size: var(--step--1);
  letter-spacing: 0.04em;
  padding-top: 0.25rem; padding-bottom: 0.25rem;
  margin-right: var(--gap-m);
  border-bottom: 1px solid transparent;
  color: var(--mute);
  transition: color 160ms ease, border-color 160ms ease;
}
.nav a:last-child { margin-right: 0; }
.nav a:hover { color: var(--paper); border-bottom-color: var(--brass-light); }
.nav a[aria-current="page"] { color: var(--paper); border-bottom-color: var(--brass-light); }

/* Language switch - at the top of every page, never in the footer. */
.lang {
  display: inline-flex; align-items: center;
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.10em;
  border: 1px solid var(--line-dark);
}
.lang a, .lang span {
  padding: 0.3rem 0.6rem;
  text-decoration: none;
  color: var(--mute);
}
.lang a:hover { color: var(--brass-light); }
.lang [aria-current="true"] {
  background: var(--brass-light);
  color: var(--ink);
  font-weight: 600;
}
/* Sticky, but only from 52rem = 832px up.
   Measured on the real page in headless Chromium at fifteen widths: the bar is
   one 72px row from 820px up, and wraps to two rows (94-124px) below it. At
   320-430px that is 16.6% of a 740px screen, spent on every scroll, for a nav
   whose targets the disc below already reaches. 52rem is the first clean
   breakpoint above the 820px transition; 40rem and 62rem both fall inside the
   two-row zone, so this is a new one rather than a reuse.
   scroll-padding-top is not optional: the home page navigates by anchor
   (#werkwijze, #diensten, #contact) and without it every jump lands with the
   heading behind the bar. --masthead-h is set from offsetHeight by site.js
   (part 4); the 4.5rem fallback is the measured 72px and is what state 1 gets.
   Both rules sit inside @supports, so an engine without sticky keeps a static
   masthead AND no anchor offset, which is the correct pair.
   z-index 10 clears the spine (1) and the disc (2) and stays under .skip (100).
   No blur and no translucency: .masthead is already opaque --ink with a
   border-bottom, so legibility over scrolling content needs nothing new. */
@supports (position: sticky) {
  @media (min-width: 52rem) {
    html { scroll-padding-top: 4.5rem; scroll-padding-top: var(--masthead-h, 4.5rem); }
    .masthead { position: sticky; top: 0; z-index: 10; }
  }
}

@media (max-width: 40rem) {
  .masthead__row { position: relative; }
  .masthead__right { width: 100%; }
  .nav { width: 100%; margin-right: 0; margin-top: 0.35rem; }
  .nav a { margin-right: 1.1rem; }
  /* The language switch stays on the wordmark's row, top right, so a phone
     does not spend a whole row on it - and it is still the first thing
     at the top of the page, never further down. */
  .lang { position: absolute; top: 0; right: 0; }
}

/* --- Hero ----------------------------------------------------------------- */
.hero > .shell { padding-top: 3.5rem; padding-bottom: 3.5rem; }
@media (min-width: 40rem) { .hero > .shell { --pad-top: 6rem; padding-top: 6rem; padding-bottom: 6rem; } }
@media (min-width: 62rem) { .hero > .shell { --pad-top: 7.5rem; padding-top: 7.5rem; padding-bottom: 7rem; } }
/* Short desktop screens (round 40). With 7.5rem of top padding the primary
   action ended at 768px, on the fold of a maximised browser, which leaves
   roughly 746-790px of viewport. 3rem is measured, not guessed: it moves the
   button up by 72px, so it ends above 700px in a 746px viewport. Only the top
   padding changes, and only up to 850px of height: a taller screen keeps the
   hero exactly as designed. --pad-top moves with it, so the spine's node stays
   level with the eyebrow. */
@media (min-width: 62rem) and (max-height: 850px) { .hero > .shell { --pad-top: 3rem; padding-top: 3rem; } }
.hero h1 {
  font-size: 3.25rem;
  font-size: var(--step-4);
  /* em, not ch: ch is measured on whichever font renders, so a ch cap moves when
     the fallback stands in and the headline reflows (state 4). 1ch of the
     display face = 0.488em; 20ch = 9.75em. */
  max-width: 9.75em;
  margin-bottom: var(--gap-m);
}
.hero__lead {
  font-size: var(--step-1);
  line-height: 1.5;
  color: var(--mute);
  max-width: 52ch;
  margin-bottom: var(--gap-m);
}
/* The rule under the title is static. It used to draw itself on load; the
   brief allows one continuous motion on the page and that is the spine. */
/* A border, not a 1px background: the rule usually lands on a fractional y
   (measured at 540.75px), and Chrome smears a 1px box across two pixel rows
   until brass reads as grey. A border snaps to the device pixel. */
.hero__rule {
  height: 0; border-top: 1px solid var(--brass-light);
  width: 100%; max-width: 30rem;
  margin-bottom: var(--gap-m);
}

/* Status strip: small, factual, monospace. */
.status {
  display: flex; flex-wrap: wrap;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--mute);
  letter-spacing: 0.06em;
  margin-top: var(--gap-l);
  padding-top: var(--gap-s);
  border-top: 1px solid var(--line-dark);
  max-width: 56rem;
}
.status span { display: inline-flex; align-items: center; margin: 0 var(--gap-m) 0.4rem 0; }
.status span::before {
  content: ""; width: 5px; height: 5px; background: var(--brass-light);
  display: inline-block; margin-right: 0.5rem;
}

/* --- Buttons -------------------------------------------------------------- */
.btn {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.10em;
  text-transform: uppercase;
  text-decoration: none;
  padding: 0.85rem 1.6rem;
  border: 1px solid var(--brass);
  background: var(--brass);
  color: #fff;
  cursor: pointer;
  border-radius: 0;
  transition: background-color 160ms ease, color 160ms ease;
}
.btn:hover { background: transparent; color: var(--brass); }
.band--dark .btn { background: var(--brass-light); border-color: var(--brass-light); color: var(--ink); }
.band--dark .btn:hover { background: transparent; color: var(--brass-light); }
.btn--ghost { background: transparent; color: var(--ink); border-color: var(--ink); }
.btn--ghost:hover { background: var(--ink); color: var(--paper); }

/* --- Section head --------------------------------------------------------- */
.section-title {
  font-size: 2.5rem;
  font-size: var(--step-3);
  max-width: 11.75em;              /* 24ch of the display face; em for state 4 */
  margin-bottom: var(--gap-m);
}
.section-intro { font-size: var(--step-1); line-height: 1.5; color: var(--steel); }
.band--dark .section-intro { color: var(--mute); }

/* --- Grids ---------------------------------------------------------------- */
/* The flat single column comes first; the min() version replaces it where min()
   parses. Before this, minmax(24rem, 1fr) overflowed a 320px phone. */
.grid {
  display: grid;
  grid-gap: var(--gap-l) var(--gap-m);
  gap: var(--gap-l) var(--gap-m);
  grid-template-columns: 1fr;
  grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
  margin-top: var(--gap-l);
}
.grid--two {
  grid-template-columns: 1fr;
  grid-template-columns: repeat(auto-fit, minmax(min(24rem, 100%), 1fr));
}

/* Proposition block: a hairline above, no card, no shadow. */
.claim { border-top: 1px solid var(--line); padding-top: var(--gap-s); }
.claim h3 { font-size: var(--step-1); line-height: 1.25; margin-bottom: 0.6rem; }
.claim p  { color: var(--steel); margin-bottom: 0; }
.band--dark .claim { border-top-color: var(--line-dark); }
.band--dark .claim p { color: var(--mute); }

/* Services list */
.services { margin-top: var(--gap-l); }
.services li {
  display: grid;
  grid-template-columns: 3.5rem 1fr;
  grid-gap: var(--gap-m);
  gap: var(--gap-m);
  padding-top: var(--gap-m); padding-bottom: var(--gap-m);
  border-top: 1px solid var(--line);
  align-items: start;
}
.services li:last-child { border-bottom: 1px solid var(--line); }
.band--dark .services li { border-color: var(--line-dark); }
.services .key {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--brass);
  letter-spacing: 0.08em;
  padding-top: 0.35rem;
}
.band--dark .services .key { color: var(--brass-light); }
.services h2, .services h3 { font-size: var(--step-1); line-height: 1.25; margin-bottom: 0.5rem; }
.services h2 a { text-decoration: none; border-bottom: 1px solid var(--brass); }
.services h2 a:hover { color: var(--brass); }
.services p  { color: var(--steel); margin-bottom: 0; }
.services p + p { margin-top: 0.6rem; }
.services p a { color: var(--brass); }
.band--dark .services p { color: var(--mute); }
.band--dark .services p a { color: var(--brass-light); }

@media (max-width: 40rem) {
  .services li { grid-template-columns: 1fr; grid-gap: var(--gap-xs); gap: var(--gap-xs); }
}

/* --- Pull quote ----------------------------------------------------------- */
/* One per page at most. Set in the display face: it is the positioning line. */
.pull {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--step-2);
  line-height: 1.25;
  letter-spacing: -0.005em;
  max-width: 12.75em;
  border-left: 2px solid var(--brass);
  padding-left: var(--gap-m);
  margin: 0;
}
.band--dark .pull { border-left-color: var(--brass-light); }
.pull cite {
  display: block; font-style: normal; font-weight: 400;
  font-size: var(--step--1); font-family: var(--font-mono);
  color: var(--steel); letter-spacing: 0.08em; line-height: 1.6;
  margin-top: var(--gap-s);
}
.band--dark .pull cite { color: var(--brass-light); }

/* --- The rule engine ("Kan deze order vandaag nog de deur uit?") ---------- */
/* Additive: DESIGN-BRIEF.md 4.2. Its colours are local tokens, so the same
   markup works on a paper band and on a marine one. Without JavaScript the
   markup shows one fixed situation with all six rules open; site.js adds
   .engine-live, reveals the controls and re-renders on every change. */
.engine {
  --e-line: var(--line);
  --e-text: var(--ink);
  --e-muted: var(--steel);
  --e-field: var(--paper-pure);
  --e-on-bg: var(--ink);
  --e-on-fg: var(--paper);
  --e-go: var(--ink);
  --e-cond: var(--brass);
  --e-cond-bg: var(--brass-tint);
  --e-stop: var(--oxide);
  --e-stop-bg: var(--oxide-tint);
  --e-accent: var(--brass);
  margin-top: var(--gap-xl);
  color: var(--e-text);
}
.band--dark .engine {
  --e-line: var(--line-dark);
  --e-text: var(--paper);
  --e-muted: var(--mute);
  --e-field: transparent;
  --e-on-bg: var(--brass-light);
  --e-on-fg: var(--ink);
  --e-go: var(--paper);
  --e-cond: var(--brass-light);
  --e-cond-bg: rgba(201, 164, 92, 0.16);
  --e-stop: var(--oxide-light);
  --e-stop-bg: rgba(224, 144, 124, 0.16);
  --e-accent: var(--brass-light);
}
.engine__title { font-size: var(--step-2); line-height: 1.2; max-width: 12.75em; margin-bottom: var(--gap-s); }
.engine__lead { color: var(--e-muted); }

.engine__panel {
  margin-top: var(--gap-m);
  border-top: 1px solid var(--e-line);
  display: grid;
  grid-template-columns: 1fr;
}
@media (min-width: 48rem) {
  .engine__panel { grid-template-columns: minmax(0, 19rem) minmax(0, 1fr); }
  .engine__in  { padding-right: var(--gap-m); border-right: 1px solid var(--e-line); }
  .engine__out { padding-left: var(--gap-m); }
}
@media (max-width: 47.99rem) {
  .engine__in { border-bottom: 1px solid var(--e-line); }
}
.engine__in, .engine__out { padding-top: var(--gap-m); padding-bottom: var(--gap-m); }

.engine__label {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: 400;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--e-muted);
  margin: 0;
}

/* The fixed situation, as text. Visible without JavaScript; replaced by the
   controls when the engine is live. */
.engine__fixed { margin: var(--gap-s) 0 0; }
.engine__fixed div {
  display: flex; justify-content: space-between; align-items: baseline;
  padding-top: 0.55rem; padding-bottom: 0.55rem;
  border-bottom: 1px solid var(--e-line);
}
.engine__fixed dt { color: var(--e-muted); font-size: 0.95rem; }
.engine__fixed dd { margin: 0 0 0 1rem; font-family: var(--font-mono); font-size: 0.92rem; }
.engine-live .engine__fixed { display: none; }

.engine__controls { margin: 0; padding: 0; border: 0; min-width: 0; }
.engine__field { margin: var(--gap-s) 0 0; padding: 0; border: 0; min-width: 0; }
.engine__field legend { padding: 0; margin-bottom: 0.45rem; font-size: 0.92rem; color: var(--e-muted); }
.seg { display: flex; flex-wrap: wrap; border: 1px solid var(--e-line); background: var(--e-field); }
.seg label { flex: 1 1 auto; position: relative; }
.seg input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }
.seg span {
  display: block; text-align: center; white-space: nowrap; cursor: pointer;
  padding: 0.55rem 0.6rem;
  font-family: var(--font-mono); font-size: 0.9rem;
  border-left: 1px solid var(--e-line);
  transition: background-color 120ms ease, color 120ms ease;
}
.seg label:first-child span { border-left: 0; }
.seg input:checked + span { background: var(--e-on-bg); color: var(--e-on-fg); }
.seg input:focus-visible + span { outline: 2px solid var(--e-accent); outline-offset: -2px; }
.seg label:hover input:not(:checked) + span { background: var(--e-cond-bg); }

/* The answer */
.verdict { display: flex; flex-wrap: wrap; align-items: baseline; margin-top: var(--gap-s); }
.verdict__word {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 2.6rem;
  font-size: clamp(2.1rem, 1.6rem + 2vw, 3.1rem);
  line-height: 1;
  letter-spacing: -0.01em;
  margin-right: 0.9rem;
  color: var(--e-go);
}
.verdict__why { color: var(--e-muted); max-width: 44ch; padding-top: 0.4rem; }
.verdict__bar { height: 3px; margin-top: 1.1rem; background: var(--e-line); }
.verdict__bar i { display: block; height: 100%; width: 100%; background: var(--e-go); transition: width 160ms ease; }
[data-state="maybe"] .verdict__word { color: var(--e-cond); }
[data-state="maybe"] .verdict__bar i { width: 66.67%; background: var(--e-cond); }
[data-state="stop"]  .verdict__word { color: var(--e-stop); }
[data-state="stop"]  .verdict__bar i { width: 33.33%; background: var(--e-stop); }

.engine__count {
  margin: var(--gap-m) 0 0;
  font-family: var(--font-mono); font-size: 0.85rem; color: var(--e-muted);
}
.engine__count b { color: var(--e-text); font-weight: 600; }

/* The rules. Each row is a sentence in business Dutch beside the verdict it
   became - that pairing is the demonstration, not the controls. */
.rules { margin-top: 0.8rem; border-top: 1px solid var(--e-line); }
.rule {
  display: grid;
  grid-template-columns: 2.2rem 1fr auto;
  grid-gap: 0.75rem;
  gap: 0.75rem;
  align-items: start;
  padding-top: 0.7rem; padding-bottom: 0.7rem;
  border-bottom: 1px solid var(--e-line);
}
.rule__id { font-family: var(--font-mono); font-size: 0.78rem; color: var(--e-muted); padding-top: 0.2rem; }
.rule__txt { font-size: 0.98rem; line-height: 1.45; }
.rule__st {
  font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.08em;
  text-transform: uppercase; white-space: nowrap;
  padding: 0.15rem 0.5rem;
  border: 1px solid var(--e-line); color: var(--e-muted);
}
.rule__st--cond  { color: var(--e-cond); background: var(--e-cond-bg); border-color: transparent; }
.rule__st--block { color: var(--e-stop); background: var(--e-stop-bg); border-color: transparent; }
@media (max-width: 30rem) {
  .rule { grid-template-columns: 1fr auto; }
  .rule__id { display: none; }
}

.engine__more {
  margin-top: 0.9rem; padding: 0; border: 0; background: none; cursor: pointer;
  font-family: var(--font-mono); font-size: 0.85rem; color: var(--e-accent);
  border-bottom: 1px solid currentColor;
}
.engine__caption {
  margin: var(--gap-m) 0 0; padding-top: var(--gap-s);
  border-top: 1px solid var(--e-line);
  color: var(--e-muted);
}
.engine__caption b { color: var(--e-text); font-weight: 600; }

/* --- Figures: portrait and case schematics -------------------------------- */
.person {
  display: grid;
  grid-template-columns: 1fr;
  grid-gap: var(--gap-m);
  gap: var(--gap-m);
  align-items: start;
}
@media (min-width: 48rem) {
  .person { grid-template-columns: minmax(0, 1fr) 11rem; grid-gap: var(--gap-l); gap: var(--gap-l); }
  /* Two portraits need two 8rem figures and one 1rem margin, with room to spare. */
  .person--team { grid-template-columns: minmax(0, 1fr) 17.5rem; }
}
.person__figure { margin: 0; }
.person__figure img { width: 128px; height: 128px; }
.person__figure figcaption {
  margin-top: 0.8rem; padding-top: 0.6rem;
  border-top: 1px solid var(--brass);
  font-family: var(--font-mono); font-size: var(--step--1);
  letter-spacing: 0.06em; color: var(--steel); line-height: 1.5;
}
.person__figure figcaption b { display: block; font-family: var(--font-sans); letter-spacing: 0; color: var(--ink); font-size: 1rem; }
/* Round 52. The LinkedIn link under a founder's name is its own line: the
   caption is one block of mono text, and a link run into the role reads as
   part of it. No colour of its own - it inherits the link colour the rest of
   the page uses. */
.person__figure figcaption .person__link { display: block; margin-top: 0.4rem; }
/* Two people, one treatment (DESIGN-BRIEF.md 12.1): the same crop, the same
   128px, and both images on one line with their tops aligned - so the two
   portraits share a baseline whatever their captions wrap to underneath.
   Margins, not flex gap (state 3); the negative margins absorb the last
   figure's. Two 8rem figures plus their margins take 18rem: they sit side by
   side from a 320px phone (17.5rem of text column + 1rem) upwards. */
.person__team {
  display: flex; flex-wrap: wrap; align-items: flex-start;
  margin: 0 -1rem -1rem 0;
}
.person__team .person__figure { width: 8rem; margin: 0 1rem 1rem 0; }

.schematic { margin: var(--gap-l) 0 0; }
.schematic img { width: 100%; max-width: 60rem; }
.schematic figcaption {
  margin-top: var(--gap-s); max-width: var(--measure);
  font-size: 0.95rem; color: var(--steel);
}
.band--dark .schematic figcaption { color: var(--mute); }

/* --- Text pages (privacy, terms) ------------------------------------------ */
/* A cautious buyer reads these to decide whether this outfit is serious, so
   they are set as documents rather than dumped: a measure, a clear h2 rhythm,
   real list markers. */
.prose { max-width: 46rem; }
.band > .prose { max-width: var(--shell); }
.prose > h2 {
  font-size: var(--step-2);
  margin-top: var(--gap-l);
  margin-bottom: var(--gap-s);
  padding-top: var(--gap-m);
  border-top: 1px solid var(--line);
  max-width: 38rem;                /* the rule spans the text measure, not the heading */
}
.prose > h2 + p { margin-top: 0; }
.prose p, .prose li { max-width: var(--measure); }
.prose a { color: var(--brass); }
.prose ul { margin: 0 0 var(--gap-s); }
.prose li { position: relative; padding-left: 1.25rem; margin-bottom: 0.55rem; }
.prose li::before {
  content: ""; position: absolute; left: 0; top: 0.72em;
  width: 5px; height: 5px; background: var(--brass);
}
.prose code {
  font-family: var(--font-mono); font-size: 0.9em;
  background: var(--brass-tint); padding: 0.05em 0.3em;
}
.prose em { color: var(--steel); }

/* --- Form ----------------------------------------------------------------- */
.form { max-width: 34rem; margin-top: var(--gap-l); }
.field { margin-bottom: var(--gap-m); }
.field label {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--steel);
  margin-bottom: 0.45rem;
}
.field input, .field textarea {
  width: 100%;
  font: inherit;
  color: var(--ink);
  background: var(--paper-pure);
  border: 1px solid var(--line);
  padding: 0.8rem 0.9rem;
  border-radius: 0;
  -webkit-appearance: none;
  appearance: none;
  transition: border-color 160ms ease;
}
.field input:hover, .field textarea:hover { border-color: var(--steel); }
.field input:focus, .field textarea:focus { border-color: var(--brass); outline: 1px solid var(--brass); outline-offset: 0; }
.field textarea { min-height: 9rem; resize: vertical; }
/* Round 73: a one-line hint between a label and its field (aria-describedby). */
.field__hint { margin: 0 0 0.45rem; }
.field-offscreen { position: absolute; left: -9999px; }
.form__note { font-size: var(--step--1); color: var(--steel); max-width: 34rem; }
.form__note a { color: var(--brass); }

/* The message shown after a submit. Appearance only: WHICH message shows is the
   :target rule in assets/css/form-status.css, which is Claude Code's and loads
   after this file. A received message is brass; a refusal is oxide.
   The --ok/--error classes come with the markup diff; without them every
   message gets the neutral brass rule, which is still legible. */
.form__note[data-form-status] {
  font-size: var(--step-0);
  color: var(--ink);
  max-width: 34rem;
  margin: var(--gap-l) 0 0;
  padding: 0.2rem 0 0.2rem var(--gap-s);
  border-left: 2px solid var(--brass);
  scroll-margin-top: var(--gap-l);
}
.form__note[data-form-status] strong { display: block; }
.form__note.form__status--error { border-left-color: var(--oxide); }
.form__note.form__status--error strong { color: var(--oxide); }
.form__note[data-form-status] + .form { margin-top: var(--gap-m); }

/* --- Footer --------------------------------------------------------------- */
.foot { background: var(--ink-deep); color: var(--mute); padding-top: var(--gap-l); padding-bottom: var(--gap-l); }
.foot a { color: var(--paper); text-decoration-color: var(--line-dark); }
.foot a:hover { text-decoration-color: var(--brass-light); }
/* Round 81: the mail and phone links get a tap height of at least 24px (WCAG
   2.5.8; Lighthouse target-size measured them at 17px in round 54). Padding
   alone cannot do it: at 390px the lines are 21.9px apart, so two 24px targets
   would overlap. An inline-block with a 24px line-height is exactly 24px tall
   and makes its own line 24px; where the paragraph's lines are already 24px or
   more apart, nothing moves. */
.foot a[href^="mailto:"], .foot a[href^="tel:"] { display: inline-block; line-height: 24px; }
.foot strong { color: var(--paper); font-weight: 600; }
.foot__row {
  display: grid; grid-gap: var(--gap-m); gap: var(--gap-m);
  grid-template-columns: 1fr;
  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
}
.foot__row p { font-size: var(--step--1); margin-bottom: 0; line-height: 1.7; }
.foot__legal {
  margin-top: var(--gap-l); padding-top: var(--gap-s);
  border-top: 1px solid var(--line-dark);
  font-family: var(--font-mono); font-size: var(--step--1);
  letter-spacing: 0.05em;
  max-width: none;
}

/* --- Back to top, on the spine (DESIGN-BRIEF.md 12.2) --------------------- */
/* A real link to #top, the last child of <main>, carrying .shell so that its
   left edge is the line's left edge.
   position: sticky keeps it at the bottom of the viewport while the reader is
   inside <main>, and lets it come to rest where <main> ends - so it cannot
   reach the footer, by construction rather than by measurement. Its own box has
   no height and adds nothing to the page; the label hangs above it, centred on
   the line and reading upwards along it, which is the direction it sends you.
   Narrow enough to stay in the phone gutter, left of the text column.
   State 1: always shown - site.js only hides it while the hero is on screen.
   State 3: without sticky it is the line's last node, at the end of <main>,
   and still a working link. Smooth scrolling is scroll-behavior on <html>,
   which the reduced-motion block below already turns off. */
.to-top {
  position: relative;
  height: 0;
  margin-top: 0; margin-bottom: 0;
  pointer-events: none;
  z-index: 2;
}
@supports (position: sticky) {
  .to-top { position: sticky; bottom: var(--gap-s); }
}
/* The control itself is a brass disc with an arrow, bottom right. It reads as a
   control rather than as an ornament on the spine, which the vertical label did
   not - see the rendered comparison in
   docs\design\proposals\sticky-header-backtotop\compare-phone.png.
   The colour is measured, and the obvious choice is wrong. --brass-light is
   what the old label used, but it only worked there because that label sat on
   its own --ink ground: a --brass-light disc floating on the paper ground is
   2.10:1 and disappears. --brass on --paper is 4.74:1, and --paper on --brass
   is 4.74:1 inside the disc - both past 1.4.11's 3:1. The weak point is the
   disc against a --band--dark section at 3.06:1, and the 1px paper-tinted ring
   in the box-shadow covers it without showing on the light ground.
   right: 1rem is measured too: the mirror of the old left placement put half
   the disc off-screen at 390px. The box is fully inside the viewport from
   320px up (Evidence 3).
   display: grid is preceded by display: block and the arrow carries
   margin: auto, so a pre-grid engine gets the arrow centred horizontally at
   the top of the disc instead of nowhere - cosmetic, and the link still works.
   #6F511A is the hover shade of --brass, a literal rather than a new token:
   the palette contract in section 1 is not changed by this round. */
.to-top a {
  position: absolute;
  bottom: 0;
  right: 1rem;
  display: block;
  display: grid;
  place-items: center;
  width: 3rem; height: 3rem;          /* 48px, above the 44px touch target */
  pointer-events: auto;
  text-decoration: none;
  color: var(--paper);
  background: var(--brass);
  border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(244, 242, 237, 0.35),
              0 2px 14px rgba(15, 34, 51, 0.30);
  transition: opacity 160ms ease, visibility 160ms ease,
              background-color 160ms ease, transform 160ms ease;
}
.to-top a svg { width: 1.3rem; height: 1.3rem; margin: auto; }
.to-top a:hover { background: #6F511A; transform: translateY(-2px); }
.to-top a:focus-visible { outline: 2px solid var(--brass); outline-offset: 3px; }
@media (min-width: 62rem) {
  .to-top a { right: 0; }
}
.at-hero .to-top a { opacity: 0; visibility: hidden; }
/* The disc steps aside while the rule engine is under it: site.js puts
   .at-engine on <html> while any of the engine's text or controls meets the
   area the disc rests in - the bottom strip of the viewport, within the
   disc's own column. No width condition: round 39 had one, below the width
   where the engine stacks, and it left the disc on the engine's text at 800px.
   The geometry is the condition (round 40). Its size, colour, ring and
   position are unchanged - it is hidden, not moved. */
.at-engine .to-top a { opacity: 0; visibility: hidden; }

/* A telephone number is one thing to read and to copy. Round 39 measured the
   one after the contact form breaking across two lines at 390px, orphaning
   "843". Whole, it is about 114px wide, so it fits a 320px line. */
a[href^="tel:"] { white-space: nowrap; }

/* --- Utilities ------------------------------------------------------------ */
/* Visually hidden, still read aloud. Checked first: this file had no such
   utility - .skip is off-screen only until it is focused, and .field-offscreen
   belongs to the form. The disc's label needs one. */
.u-sr {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.stack-s > * + * { margin-top: var(--gap-s); }
.mt-m { margin-top: var(--gap-m); }
.mt-l { margin-top: var(--gap-l); }
.band a:not([class]) { text-decoration-color: var(--brass); }
.band--dark a:not([class]) { text-decoration-color: var(--brass-light); }

/* --- Reduced motion (state 2) --------------------------------------------- */
/* Removes motion. Hides nothing, disables nothing: the rule engine still
   answers every change, instantly. The spine is returned to its final state -
   fully drawn, every node registered - even if site.js is running, which is
   also why site.js does not start the tracker under this preference. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  /* The disc keeps its colour change and loses its 2px lift. Nothing else is
     needed: the rule above already holds every transition to 0.001ms, so the
     prototype's per-element transition override was redundant and is not here. */
  .to-top a:hover { transform: none; }
  .spine-live .band > .shell { --fill: 1 !important; }
  .spine-live .band:not(.is-passed) > .shell::before { background: var(--spine-ink); box-shadow: none; }
}

/* ==========================================================================
   Fifth pass (round 34) - the only three rules site.css gains.
   Append to src/assets/css/site.css; nothing existing is edited.
   No new token, no new colour: --line-dark and the grid are already there.
   ========================================================================== */

/* 1. Two adjacent marine bands. #werkwijze and the new #traject are both
      band--dark, so without a seam they read as one very long section. A
      hairline is the site's own separator (DESIGN-BRIEF 1: no cards, blocks
      separated by hairlines), and it is the only way to keep the new section
      legible as a section. Place it next to the .band rules. */
.band--dark + .band--dark { border-top: 1px solid var(--line-dark); }

/* 2. The loop schematic is drawn portrait, 480 x 520, so its words survive a
      360px viewport. .schematic img caps at 60rem, which would blow it up to
      960px on a desktop and print 38px labels; this caps it at its intrinsic
      width. Place it next to the .schematic rules. */
.schematic--loop img { max-width: 30rem; }

/* 3. A fifth .claim in a two-column .grid--two would leave an orphan. The
      fifth block spans both columns instead: no orphan, no dropped block, no
      padding sixth. Its <p> keeps --measure, so the line length does not grow
      with the column. Place it next to the .claim rules. */
.claim--wide { grid-column: 1 / -1; }

/* ==========================================================================
   Sixth pass (round 40) - one rule. Append to src/assets/css/site.css next to
   the .schematic block. No new token, no new colour, no new font.
   ========================================================================== */

/* The takeover figure carries two drawings in one <picture>: the wide one
   (960 x 400) from 63rem up, the narrow one (400 x 560) below it. The
   <picture> chooses the file; this chooses the cap, and the query is the same
   one, deliberately - if one moves, the other has to move with it.

   Below 63rem: cap at 30rem. The narrow drawing is drawn 400px wide, so it is
   never scaled below 0.875 (a 390px viewport gives it 350px) and never blown
   up past 1.2. That is the rule FIFTH-PASS-RESULT.md wrote down: draw it at
   the size it is shown at, and cap it in CSS.

   From 63rem up the default .schematic img cap of 60rem applies, which is the
   wide drawing at its natural width - the desktop rendering the browser review
   found useful, unchanged. */
@media (max-width: 62.99rem) {
  .schematic--takeover img { max-width: 30rem; }
}

/* ==========================================================================
   Round 68 - "Van A tot Z" / "A to Z": the customers' questions, one <details>
   each (docs\proposals\home-van-a-tot-z.md 3e). No cards (DESIGN-BRIEF 1): a
   hairline above every question, as .claim has, and one below the last. The
   browser's own marker differs per engine, so it is hidden and a + / minus in
   brass is drawn instead. Without JavaScript <details> still opens and closes,
   and every answer is in the HTML (state 1); nothing animates (state 2); an
   engine that cannot lay out a flex <summary> shows the question and the sign
   one after the other (state 3).
   ========================================================================== */
.qa { margin-top: var(--gap-m); max-width: 52rem; }
.qa__item { border-top: 1px solid var(--line); }
.qa__item:last-child { border-bottom: 1px solid var(--line); }
.qa__item > summary {
  list-style: none;
  cursor: pointer;
  display: flex; justify-content: space-between; align-items: baseline;
  padding-top: var(--gap-s); padding-bottom: var(--gap-s);
  font-size: var(--step-1); line-height: 1.25; font-weight: 600; letter-spacing: -0.015em;
}
.qa__item > summary::-webkit-details-marker { display: none; }
.qa__item > summary::after {
  content: "+";
  flex: none;
  margin-left: var(--gap-s);
  font-family: var(--font-mono);
  color: var(--brass);
}
.qa__item[open] > summary::after { content: "\2212"; }
.qa__item > p { color: var(--steel); }
.qa__item > p:last-child { margin-bottom: 0; padding-bottom: var(--gap-s); }

/* ---------------------------------------------------------------------
   Round 69: the proof page (/bewijs/, /en/proof/). One <dl>, a row per
   item: the label, then the state and the date. Without CSS it reads
   "Laatste hersteltest / OK · 1 oktober 2026, 18:18". The state is a word,
   never only a colour.
   --------------------------------------------------------------------- */
.proof { margin: var(--gap-m) 0; max-width: 46rem; }
.proof__row {
  display: grid; grid-template-columns: 1fr; gap: 0.15rem;
  padding: var(--gap-s) 0;
  border-top: 1px solid var(--line);
}
.proof__row:last-child { border-bottom: 1px solid var(--line); }
.proof dt { font-weight: 600; }
.proof dd { margin: 0; color: var(--steel); }
.proof__state { color: var(--ink); }
.proof__state--attention { color: var(--brass); }
@media (min-width: 48rem) {
  .proof__row { grid-template-columns: 1fr 1fr; gap: var(--gap-s); }
}
