/*
  Reset, typography, and the pieces both pages share: the top bar, section
  scaffolding, buttons, eyebrows, the reveal-on-scroll base state and the
  reduced-motion overrides.
*/

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  /* Anchors land below the fixed top bar instead of underneath it. */
  scroll-padding-top: calc(var(--topbar-h) + 16px);
  -webkit-text-size-adjust: 100%;
}

/* Only where the visitor has not asked for stillness. */
@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: var(--t-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  /*
    A guard, not a layout tool: nothing here is allowed to overflow sideways,
    and this stops a single mistake turning into a horizontally scrolling page
    on a phone. Anything wider than the viewport scrolls its own frame — see
    `.config-stepper` and `.cats-viewport`, which do exactly that.
  */
  overflow-x: hidden;
}

img,
picture,
svg,
canvas {
  display: block;
  max-width: 100%;
}

/*
  The UA rule for `hidden` is `display: none` at the lowest possible specificity,
  so any component rule that sets `display` silently defeats it. Tab panels here
  are grids and flex rows, and they must actually disappear when hidden.
*/
[hidden] {
  display: none !important;
}

img {
  height: auto;
}

button {
  font: inherit;
  color: inherit;
}

/* ── Type ──────────────────────────────────────────────────────────────── */

h1,
h2,
h3,
h4 {
  font-family: var(--font-display);
  font-weight: 800;
  /*
    Tight, and tightened further by negative tracking: Figtree's default
    sidebearings are generous, which reads as loose at display sizes.
  */
  letter-spacing: -0.021em;
  line-height: 0.98;
  text-wrap: balance;
}

h1 {
  font-size: var(--t-display);
  line-height: 0.95;
}

h2 {
  font-size: var(--t-h2);
  line-height: 0.96;
}

h3 {
  font-size: var(--t-h3);
  letter-spacing: -0.014em;
  line-height: 1.12;
}

p {
  text-wrap: pretty;
}

a {
  color: inherit;
}

/*
  Section label. Mono and uppercase, so it reads as metadata rather than as the
  first line of the copy.
*/
.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  line-height: 1.4;
  color: var(--fg-sub);
}

.lead {
  font-size: var(--t-lead);
  line-height: 1.55;
  color: var(--fg-sub);
  max-width: var(--measure-lead);
}

/* Off screen but still announced — for labels the visual design carries. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/*
  One visible focus style everywhere, on the colour of whichever theme the
  element sits in. Never removed — keyboard operation is a requirement here, not
  a nicety.

  The fallback is not decoration, and it is why this reads `var(--fg, currentColor)`
  rather than `var(--fg)`. `--fg` is declared per themed block — `.section` and the
  surfaces that re-theme inside it — so it resolves for 92 of the 93 focusable
  elements on the home page and not for the 93rd. `.skip-link` is a direct child of
  `<body>`, outside every one of those blocks, and on an undefined custom property the
  whole shorthand is invalid at computed-value time: not "fall back to the previous
  declaration", but every longhand reset to its initial value. `outline-style: none`,
  `outline-width: medium`. No ring at all, on the first thing a keyboard reaches.

  That failure is silent in a way a missing colour is not — nothing in devtools reads
  as broken, the rule shows as matched, and the element is simply not outlined. So the
  fallback belongs on the rule rather than on the one element known to need it today:
  any future control placed outside a themed block inherits a working focus ring
  instead of the same bug. `currentColor` is the right value for it, not a hardcoded
  ink — it is by definition legible against whatever the element is painted on, which
  for the skip link is its own `--pure-white` pill.
*/
:focus-visible {
  outline: 2px solid var(--fg, currentColor);
  outline-offset: 3px;
  border-radius: 3px;
}

.skip-link {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 100;
  padding: 10px 16px;
  border-radius: var(--r-sm);
  background: var(--pure-white);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  text-transform: uppercase;
  letter-spacing: 0.09em;
  text-decoration: none;
  transform: translateY(-160%);
  transition: transform var(--dur-fast) var(--ease);
}

.skip-link:focus {
  transform: none;
}

/* ── Sections ──────────────────────────────────────────────────────────── */

.section {
  position: relative;
  padding: var(--section-y) var(--gutter);
  background: var(--bg);
  color: var(--fg);
  /* The dark↔light change is the page's main structural signal — ease it. */
  transition: background-color var(--dur) var(--ease);
}

.shell {
  max-width: var(--shell);
  margin-inline: auto;
}

/* Standard section opening: headline, one paragraph. */
.head {
  margin-bottom: clamp(40px, 5vw, 76px);
}

.head h2 {
  max-width: 18em;
  margin-bottom: 22px;
}

.head .lead {
  margin-bottom: 0;
}

/* ── The closing CTA, on every page that has one ───────────────────────── */

