/*
  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.
*/
:focus-visible {
  outline: 2px solid var(--fg);
  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;
}

/*
  Closes the loop back to the hero: the same arc language as the globe's lanes,
  drawn once across the bottom of the page. Ambient, so it is hidden from
  assistive technology and stops moving under reduced motion.
*/
.cta-routes {
  position: absolute;
  inset: auto 0 0;
  z-index: -1;
  width: 100%;
  height: 60%;
  opacity: 0.55;
  /*
    Fades in from nothing at its own top edge, so the arcs are strongest in the
    empty band below the buttons and absent where the copy is.

    Without it the bottom 60% of the section is enough to reach the last line of
    the paragraph on a phone — 486px tall there, so the box starts at y 194 and
    the paragraph ends near 315 — and dashed orange ran straight through the
    text. Masking rather than shortening the box per breakpoint: the copy block
    changes height with the viewport and the wording, and a percentage tuned to
    today's line count would drift the next time either changes.
  */
  mask-image: linear-gradient(to bottom, transparent 0%, #000 70%);
}

.cta-routes path {
  fill: none;
  stroke: var(--signal);
  stroke-width: 1.2;
  stroke-linecap: round;
  stroke-dasharray: 5 14;
  animation: route-travel 5.5s linear infinite;
}

.cta-routes path:nth-child(2) {
  stroke: var(--info);
  animation-duration: 7s;
}

.cta-routes path:nth-child(3) {
  animation-duration: 9s;
  animation-direction: reverse;
}

@keyframes route-travel {
  to {
    stroke-dashoffset: -190;
  }
}

/*
  Quieter and shorter on a phone. The copy takes three or four lines here instead
  of two, so 60% of the section still reached the last line of it even with the
  fade — /manifesto's paragraph is the long one. A third of the section starts the box
  below the paragraph on both pages, and the fade then holds the arcs off until
  roughly the buttons.
*/
@media (max-width: 720px) {
  .cta-routes {
    height: 34%;
    opacity: 0.35;
  }
}

/*
  The copy block. Narrow on purpose: this is the last thing on the page and it is
  three short lines and two buttons, so holding it to a quarter of the measure
  leaves the arcs the rest of the band and stops the headline running the full
  1240px above a 12px gap between two buttons.

  The heading is 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: 24em;
}

.cta-inner h2 {
  font-size: var(--t-display);
  line-height: 0.95;
  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 ───────────────────────────────────────────────────────────── */

.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;
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  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);
}

/* A text link that reads as an action without becoming a third button style. */
.link {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  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: '→';
  /* Turn off the letterspacing so the arrow is not pushed off its own baseline. */
  letter-spacing: 0;
  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 one exception, and then a second: 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.

  `transparent` is the hero's, and it now means *less* tint rather than none: the
  bar's own glass is what carries the sky behind the nav, so the hero no longer has
  to hand the bar a background — see `.topbar` above. The value is kept because the
  hero is the one section that also darkens under the bar on its own account
  (`.hero::before`), and the bar has to know not to stack a second full tint on top
  of it. What is left of the rule is the narrow-viewport case in home.css.

  The two `paper` values are the three sections under the hero. 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 band between a black hero and the white body is printed on
  grey — and a bar drawn in the light theme's white over a grey 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.

  Only ever set by the script, never shipped in the static HTML. Each of these 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, none of them must arrive.
*/
.topbar[data-bar='transparent'] {
  background: color-mix(in srgb, var(--bg) 44%, transparent);
}

/*
  `--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;
}

/*
  Two files, not one recoloured: the mark for dark ground is distressed and the
  one for paper is drawn clean, so they are different artwork and neither can be
  derived from the other. The bar is ink on /, /suppliers and /faq and paper on
  /manifesto and the estimator; the footer is ink everywhere. Dark is the default
  so a bar whose theme never gets set still shows a legible mark.

  Both files are trimmed to the ring itself, so the box and the mark are the same
  rectangle and the ring's left edge lands on the gutter the wordmark and the
  headline below it already sit on. Untrimmed they carried 116px and 52px of empty
  canvas, which read as the mark being indented from everything under it. The
  ratios differ because the two drawings do.
*/
.brand-mark {
  height: 38px;
  aspect-ratio: 1079 / 1084;
  background-image: url('../assets/logo-mark.png');
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
}

[data-theme='light'] .brand-mark {
  aspect-ratio: 1172 / 1143;
  background-image: url('../assets/logo-mark-light.png');
}

.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;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 24px;
}