/*
  Shared chrome, and here rather than in home.css because three pages end with this
  section and the same route SVG. These rules used to live in home.css, which
  /manifesto does not load — so on that page the three paths rendered with no stroke,
  no `fill: none` and no positioning: SVG's default fill is black, so they came out
  as three filled wedges sitting in the flow, 350px of near-invisible smear above
  the headline. Anything the CTA needs belongs in this file.

  It happened a second time when /faq was added: the copy block below was still in
  home.css, so the new page's headline came out at body-heading size with an
  unmeasured paragraph under it and two buttons touching. Hence the rule, restated:
  a class used by more than one page lives here, and the third page is not the
  moment to find that out.
*/
.cta {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

/*
  Five strands running in from the left, meeting at one point, leaving as one line.

  ── What it replaced, and why ──────────────────────────────────────────────

  Three dashed arcs sweeping the width of the band, whose comment here read
  "closes the loop back to the hero: the same arc language as the globe's lanes."
  The globe is gone and the lanes with it, so the loop closed on nothing: an arc
  drawn between two edges of the page was the last thing on this site still
  tracing a shipping route across an ocean. That is a sourcing agency's picture,
  and it sat under a headline about buying together.

  What is drawn instead is the one diagram this page argues for. Separate buyers
  arrive on their own account, at their own height, from off the left edge; they
  meet at a single point; one line leaves it. The geometry is in build.mjs, as
  `CTA_FLOW` — six paths and the node, in viewBox units, with the arithmetic.

  ── Why one duration for all of them ───────────────────────────────────────

  The three arcs ran at 5.5s, 7s and 9s and one of them backwards, which is three
  speeds and a collision: traffic. Every strand here carries the same dash pattern
  and the same duration, and that is what makes the dots move *together* —
  `stroke-dashoffset` steps in path units, so one offset over one duration is one
  linear speed whatever shape a strand is. -170 is ten whole 4+13 periods, so the
  loop has no seam at the restart.

  The line out is solid and unanimated: the dashes arrive at the node and what
  leaves it is continuous. That also sidesteps the phase mismatch a second dashed
  path at the junction would have.

  Ambient throughout — hidden from assistive technology, and it stops moving under
  reduced motion.
*/
.cta-flow {
  position: absolute;
  inset: auto 0 0;
  z-index: -1;
  width: 100%;
  height: 72%;
  opacity: 0.6;
  /*
    Diagonal, and the angle is the whole trick: transparent across the top left
    where the copy is, opaque at the bottom right where the node is. The copy
    block is 726px in a 1440px band, so the figure and the text share the same
    box and must not share the same pixels — measured on the Dutch home page, the
    fan reaches roughly 15% opacity behind the last line of the paragraph and
    full strength at the node, 300px to the right of the buttons.

    A mask rather than a shorter box or a narrower one. Shortening it per
    breakpoint fails because the copy changes height with the viewport and the
    wording, and a percentage tuned to today's line count drifts the next time
    either changes. Narrowing it to the right-hand half is worse: the SVG clips
    at its own edge, so five strands would stop dead in a vertical line halfway
    across the band — the one thing `CTA_FLOW` is laid out to avoid.

    Taller than the 60% the arcs had, because this figure has a shape to read:
    `slice` scales to *cover*, so a short box crops the viewBox hard horizontally
    and the convergence — the whole point of it — ends up off the right edge. The
    viewBox is 1200×520 for the same reason, close enough to the band's own ratio
    that the full width survives at 1440 and only its top and bottom trim.
  */
  mask-image: linear-gradient(100deg, transparent 30%, #000 62%);
}

.cta-flow path {
  fill: none;
  stroke: var(--info);
  stroke-width: 1.1;
  stroke-linecap: round;
  stroke-dasharray: 4 13;
  animation: flow-travel 6s linear infinite;
}

/*
  The line out. Heavier and solid: it is the thing the five became, and the only
  part of the figure that is not still on its way somewhere.
*/
.cta-flow path:last-of-type {
  stroke-width: 1.7;
  stroke-dasharray: none;
  animation: none;
}

/* Where they meet. The one static mark in the band. */
.cta-flow circle {
  fill: var(--info);
}

@keyframes flow-travel {
  to {
    stroke-dashoffset: -170;
  }
}

/*
  Gone below 1024px, and the breakpoint is measured rather than picked.

  The figure needs the copy to be narrower than the band — that is what the
  diagonal mask clears. Once the band is not much wider than 44em the copy takes
  all of it and there is nothing left to clear to. The node is the number that
  decides it: `slice` puts it at 0.583 × the band's width while the crop is
  horizontal, the two buttons end 492px past the gutter, and the two cross at
  about 1000px. 1024 is the first standard width where the node clears the second
  button — by 54px, screenshotted — and everything below it is the failure.

  At 390px it is not close: the section is 544px tall, the copy block ends 76px
  above its bottom edge, and every version of this that kept the art ran a dashed
  line across the second button. A 76px strip is not room for a picture whose
  whole point is a shape.

  The old three arcs stayed on at 34% height and 0.35 opacity, which was the right
  call for them — they were texture, and texture crops to any strip. This is a
  diagram, and half a diagram behind a button is worse than none. What the phone
  keeps is the headline, the sentence and the two buttons, which is what the band
  is for; what it loses is an ambient drawing and one perpetual animation on the
  device most likely to be running on a battery.
*/
@media (max-width: 1023px) {
  .cta-flow {
    display: none;
  }
}

/*
  The copy block.

  44em, and it was 24em until 2026-09-11. A quarter of the measure was the right
  column for the fork's closing line — two short sentences that wanted to break
  between them, two short labels that fitted side by side inside it — and it is
  the wrong column for this one. `Wat betaal jij dat samen goedkoper kan?` set as
  five lines of 76px type down the left quarter of a 1440px band, and
  `Doe de gratis besparingsscan` plus `Plan een gesprek` came to 492px in a 396px
  column, so the two buttons stacked. 44em is 726px at this root size: the pair
  sits on one row with room, and the headline takes three lines instead of five.

  The heading then takes its own measure in `em` — em of --t-display here, so the
  break point travels with the type as it scales down instead of holding a pixel
  width that means something different at every viewport. 9.4em is the number
  /faq arrived at for its own headline, which is why that page no longer needs an
  override for either of these: see the note where it used to be, in faq.css.

  Both are scoped to `.cta-inner` rather than to `.cta`, because /manifesto
  carries both `cta` and `mf-cta` and sets its closing headline in its own column
  at the ordinary h2 size — targeting `.cta h2` here would size that page's
  heading as a side effect of tidying this one.
*/
.cta-inner {
  max-width: 44em;
}

/*
  Every other band on the home page opens with one of these and this section did not,
  which is what made it read as a footer with a headline in it. The margin is here
  rather than on `.eyebrow` because that class carries none — base.css resets every
  margin to 0 and each section spends its own.

  Scoped to `.cta-inner` for the same reason the heading below is: /manifesto carries
  both `cta` and `mf-cta`, and its closing eyebrow already has a spacing of its own.
*/
.cta-inner .eyebrow {
  display: block;
  margin-bottom: clamp(16px, 1.8vw, 24px);
}

.cta-inner h2 {
  font-size: var(--t-display);
  line-height: 0.95;
  max-width: 9.4em;
  margin-bottom: 24px;
}

.cta-body {
  font-size: var(--t-lead);
  line-height: 1.55;
  color: var(--fg-sub);
  max-width: 26em;
  margin-bottom: 34px;
}

.cta-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

/* ── Buttons ───────────────────────────────────────────────────────────── */

/*
  ── Where mono is allowed, and where it stopped being allowed ────────────────

  Until 2026-09-11 every piece of interface type on this site was IBM Plex Mono,
  uppercase, tracked out: the four nav links, both buttons, `.link`, the menu
  toggle, the panel rows. The brief's header section asks in one line for exactly
  this to be re-examined — *don't keep the condensed all-caps nav look if it reads
  overly technical* — and at 1440 it read overly technical, for a reason worth
  writing down rather than rediscovering.

  The reason is the audience and the hero card. This is a purchasing collective
  addressed to Dutch SME owners — a bakery, a workshop, a small manufacturer —
  and a tracked-out monospace capital is the typography of a developer tool. The
  page said so twice in the same viewport: `CATEGORIEËN HOE HET WERKT VOOR WIE`
  above a warm sentence-case Figtree headline, and `PLAN EEN GESPREK →` in mono
  capitals 60px away from `Doe de gratis besparingsscan →` set sentence-case in
  the body face inside the scan card. The secondary action was shouting and the
  primary one was talking, which is the hierarchy upside down — and the card that
  already had it right is the best-resolved thing on the page.

  So the rule, and it is a rule rather than a sweep:

    **The body face carries navigation and actions. Mono carries labels, codes
    and data.**

  Body face, sentence case, no tracking: `.btn` (here), `.link`, `.nav-links a`,
  `.nav-drop-toggle`, `.nav-login`, `.nav-toggle`, `.nav-panel a`. Those are the
  things a reader clicks, and they now read in the same voice as the copy that
  asks them to.

  Still mono, deliberately, and each for the same reason — it is a label for
  something rather than a sentence to a person: `.eyebrow` and the section
  kickers (`MKB INKOOPCOLLECTIEF` is a category, not a greeting), the scan card's
  `1 CATEGORIEËN — 2 BEDRIJF — 3 RESULTAAT` step rail, `.utility`'s email address
  and phone number, `.lang-switch`'s two-letter locale codes, `.nav-drop-all`. An
  address and a step number are data; mono is the right face for data and its
  tabular figures are the whole point in the two that carry digits.

  What did *not* change: every box. Same `min-height`, same padding, same 4px
  radius, same hover inversion, same `--fg-sub` → `--fg` on the nav. Sentence
  case in a proportional face is narrower than tracked capitals at these sizes,
  so every button and the whole nav row got *narrower* — which is the safe
  direction for a bar whose width is this site's tightest constraint. The
  measured new fit point is at the foot of this file, where the bar's breakpoints
  are.
*/
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Above the 44px minimum tap target at every breakpoint. */
  min-height: 48px;
  padding: 0 28px;
  border: 1px solid transparent;
  /*
    Almost square. A pill is a soft shape and this is not a soft product — the
    panels, document frames, map cards and tab rail on this site are all built out
    of right angles, and a fully rounded button was the one element arguing with
    them. 4px keeps the corner from looking cut without reading as a curve.
  */
  border-radius: var(--r-xs);
  background: none;
  /*
    See the block above this rule for why this is the body face. 14px at 600 and
    not 16.5 at 600: `.scan-cta` in the hero card is the one action set at body
    size, it is full-width inside a card and it is the page's primary, so a
    button in a row of two — or a `Verstuur` under a form — sitting a step below
    it is the hierarchy those two positions actually have.
  */
  font-family: var(--font-body);
  font-size: var(--t-small);
  font-weight: 600;
  letter-spacing: 0;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition:
    background-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease);
}

/* Inverts with the theme: white on ink, ink on white. */
.btn--solid {
  background: var(--fg);
  border-color: var(--fg);
  color: var(--bg);
}

/*
  Hover empties the button out: the fill drops to the page's own ground, the copy
  and a 1px outline take the colour the fill had. On a dark section that is black
  copy on white becoming white copy on black inside a white outline; on a light
  one it is the same move the other way round.

  It replaces an orange fill. Orange means physical movement everywhere else here
  — routes, ships, the stage clock — and spending it on "the cursor is over this"
  made it mean two things. An inversion is a stronger state change than a hue
  swap anyway, and it costs no meaning.
*/
.btn--solid:hover,
.btn--solid:focus-visible {
  background: var(--bg);
  border-color: var(--fg);
  color: var(--fg);
}

.btn--ghost {
  border-color: var(--line-strong);
  color: var(--fg);
}

.btn--ghost:hover {
  background: var(--fg);
  border-color: var(--fg);
  color: var(--bg);
}

/*
  ── The arrow on the two conversion actions ──────────────────────────────────

  The brief spells both of this site's major calls to action with a trailing arrow —
  `Doe de gratis besparingsscan →` and `Plan een gesprek →` — and names them that way
  three times, in the two sections that carry them and again in the conversion
  hierarchy. It is part of the label, not decoration.

  Drawn here rather than typed into the strings, for the reason `.link` above does the
  same: the label travels. `NAV.cta.label` is read for the bar and the menu panel, the
  closing band's two actions are read on three pages, and the same Dutch string is a row
  in the footer and a subject line in the estimator's mail draft. An arrow inside the
  string would arrive in all of them; an arrow in a pseudo-element arrives only where a
  button is drawn.

  Scoped to the two places a conversion action *is* a button, and deliberately not to
  `.btn` itself. Every form in the site submits through this class — the brief, the
  contact panel, the supplier form, the estimator's own controls — and an arrow on a
  `Verstuur` is an arrow that promises a next screen the button does not go to.

  The hero's scan already has one: `.scan-cta` carries its own, which is why
  `Doe de gratis besparingsscan →` reads the same in the card and in the closing band
  even though the two share no rule.

  Both of these rules carried a `letter-spacing: 0` until 2026-09-11, with a note
  that the button's tracking would otherwise push the glyph off its own baseline.
  True while buttons were tracked-out mono capitals; inert now that `.btn` and
  `.link` are untracked (see the rule above `.btn`), so the declaration is gone
  rather than kept as two lines that look load-bearing and are not.
*/
.nav-cta,
.nav-panel .btn,
.cta-actions .btn {
  gap: 10px;
}

.nav-cta::after,
.nav-panel .btn::after,
.cta-actions .btn::after {
  content: '→';
  transition: transform var(--dur-fast) var(--ease);
}

.nav-cta:hover::after,
.nav-panel .btn:hover::after,
.cta-actions .btn:hover::after {
  transform: translateX(3px);
}

/*
  A text link that reads as an action without becoming a third button style.

  Body face at `.btn`'s size and weight — see the rule above `.btn` — because this
  is the third thing on the site a reader clicks and it should not be the one
  written in a different voice. The underline and the arrow are what separate it
  from a button; the type no longer has to.
*/
.link {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-body);
  font-size: var(--t-small);
  font-weight: 600;
  letter-spacing: 0;
  text-decoration: none;
  color: var(--fg);
  border-bottom: 1px solid var(--line-strong);
  padding-bottom: 4px;
  transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.link:hover {
  border-color: var(--signal);
}

.link::after {
  content: '→';
  transition: transform var(--dur-fast) var(--ease);
}

.link:hover::after {
  transform: translateX(3px);
}

/* ── Top bar ───────────────────────────────────────────────────────────── */

/*
  Solid rather than transparent or blurred. It sits over black in the hero, over
  white through the middle of the page and over black again at the end, and
  a solid bar that changes colour with the section beneath it stays legible
  everywhere without a scrim, a glass effect or a text shadow.

  The colour comes from its own `data-theme`, which js/nav.js rewrites from
  whichever section is currently under the bar — so the whole theme table in
  tokens.css is reused rather than restated here. The static HTML ships
  `data-theme="dark"`, which is what both heroes need, so a failed script leaves
  the bar correct rather than unreadable.
*/
.topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 50;
  /*
    The utility strip is the top of the page, not part of the furniture: the bar
    rides up by exactly the strip's height and then pins, so the strip leaves the
    screen the way it would if it were in the document flow, the nav row lands at
    y=0, and nothing brings it back. js/nav.js publishes `--bar-shift`, clamped to
    the strip's measured height.

    A sticky header with the strip as a preceding flow sibling would do this with
    no script at all, and is the wrong trade: it puts the bar in normal flow, and
    `--topbar-h` is reserved as padding by a dozen rules across five stylesheets
    that would then all count the bar twice. Fixed plus a transform changes the
    bar's behaviour and no page's layout.
  */
  transform: translateY(calc(var(--bar-shift, 0px) * -1));
  /*
    Glass, not a panel. The tint is the ground of whichever section is underneath —
    `--bg` follows `data-theme` and `data-bar`, so the two paper sections keep their
    own value rather than having one restated here — held at 58% over ink, with an
    18px blur behind it.

    What is behind the bar has to stay legible as shape and movement and stop being
    legible as text. That is the whole of the effect: the globe's top limb and the
    sky still run to the top of the window instead of ending at a horizontal edge
    92px down, and a paragraph travelling up through the nav on a narrow screen
    arrives as a smear rather than as grey body copy crossing the wordmark.
  */
  background: color-mix(in srgb, var(--bg) 58%, transparent);
  backdrop-filter: blur(18px) saturate(115%);
  -webkit-backdrop-filter: blur(18px) saturate(115%);
  color: var(--fg);
  /*
    8% of the theme's own ink — near-white over the dark half, near-black over the
    paper. A translucent bar needs the edge an opaque one did not: with the page
    showing through, the blur is the only thing saying where the bar ends, and it
    says it several pixels late and out of focus.
  */
  border-bottom: 1px solid color-mix(in srgb, var(--fg) 8%, transparent);
  transition:
    background-color var(--dur) var(--ease),
    color var(--dur) var(--ease),
    border-color var(--dur) var(--ease);
}

/*
  Paper takes more tint than ink, and a little more saturation with it. Dark type on
  glass over a light page has none of the black half's margin: at 58% the section
  underneath reads through as texture behind the nav labels.
*/
.topbar[data-theme='light'] {
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  backdrop-filter: blur(18px) saturate(120%);
  -webkit-backdrop-filter: blur(18px) saturate(120%);
}

/*
  With an exception: a section may name the background it wants the bar to have, and
  js/nav.js copies `data-bar` onto the bar from whichever section is underneath it,
  the same way it copies the theme.

  Two values, and they are the four paper sections — the hero and the three under it.
  They are the only place on the page where a section's ground is not `--bg` for its
  own theme: they rebind it, because the first screen and the band below it are printed
  on two tones of paper rather than on white, and a bar drawn in the light theme's white
  over an off-white section is a white strip along the top of the window with a seam
  under it. Naming the ground is the cheapest fix available: no second theme, no
  per-section bar rules, and the value resolves from the same paper family the sections
  bind.

  There was a third, `transparent`, at 44% tint. It was the black hero's — a bar you
  looked through at a starfield, over a section that also darkened under the bar on its
  own account — and it went with that hero. Nothing emits the attribute now, so the rule
  went with it rather than being left as a value a future section might set by accident
  and get 44% of the wrong ground.

  Only ever set by the script, never shipped in the static HTML. Each is legible over
  its own section and nowhere else, so they have to be able to go away again — and on a
  page where the script never runs, neither must arrive.
*/

/*
  `--line` comes with the ground. The two rules the bar draws — the one between the
  utility strip and the nav row, and the one under the whole bar — are the light
  theme's #E1E1E1, which is calibrated against white and lands 1.06:1 on this grey.
  The paper family has its own hairline and it is the one the sections underneath
  are already using.
*/
.topbar[data-bar='paper'] {
  --bg: var(--paper-grey);
  --line: var(--paper-line);
}

.topbar[data-bar='paper-white'] {
  --bg: var(--paper-white);
  --line: var(--paper-line);
}

/*
  Where the bar ends, on the paper half specifically. `--fg` at 8% is the rule
  everywhere (see `.topbar`), and on paper the section's own hairline is a better
  match than 8% of the ink: the sections underneath are already separating with it,
  and the two are within a value of each other anyway.

  On the dark half this used to be no edge at all — black over black, and the
  content scrolling under it said where the bar was. A bar you can see through
  cannot make that argument.
*/
.topbar[data-theme='light'] {
  border-bottom-color: var(--line);
}

/*
  The strip that leaves. Nothing here is fixed or sticky on its own account: it is
  the first row of a fixed bar that rides up by exactly this row's height, so it
  scrolls off the top with the hero and the nav row closes the gap behind it.

  It holds the inbox and the phone number, and nothing on the left. `flex-end`
  rather than `space-between` because there is one child and no second one coming:
  the row is furniture, and a label invented to occupy the left half ("Local time"
  used to, over two live clocks) would be a heading over two links that already
  say what they are.
*/
.utility {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 24px;
  padding: 11px var(--gutter);
  font-family: var(--font-mono);
  font-size: var(--t-mono-sm);
  letter-spacing: 0.07em;
  color: var(--fg-soft);
}