.nav-links a {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  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. `.nav-links a` is mono, uppercase and
   letter-spaced, which is right for a nav item and wrong for twelve product
   category names, so the summary keeps that treatment and the list inside drops
   back to the body face.
*/

.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;
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  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;
  /* Optically centred against mono capitals, which sit above the box's middle. */
  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;
}

/* 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;
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  text-transform: uppercase;
  letter-spacing: 0.09em;
  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;
}

.nav-panel a {
  display: block;
  padding: 13px 0;
  border-bottom: 1px solid var(--line);
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  text-decoration: none;
}

.nav-panel .btn {
  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 band just above the breakpoint, where the six items and the brand come closest to
  touching. 24px between them 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. Worth
  30px of row: the five gaps are 120px at 24 and 90px at 18.

  1279, and it shares the number with the gutter rather than with the breakpoint. The
  gutter is `clamp(22px, 5vw, 64px)`, so 1280px is the width at which it stops growing
  and the row's usable space stops tracking the window — a real change of shape in the
  layout already. Putting the gap change there means the row changes shape once instead
  of twice. Both sides have room: the Dutch bar needs 1081.50px at gap 24 against
  1152.00px of usable width at 1280 (70.50px clear), and 1051.50px at gap 18 against
  1151.10px at 1279 (99.60px clear).

  The order of the two bands is the thing to keep right, and it was wrong once: with the
  tight gap ending at 1240 there was a band between 1241 and 1331 where the bar was at
  its wide gap and overset. The rule is that the tight gap has to reach *down* to the
  breakpoint and *up* to the first width the wide gap fits — 1200 and 1280 here.
*/
@media (max-width: 1279px) {
  .nav-links {
    gap: 18px;
  }
}

/*
  Where the bar hands over to the burger.

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

  Measured, not guessed, and re-measured four times: when Learn made the row seven
  items, when the site became bilingual, when the `NL | EN` switcher took the corner,
  and when Network left the bar and took it back to six. Every number below is from
  Chrome at the real faces (Figtree 500, IBM Plex Mono 500) 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:

    NL items    82.28 + 73.45 + 106.09 + 65.28 + 65.28 + 57.80 = 450.18px
                Sectoren Voordelen "Hoe het werkt" "Over ons" Platform Learn
    EN items    98.60 + 65.28 +  97.92 + 40.80 + 65.28 + 57.80 = 425.68px
                Industries Benefits "How it works" About Platform Learn

  and the CTA is the bigger difference — "Start gratis briefing" is 215.88px against
  "Start free brief"'s 174.48px. The switcher is 101.14px in both, because `NL | EN`
  is the same two labels either way. Net: the Dutch bar needs 73px more window than
  the English one, so an English measurement would have set this breakpoint 73px too
  low and shipped a folded Dutch label at every width in between.

  ── The Dutch row, at gap 18 ────────────────────────────────────────────────

    .nav-links      450.18 + 5 × 18            = 540.18px
    brand                                        124.30px
    .nav-end        CTA 215.88 + switch 101.14
                    + one 14px gap             = 331.02px
    two 28px nav gaps                          =  56.00px
                                                ─────────
                                                1051.50px 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 ≥ 1051.50  →  window ≥ 1168.33px

  Confirmed by sweeping the viewport rather than trusting the arithmetic: at 1169px
  the row has +0.60px, at 1168px it has −0.30px. **The Dutch bar first fits at
  1169px.** At gap 24 it needs 1081.50px, which arrives at 1209.50px.

  ── 1199, and the 1280px laptop it recovers ─────────────────────────────────

  1199 and not 1168, for 28.50px of clearance at 1200 (measured): a breakpoint set at
  the exact crossing fails the first time a label is one character longer, and the
  Dutch CTA in particular is copy somebody may still improve.

  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. At 1280 the Dutch bar now measures
  100.50px of clearance, so the commonest laptop width is back on the desktop side
  with room to spare.

  1199 is also where the bar loses a row of padding, so the row changes shape once
  instead of twice. js/nav.js's `matchMedia('(min-width: 1200px)')` is the same
  number, one past this one: the two have to move together or a window between them
  shows both nav rows or neither.
*/
@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.

  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) {
  .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.
  */
  .cta-routes path {
    animation: none;
  }
}