/*
  Out of the tab order the moment the strip is fully retracted, set by js/nav.js
  (`data-strip`) off the same measurement that publishes `--bar-shift`.

  This row is the one part of the bar that leaves the screen, and it now holds two
  focusable elements where it used to hold two clocks' worth of text. Tabbing to a
  link translated above the viewport puts the focus ring somewhere nothing will
  scroll it back to — the exact trap that keeps the language control down in the nav
  row. `visibility: hidden` is the property that fixes it: it removes focusability as
  well as paint, unlike `opacity`, and it keeps the box in layout, unlike
  `display: none` — which matters, because js/nav.js reads this element's
  `offsetHeight` every pass to know how far the bar should ride up.

  Never `aria-hidden`: that would hide the links from a screen reader and leave them
  tabbable, which is the worse half of both.
*/
.topbar[data-strip='gone'] .utility {
  visibility: hidden;
}

/* The two contact links, at the right-hand end. */
.utility-end {
  display: flex;
  align-items: center;
  gap: 12px;
}

/*
  Inherits the strip's mono micro-type; only the ink and the underline are its own.
  `--fg-sub` rather than the strip's `--fg-soft` — one step up, enough that a link
  reads as a link beside the `/` and not enough to compete with the nav row below it.
*/
.utility-link {
  color: var(--fg-sub);
  text-decoration: none;
  /* Tabular, so the number does not reflow between the two faces' digit widths. */
  font-variant-numeric: tabular-nums;
  /*
    An address and a phone number are single values, and a value broken across two
    lines is a value someone reads back wrong. Without this the flex row shrinks the
    items past their content width at 320px and `+31 6 18348222` arrives as
    `+31 6` / `18348222`. The row stacks instead — see the 359px step below.
  */
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease);
}

.utility-link:hover,
.utility-link:focus-visible {
  color: var(--fg);
}

.sep {
  color: var(--fg-faint);
}

/* ── The language switcher ─────────────────────────────────────────────────
   `NL | EN`, in the corner of the nav row directly right of the CTA, and again as a
   row inside the menu panel. Two options and no disclosure: with the locale set fixed
   at two (content/I18N.md §14) a menu would be a click in front of a choice that is
   already visible. See `localeSwitch()` in build.mjs for what it replaced.

   This is where the old seven-language `details` picker lived — ~180 lines of caret,
   floating menu, tick and a note explaining why five of its seven rows did nothing.
   All of that is gone; nothing else in the site referenced `.lang`, `.lang-menu` or
   `.lang-option`. `.lang-flag` survives it, because the round flags were the one part
   worth keeping and are now in both arms of the toggle.
*/

/*
  Its own type, because it inherits none: in the nav row it would take the body face
  at the body size, which is a different register from the two-letter code this is.
  Mono small is the register the row's other small furniture uses — the contact links
  above it and the menu button beside it.
*/
.lang-switch {
  display: flex;
  align-items: center;
  font-family: var(--font-mono);
  font-size: var(--t-mono-sm);
  letter-spacing: 0.07em;
}

/*
  Both arms are the same box whether they are a link or the current locale's `span`,
  so the pair does not shift by a pixel when the reader crosses over — the only thing
  that changes between them is colour.

  `min-height` and not padding for the touch target: the bar's own CTA is 40px and
  `align-items: center` on `.nav-end` centres this against it, so a 34px control with
  a real 34px hit area is honest at desktop, and the panel copy below raises it.
*/
.lang-opt {
  display: inline-flex;
  align-items: center;
  /* Flag to label. Tighter than the nav's rhythm on purpose: they are one thing. */
  gap: 6px;
  min-height: 34px;
  /*
    6px and not the 8 this started at. The switcher is 109px of new furniture in the
    row whose width is the site's tightest constraint, and 2px a side across two arms
    is 8px of breakpoint — measured, and it is the difference between the Dutch bar
    fitting at 1292 and at 1300. The divider still has air on both sides at 6.
  */
  padding: 0 6px;
  line-height: 1;
  color: var(--fg-soft);
  text-decoration: none;
  /* Two letters in a mono face still benefit: NL and EN must be the same width. */
  font-variant-numeric: tabular-nums;
  transition: color var(--dur-fast) var(--ease);
}

/*
  The round crop, and the only place it happens. The drawings are square and know
  nothing about it — see `FLAG_ART` in build.mjs — so a flag can be recropped, resized
  or set square again from this one rule.

  The inset ring is not decoration, and 24% is measured rather than inherited. The Dutch
  flag has white across its middle band and reaches the edge with it, so against paper
  the disc loses its edge exactly there: screenshotted on /manifesto's light bar at the
  12% this carried in the seven-flag set, it read as a red arc and a blue arc with a gap
  between them rather than as a flag. 12% was tuned when the Dutch flag was one of seven
  and two of the others also had white at an edge; it is now one of two, and the primary
  one. At 24% the circle closes on paper, and on the Union Jack — whose edge is navy
  everywhere — the ring still does not read at all, which is why one value serves both.

  `flex: none`, because the flag is the one thing in this control that must not shrink:
  a flexbox that is short of room takes it from here first, and a 14px flag is a smudge.
*/
.lang-flag {
  flex: none;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  overflow: hidden;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 24%, transparent);
}

.lang-flag svg {
  display: block;
  width: 100%;
  height: 100%;
}

/*
  The rule between them is a border, not a `|` character. As a character it is in the
  accessibility tree, and "N L pipe E N" is not what this control says; as a border it
  is drawn at the section's own hairline colour and reads as a divider to the only
  sense that should be given one.
*/
.lang-opt + .lang-opt {
  border-left: 1px solid var(--line-strong);
}

/*
  Which language you are in, stated as ink against muted rather than with a tick or a
  box. `aria-current="true"` carries the same fact to a screen reader, and the pair is
  the whole of the state — there is no third appearance to design.
*/
.lang-opt[aria-current] {
  color: var(--fg);
}

/* Only the arm that goes somewhere responds to a pointer. */
a.lang-opt:hover,
a.lang-opt:focus-visible {
  color: var(--fg);
}

/*
  In the panel. `.nav-panel a` makes every link in there a full-width block row with a
  hairline under it, which is right for a destination and wrong for two halves of one
  control — as blocks they would stack into two rows and take two of the panel's
  hairlines with them. So the pair keeps its own line, sized like a panel row and
  labelled by nothing, sitting under the last nav link and above the CTA.
*/
.lang-switch--panel {
  margin-top: 4px;
  padding: 6px 0;
}

.nav-panel .lang-opt {
  display: inline-flex;
  min-height: 44px;
  padding: 0 12px;
  border-bottom: 0;
  font-size: var(--t-mono);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* The first arm starts on the gutter the rows above it start on. */
.nav-panel .lang-opt:first-child {
  padding-left: 0;
}

/* A 44px row carries a larger flag without crowding, and this is the touch surface. */
.nav-panel .lang-flag {
  width: 20px;
  height: 20px;
}

.nav {
  display: flex;
  align-items: center;
  gap: 28px;
  padding: 0 var(--gutter) 14px;
  border-top: 1px solid var(--line);
  padding-top: 14px;
}

/*
  The right-hand end: the CTA, then the `NL | EN` switcher, then the menu button that
  replaces the first two at the breakpoint. A wrapper rather than three children of
  `.nav`, because the gap between the CTA and the switcher is 18px and the nav's own
  rhythm is 28 — the switcher belongs to the button, closer to it than the nav links
  are to each other, and one nested gap says that without a negative margin.

  `align-items: center` on both rows is what centres the switcher's 34px against the
  CTA's 40px.
*/
.nav-end {
  display: flex;
  align-items: center;
  /*
    14 and not the 18 this started at, for the reason `.lang-opt`'s padding came down:
    this group now holds three things rather than two, and the two gaps in it are worth
    8px of the width at which the Dutch bar stops fitting. Still visibly wider than the
    switcher's own internal 6, which is what keeps the CTA from reading as part of it.
  */
  gap: 14px;
}

.brand {
  display: inline-flex;
  align-items: center;
  /*
    Tighter than the 11px the old arch mark needed: that one was tall and narrow
    and the round mark reads as one block with the wordmark at closer range.
  */
  gap: 8px;
  margin-right: auto;
  color: inherit;
  text-decoration: none;
}

/*
  One file, masked — not two drawings, which is what this was until 2026-09-11.

  The old globe came as a pair: a distressed mark for dark ground and a clean one
  for paper, neither derivable from the other, each with its own aspect ratio. The
  mark that replaced it is flat geometry in a single colour — six discs and six
  rounded bars on a hexagonal pinwheel — so it has no light and no dark version to
  draw. That makes the ground problem a colour problem, and a colour problem is
  solved by `currentColor`: the mask carries the shape and `.brand` supplies the
  ink, so the bar being ink on /, /suppliers and /faq and paper on /manifesto and
  the estimator needs no second asset and no `[data-theme]` override. A bar whose
  theme never gets set still shows a legible mark, because it inherits whatever
  colour the text beside it has.

  `background-color` rather than `background-image`, with the PNG's alpha as the
  mask. The file is black on transparent, so the same one asset is also the favicon
  and the OG image, where a mask does not apply and the ink has to be really there.

  Trimmed to the artwork, so the box and the mark are the same rectangle and the
  mark's left edge lands on the gutter the wordmark and the headline below it
  already sit on. Untrimmed it carried 170px of empty canvas on the left, which
  would read as the mark being indented from everything under it. Taller than it is
  wide because the discs at 90 and 270 degrees set the height while the ones at 30
  and 330 set the width.
*/
.brand-mark {
  height: 34px;
  aspect-ratio: 918 / 1026;
  background-color: currentColor;
  mask-image: url('../assets/logo-mark.png');
  mask-repeat: no-repeat;
  mask-position: center;
  mask-size: contain;
}

.brand-text {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 700;
  /* Two rows, set tight so they read as one block beside the mark. */
  line-height: 1.02;
  letter-spacing: -0.014em;
  white-space: nowrap;
}

/*
  The wordmark in the bar, set as a mark rather than as two words of type: heavier
  face, tighter leading, slight negative tracking, so it reads as one compact block
  beside the globe instead of as a caption next to it. `<br>` between the lines and
  `nowrap` above are what keep it at exactly two lines at every width.

  Deliberately scoped to the bar. The footer carries the same markup and keeps the
  17px setting above, because the brief was the header lockup and the footer is a
  different job — a small name over an address block, not the mark.

  21px, and the number is a compromise between two figures that cannot both be had.
  Two lines at 0.86 measure 1.56 × the font size from the cap of "Library" to the
  baseline of "Trade", so:

    · 8–12% under the 24px this started at gives 21.1–22.1px, which is 87–91% of the
      38px mark
    · 80–85% of the mark asks for 19.5–20.7px, which is 14–19% under 24px

  21px lands a half-percent outside the first band and a point outside the second,
  which is as close to both as one value gets. It measures ~33px against the mark's
  38px — the mark now reads as the larger of the two, which was the point.

  No colour here. It inherits, which is white on the three ink bars and ink on the two
  paper ones — the same rule the two drawings of the mark follow, and the reason the
  wordmark does not disappear on /manifesto.
*/
.topbar .brand-text {
  font-family: var(--font-wordmark);
  font-size: 21px;
  line-height: 0.86;
  letter-spacing: -0.025em;
}

/*
  1px over the shared 8px, header only, and the whole of the header lockup's spacing
  lives in this one number: the mark is first in the flex row and keeps its place, so
  the gap is the wordmark's horizontal position. It went 8 → 5 to hold the density
  after the text got smaller, then 5 → 9 because at 5px the ring and the "L" read as
  touching. The footer keeps 8px, where the wordmark is 17px Figtree and the ratio is
  different again.

  Nothing else moves with it. `.brand` carries `margin-right: auto`, so the links and
  the CTA are pinned to the right edge and a 4px wider lockup does not push them.
*/
.topbar .brand {
  gap: 9px;
}

/*
  28px, up from 24 on 2026-09-11, and it is the tracking's replacement rather than
  new air for its own sake. Tracked-out capitals separate themselves: the 0.08em
  inside each item ended in space after its last letter, so 24px read as more.
  Sentence case has no such margin, and two of the four items are three words —
  `Hoe het werkt`, `Voor wie` — so the gap between items now has to beat the word
  space *inside* them by enough that the row reads as four things and not as one
  sentence. The row still came out 74px narrower than the mono version it replaced,
  so this is spent out of what the change gave back.
*/
.nav-links {
  display: flex;
  align-items: center;
  gap: 28px;
}

/*
  Body face, sentence case, untracked — the change the brief's header section asks
  for, argued in full above `.btn`.

  14px and weight 500. The size is the one the dropdown list below already used
  for the same job, so this row and that list are now one size rather than two.
  500 rather than 400 because these four sit in `--fg-sub` against a wordmark set
  in a display face at 20px: at 400 the row goes quiet enough to read as caption
  text, and at 600 it competes with the wordmark for the eye that has just landed
  on the page.

  The strings arrive sentence-cased from `NAV.links` — `Categorieën`, `Hoe het
  werkt` — so dropping `text-transform` is the whole edit here and no content
  changed with it.
*/
.nav-links a {
  font-family: var(--font-body);
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: 0;
  text-decoration: none;
  color: var(--fg-sub);
  transition: color var(--dur-fast) var(--ease);
}

.nav-links a:hover,
.nav-links a[aria-current='page'] {
  color: var(--fg);
}

/*
  An item in this row is one line or it is a mistake. Flexbox does not overflow a
  row, it shrinks the items in it — so without this the bar's answer to being 20px
  too narrow is to fold "Hoe het werkt" into two lines and carry on, which looks
  deliberate and is not. With it, too little room is visible as too little room,
  which is what the breakpoint at the foot of this file is for, and it is also what
  makes that breakpoint measurable at all: `nowrap` plus a forced `flex-shrink: 0`
  turns "shrunk" into "overflowing", and only overflow gives a number.

  Both kinds of item: four of the six are links and two are dropdown `summary`
  elements — Sectoren and Learn — and the guarantee is about the row rather than
  about anchors.
*/
.nav-links a,
.nav-links .nav-drop-toggle {
  white-space: nowrap;
}

/* ── The nav's dropdowns ───────────────────────────────────────────────────
   One so far: Industries, whose twelve pages are too many to be twelve items in
   the bar. A `details` — see the note at `menu()` in build.mjs — so it opens with no
   script, and js/nav.js adds only Escape and closing on a click elsewhere. It is now
   the only disclosure in the bar; the language control beside it is two visible
   options, because there are only ever two.

   Everything below restates the type, and since 2026-09-11 it restates less of
   it. The note here used to say that `.nav-links a` is mono, uppercase and
   letter-spaced — right for a nav item, wrong for twelve product category names —
   so the summary kept that treatment and the list inside dropped to the body
   face. The first half of that is no longer true: the nav row is body face and
   sentence case now (see the rule above `.btn`), which means the summary and its
   list are the same face at the same size, and the only thing still separating
   them is weight and the caret.

   **Nothing below renders on any page today.** `grep -rlo nav-drop nl en` returns
   nothing: `Sectoren` / `Industries` is not in the bar, so this whole disclosure
   is dormant CSS kept against the route coming back. It is aligned with the new
   nav type rather than left in the old one so that returning it to the bar is a
   markup change and not a typography argument.
*/

.nav-drop {
  position: relative;
  /* The menu hangs off the bar, whose own stacking context is shallow. */
  z-index: 20;
}

/*
  `flex`, not `inline-flex`: as an inline box the summary sits on its parent's
  baseline and the line box adds descender space under it, which is that much out of
  step with the links either side.
*/
.nav-drop-toggle {
  display: flex;
  align-items: center;
  gap: 7px;
  list-style: none;
  cursor: pointer;
  /* `.nav-links a`'s values: this is a nav item that happens to be a `summary`. */
  font-family: var(--font-body);
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: 0;
  color: var(--fg-sub);
  transition: color var(--dur-fast) var(--ease);
}

.nav-drop-toggle::-webkit-details-marker {
  display: none;
}

/*
  `data-current` is on the details while the visitor is on one of the pages in the
  list. Not `aria-current` — this control is not the page, one of the links inside
  it is — but the label should still read as the section they are in.
*/
.nav-drop-toggle:hover,
.nav-drop[open] > .nav-drop-toggle,
.nav-drop[data-current] > .nav-drop-toggle {
  color: var(--fg);
}

.nav-drop-caret {
  width: 10px;
  height: 10px;
  /*
    1px was optical centring against mono capitals, which sit above the box's
    middle. The toggle is sentence case in the body face since 2026-09-11 and its
    x-height sits lower, so the true value is now 0 or negative — left at 1 and
    flagged rather than guessed, because this disclosure renders on no page today
    and a number nobody can look at is not worth changing blind.
  */
  margin-top: 1px;
  transition: transform var(--dur-fast) var(--ease);
}

.nav-drop[open] .nav-drop-caret {
  transform: rotate(180deg);
}

/*
  Hung under the summary's left edge rather than centred on it. The label is one
  word and the list is twelve product categories, so the panel is several times the
  width of its control; anchored left it grows into the middle of the bar, which is
  where the room is.

  `--bg` and `--line` are whichever theme the bar is currently in, so the panel is
  black over the hero and paper further down the page with no second rule, and the
  shadow token resolves to `none` on dark.
*/
.nav-drop-menu {
  position: absolute;
  top: calc(100% + 14px);
  left: -14px;
  width: 452px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--bg);
  box-shadow: var(--shadow);
}

/*
  A short list, sized to itself.

  The 452px panel above is measured for twelve industry names in two columns. Learn has
  three rows in it — Blog, Guides and FAQ — and inheriting that width is what produced
  the bug this modifier exists to close: a short menu spread across a third of the
  viewport, `Blog` in the left column and `Guides` in the right, reading as a layout
  rather than as a dropdown. Adding the third row on 2026-09-07 did not reopen it: the
  panel is sized to its content either way.

  `width: auto` with a floor, so the panel is as wide as its longest label and never
  narrower than its own control. `max-content` would do the same and would also let a
  future long label push the panel off the right edge of a 1200px bar.

  Set here rather than by counting rows in CSS, because there is no CSS that counts rows
  of a grid — see `menu()` in build.mjs, which switches on `entries.length <= 4`.
*/
.nav-drop-menu--compact {
  width: auto;
  min-width: 176px;
}

/*
  Two columns. Twelve names in one column is a 500px drop that reaches a third of
  the way down the window; in two it is six rows, which is a list a reader takes in
  at once.
*/
.nav-drop-list {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1px 6px;
  list-style: none;
}

/* One column, because a three-row list in two columns is a row and a half. */
.nav-drop-menu--compact .nav-drop-list {
  grid-template-columns: 1fr;
}

.nav-drop-list a {
  display: block;
  padding: 7px 9px;
  border-radius: var(--r-xs);
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 400;
  line-height: 1.4;
  letter-spacing: 0;
  text-transform: none;
  color: var(--fg);
}

.nav-drop-list a:hover {
  background: var(--bg-raised);
}

.nav-drop-list a[aria-current='page'] {
  font-weight: 600;
}

/*
  The way to the index page itself, at the foot of the list. A `summary` cannot
  also be a link, so without this row /industries is the one route in the bar the
  bar cannot reach. Mono and small, so it reads as the list's own footer rather
  than as a thirteenth category.
*/
.nav-drop-all {
  display: block;
  margin-top: 10px;
  padding: 12px 9px 3px;
  border-top: 1px solid var(--line);
  font-family: var(--font-mono);
  font-size: var(--t-mono-sm);
  letter-spacing: 0.07em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--fg-soft);
  transition: color var(--dur-fast) var(--ease);
}

.nav-drop-all:hover,
.nav-drop-all[aria-current='page'] {
  color: var(--fg);
}

.nav-cta {
  min-height: 40px;
  padding: 0 20px;
}

/*
  `Log in`, immediately left of the CTA — and set as one of the nav links rather than as
  a second button. There are two actions on this site and this is neither of them: it is
  the way back in for somebody who is already a member, which is a signpost and not an
  offer. A second pill beside `Plan a call` would read as a choice between two things to
  do, and the one on the left would win by being first.

  Every value here is `.nav-links a`'s, and the duplication is deliberate: this link is
  in `.nav-end` rather than in `.nav-links` — it belongs to the button group, 14px from
  the CTA instead of 24px from the last nav item — so it cannot inherit them. Not worth a
  shared class for six declarations that would then have to be kept out of the panel.

  Nothing for the panel. `.nav-panel a` above already makes every link in there a
  full-width block row, and this is one.

  Rendered only when `PUBLIC_PORTAL_LOGIN_URL` is set — see `topbar()`. A link to a
  portal that does not exist yet is worse than no link, so the slot ships empty and the
  build prints which it is.
*/
.nav-login {
  font-family: var(--font-body);
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: 0;
  text-decoration: none;
  white-space: nowrap;
  color: var(--fg-sub);
  transition: color var(--dur-fast) var(--ease);
}

.nav-login:hover {
  color: var(--fg);
}

/* Disclosure button — revealed with the panel at the mobile breakpoint. */
.nav-toggle {
  display: none;
  align-items: center;
  gap: 9px;
  min-height: 44px;
  padding: 0 4px;
  border: 0;
  background: none;
  /* `Menu` is a word, and since 2026-09-11 it is set like one. See above `.btn`. */
  font-family: var(--font-body);
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: 0;
  cursor: pointer;
}

.nav-toggle-bars {
  position: relative;
  width: 18px;
  height: 9px;
}

.nav-toggle-bars::before,
.nav-toggle-bars::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  height: 1.5px;
  background: currentColor;
  transition: transform var(--dur-fast) var(--ease);
}

.nav-toggle-bars::before {
  top: 0;
}

.nav-toggle-bars::after {
  bottom: 0;
}

.nav-toggle[aria-expanded='true'] .nav-toggle-bars::before {
  transform: translateY(3.75px) rotate(45deg);
}

.nav-toggle[aria-expanded='true'] .nav-toggle-bars::after {
  transform: translateY(-3.75px) rotate(-45deg);
}

.nav-panel {
  display: none;
  padding: 8px var(--gutter) 26px;
  border-top: 1px solid var(--line);
  /*
    Its own ground, and opaque. It used to inherit the bar's, and the bar's is now
    glass — which is right for a strip of chrome you look past and wrong for an open
    menu, where six links and a button would each sit on whatever happened to be
    scrolling behind them. The bar is furniture; the panel is a surface.
  */
  background: var(--bg);
  /*
    Capped to the room under the bar, and scrolling inside that. The panel is a flow
    child of a `position: fixed` bar, so once it is taller than the space below the
    bar the overflow is simply not on screen and scrolling the page cannot bring it
    back — the bar does not move. Five links and a button fitted; five links, twelve
    industries and a button do not, and the row that fell off the bottom was the
    call to action.

    Two heights, oldest first, because a browser that does not know `dvh` must not
    be left with no cap at all. `dvh` is the one that matters on a phone: `vh` is the
    tallest the viewport ever gets, so with the URL bar showing it over-reports by
    exactly the amount that would push the button off again.

    `--bar-h` is what is actually left of the bar, published by js/nav.js. The
    fallback is for the page with that module blocked, and deliberately subtracts
    more than the bar takes: too small a panel merely scrolls sooner, too large a one
    hides a row.
  */
  max-height: calc(100vh - var(--bar-h, calc(var(--topbar-h) + 44px)));
  max-height: calc(100dvh - var(--bar-h, calc(var(--topbar-h) + 44px)));
  overflow-y: auto;
  /* The page behind must not take over once the panel reaches its end. */
  overscroll-behavior: contain;
}

.nav-panel[data-open='true'] {
  display: block;
}

/*
  Body face and sentence case, as the bar's own row is — see above `.btn`. Body
  size rather than the bar's 14px: these are full-width rows a thumb reaches for on
  a phone, where the nav has stopped being furniture at the edge of a wide row and
  become the only thing on screen. 16.5 is what the page's own copy is set at, and
  a menu row should not be smaller than the paragraph behind it.
*/
.nav-panel a {
  display: block;
  padding: 13px 0;
  border-bottom: 1px solid var(--line);
  font-family: var(--font-body);
  font-size: var(--t-body);
  font-weight: 500;
  letter-spacing: 0;
  text-decoration: none;
}

/*
  `display` is restated, and it is a fix rather than a tidy.

  This button is an `<a class="btn btn--solid nav-cta">` sitting inside the panel, so
  `.nav-panel a` above it — one class and one type, (0,1,1) — outranks `.btn`'s own
  `display: inline-flex` at (0,1,0), and has been turning the panel's only call to
  action into a block box since the panel was written. A block box has no flex
  centring and no `gap`, so the label sat against the left padding edge and the
  `::after` arrow butted straight onto the last letter.

  It was survivable while the label was tracked-out mono: 0.09em after `GESPREK`
  left roughly a space in front of the arrow, and a left-aligned label in a
  full-width button is a defensible look. Both halves of that went with the type
  change on 2026-09-11 — the tracking is 0 now, so `Plan een gesprek→` closed up and
  made the older half visible too.

  `.nav-panel .btn` is (0,2,0) and wins, which brings back `justify-content: center`
  and the 10px `gap` from the arrow rule. Nothing else in the panel needs this: every
  other row *is* a block.
*/
.nav-panel .btn {
  display: inline-flex;
  width: 100%;
  margin-top: 20px;
}

/*
  The same dropdown, in the open menu panel — where there is nothing to hang a
  floating panel over. So it stops floating: the twelve become rows in the same
  stack as the other four links, indented one step under the label that opened
  them, and `.nav-panel a` above already gives every row its hairline.
*/
.nav-panel .nav-drop-toggle {
  padding: 13px 0;
  border-bottom: 1px solid var(--line);
}

.nav-panel .nav-drop-menu {
  position: static;
  width: auto;
  /* Undoes `--compact`'s floor: in the stack the panel is already full width. */
  min-width: 0;
  padding: 0 0 0 16px;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
}

.nav-panel .nav-drop-list {
  grid-template-columns: 1fr;
  gap: 0;
}

.nav-panel .nav-drop-list a {
  padding: 12px 0;
  border-radius: 0;
  /* A category name, at reading size, in a stack of uppercase nav rows. */
  color: var(--fg-sub);
}

.nav-panel .nav-drop-all {
  margin-top: 0;
  padding: 13px 0;
  /* `.nav-panel a` gives it a bottom hairline; a top one as well is a double rule. */
  border-top: 0;
}

/* ── The bar's breakpoints ─────────────────────────────────────────────── */

/*
  Here rather than in home.css for the same reason the CTA is: every page carries
  this bar, and these three steps are the bar collapsing, not the homepage
  responding. They used to live in home.css, which /manifesto does not load — so
  that page kept the full desktop nav at every width and the six links wrapped
  into the brand at 390px with the last one running off the edge.

  `--topbar-h` is the height the rest of the page reserves for the bar, so it has
  to come down with it: the nav row loses a line at 720px and the bar loses a row of
  padding at 1199px, where the burger takes over. There is a fourth step at 359px,
  which is the utility strip stacking and is the one that does not move `--topbar-h`.
*/
/*
  ── The tight-gap band, deleted 2026-09-11 ───────────────────────────────────

  There was a `@media (max-width: 1279px) { .nav-links { gap: 18px } }` here, and the
  note above it argued the case well: 24px between items is right when there is room
  and is the first thing to give up when there is not — sooner than the type, which is
  a nav item's whole voice — and 1279 shared its number with the gutter's clamp so the
  row changed shape once rather than twice.

  It is gone because the row it was rescuing no longer exists. That band was measured
  against a six-item bar (Sectoren, Voordelen, Hoe het werkt, Over ons, Platform,
  Learn) with `Start gratis briefing` as its CTA, needing 1081.50px against 1152.00px
  of usable width at 1280 — 70.50px clear, which is close enough that 30px of gap is
  worth having. The rebrand took the bar to four items and a shorter CTA, and the type
  change took another 74px out of it. Measured now, at gap 28 and 1279px:

      usable width   1151.10px
      the row needs   839.49px
                     ─────────
      clearance      +311.61px

  Tightening a row with 311px of slack is not a rescue, it is a rhythm change at 1280
  that a reader can see and cannot account for. The remaining bands — the burger at
  1199, the nav row losing a line at 720, the strip stacking at 359 — are all real.

  The ordering rule the old note ended with is worth keeping even though nothing
  applies it now: if a tight-gap band ever comes back, it has to reach *down* to the
  breakpoint and *up* to the first width the wide gap fits, or there is a band in
  between where the bar is at its wide gap and overset. That is how it was wrong once.
*/

/*
  Where the bar hands over to the burger.

  ── Measured in Dutch, because Dutch is the wide language ────────────────────

  Measured, not guessed, and re-measured five times: when Learn made the row seven
  items, when the site became bilingual, when the `NL | EN` switcher took the corner,
  when Network left the bar and took it back to six, and on 2026-09-11 when the
  rebrand's four-item bar met the nav's type change. Every number below is from Chrome
  at the real face (Figtree 500 at 14px) at the widths named, with `flex-shrink: 0`
  forced on the row so that "shrunk" became "overflowing" and therefore countable.
  Flexbox shrinks rather than overflows, so without that the bar simply looks tight
  and no number is available at all.

  Dutch, not English, and it is not close — `Categorieën` against `Categories`,
  `Kennisbank` against `Learn`. The switcher is 101.14px in both, because `NL | EN` is
  the same two labels either way, so the whole difference is in the four labels and
  the CTA. Measuring in English would set this breakpoint too low and ship a folded
  Dutch label at every width in between.

  ── The Dutch row, at gap 28 ────────────────────────────────────────────────

    items           78.20 + 90.01 + 56.02 + 72.14 = 296.37px
                    Categorieën "Hoe het werkt" "Voor wie" Kennisbank
    .nav-links      296.37 + 3 × 28              = 380.37px
    brand                                          114.45px
    .nav-end        CTA 173.90 + switch 101.14
                    + one 14px gap               = 289.04px
    two 28px nav gaps                            =  56.00px
                                                  ─────────
                                                   839.49px between the gutters
    the gutter      clamp(22px, 5vw, 64px) — 5vw below 1280px, so the usable width
                    is 0.9 × window there, and window − 128 from 1280px up

    0.9 × window ≥ 839.49  →  window ≥ 932.77px

  Confirmed by sweeping the viewport rather than trusting the arithmetic: at 940px the
  row has +6.51px and at 900px it has −29.49px. **The Dutch bar now first fits at
  933px** — 236px lower than the 1169px this block recorded before, because the bar
  lost two items and a long CTA in the rebrand and then lost the nav's letter-spacing
  on top of that.

  ── 1199, kept, and the headroom that is now sitting unused ─────────────────

  1199 is deliberately far above 933 and no longer defensible as *tight*: at 1200 the
  row measures **+240.51px** of clearance, at 1280 **+312.51px**, at 1440 **+472.51px**.
  The breakpoint is 266px above the width where the desktop bar would still fit.

  It stays where it is on purpose, and the reason is coupling rather than typography.
  js/nav.js's `matchMedia('(min-width: 1200px)')` is this number, one past it: the two
  have to move together or a window between them shows both nav rows or neither. 1199
  is also where the bar loses a row of padding and `--topbar-h` steps to 86, so moving
  it is three coordinated changes and a re-test of the panel, not one media query.

  So this is recorded rather than acted on: **the bar could hand over to the burger
  around 960–1000px instead of 1199**, which would put a 1024px tablet on the desktop
  side with roughly 60px clear. That is an available improvement with a real handover
  to re-test, not a defect — and it is a decision about tablets, which is not a
  decision the type change gets to make on its own.

  This was 1319 when the bar had seven items, and that number had to accept a real
  cost — **it put every 1280px-wide laptop on the burger side**, 15.47px short. Losing
  Network from the bar (the item, not the section: `#network` is still on the page)
  took 57px out of the row and the cost with it, and nothing since has come close to
  putting it back.
*/
@media (max-width: 1199px) {
  :root {
    --topbar-h: 86px;
  }

  /*
    The panel takes over all three: the links, the CTA and the switcher. The switcher
    goes with them rather than staying in the bar beside the burger, because two
    controls in that corner is what the burger exists to prevent — and the panel's
    copy of it is a real second element in the document, not this one moved.
  */
  .nav-links,
  .nav-end > .btn,
  .nav-end > .lang-switch {
    display: none;
  }

  .nav-toggle {
    display: inline-flex;
  }
}

@media (max-width: 720px) {
  :root {
    /* The nav row loses a line. The strip keeps its height until 359px, below. */
    --topbar-h: 78px;
  }
}

/*
  The narrowest step, and the only one that is about the utility strip rather than the
  nav: below this the two contact links stop fitting on one line, so they stack.

  359 is measured, not chosen. At 375px the pair occupies 291.34px — 161.80 for the
  address, 83.13 for the number, the `/` and two 12px gaps — against a usable width of
  viewport minus two gutters, and the gutter has bottomed out at 22px by here
  (`clamp(22px, 5vw, 64px)`). So one line needs 335.34px of window, 360px is the
  narrowest width in common use that clears it (316px usable, 24.66px clear), and
  everything below it gets two lines instead of a number cut in half.

  `--topbar-h` does not follow this one, and that is not an oversight: it reserves the
  height the bar *settles* at once the strip has ridden up, which is the nav row alone.
  The strip growing a line changes the bar at scroll position 0 and nothing after it.

  `--strip-h` does follow it, for exactly that reason: it is the strip, it is the other
  half of what the bar measures at scroll position 0, and the hero reserves the pair so
  its first line is not behind the glass on arrival (see `.hero` in collective.css). 59px
  measured here — two 10.5px mono lines, a 3px gap and the same 11px of padding.

  The `/` goes with the single line it was separating. Two values on their own lines are
  already separated, and a slash left dangling at the end of the first one reads as
  though something is missing.
*/
@media (max-width: 359px) {
  :root {
    --strip-h: 59px;
  }

  .utility-end {
    flex-direction: column;
    align-items: flex-end;
    gap: 3px;
  }

  .utility-end .sep {
    display: none;
  }
}

/* ── Footer ────────────────────────────────────────────────────────────── */

.footer {
  padding: clamp(56px, 6vw, 88px) var(--gutter) 34px;
  background: var(--bg);
  color: var(--fg);
}

.footer-top {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) repeat(auto-fit, minmax(150px, 1fr));
  gap: 44px;
  padding-bottom: 44px;
  border-bottom: 1px solid var(--line);
}

.footer-brand .brand {
  margin-right: 0;
  margin-bottom: 18px;
}

.footer-location {
  font-family: var(--font-mono);
  font-size: var(--t-mono-sm);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--fg-soft);
}

.footer-group h2 {
  font-family: var(--font-mono);
  font-size: var(--t-mono-sm);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-soft);
  margin-bottom: 16px;
}

.footer-group ul {
  list-style: none;
  display: grid;
  gap: 10px;
}

.footer-group a {
  font-size: var(--t-small);
  text-decoration: none;
  color: var(--fg-sub);
  transition: color var(--dur-fast) var(--ease);
}

.footer-group a:hover {
  color: var(--fg);
}

/* ── The Contact column ──────────────────────────────────────────────────
   Fourth and last, and the only footer group that is not a list of routes. It reuses
   `.footer-group`'s heading, link colour and hover rather than restating them: three
   copies of the same hover is how one of them ends up different.

   No `grid-template-columns` change above. `.footer-top`'s `repeat(auto-fit,
   minmax(150px, 1fr))` was already sized for more tracks than the footer had columns —
   the two `Portals` and `Elsewhere` groups behind flags would have filled them — so the
   fourth column is one more track taken, not a new template. `1fr` gives it the same
   width as its neighbours and its content is the widest of the four, which is exactly the
   arrangement §10 asks for.
*/

/*
  `<address>` is italic by UA default, which is the one thing this element brings that is
  not wanted: the semantics are the reason it is here, the styling is not.
*/
.footer-contact address {
  font-style: normal;
}

/*
  The registered name and the two postal lines, as one block a reader scans in one go.
  Body face at `--t-small`, matching the links under it: this is prose, not a micro-label
  like the heading above it or the location line in the identity block.

  `--fg-soft` against the links' `--fg-sub` is the whole hierarchy here — the address is
  something to read, the two lines under it are things to press.
*/
.footer-company,
.footer-postal {
  font-size: var(--t-small);
  color: var(--fg-soft);
}

.footer-postal {
  margin-bottom: 16px;
}

/*
  The row that links /contact, under the inbox and the number.

  ── An arrow, and why it is not `.link` ──────────────────────────────────────

  `.link` is this site's text-link-as-action and it already carries this glyph in
  `::after`. It is also mono, uppercased and letterspaced with a 1px rule under it, which
  is a control sized for a section footer — dropped into a 150px footer column between two
  `--t-small` links it would be the loudest thing in the footer and would read as a button
  somebody forgot to draw.

  So this borrows the one part that carries meaning. The arrow says "this goes somewhere
  else", which is exactly what distinguishes this row from the two above it: those act
  *here* — they open a mail client, they dial — and this one navigates. Everything else
  comes from `.footer-group a` by the cascade, which is one specificity point up and would
  win over a restated `color` here anyway.

  `translateX` on hover rather than a colour change of its own, because the colour change
  is already `.footer-group a:hover`'s and two hover effects on one link is one too many.
  `.link:hover::after` uses the same 3px, so the two arrows on this site move alike.
*/
.footer-action-row {
  /* The `<ul>` above it is a 10px stack. This continues it rather than starting a block. */
  margin-top: 10px;
}

.footer-action {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}

.footer-action::after {
  content: '→';
  transition: transform var(--dur-fast) var(--ease);
}

.footer-action:hover::after {
  transform: translateX(3px);
}

/*
  The mark sits under the two links with the same gap the links have between them, so the
  column reads as one stack rather than a block with a badge under it. `inline-flex` and
  `width: max-content` keep the hit area — and the focus ring — around the 15px glyph
  instead of the full width of the column, which a block-level anchor would take.

  Its ink, its transition and its hover are all `.footer-group a`'s: this is an anchor
  inside a footer group, so it takes `--fg-sub` at rest and `--fg` on hover from the two
  rules above without a word here. Restating them was the first version, and the cascade
  had the last word anyway — `.footer-group a` is one specificity point up, so a
  `color` here reads as an intent the browser is ignoring. The SVG is `currentColor`, so
  the glyph follows whatever those rules resolve to.
*/
.footer-social {
  display: inline-flex;
  width: max-content;
  margin-top: 16px;
}

/*
  The one state the group's rules do not cover. `:focus-visible` gets the site's 2px ring
  from base.css's global rule, and on a 15px glyph the ring alone is a box around
  something that has not changed — so the mark brightens to match what a hover does.
*/
.footer-social:focus-visible {
  color: var(--fg);
}

.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 22px;
  padding-top: 26px;
  font-family: var(--font-mono);
  font-size: var(--t-mono-sm);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--fg-soft);
}

.footer-bottom a {
  text-decoration: none;
}

.footer-bottom a:hover {
  color: var(--fg);
}

.footer-legal {
  display: flex;
  gap: 18px;
  margin-left: auto;
}

/* ── Reveal on scroll ──────────────────────────────────────────────────── */

/*
  Scoped to `html[data-js]`, which an inline script in <head> sets before first
  paint. Without that guard a blocked or failed module would leave every
  revealed element permanently at opacity 0 — the page has to survive its own
  JavaScript not running.
*/
html[data-js='true'] [data-reveal] {
  opacity: 0;
  transform: translateY(15px);
  transition:
    opacity var(--dur-slow) var(--ease),
    transform var(--dur-slow) var(--ease);
}

html[data-js='true'] [data-reveal].is-in {
  opacity: 1;
  transform: none;
}

/* ── Reduced motion ───────────────────────────────────────────────────── */

/*
  Everything that moves on its own stops. Interactions still work — reduced
  motion means "do not animate", not "do not respond".
*/
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }

  html[data-js='true'] [data-reveal] {
    opacity: 1;
    transform: none;
  }

  /*
    The blanket rule above already stops these travelling — one 1ms iteration and
    done. Stated anyway, because "1ms then hold" leaves the dashes parked at the
    end of their offset, and because this is the page's one piece of perpetual
    motion: it should be obvious from reading the file that it stops.

    The figure loses nothing by holding still. The five strands, the node and the
    line out are all there in the static frame; what the movement adds is the
    direction, and a reader who has asked for no movement can read it off the
    geometry.
  */
  .cta-flow path {
    animation: none;
  }
}
