/* Altitude — iteration 1.
   One stylesheet, no build step, no third-party requests. Brand tokens come from the
   brandbook and are not reinvented per section (see CLAUDE.md "Brand system").
   Contrast pairs are the verified ones in WEBSITE-PROPOSAL.md §6.2 — white on Soft Rose
   fails at 2.0:1 and is never used. */

/* ---------- fonts: self-hosted, latin subset, OFL ---------- */
@font-face {
  font-family: 'Lora';
  src: url('fonts/lora-latin.woff2') format('woff2');
  font-weight: 400 700;   /* variable file — covers the 600 used for headings */
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Poppins';
  src: url('fonts/poppins-400-latin.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Poppins';
  src: url('fonts/poppins-500-latin.woff2') format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
}

/* ---------- tokens ---------- */
:root {
  --burgundy: #682737;
  --rose:     #D4A5A5;
  --glacier:  #CFD7E7;
  --forest:   #2D4A3E;
  --charcoal: #2C2C2A;
  --ground:   #F5EFEA;
  --ground-2: #FBF7F4;
  --line:     #E2D6CD;

  --serif: 'Lora', Georgia, 'Times New Roman', serif;
  --sans:  'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

  --s-1: 4px;  --s-2: 8px;  --s-3: 12px; --s-4: 16px; --s-5: 24px;
  --s-6: 32px; --s-7: 48px; --s-8: 64px; --s-9: 96px; --s-10: 128px;

  --step--1: clamp(0.83rem, 0.80rem + 0.15vw, 0.89rem);
  --step-0:  clamp(1.00rem, 0.96rem + 0.20vw, 1.13rem);
  --step-1:  clamp(1.25rem, 1.16rem + 0.45vw, 1.50rem);
  --step-2:  clamp(1.56rem, 1.39rem + 0.85vw, 2.00rem);
  --step-3:  clamp(1.95rem, 1.65rem + 1.50vw, 2.66rem);
  --step-4:  clamp(2.44rem, 1.94rem + 2.50vw, 3.55rem);
  --step-5:  clamp(2.60rem, 1.90rem + 3.60vw, 4.60rem);

  /* The hero headline is off the scale, and below 900px it is sized to fill the content
     column rather than by taste: its longest line, "De wintersport", measures 6.78em in Lora
     at display size (measured off the rendered page, letter-spacing included), so dividing the
     column — the viewport minus its two 24px gutters — by 7 fills it with a little slack. That
     is 49px on a 390px phone and 39px at 320. The 4.5rem ceiling is where --step-hero-lg
     starts at 900px, so a tablet never overshoots the desktop headline. Re-measure the 7 if the
     headline's words change. */
  --step-hero: min(calc((100vw - 3rem) / 7), 4.5rem);
  /* From 900px up the headline is two lines of roughly equal length, and the longer of the
     two is the first — "De wintersport waar je", 21 characters plus the gap between the two
     boxes it is made of. Same arithmetic as above and the same 0.49em advance measured off
     Lora at display size: that line needs (21 x 0.49 + 0.26) x font-size, so about 10.6 ems,
     and it has to fit the content column — min(1200px, 100vw) minus its two 24px gutters.
     The size has a floor as well as a ceiling here, because the second line is made by
     wrapping and not by a <br>: it has to be too big for "waar je samen" to follow on the
     first line, i.e. above column / 13.75em. The clamp sits between the two with room on
     each side — 72px where the column is 852px wide (900px viewport), against a 62px floor
     and an 81px ceiling, up to 96px where the column reaches its 1152px cap (1200px and
     above), against 84px and 109px. The negative intercept is what makes the line pass
     through both. Re-derive it, and --step-hero, if the headline's words change. */
  --step-hero-lg: clamp(4.50rem, -0.39rem + 8.70vw, 6.00rem);

  --rhythm: clamp(64px, 10vh, 128px);
  --head-h: 64px;
  --wrap: 1200px;
  --radius: 2px;
  --radius-pill: 999px;
  /* Buttons: rounded, not pilled. Enough curve to read as a soft-cornered block, far enough
     from --radius-card that a button never looks like a small card. */
  --radius-btn: 6px;
  /* Cards get their own, softer radius: at 2px a large surface reads as an accident
     rather than a shape. Only surfaces you can pick up (the USP cards) use it. */
  --radius-card: 14px;
  /* The form panel is the largest single surface on the page and sits on the burgundy band.
     It needs more curve than a card for the corners to read as intentional at that size. */
  --radius-shell: 28px;
  /* The header bar ignores the 1200px cap: the brand hugs the left edge, the menu the right.
     One gutter token so the two sides stay symmetrical at every width. */
  --head-gutter: clamp(16px, 2.4vw, 40px);
}

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--ground);
  color: var(--charcoal);
  font: 400 var(--step-0)/1.65 var(--sans);
}
h1, h2, h3 { font-family: var(--serif); font-weight: 600; line-height: 1.15; margin: 0; }
p, ul, ol, blockquote, figure { margin: 0; }
ul, ol { padding: 0; list-style: none; }
img, svg { display: block; max-width: 100%; }
img { height: auto; }
a { color: var(--burgundy); }
:where(a):hover { color: var(--charcoal); }
button { font: inherit; }

:focus-visible {
  outline: 2px solid var(--forest);
  outline-offset: 2px;
  border-radius: var(--radius);
}
/* The forest ring disappears on the dark bands, so invert it there — but the form shell
   inside the burgundy band is light again, and rose on it is 1.8:1. Put forest back. */
.form-band :focus-visible, .foot :focus-visible { outline-color: var(--rose); }
.form-shell :focus-visible { outline-color: var(--forest); }

.skip {
  position: absolute; left: var(--s-4); top: -100px; z-index: 60;
  background: var(--burgundy); color: #fff; padding: var(--s-3) var(--s-5);
  text-decoration: none; border-radius: var(--radius);
}
.skip:focus { top: var(--s-3); color: #fff; }

/* ---------- layout ---------- */
.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--s-5); }
.band { padding-block: var(--rhythm); position: relative; }
.band-mist { background: var(--glacier); }
[id] { scroll-margin-top: calc(var(--head-h) + var(--s-5)); }

/* ---------- header ----------
   Fixed, not sticky: the hero is exactly one viewport tall and the bar sits *on* the photo
   rather than above it, so it must not take a row of layout. At the top of the page it is
   transparent, on bare photograph, and the burgundy has to clear AA against whatever frame is
   in that slot on its own — nothing is laid under it. That makes the top band of a frame part
   of whether the frame can take the slot at all; see CLAUDE.md.
   From the first scroll (and while the mobile drawer is open) it becomes the solid ground bar
   it is over every other band. */
.head {
  position: fixed; top: 0; left: 0; right: 0; z-index: 40;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: border-color 200ms ease, background 200ms ease;
}
.head.is-stuck, .head.is-open {
  background: color-mix(in srgb, var(--ground) 92%, transparent);
  backdrop-filter: blur(8px);
  border-bottom-color: var(--line);
}
.head-in {
  padding: var(--s-3) var(--head-gutter);
  min-height: var(--head-h);
  display: flex; align-items: center; gap: var(--s-5);
}
.brand {
  display: flex; align-items: center; gap: var(--s-3);
  color: var(--burgundy); text-decoration: none; margin-right: auto;
}
.brand:hover { color: var(--burgundy); }
/* The brandmark is the supplied `-lines` SVG — the line-drawn variant, whose interior contours
   sit inside the same artwork bounds as the solid one, which is why the inlined lockup keeps the
   solid file's `viewBox`. It is inlined with its single burgundy fill swapped for
   `currentColor` — that swap is what lets the footer carry the same file in rose on charcoal
   and the thanks state in rose on the panel. Three lockups, one per slot, and they are not
   interchangeable: `.lockup-h` (the horizontal lockup, icon beside the wordmark) signs the
   page once, in the header bar, and is sized by height because the bar is what constrains it;
   `.mark` is the icon alone, for every other slot — the footer column and the thanks state,
   where the word has already been said and the mark is a flourish rather than a signature.
   The other axis stays `auto` so the viewBox keeps the ratio. Never letterspace or re-set the
   word: the lettering is drawn, not type. The stacked lockup in `assets/` has no slot on this
   page. */
.lockup { display: block; width: auto; height: auto; color: inherit; }
.brand .lockup-h { height: 30px; }
.mark { width: 30px; height: auto; color: inherit; }
.nav ul { display: flex; gap: var(--s-6); }
/* Scoped to the list on purpose: the CTA pill is a sibling of the <ul>, and these rules would
   otherwise repaint it charcoal-on-burgundy and hang a divider under it in the drawer. */
.nav ul a {
  text-decoration: none; color: var(--charcoal); font-size: var(--step--1);
  letter-spacing: 0.02em; padding-block: var(--s-2);
  border-bottom: 1px solid transparent;
}
.nav ul a:hover { color: var(--burgundy); border-bottom-color: var(--rose); }
/* The page you are on. A permanent rose underline rather than a colour change: the hover
   state already owns the colour, and a link that looks hovered without being hovered is
   worse than no marker at all. */
.nav ul a[aria-current="page"] { color: var(--burgundy); border-bottom-color: var(--rose); }

.burger {
  display: none; width: 44px; height: 44px; padding: 10px;
  background: none; border: 0; cursor: pointer;
}
.burger span {
  display: block; height: 2px; background: var(--burgundy); border-radius: 2px;
  transition: transform 200ms ease, opacity 150ms ease;
}
.burger span + span { margin-top: 5px; }
.burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; padding: var(--s-3) var(--s-5);
  font-family: var(--sans); font-size: var(--step--1); font-weight: 500;
  letter-spacing: 0.02em; text-decoration: none; border-radius: var(--radius-btn);
  gap: var(--s-2);
  border: 1px solid transparent; cursor: pointer;
  transition: background 180ms ease, color 180ms ease, border-color 180ms ease;
}
/* Solid by default: a button is a surface, so it carries an opaque fill everywhere it isn't
   sitting bare on a photograph. Hover deepens the same colour rather than shifting hue. */
.btn-p {
  background: var(--burgundy);
  color: #fff;
}
.btn-p:hover, .btn-p:focus-visible {
  background: color-mix(in srgb, var(--burgundy) 85%, black);
  color: #fff;
}
.btn-s {
  background: var(--ground-2);
  color: var(--burgundy); border-color: var(--burgundy);
}
.btn-s:hover, .btn-s:focus-visible {
  background: color-mix(in srgb, var(--ground-2) 70%, var(--rose));
  color: var(--burgundy);
}
/* `.btn-glass` is the one exception: a button sitting bare on a photograph or video, with no
   card or band behind it (the hero pair, the steps-band CTA below "Hoe werkt het?"). A solid
   fill there reads as a badge stamped on the frame — the shouty treatment the brand's
   restraint rule exists to avoid — so this swaps the fill for a translucent, blurred one and
   resolves to opaque on hover instead. Every other `.btn-p`/`.btn-s` stays solid; don't reach
   for this on a button that has a surface under it. */
.btn-glass.btn-p {
  background: color-mix(in srgb, var(--burgundy) 55%, transparent);
  backdrop-filter: blur(6px);
}
.btn-glass.btn-p:hover, .btn-glass.btn-p:focus-visible { background: var(--burgundy); }
.btn-glass.btn-s {
  background: color-mix(in srgb, var(--ground-2) 45%, transparent);
  backdrop-filter: blur(6px);
}
.btn-glass.btn-s:hover, .btn-glass.btn-s:focus-visible { background: var(--ground-2); }
/* `.btn-ghost` is a route out that has to stay quiet beside the card it sits in — the
   "Voor wie?" cards, where three solid buttons in a row would outshout the photographs above
   them. A burgundy outline on no fill, which resolves to the solid primary on hover. */
.btn-ghost {
  background: transparent;
  color: var(--burgundy); border-color: var(--burgundy);
}
.btn-ghost:hover, .btn-ghost:focus-visible { background: var(--burgundy); color: #fff; }
.btn-lg { min-height: 52px; padding-inline: var(--s-7); font-size: var(--step-0); }

/* The forward arrow on the primary CTAs. Drawn, not typed: the Poppins subset carries no
   arrow glyph, so a text "→" would come from whatever fallback font the OS picks and sit at
   its own weight and baseline. Sized in em so it tracks --step on .btn-lg. */
.btn-arrow {
  flex: none; width: 1.05em; height: 1.05em;
  transition: transform 180ms ease;
}
.btn:hover .btn-arrow, .btn:focus-visible .btn-arrow { transform: translateX(3px); }

/* ---------- hero ----------
   One viewport exactly: `svh` is the *smallest* viewport height, so the first screen is whole
   whether or not a mobile browser's chrome is showing, and it never grows a scroll of its own.
   No max-height cap and no min-height either — the height is the viewport, full stop. */
.hero {
  position: relative;
  height: 100vh;          /* fallback for browsers without the viewport-relative units below */
  height: 100svh;
  display: grid;
  /* Dead centre on the viewport at every width, and symmetric block padding is what makes it
     dead centre: an asymmetric pad would sit the block half a header-height off. The pad itself
     stays as a guard, so a tall block stops short of the bar instead of sliding under it.
     On a phone the portrait crop shows the frame's middle third — sunlit snow slopes in every
     scene of the loop — and centring puts the copy on it; on a desktop the crop turns vertical
     and centring puts the headline on the upper-middle peaks. Both positions are measured
     (WEBSITE-PROPOSAL.md §6.2) and there is nothing here to absorb a bad one, so re-measure if
     the footage or the copy block changes. */
  align-items: center;
  padding-block: var(--head-h);
  overflow: hidden;
}
.hero-fig {
  position: absolute; inset: 0;
  /* Under the copy, without taking it out of the flow. */
  z-index: 0;
  /* The slow settle: the photograph opens 12% in and eases back out over twelve seconds,
     so the first screen is still moving while it is being read. The transform goes on the
     figure, not on the img — the parallax pass in app.js owns the img's inline transform
     and rewrites it every frame; the two compose instead of fighting. It never scales below
     1, so the frame stays covered, and .hero's overflow clip hides the overshoot. */
  animation: hero-settle 12s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
}
@keyframes hero-settle { from { transform: scale(1.12); } to { transform: none; } }
/* The video is centred by default (no crop class): every scene in the loop is high-key snow
   and sky with the subject near the frame's middle, so a centred crop never puts a peak or a
   dark rock face directly behind the headline whatever the viewport's aspect ratio. */
.hero-fig img, .hero-fig video { width: 100%; height: 100%; object-fit: cover; }

/* Nothing over the frame. The video runs at full strength edge to edge and the copy's
   legibility comes from the frame itself: this is high-key snow and a pale sky throughout the
   loop, and the type is dark, so the pairing the footage already gives is the one that works.
   The numbers, measured by sampling the video's own centre band across every frame of the
   loop (worst case, not an average), are in WEBSITE-PROPOSAL.md §6.2 — re-measure them the same
   way if the footage changes, because there is nothing else holding the type up and nothing is
   allowed to be added. The header bar is on bare footage too, and over this frame's top band
   the burgundy nav clears AA on its own. */

.hero-copy {
  position: relative; z-index: 2;
  padding-inline: var(--s-5); max-width: var(--wrap); margin-inline: auto; width: 100%;
}
.hero h1 {
  font-size: var(--step-hero);           /* fallback where svh is unknown */
  /* A ceiling in viewport height, not width: on a short laptop window two lines at the full
     size would push the lede and the buttons off the first screen, and the hero clips. */
  font-size: min(var(--step-hero), 19svh);
  color: var(--burgundy); font-weight: 400; letter-spacing: -0.02em;
  /* Tighter than the 1.15 the other headings run at: leading tuned for a paragraph opens a
     gap at display size, and the two lines should read as one block. */
  line-height: 0.95;
}

/* ---------- hero headline, load animation ----------
   Each line sits in its own clipping box and rises from behind its own baseline. Pure CSS and
   no JS hook: this is the first thing on the first screen, so it cannot wait on a script — and
   with script off it plays exactly the same. The boxes are padded and negatively margined by
   the same amount, so the clip edge clears the glyphs without moving anything. They cover
   more than the leading does: at line-height 0.95 the ascenders and descenders already
   overhang the line box. */
.hero-title .hl {
  /* Block, not inline-block, and `fit-content` so the clip box is only as wide as its own
     words: the lines break where the boxes say they do, with no <br> to keep in step with
     the copy. */
  display: block; width: fit-content; overflow: hidden;
  padding: 0.24em 0 0.28em; margin: -0.24em 0 -0.28em;
}
/* Three lines here, out of four boxes: "waar je" and "samen" go inline-block so they sit
   side by side on the middle line, and the whitespace between them in the markup is the word
   space that separates them. The two that bookend them stay block, so they keep a line each
   whatever the width — the phone layout is fixed by the display values, not by where the type
   happens to run out of room. */
.hero-title .hl:nth-of-type(2),
.hero-title .hl:nth-of-type(3) { display: inline-block; vertical-align: top; }
/* Italic overhangs its own advance width at the right edge, which the clip box's fit-content
   width doesn't account for — pad it back the same amount so the overflow:hidden edge clears
   the slanted "n" instead of cutting it. */
.hero-title .hl-em { font-style: italic; padding-right: 0.12em; margin-right: -0.12em; }
.hero-title .hl > span {
  display: inline-block;
  animation: hero-line 900ms cubic-bezier(0.16, 1, 0.3, 1) 80ms both;
}
/* One delay per rendered *line*, so the words of a line rise together. The grouping differs
   between the two layouts, so the delays are set per breakpoint: here it is 1 / 2+3 / 4. */
.hero-title .hl:nth-of-type(2) > span,
.hero-title .hl:nth-of-type(3) > span { animation-delay: 160ms; }
.hero-title .hl:nth-of-type(4) > span { animation-delay: 240ms; }
@keyframes hero-line { from { transform: translateY(115%); } to { transform: none; } }

/* The rest of the block follows the headline up rather than arriving with it. */
.hero .lede    { animation: hero-rise 700ms cubic-bezier(0.16, 1, 0.3, 1) 460ms both; }
.hero .hero-cta{ animation: hero-rise 700ms cubic-bezier(0.16, 1, 0.3, 1) 580ms both; }
@keyframes hero-rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}
.lede {
  font-size: var(--step-1); line-height: 1.5; max-width: 34ch;
  margin-top: var(--s-5); color: var(--charcoal);
}
/* 24px exactly at every width, which is where WCAG's "large text" starts for an unbolded face:
   on a bare frame that is the difference between a 3.0 bar and a 4.5 one. */
.hero .lede { font-size: 1.5rem; }
.hero-cta { display: flex; flex-wrap: wrap; gap: var(--s-3); margin-top: var(--s-6); }
/* On a phone the pair stacks at one shared width, the column's, rather than two ragged
   content widths one above the other. Still secondary first, primary last. */
@media (max-width: 639px) {
  .hero-cta { display: grid; margin-top: var(--s-7); }
  /* The column sets the width now, so .btn-lg's wide side padding only costs the label room:
     at 320px it wrapped "Plan jouw wintersport" onto two lines. */
  .hero-cta .btn { padding-inline: var(--s-5); }
}
/* On a phone the block sits optically rather than geometrically centred, 5svh high, and the
   lift is measured, not taste: at dead centre the loop's worst frame puts the headline at
   2.97:1, a hair under the large-text bar, because the block's lower edge meets the shaded
   flank of the ridge; 5svh up clears it on every frame (§6.2). On the copy block, not the
   figure, so the frame's crop is unchanged. Not on a screen shorter than 640px, where the
   block already fills the frame and the lift would push the headline under the bar. */
@media (max-width: 639px) and (min-height: 640px) {
  .hero-copy { transform: translateY(-5svh); }
}
/* A portrait tablet is the one width where the centre of the frame cannot carry the copy at
   all — the crop is wide enough to take in the dark rock and lift pylons, and the lede measures
   2.6:1 there. The top of the frame, pale sky and haze in every scene, is where it clears, so
   the block sits under the header instead. */
@media (min-width: 640px) and (max-width: 899px) {
  .hero { align-items: start; padding: calc(var(--head-h) + var(--s-6)) 0 var(--s-8); }
}
/* The effort line that goes with the form: what filling it actually costs. It lives with the
   form's own heading rather than in the hero, and the reason is measured — on the hero
   photograph, small charcoal text over the hazy massif behind the copy block is 3.7:1 and
   fails AA, and charcoal is already the darkest ink there is. Everything the hero keeps
   (display headline, lede at 44ch, two buttons) clears the bar on the bare frame; a 14px line
   does not, and there is no fix short of laying something over the frame, which this site does
   not do. On the burgundy band it is 6.9:1 and it is also next to the questions it describes. */
.sec-note {
  margin-top: var(--s-4); font-size: var(--step--1);
  color: color-mix(in srgb, var(--ground) 82%, var(--burgundy));
}

/* ---------- page hero ----------
   The opening band on every page below the front door. A whole viewport, like the home hero,
   now that it is 100vh — the "show there's content under the fold" reasoning that used to cap
   it short of that no longer applies at this height. The copy sits straight on the photograph,
   at the client's request — no frosted card behind it, so each frame's text band is a
   per-photograph legibility call rather than a guarantee. The frame itself runs at full
   strength; only its top is faded, and that is for the header bar. */
.page-hero {
  position: relative;
  min-height: 100vh;        /* fallback where svh is unknown */
  min-height: 100svh;
  display: grid; align-items: center;
  /* minmax(0, …) rather than the implicit auto track: an auto track grows to the copy's
     min-content width, so one long word in the title widens the whole page past the screen. */
  grid-template-columns: minmax(0, 1fr);
  padding: calc(var(--head-h) + var(--s-7)) 0 var(--s-7);
  overflow: hidden;
  background: var(--ground);
}
/* The same twelve-second settle as the home hero, and the same reason: the transform goes on
   the figure rather than the img so it composes with the parallax pass instead of fighting it. */
.page-hero-fig {
  position: absolute; inset: 0; z-index: 0;
  animation: hero-settle 12s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
}
.page-hero-fig img { width: 100%; height: 100%; object-fit: cover; }
/* Nothing is laid over the frame — not at the top for the header bar, not at the bottom for
   the copy. The copy sits straight on the photograph, so the header bar and the copy are both
   a photography problem now: a frame whose top band cannot hold the burgundy nav, or whose
   text band doesn't hold the h1/lede legibly, is the wrong frame for this slot — the
   fix is to change the photograph or its crop rather than to put anything over it. */
/* Shifted above the frame's true centre — a title this size reads as sitting too low against
   the horizon at dead centre, and there is no eyebrow above it any more to hold that weight. */
.page-hero-copy { position: relative; z-index: 2; transform: translateY(-4svh); }
.page-hero-card {
  max-width: 54rem;
  padding: clamp(var(--s-5), 3.5vw, var(--s-7));
}
/* On a phone the card's own inset would stack on the page gutter and leave the title a column
   too narrow for "Groepswintersport,", the longest word any page hero opens on. */
@media (max-width: 639px) { .page-hero-card { padding-inline: 0; } }
/* No eyebrow on this card: with the eyebrow gone the title is the only thing announcing the
   page, so it is set at the size a page title should carry rather than at a section heading's
   --step-3 (the size .sec-head h2 still uses). */
.page-hero h1 {
  /* Below 640px the size is set by that word rather than by the scale: 18 characters at
     Lora's ~0.49em advance, plus slack, across the content column (100vw minus two 24px
     gutters). Re-derive the 9.4 if a hero title's longest word changes. */
  font-size: min(var(--step-4), calc((100vw - 3rem) / 9.4));
  color: var(--burgundy); font-weight: 400; letter-spacing: -0.015em;
}
.page-hero .lede { max-width: 48ch; }
/* The same opening as the home hero, minus the line-by-line lift: at this size, the block
   arriving as one reads better than three lines climbing out of a clip box. No .reveal on any
   of it — the scroll pass would have to un-hide the first screen, and a first screen that
   waits on a script is the one thing this page cannot do. */
.page-hero-card > * { animation: hero-rise 700ms cubic-bezier(0.16, 1, 0.3, 1) both; }
.page-hero h1 { animation-delay: 60ms; }
.page-hero .lede { animation-delay: 200ms; }

/* Placeholder ground: a missing photo degrades to a deliberate block, not a broken box. Flat
   glacier rather than a burgundy-to-glacier gradient — every photograph on the site is
   `object-fit: cover` over this, so the only time it is on screen is while a frame is still
   arriving or has failed, and a two-colour diagonal reads as a gradient laid *over* the
   photograph rather than as the blank it is. A quiet tint reads as an empty slot. */
.ph { background: color-mix(in srgb, var(--glacier) 55%, var(--ground)); overflow: hidden; }

/* ---------- section heads ---------- */
.eyebrow {
  font-size: var(--step--1); font-weight: 500; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--burgundy); margin-bottom: var(--s-3);
}
.sec-head { max-width: 46ch; margin-bottom: var(--s-7); }
.sec-head h2 { font-size: var(--step-3); color: var(--burgundy); }
.sec-head .lede { font-size: var(--step-0); max-width: 52ch; margin-top: var(--s-4); }
/* "Op maat" is a short title over a full-width grid of points: the 46ch cap is a body-text
   measure, far narrower than the title at display size, so it broke as "Voor elke groep een /
   optie." It takes the band's width, and balances wherever it still has to wrap. */
.plate-band .sec-head { max-width: none; }
.plate-band .sec-head h2 { text-wrap: balance; }
/* The closing note under a grid or a list. The client's copy repeatedly ends a section with a
   short claim and one sentence under it, so it gets a block of its own rather than being set
   as another paragraph — it is the section's conclusion, not its last item. */
.sec-foot { max-width: 56ch; margin-top: var(--s-7); }
.sec-foot h3 { font-size: var(--step-1); color: var(--burgundy); margin-bottom: var(--s-3); }
.sec-foot p { font-size: var(--step-0); }

/* ---------- points ----------
   Titled claims in a grid — the shape the client's copy takes when it lists reasons rather
   than moments. No icons: the rose rule over each is what stands in for one. */
.points { display: grid; gap: var(--s-6) var(--s-5); }
.points li {
  border-top: 2px solid color-mix(in srgb, var(--rose) 75%, transparent);
  padding-top: var(--s-4);
}
.points h3 { font-size: var(--step-1); color: var(--burgundy); margin-bottom: var(--s-3); }
.points p { font-size: var(--step--1); max-width: 44ch; }
/* Voor merken lists five services and the client's page draws one icon over each, so this is
   the one `.points` that carries an icon: a line drawing in the burgundy, above the title. */
.points .ico { display: block; color: var(--burgundy); margin-bottom: var(--s-4); }
.hero-sub { max-width: 48ch; margin-top: var(--s-4); }
/* One button, two lines: the call to action over the address it opens, arrow at the far end.
   The address is the same link, so it is a caption on the button rather than a second target. */
.btn-mail { gap: var(--s-5); padding-block: var(--s-3); }
.btn-mail-text { display: flex; flex-direction: column; align-items: flex-start; line-height: 1.2; }
.btn-mail-label { font-weight: 600; }
.btn-mail-addr { font-size: var(--step--1); opacity: .85; }

/* ---------- compare ----------
   Two lists set against each other: what organising a group trip normally costs, and what it
   costs here. Not a table — the left column is what the visitor already knows and the right
   one is the claim, so the two carry different weights rather than matching cells. The left
   markers are dashes, not crosses: that column is not wrong, it is just work nobody wants. */
.compare { display: grid; gap: var(--s-7); }
.compare-col h3 {
  font-family: var(--sans); font-size: var(--step--1); font-weight: 500;
  letter-spacing: 0.08em; text-transform: uppercase; margin-bottom: var(--s-5);
}
.compare-col ul { display: grid; gap: var(--s-3); }
.compare-col li { position: relative; padding-left: var(--s-6); }
.compare-was, .compare-was h3 { color: #6C6560; }
.compare-was li::before {
  content: ""; position: absolute; left: 0; top: 0.78em;
  width: 14px; height: 2px;
  background: color-mix(in srgb, var(--charcoal) 32%, transparent);
}
.compare-now h3 { color: var(--burgundy); }
.compare-now li { font-weight: 500; }
/* The same mark as .ticks, and for the same reason: this column is the one that is included. */
.compare-now li::before {
  content: ""; position: absolute; left: 0; top: 0.55em;
  width: 14px; height: 7px;
  border-left: 2px solid var(--rose); border-bottom: 2px solid var(--rose);
  transform: rotate(-45deg);
}

/* ---------- statement ----------
   One short claim on a band of its own, used as punctuation between two long sections. Narrow
   and centred, because it is one thought and should be read as one. Two grounds: burgundy
   (`.statement-band`) where it is a full stop mid-page, and the page's own ground where it is
   the closing CTA. */
.statement { max-width: 48ch; margin-inline: auto; text-align: center; }
.statement h2 { font-size: var(--step-3); color: var(--burgundy); margin-bottom: var(--s-5); }
.statement p { line-height: 1.6; }
.statement p + p { margin-top: var(--s-4); }
.statement-cta { display: flex; justify-content: center; margin-top: var(--s-7); }
.statement-note { margin-top: var(--s-5); font-size: var(--step--1); }
.statement-band { background: var(--burgundy); color: var(--ground); }
.statement-band .statement h2 { color: var(--ground); }
.statement-band .eyebrow { color: var(--rose); }
/* Rose on burgundy is 5.0:1 — AA for body text, and the same pairing the footer already uses.
   White on rose is 2.0:1 and is banned; this is the other way round. */
.statement-band a { color: var(--rose); }
.statement-band a:hover { color: #fff; }
/* The band the page ends on. A hairline rather than a colour change: it closes the page
   rather than making another claim. */
.cta-band { background: var(--ground-2); border-top: 1px solid var(--line); }

/* ---------- photo bands ----------
   Four sections are carried by one photograph each, and they all use the same layout: the frame
   takes half the *viewport*, bleeds off a page edge and fills the section top to bottom, with
   the copy on the ground beside it. `.visie-flip` mirrors which edge.

   The copy is never set on the frame in this layout: that needs a frame with a large empty
   bright area on one side, and none of the delivered photographs can
   carry it. That layout needs a frame with a large empty bright area on one side; every frame in
   this set has its subject at or near the centre, the crop in a wide band is vertical only, and
   so no `object-position` value can move a subject out from behind the copy. Cropping until the
   type is clear loses the subject and leaves a texture. If Altitude ever supplies a frame with
   real empty space, that layout is worth bringing back — from git, not from memory.

   Below 1250px every panel is the same stacked thing — the photograph full-bleed, the copy
   under it. */
/* `.prose` is the same block away from a photograph — a run of paragraphs under a .sec-head,
   which is what most of the client's copy is. One rule for both so the measure and the
   rhythm cannot drift apart between a photo band and a plain one. */
.photo-copy h2, .prose h2 { font-size: var(--step-3); color: var(--burgundy); margin-bottom: var(--s-5); }
.photo-copy p, .prose p { max-width: 62ch; }
.photo-copy p + p, .prose p + p { margin-top: var(--s-4); }
/* A button row closing a prose block. Bigger gap than between paragraphs: it is a different
   kind of thing, not the next sentence. */
.prose-cta, .steps-cta {
  display: flex; flex-wrap: wrap; gap: var(--s-3); margin-top: var(--s-7);
}
/* The frame bleeds past the wrap's gutter, and it is cropped by the box rather than by the
   encoder — see tools/prepare-photos.py. */
.visie-fig img { width: 100%; height: 100%; object-fit: cover; }

.ticks { margin-top: var(--s-6); display: grid; gap: var(--s-3); }
/* A list introduced by the paragraph above it ("…geregeld:") belongs to that line, so it sits
   at the list's own rhythm rather than a section's distance below it. */
.photo-copy p + .ticks { margin-top: var(--s-3); }
.ticks li { position: relative; padding-left: var(--s-6); font-size: var(--step--1); }
.ticks li::before {
  content: ""; position: absolute; left: 0; top: 0.55em;
  width: 14px; height: 7px; border-left: 2px solid var(--rose);
  border-bottom: 2px solid var(--rose); transform: rotate(-45deg);
}
/* Two-up where the column is wide enough to take it: five short lines stacked under three
   paragraphs run the height of a whole photograph for no reason. auto-fit rather than a fixed
   pair of tracks, and minmax(min(100%, …)) so the single-column phone case falls out of the
   same rule instead of needing a query of its own. */
.ticks-2 {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
  column-gap: var(--s-5);
}

.pull {
  margin-top: var(--s-6); padding-left: var(--s-5);
  border-left: 2px solid var(--rose);
  font-family: var(--serif); font-size: var(--step-2); font-style: italic;
  color: var(--burgundy); line-height: 1.35;
}

/* The panel. `cover` on a box shaped differently from the frame decides how much of the frame
   there is, and in this layout the box's shape comes from the *copy's* height — so a long copy
   block makes a tall panel, and a tall panel crops a landscape frame sideways rather than
   vertically. That is why the crops for two of these have a 1250px override, and why the home
   page's longest section is not a panel. 3:2 stacked, which is near enough to most of these
   frames to show all of them. */
/* The photograph meets the section's top edge in both layouts: stacked, the band keeps only its
   bottom padding, so the frame opens the section instead of floating a rhythm below the band
   before it — the same edge the panel layout gives it from 1250px up. */
.visie-band { padding-top: 0; }
.visie-grid { display: grid; }
.visie-fig { aspect-ratio: 3 / 2; margin-bottom: var(--s-7); }
/* The copy carries the gutter itself rather than sitting in a .wrap: the figure beside it has
   to reach the page edge, and a wrap around both would stop it short. */
.visie-copy { padding-inline: var(--s-5); }
/* No `data-parallax` on this figure, unlike the other photographs: the scale(1.08) the rAF
   pass writes would cost a further eight per cent of the frame's width, and in the panel
   layout `cover` has already blown the frame up well past its box. The crop itself is in the
   "photograph crops" section near the bottom of this file. */

/* ---------- USPs ---------- */
/* Each card is only as tall as its own content: `align-items: start` stops the grid from
   stretching the short ones to the tallest row. */
.usps { display: grid; gap: var(--s-5); align-items: start; }
.usp {
  background: var(--ground-2);
  border: 1px solid color-mix(in srgb, var(--line) 65%, var(--glacier));
  border-radius: var(--radius-card);
  /* the photo bleeds to the card edge, so the corners have to clip it */
  overflow: hidden;
  transition: transform 300ms ease, box-shadow 300ms ease, border-color 300ms ease;
}
.usp figure { aspect-ratio: 16 / 10; margin: 0 0 var(--s-5); overflow: hidden; }
.usp img { width: 100%; height: 100%; object-fit: cover; transition: transform 600ms ease; }
.usp h3 {
  font-size: var(--step-1); color: var(--burgundy);
  margin-bottom: var(--s-2); padding-inline: var(--s-5);
}
/* The gutter is padding on each block rather than on the card, because the photograph has to
   reach the card's edges. The card's own bottom gutter therefore hangs off whatever block ends
   up last — a card here may carry one paragraph or three plus a link. */
.usp p { font-size: var(--step--1); max-width: 42ch; padding-inline: var(--s-5); }
.usp p + p { margin-top: var(--s-3); }
.usp > :last-child { padding-bottom: var(--s-5); }
/* The line that closes a card and sends you on. Larger gap than between paragraphs: it is a
   route out, not the next sentence. */
.usp-link { margin-top: var(--s-4); }
.usp-link a {
  display: inline-flex; align-items: center; gap: 0.4em;
  font-weight: 500; text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--rose) 70%, transparent);
}
.usp-link a:hover { color: var(--burgundy); border-bottom-color: var(--burgundy); }
.usp-link .btn-arrow { width: 0.9em; height: 0.9em; }
.usp-link a:hover .btn-arrow { transform: translateX(3px); }
/* The three-up row stretches, unlike the four-up: every card in it ends on a route out or a
   closing line, and ragged bottoms under a row of calls to action read as a mistake rather
   than as cards sized to their content. The last block is pushed to the floor so those lines
   sit on one baseline across the row. */
.usps-3 { align-items: stretch; }
.usps-3 .usp { display: flex; flex-direction: column; }
/* `margin-top: auto` is the floor, and the padding is the minimum gap the auto margin cannot
   provide: on the card whose copy is *longest* there is no slack left for the margin to
   absorb, it resolves to zero, and the route out ends up against the last line of the
   paragraph. The padding is not slack, so it holds whichever card is the tall one. */
.usps-3 .usp-link, .usps-3 .usp-cta { margin-top: auto; padding-top: var(--s-5); }

/* `.usps-cta`: the "Voor wie?" cards — a photograph, a title, one italic line, and a ghost
   button to the form. On a phone or a touch screen the button is simply there. On a desktop
   with a pointer it waits for the card: hovering (or tabbing into) one slides the card's floor
   down by exactly the button's room and the button appears in it.

   The card grows without the row growing, and that is the negative margin: a grid item's
   contribution to its row is its margin box, so a card that gains --cta-room of content and
   gives back the same as margin-bottom asks for no more height than it did, and stretch then
   hands it the row *plus* that room. The neighbours stay put and the card extends past them.
   That only balances if the room is exact, which is why the button's height is fixed here
   rather than left to min-height and its line box. The span inside .usp-cta is the
   0fr→1fr row that collapses the button to nothing at rest. */
.usps-cta .usp { --cta-h: 44px; --cta-room: calc(var(--cta-h) + var(--s-5)); }
.usps-cta .usp > p:not(.usp-cta) {
  font-family: var(--serif); font-style: italic; font-size: var(--step-0);
}
.usps-cta .usp-cta { display: grid; grid-template-rows: 1fr; }
.usps-cta .usp-cta > span { overflow: hidden; min-height: 0; }
.usps-cta .usp-cta .btn { min-height: 0; height: var(--cta-h); padding-block: 0; }
@media (hover: hover) and (min-width: 900px) {
  /* .usps.usps-cta: .reveal.is-in sets its own transition later in the file and would
     otherwise win, dropping the margin from it. */
  .usps.usps-cta .usp {
    transition: transform 300ms ease, box-shadow 300ms ease, border-color 300ms ease,
      margin-bottom 300ms ease;
  }
  .usps-cta .usp-cta {
    grid-template-rows: 0fr; padding-bottom: 0; opacity: 0;
    transition: grid-template-rows 300ms ease, padding-bottom 300ms ease, opacity 200ms ease;
  }
  .usps-cta .usp:is(:hover, :focus-within) { margin-bottom: calc(-1 * var(--cta-room)); }
  .usps-cta .usp:is(:hover, :focus-within) .usp-cta {
    grid-template-rows: 1fr; padding-bottom: var(--s-5); opacity: 1;
    transition-delay: 0s, 0s, 100ms;
  }
}

@media (hover: hover) {
  /* .usps .usp — the reveal rules further down also set transform, and a bare .usp:hover
     ties on specificity and loses on order. */
  .usps .usp:hover {
    transform: translateY(-4px);
    border-color: color-mix(in srgb, var(--rose) 55%, var(--line));
    box-shadow: 0 16px 32px -14px color-mix(in srgb, var(--burgundy) 34%, transparent);
  }
  .usp:hover img { transform: scale(1.045); }
}

/* ---------- plate band ----------
   A band carried by a full-bleed frame with the band's copy set straight onto it — no card and
   nothing lifted off the frame. That works only because the frame
   is a *plate* rather than a photograph of something: `sneeuwveld` behind #op-maat is an empty
   snow field with no subject, no horizon and no dark region anywhere, so even its darkest patch
   measures 7.4:1 against burgundy and 9.6:1 against charcoal — AAA at the 14px eyebrow and the
   16px closing line, which are the smallest type in the band. That measurement is this
   component's whole licence, and it is taken on the encoded 1696 derivative, not on the master.
   A frame with a dark quarter belongs in `.steps-band`'s frosted card instead; measure before
   putting a second frame here.

   No parallax. The plate has nothing in it to see move, and a background that slides under body
   copy costs legibility for no picture.

   The moment cards keep their opaque `--ground-2` fill, so they read as cards laid on the snow
   rather than as windows onto it. */
.plate-band { overflow: hidden; background: var(--ground); }
.plate-bg { position: absolute; inset: 0; z-index: 0; }
.plate-bg img {
  width: 100%; height: 100%;
  object-fit: cover;
  /* No crop class: the frame is featureless, so every crop of it is the same picture and the
     browser's own 50% 50% is the honest value. See the "photograph crops" section. */
}
.plate-band .wrap { position: relative; z-index: 1; }

/* ---------- steps ----------
   The band is the photograph, full strength — nothing is laid over it. What carries the type
   is the card itself: a frosted off-white surface the copy sits *inside*. From 900px up the card holds the left of the content column
   and the ridge and the four figures keep the right. */
.steps-band { overflow: hidden; background: var(--ground); }
/* Nothing is laid over the frame. Every piece of type in the band, the heading included, sits
   on the frosted surface instead — the card is this band's whole answer to legibility, which is
   what lets the photograph run as a photograph. Burgundy on bare frame here measures 1.7:1
   against the skier's torso, so a heading outside the card is not an option. */
.steps-bg { position: absolute; inset: 0; z-index: 0; }
.steps-bg img {
  width: 100%; height: 100%;
  object-fit: cover;
  /* The crop is per photograph — see the "photograph crops" section near the bottom. */
}
.steps-band .wrap { position: relative; z-index: 2; }

/* The card. .steps-card is only the frame the ghost pane hangs off; .steps-panel inside it is
   the surface. They have to be two elements: backdrop-filter makes its element a stacking
   context, so a pane drawn as that element's own ::before would paint over the card, not
   behind it. The panel carries the heading as well as the list — see the band's note above. */
.steps-card {
  position: relative;
  /* The numbers hang half outside the card's left edge, so the card is pushed in by exactly
     that overhang — which puts the badges back on the content gutter, flush under the
     heading. Everything else here is derived from these two, so the breakout stays put
     when either changes. */
  --badge: 52px;
  --overhang: calc(var(--badge) / 2);
  --pad-i: clamp(var(--s-4), 3vw, var(--s-6));
  margin-left: var(--overhang);
}
/* The second pane, offset behind the card and barely there — it is what stops the card
   reading as a rectangle pasted onto a photograph. It hangs off the right and the bottom;
   the left edge belongs to the badges. */
.steps-card::before {
  content: ""; position: absolute;
  inset: -14px -16px -18px 40px;
  border-radius: calc(var(--radius-card) + 6px);
  background: color-mix(in srgb, var(--ground-2) 32%, transparent);
}
.steps-panel {
  position: relative;   /* paints above the ghost pane — both are positioned, DOM order wins */
  background: color-mix(in srgb, var(--ground-2) 94%, transparent);
  /* Enough blur that a figure standing behind the card reads as light, not as a shape. Any
     less and a dark jacket shows through as a smudge under the copy. */
  backdrop-filter: blur(18px);
  border: 1px solid color-mix(in srgb, var(--line) 60%, transparent);
  border-radius: var(--radius-card);
  /* The left pad clears the half of the badge that lands inside the card, and then some. */
  padding-left: calc(var(--overhang) + var(--pad-i));
  padding-right: var(--pad-i);
  box-shadow: 0 28px 56px -36px color-mix(in srgb, var(--charcoal) 55%, transparent);
}
/* The band's heading, on the panel rather than on the frame. It takes a row of the same
   rhythm as a step and closes on the same hairline, so it reads as the card's first entry
   rather than as a caption stuck on top of it. No badge, so it starts at the column the step
   titles start at — the numbers hang off to the left of everything, heading included. */
.steps-head {
  padding-block: var(--s-5);
  border-bottom: 1px solid color-mix(in srgb, var(--line) 75%, transparent);
}
.steps-head h2 { font-size: var(--step-3); color: var(--burgundy); }
.steps { display: grid; }
.step { position: relative; padding-block: var(--s-5); }
/* Hairlines between the rows, not around them: the card is the box, the rows are entries. */
.steps .step + .step { border-top: 1px solid color-mix(in srgb, var(--line) 75%, transparent); }
/* Taken out of flow rather than given a grid column: in flow the badge would have to sit in a
   track the card's padding has already pushed inward, and the negative margin that dragged it
   back out would shrink the track it lives in. Absolute against the row, the row's own top
   padding is what lines it up with the title. */
.step-n {
  position: absolute; top: var(--s-5);
  left: calc(-1 * (var(--overhang) + var(--pad-i)) - var(--overhang));
  display: grid; place-items: center;
  width: var(--badge); height: var(--badge);
  border-radius: 12px;
  background: color-mix(in srgb, var(--glacier) 88%, var(--ground-2));
  font-family: var(--serif); font-size: var(--step-1); line-height: 1;
  color: var(--charcoal);
  box-shadow: 0 10px 20px -12px color-mix(in srgb, var(--charcoal) 60%, transparent);
  transition: background 250ms ease, color 250ms ease;
}
/* The scroll pass walks .is-active down the list. It moves nothing — a row shifting inside a
   card reads as a glitch — the badge just fills in. */
.step.is-active .step-n { background: var(--burgundy); color: var(--ground-2); }
.step h3 { font-size: var(--step-1); color: var(--burgundy); margin-bottom: var(--s-2); }
.step p { font-size: var(--step--1); max-width: 46ch; }
.step p + p { margin-top: var(--s-3); }
/* The one-line summary the client's copy ends most steps on. Serif and burgundy so it reads
   as the step's conclusion rather than as another sentence of explanation. */
.step-line {
  font-family: var(--serif); font-style: italic; color: var(--burgundy);
  font-size: var(--step-0);
}

/* ---------- steps, the piste ----------
   The five-step version of the same list, and it is a different layout rather than the same one
   run longer. Five rows in the 560px column beside a photograph is a section two viewports
   tall; and the photograph that took the slot was a mountain massif on the one page whose own
   hero is already a mountain massif, so the two frames were arguing. This variant
   drops the frame and the card and draws the list as what it describes: a run down a mountain.
   A dashed route descends the middle of the content column, switching back at every step, and
   each step's number is the marker on its turn. No surfaces, no boxes, no icons — the route and
   its markers are the whole graphic, which is what keeps a piste map from becoming an
   illustration. The step count is content, not a constant this layout depends on — the last
   leg's side is derived from the last step's own parity (see the last-leg rules below) rather
   than assumed, so the route still lands correctly if a step is added or removed.
   `.step`, `.step-n`, `.step-line` and the scroll pass's `.is-active` are the same as in the
   card; what changes is the container, where the number sits, and that the number now sits on
   a line. */
.steps-piste {
  display: grid;
  /* No row gap. Each row draws its own segment of the route, corner to corner of its own box,
     so a gap between rows would be a gap in the line. The rows' padding is the air instead. */
  row-gap: 0;
  /* The corridor the route descends and the marker that sits on its turns. Everything else
     here is derived from these two, so the route, the markers and the copy stay in step when
     either changes. */
  --trail: clamp(76px, 13vw, 200px);
  --node: clamp(38px, 8vw, 46px);
  /* How far down its row a turn happens, measured to the middle of a step's title: the marker
     sits on the turn, so this is what puts it beside the title it belongs to instead of above
     it. The route is shifted down by it as a whole — every leg starts this far into its row and
     ends this far into the next — so the legs still meet exactly. */
  --turn: calc(var(--s-6) + 0.7em);
  /* A marker is centred on the route, so half of it always lands in the copy's column. This is
     the strip the copy gives back to it — half a marker and a little air — taken as padding on
     the copy itself rather than on the step, whose full width is what the corridor and the two
     columns are measured from. */
  --clear-n: calc(var(--node) / 2 + var(--s-3));
  /* The route's ink, as a custom property rather than a colour on the line, because the whole
     segment — a flat line below 860px, a gradient above it — is repainted by the scroll pass.
     One inherited token, two very different backgrounds. */
  --ink: color-mix(in srgb, var(--burgundy) 42%, var(--glacier));
}
/* The scroll pass walks .is-active down the list. It moves nothing — the marker fills and its
   own leg of the route comes up out of the background. Where you are on the run. */
.steps-piste > .step.is-active { --ink: color-mix(in srgb, var(--burgundy) 85%, var(--glacier)); }

/* Below 860px the route is a straight rail down the left: switchbacks need a corridor wide
   enough to be read as turns, and a phone has no width to give one. */
.steps-piste > .step {
  padding: var(--s-6) 0 var(--s-7) calc(var(--node) + var(--s-5));
}
.steps-piste > .step::before {
  content: ""; position: absolute;
  top: 0; bottom: 0;
  left: calc(var(--node) / 2 - 1px);
  width: 2px;
  background: var(--ink);
  /* Dashes, cut horizontally rather than square to the line. Every switchback above 860px is
     far steeper than 45deg, so a horizontal cut is near enough perpendicular there, and one
     rhythm in px then serves the straight rail and the diagonals alike. */
  -webkit-mask-image: repeating-linear-gradient(to bottom, #000 0 9px, transparent 9px 17px);
  mask-image: repeating-linear-gradient(to bottom, #000 0 9px, transparent 9px 17px);
}
/* The run begins at the first marker, not above it. */
.steps-piste > .step:first-child::before { top: calc(var(--s-6) + var(--node) / 2); }
/* And ends on the call to action: the last leg runs on past its own step and through the
   CTA's top margin, so the run lands on the button. The reach is the CTA's own margin — the
   one length the list and the button share — so the line touches the button at any width. */
.steps-piste { --reach: var(--s-7); }
.steps-piste > .step:last-child::before { bottom: calc(-1 * var(--reach)); }
.steps-piste + .steps-cta { margin-top: var(--s-7); }

/* The number stops being a badge hanging off a card's edge and becomes the marker on a turn:
   a ring on the line, the way a lift station or a waypoint is marked on a piste map. Ground-2
   rather than transparent, so the dashes stop at the ring instead of running under it. */
.steps-piste .step-n {
  position: absolute;
  top: var(--s-6); left: 0;
  width: var(--node); height: var(--node);
  border-radius: var(--radius-pill);
  background: var(--ground-2);
  border: 1.5px solid color-mix(in srgb, var(--burgundy) 40%, var(--glacier));
  box-shadow: none;
  font-size: var(--step-0);
  color: color-mix(in srgb, var(--burgundy) 80%, var(--glacier));
  transition: background 250ms ease, color 250ms ease, border-color 250ms ease;
}
.steps-piste .step.is-active .step-n {
  background: var(--burgundy); border-color: var(--burgundy); color: var(--ground-2);
}

/* 860px, not the usual 900: the corridor plus two columns of this copy need about 380px a side
   plus the wrap's gutters, and they have it a little before the site's own breakpoint. */
@media (min-width: 860px) {
  .steps-piste > .step {
    display: grid;
    grid-template-columns: 1fr var(--trail) 1fr;
    align-content: start;
    /* No gap: the rows inside a step are spaced by the same margins as in the card, so the
       rhythm inside a step does not change with the layout around it. */
    gap: 0;
    padding: var(--s-6) 0 var(--s-8);
  }
  /* The copy takes one side of the corridor and the next step takes the other — that is what
     makes the route switch back instead of running straight down. It is the step's *children*
     that pick a side, not the step: a step has to stay a full-width row for its segment of the
     route to span the corridor at all. `:not(.step-n)` because an absolutely positioned child
     of a grid container is placed into the grid area its placement names, and a marker given a
     column would then be positioned against that column instead of the row. */
  .steps-piste > .step > :not(.step-n) { grid-column: 1; padding-right: var(--clear-n); }
  .steps-piste > .step:nth-child(even) > :not(.step-n) {
    grid-column: 3; padding-right: 0; padding-left: var(--clear-n);
  }
  /* The left-hand steps are set right-aligned, against the corridor. Ragged-left is the price
     of a route with copy on both sides of it, and the alternative — both columns reading away
     from the line — is what makes a layout like this read as two lists rather than one run. */
  .steps-piste > .step:nth-child(odd) { text-align: right; }
  .steps-piste > .step:nth-child(odd) p { margin-left: auto; }

  /* The switchback. It is a background gradient rather than an SVG for one reason: the corner
     keywords put a gradient's 50% stop exactly on the box's own diagonal, whatever the box's
     proportions — `to bottom left` on the top-left/bottom-right one, `to bottom right` on the
     other. So a leg always meets the leg above and below it at the exact corner they share, at
     any row height and any width, with nothing measured and nothing to keep in step. Two stops
     a hairline apart are the line; the half-pixel either side is what keeps a diagonal from
     stepping. */
  .steps-piste > .step::before {
    top: var(--turn);
    bottom: calc(-1 * var(--turn));
    left: calc(50% - var(--trail) / 2);
    width: var(--trail);
    background: linear-gradient(to bottom left,
      transparent calc(50% - 1.4px), var(--ink) calc(50% - 0.8px) calc(50% + 0.8px),
      transparent calc(50% + 1.4px));
  }
  /* Same specificity as the rule that starts the rail at the first marker below 860px, or that
     one would keep leg 1 out of step with its own turn. */
  .steps-piste > .step:first-child::before { top: var(--turn); }
  .steps-piste > .step:nth-child(even)::before {
    background-image: linear-gradient(to bottom right,
      transparent calc(50% - 1.4px), var(--ink) calc(50% - 0.8px) calc(50% + 0.8px),
      transparent calc(50% + 1.4px));
  }
  /* The markers sit on the turns, which are the corners of the corridor: odd steps turn at its
     left edge, even steps at its right, and each marker is centred on the point where its own
     leg starts — so it reads as the step the route turns at, with its copy hanging off it. */
  .steps-piste .step-n {
    top: calc(var(--turn) - var(--node) / 2);
    left: calc(50% - var(--trail) / 2 - var(--node) / 2);
  }
  .steps-piste .step:nth-child(even) .step-n {
    left: calc(50% + var(--trail) / 2 - var(--node) / 2);
  }
  /* The last leg does not switch back to the far edge: it runs from its own marker to the
     middle of the corridor, where the CTA sits centred under the list. Half the corridor wide,
     so the same corner-to-corner gradient still draws it — mirrored for an even last step,
     whose marker is on the right. */
  .steps-piste > .step:last-child::before { bottom: calc(-1 * var(--reach)); width: calc(var(--trail) / 2); }
  .steps-piste > .step:last-child:nth-child(even)::before { left: 50%; }
  .steps-piste + .steps-cta { justify-content: center; }
}

/* ---------- quotes ----------
   Two layouts, and which one applies is decided by whether app.js actually ran, not by
   whether scripting is available: `.js-quotes` is set at the end of the carousel's own setup.
   Default (no script, blocked script, a throw before that line) the track is a stack and the
   three quotes read one under the other; the dots stay off screen, because a dot is the one
   part of this that cannot degrade into something useful. Snap-scrolling is native, so the
   carousel answers a swipe, a trackpad and the arrow keys with the dots doing nothing but
   jumping and reporting. No autoplay — §4.3 bans it, and it would undo the whole band.
   The band has no heading — the quotes are the section — so the glyphs are what frame it. They
   sit on the frame rather than inside a slide, so they hold the corners while the quotes move
   past. They are markup with aria-hidden rather than generated content: the
   `content: "x" / ""` alt syntax needs Safari 17.4 and §8.3 goes back to iOS 15. */
.quotes-band { background: var(--glacier); }
/* On a phone there is no room beside the type for a mark this size, so the opening glyph goes
   above the quote and the closing one goes away — one mark centred over a quote is a deliberate
   composition, two marks crowding the first and last words is an accident. They move out to
   flank the type from 640px up. */
.quotes {
  position: relative;
  max-width: 780px;
  margin-inline: auto;
  padding-top: 2.4rem;
}
.quote-glyph {
  position: absolute;
  font-family: var(--serif);
  font-size: clamp(3rem, 7vw, 5.5rem);
  line-height: 1;
  color: color-mix(in srgb, var(--burgundy) 22%, transparent);
  pointer-events: none;
  user-select: none;
}
.quote-glyph-open { top: 0; left: 50%; transform: translateX(-50%); }
.quote-glyph-close { display: none; }

.quote-track { display: grid; gap: var(--s-7); }
.quote-slide { display: grid; }
.quote { text-align: center; display: grid; gap: var(--s-5); }
.quote blockquote p {
  font-family: var(--serif);
  font-size: var(--step-2);
  line-height: 1.4;
  color: var(--charcoal);
  max-width: 34ch;
  margin-inline: auto;
}
.quote figcaption {
  font-size: var(--step--1);
  color: color-mix(in srgb, var(--charcoal) 85%, var(--glacier));
}
/* The dash is decoration on an attribution, not part of it — the caption stays a swappable
   string, per the content rule in CLAUDE.md. */
.quote figcaption::before { content: "\2014\00a0"; }
.quote-tag {
  font-size: 0.75em; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--burgundy);
  border: 1px solid color-mix(in srgb, var(--burgundy) 35%, transparent);
  border-radius: var(--radius-pill);
  padding: 2px var(--s-2);
  margin-left: var(--s-2);
  white-space: nowrap;
}

.quote-dots { display: none; justify-content: center; gap: var(--s-2); margin-top: var(--s-6); }
/* A 10px dot is a 10px target. The button is 24px so it can actually be tapped (WCAG 2.5.8)
   and the dot is drawn inside it. */
.quote-dot {
  width: 24px; height: 24px; padding: 0; border: 0; background: none;
  display: grid; place-items: center; cursor: pointer;
  border-radius: var(--radius-pill);
}
/* 60%, not the mockup's much paler dot: an inactive dot is still a control, and below this
   mix it drops under the 3:1 that §6.2 holds UI to. The active one is full burgundy and a
   touch larger, so the state still reads at a glance. */
.quote-dot::before {
  content: ""; width: 9px; height: 9px; border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--burgundy) 60%, transparent);
  transition: background 200ms ease, transform 200ms ease;
}
.quote-dot:hover::before { background: color-mix(in srgb, var(--burgundy) 80%, transparent); }
.quote-dot[aria-current="true"]::before { background: var(--burgundy); transform: scale(1.15); }

html.js-quotes .quote-track {
  display: flex;
  gap: 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  /* clientWidth is the slide width the carousel scrolls by, so this box keeps no padding of
     its own — it lives on .quotes. */
  padding: 0;
}
html.js-quotes .quote-track::-webkit-scrollbar { display: none; }
/* The track is as tall as its tallest quote, so a two-line one is centred in that height
   rather than hanging from the top with the gap below it. */
html.js-quotes .quote-slide { flex: 0 0 100%; scroll-snap-align: center; align-content: center; }
html.js-quotes .quote-dots { display: flex; }

/* ---------- form ---------- */
.form-band { background: var(--burgundy); color: var(--ground); }
.form-band .eyebrow { color: var(--rose); }
.form-band .sec-head h2 { color: var(--ground); }
.form-band .sec-head .lede { color: color-mix(in srgb, var(--ground) 88%, var(--burgundy)); }
.form-wrap { max-width: 900px; }
.form-shell {
  background: var(--ground); color: var(--charcoal);
  padding: var(--s-6) var(--s-5);
  border-radius: var(--radius-shell);
}

/* ---------- form: the step deck ----------
   The three groups are one form with one submit — the deck is presentation only, and every
   rule here is gated on html.js-steps, which app.js sets at the end of its own setup. With
   script off the shell is one panel with the three groups stacked inside it, which is what
   the markup already is; nothing here has to un-hide for the form to work.
   The card on top is the only one left in flow, so the deck is exactly as tall as it and a
   short card gets no dead space under it. The others are absolute, the same size, clipped,
   and have their contents hidden — a corner sticking out is blank paper, never half a
   question. What separates them is a tilt of one --tilt per card of distance (--d, set by
   app.js), fanning left for the cards behind you and right for the ones ahead, so the deck
   also says where in it you are. Rotation costs no layout, so the only room the deck has to
   reserve is --fan-room for the corners: the vertical reach of a rotated card is half its
   width times the sine of its angle, which no stylesheet can work out, so re-measure it if
   --tilt or the 900px column changes. */
html.js-steps .form-shell { background: none; padding: 0; }

.steps-nav, .deck-nav { display: none; }

.deck { --tilt: 1.6deg; --fan-room: 30px; }
html.js-steps .deck {
  position: relative;
  margin-block: var(--fan-room);
}
html.js-steps .fs {
  margin: 0; min-inline-size: 0;
  padding: var(--s-6) var(--s-5);
  background: var(--ground); border-radius: var(--radius-shell);
}
/* A legend is laid out on the fieldset's block-start *border* edge, so once the card has
   padding it straddles the top corner instead of sitting inside it. Floated, it is back in
   the padding box; the next block clears it. */
html.js-steps .fs > legend { float: left; width: 100%; }
html.js-steps .fs > legend + * { clear: left; }
html.js-steps .fs[data-rel="active"] {
  position: relative; z-index: 3;
  animation: step-in 300ms ease-out;
}
html.js-steps .fs:not([data-rel="active"]) {
  position: absolute; inset: 0; overflow: hidden; cursor: pointer;
  z-index: calc(3 - var(--d));
  opacity: calc(1 - 0.24 * var(--d));
  transform: rotate(calc(var(--d) * var(--dir) * var(--tilt)));
  transition: transform 300ms ease, opacity 300ms ease;
}
/* visibility, not display: the fields stay in the form and still post. It also takes them
   out of the tab order, which display:none would do at the cost of the values. */
html.js-steps .fs:not([data-rel="active"]) > * { visibility: hidden; }
html.js-steps .fs[data-rel="prev"] { --dir: -1; }
html.js-steps .fs[data-rel="next"] { --dir:  1; }
/* A corner that reaches past the column is fine on the band; a corner that reaches past the
   viewport is a horizontal scrollbar. Clip, not hidden: clipping makes no scroll container. */
html.js-steps .form-band { overflow-x: clip; }
/* The card takes focus on arrival, so the ring is drawn on the card's own radius rather
   than the 2px default — at 28px the two shapes do not line up. */
html.js-steps .fs:focus-visible { outline-offset: 4px; border-radius: var(--radius-shell); }
@keyframes step-in { from { opacity: 0.55; transform: translateY(6px); } }

/* The tabs sit on the burgundy band, above the deck: a jump to any card and the answer to
   "how much is left". Rose on burgundy is 1.8:1, so the dimmed label is a ground mix. */
html.js-steps .steps-nav {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--s-2) var(--s-5); margin-bottom: var(--s-4);
  /* app.js scrolls to the tabs rather than to the deck — the row is how you get to another
     card, so landing with it behind the header bar would hide the way out. */
  scroll-margin-top: calc(var(--head-h) + var(--s-5));
}
.step-tab {
  display: inline-flex; align-items: center; gap: var(--s-3);
  padding: var(--s-2) 0; background: none; border: 0; cursor: pointer;
  font-size: var(--step--1); font-weight: 500; letter-spacing: 0.02em;
  color: color-mix(in srgb, var(--ground) 66%, var(--burgundy));   /* 4.7:1 */
  transition: color 180ms ease;
}
.step-tab-n {
  display: grid; place-items: center; flex: none; width: 28px; height: 28px;
  border: 1px solid color-mix(in srgb, var(--rose) 55%, transparent);
  border-radius: var(--radius-pill);
  transition: background 180ms ease, border-color 180ms ease, color 180ms ease;
}
.step-tab:hover { color: var(--rose); }
.step-tab[data-state="done"] .step-tab-n { border-color: var(--rose); color: var(--rose); }
.step-tab[aria-current] { color: var(--ground); }
.step-tab[aria-current] .step-tab-n {
  background: var(--rose); border-color: var(--rose); color: var(--burgundy);   /* 5.0:1 */
}

html.js-steps .deck-nav {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3);
  margin-top: var(--s-7);
}
html.js-steps .deck-nav .btn-p { margin-left: auto; }
/* On the last card the row holds only "Vorige", under the submit block — a way back rather
   than a way on, so it reads as the card's footer. */
html.js-steps .submit + .deck-nav {
  margin-top: var(--s-5); padding-top: var(--s-5);
  border-top: 1px solid var(--line);
}
/* The tabs are the one part of the shell that sits on the burgundy band, so the ring there
   is the band's rose again, not the shell's forest. */
html.js-steps .steps-nav :focus-visible { outline-color: var(--rose); }

/* With the shell transparent, the two things that replace the form need the surface the
   shell used to give them. The error summary already carries its own. */
html.js-steps .thanks {
  max-width: 56ch; padding: var(--s-6) var(--s-5);
  background: var(--ground); border-radius: var(--radius-shell);
}

.errsum {
  border: 1px solid var(--burgundy); border-left: 3px solid var(--burgundy);
  background: color-mix(in srgb, var(--rose) 28%, var(--ground));
  padding: var(--s-4) var(--s-5); margin-bottom: var(--s-6);
}
.errsum ul { margin-top: var(--s-2); display: grid; gap: var(--s-1); }
.errsum a { font-size: var(--step--1); }

.fs { border: 0; padding: 0; margin: 0 0 var(--s-7); }
.fs > legend {
  font-family: var(--serif); font-size: var(--step-2); color: var(--burgundy);
  padding: 0; margin-bottom: var(--s-5);
}
.opts { border: 0; padding: 0; margin: 0; min-inline-size: 0; }
.field { margin-bottom: var(--s-6); }
/* The last thing in a fieldset needs no trailing gap — but a .field that happens to be the
   second one inside a .row still does, since below 640px the row is just a wrapper. */
.fs > *:last-child { margin-bottom: 0; }
.deck > .fs:last-child { margin-bottom: 0; }
.lbl {
  display: block; font-size: var(--step--1); font-weight: 500;
  margin-bottom: var(--s-2); color: var(--charcoal);
}
.req { color: var(--burgundy); }
.opt-tag {
  font-weight: 400; font-size: var(--step--1); color: #6C6560;
  letter-spacing: 0.02em;
}
.hint { font-size: var(--step--1); color: #6C6560; margin: calc(-1 * var(--s-1)) 0 var(--s-3); }

input[type="text"], input[type="email"], input[type="tel"], input[type="date"], select, textarea {
  width: 100%; min-height: 48px; padding: var(--s-3) var(--s-4);
  font: inherit; font-size: var(--step-0); color: var(--charcoal);
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
  transition: border-color 150ms ease, box-shadow 150ms ease;
}
textarea { min-height: 120px; resize: vertical; line-height: 1.55; }
select { appearance: none; padding-right: var(--s-7);
  background-image: linear-gradient(45deg, transparent 50%, var(--burgundy) 50%),
                    linear-gradient(135deg, var(--burgundy) 50%, transparent 50%);
  background-position: right 22px center, right 16px center;
  background-size: 6px 6px, 6px 6px; background-repeat: no-repeat; }
input:hover, select:hover, textarea:hover { border-color: var(--rose); }
input:focus, select:focus, textarea:focus {
  border-color: var(--burgundy); box-shadow: 0 0 0 3px color-mix(in srgb, var(--rose) 45%, transparent);
}

.radios { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.opt {
  display: inline-flex; align-items: center; gap: var(--s-3);
  min-height: 48px; padding: var(--s-2) var(--s-5) var(--s-2) var(--s-4);
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius-pill);
  font-size: var(--step--1); cursor: pointer;
  transition: border-color 150ms ease, background 150ms ease;
}
.opt:hover { border-color: var(--rose); }
.opt input { accent-color: var(--burgundy); width: 18px; height: 18px; margin: 0; flex: none; }
.opt:has(input:checked) { border-color: var(--burgundy); background: color-mix(in srgb, var(--rose) 20%, #fff); }
.radios-tight .opt { padding-inline: var(--s-4); }
/* Follow-up questions — "Anders, namelijk", the "wanneer vertrekken" branch below. The answer
   above reveals them in CSS rather than in script, so they are reachable with JavaScript off:
   the server requires the follow-up once its parent answer is given, and a required field
   a visitor cannot make appear is a dead end. app.js only clears them again. */
.sub { display: none; margin-top: var(--s-4); max-width: 420px; }
.sub-wide { max-width: none; }        /* four date-range chips need the full row */
[data-field="typeGroep"]:has(input[value="Anders"]:checked) > .sub,
[data-field="faciliteiten"]:has(input[value="Anders"]:checked) > .sub { display: block; }
/* "Wanneer vertrekken" fans out to one of three follow-ups depending on the radio above it —
   an exact date pairs with the flexibility question, a period stands alone (it is already an
   indication, so asking again would ask the same thing twice), and "nog niet bekend" has no
   follow-up at all. */
[data-field="vertrekType"]:has(input[value="Exacte datum"]:checked) > .sub[data-field="vertrekdatum"],
[data-field="vertrekType"]:has(input[value="Exacte datum"]:checked) > .sub[data-field="flexibel"],
[data-field="vertrekType"]:has(input[value="Periode"]:checked) > .sub[data-field="periode"] { display: block; }

.field-consent { margin-top: var(--s-6); }
.consent {
  align-items: flex-start; padding: var(--s-4); gap: var(--s-4);
  line-height: 1.5; max-width: 64ch;
  border-radius: var(--radius);   /* a multi-line block, not a chip */
}
.consent input { margin-top: 2px; }

.err {
  margin-top: var(--s-2); font-size: var(--step--1); font-weight: 500;
  color: #8A1F30;
}
.err::before { content: "↑ "; }
.field.is-bad input, .field.is-bad select, .field.is-bad textarea,
.field.is-bad .opt,
.sub.is-bad input, .sub.is-bad .opt { border-color: #8A1F30; }

.submit { display: grid; gap: var(--s-4); margin-top: var(--s-7); }
/* Full-width and thumb-sized on a phone; sized to its label from 640px up. */
.submit .btn { justify-self: stretch; padding-inline: var(--s-5); }
@media (min-width: 640px) {
  .submit .btn { justify-self: start; padding-inline: var(--s-7); }
}
.fineprint { font-size: var(--step--1); color: #6C6560; max-width: 60ch; }

.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.thanks { text-align: left; max-width: 52ch; }
.thanks-mark { color: var(--rose); margin-bottom: var(--s-5); }
.thanks-mark .mark { width: 40px; }
.thanks h3 { font-size: var(--step-3); color: var(--burgundy); margin-bottom: var(--s-4); }
.thanks p + p { margin-top: var(--s-4); }

/* ---------- document pages ----------
   FAQ, algemene voorwaarden, privacyverklaring and disclaimer: pages people come to read or
   look something up in, reached only from the footer. They open on `.doc-hero`, a plain
   ground band rather than a `.page-hero` photograph — a full-viewport mountain above a set of
   terms would be the photograph arguing with the page's job. The header sits transparent on
   that ground, which holds the burgundy nav on its own, so no frame has to be measured for it. */
.doc-hero {
  padding: calc(var(--head-h) + var(--s-9)) 0 var(--s-8);
  background: var(--ground);
  border-bottom: 1px solid var(--line);
}
.doc-hero h1 {
  font-size: min(var(--step-4), calc((100vw - 3rem) / 9.4));
  color: var(--burgundy); font-weight: 400; letter-spacing: -0.015em;
}
.doc-hero .lede { max-width: 52ch; margin-top: var(--s-5); }
.doc-band { background: var(--ground-2); }
/* The index is a list above the text on a phone and a sticky column beside it from 1000px,
   where a long page is otherwise a scroll with no landmarks. It is anchors only: nothing
   tracks which section is on screen, so it adds no scroll listener to the shared pass. */
.doc-grid { display: grid; gap: var(--s-7); }
.doc-index {
  font-size: var(--step--1);
  padding-bottom: var(--s-6); border-bottom: 1px solid var(--line);
}
.doc-index-h {
  font-weight: 500; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--burgundy); margin-bottom: var(--s-4);
}
.doc-index ul { display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-5); }
.doc-index a { color: var(--charcoal); text-decoration: none; border-bottom: 1px solid transparent; }
.doc-index a:hover { color: var(--burgundy); border-bottom-color: var(--rose); }
@media (min-width: 1000px) {
  .doc-grid { grid-template-columns: 220px minmax(0, 1fr); gap: var(--s-9); align-items: start; }
  .doc-index {
    position: sticky; top: calc(var(--head-h) + var(--s-6));
    padding-bottom: 0; border-bottom: 0;
  }
  .doc-index ul { display: grid; gap: var(--s-3); }
}
.doc-body { max-width: 68ch; }
.doc-meta { font-size: var(--step--1); color: #6C6560; }
.doc-intro { margin-top: var(--s-4); }
.legal section { padding-top: var(--s-7); }
.legal h2 { font-size: var(--step-2); color: var(--burgundy); margin-bottom: var(--s-4); }
.legal-n { font-family: var(--sans); font-size: 0.6em; font-weight: 500; color: var(--rose);
  vertical-align: 0.2em; margin-right: var(--s-2); }
.legal section > * + * { margin-top: var(--s-4); }
.legal h3 { font-size: var(--step-1); color: var(--burgundy); }
.legal section > * + h3 { margin-top: var(--s-6); }
/* The client's clause numbers (2.1, 9.1.2.1) lead their paragraph. Burgundy, not the rose of
   the article number: at body size rose on the ground is too faint to read. */
.legal-c { font-family: var(--sans); font-weight: 500; font-size: 0.9em; color: var(--burgundy);
  font-variant-numeric: tabular-nums; margin-right: var(--s-1); }
.legal ul { display: grid; gap: var(--s-3); }
.legal li { position: relative; padding-left: var(--s-6); }
.legal li::before {
  content: ""; position: absolute; left: 0; top: 0.8em;
  width: 12px; height: 1px; background: var(--rose);
}
/* Ordered lists keep the client's own markers — i), a., 1. — which the text refers back to. */
.legal ol { gap: var(--s-3); display: grid; padding-left: var(--s-6); list-style-type: decimal; }
.legal ol[type="i"] { list-style-type: lower-roman; }
.legal ol[type="a"] { list-style-type: lower-alpha; }
.legal ol li { padding-left: var(--s-2); }
.legal ol li::before { content: none; }
.legal ol li::marker { color: var(--burgundy); font-family: var(--sans); font-size: 0.9em; }
.doc-body .tbd { font-size: var(--step--1); }

/* The FAQ accordion is native <details>: it opens, closes, is keyboard-operable and is found
   by the browser's own find-in-page with no script at all. Several can be open at once — it is
   a page for reading, and closing one answer because another opened loses your place. */
.faq-group + .faq-group { margin-top: var(--s-8); }
.faq-group h2 { font-size: var(--step-2); color: var(--burgundy); margin-bottom: var(--s-4); }
.faq-group > .faq-item:first-of-type { border-top: 1px solid var(--line); }
.faq-item { border-bottom: 1px solid var(--line); interpolate-size: allow-keywords; }
.faq-item summary {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-5);
  padding: var(--s-5) 0; cursor: pointer; list-style: none;
  font-weight: 500; font-size: var(--step-0); line-height: 1.4;
  transition: color 200ms ease;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary:hover, .faq-item[open] summary { color: var(--burgundy); }
/* A drawn plus that folds to a minus: two strokes, the vertical one rotating flat. Drawn
   rather than a glyph for the same reason the button arrow is — the Poppins subset has none. */
.faq-toggle { position: relative; flex: none; width: 16px; height: 16px; color: var(--burgundy); }
.faq-toggle::before, .faq-toggle::after {
  content: ""; position: absolute; left: 0; top: 50%;
  width: 16px; height: 1.6px; margin-top: -0.8px; border-radius: 1px;
  background: currentColor;
  transition: transform 300ms cubic-bezier(0.16, 1, 0.3, 1);
}
.faq-toggle::after { transform: rotate(90deg); }
.faq-item[open] .faq-toggle::after { transform: rotate(0deg); }
.faq-answer { padding: 0 var(--s-7) var(--s-6) 0; }
.faq-answer p + p { margin-top: var(--s-3); }
/* Where the browser can animate to `auto`, the answer unfolds; everywhere else it simply
   appears, which is the same accordion minus the motion. */
@supports selector(::details-content) {
  .faq-item::details-content {
    block-size: 0; overflow: hidden;
    transition: block-size 350ms cubic-bezier(0.16, 1, 0.3, 1), content-visibility 350ms allow-discrete;
  }
  .faq-item[open]::details-content { block-size: auto; }
}
/* The search filters markup that is already on the page, so it is only on screen once app.js
   has wired it — the same `html.js-*` gate the quote dots use, for the same reason: a search
   box with nothing behind it is a control that lies. */
.faq-search { display: none; margin-bottom: var(--s-8); }
html.js-faq .faq-search { display: block; }
.faq-search label { display: block; font-weight: 500; font-size: var(--step--1); margin-bottom: var(--s-2); }
.faq-search input {
  width: 100%; min-height: 48px; padding: 0 var(--s-4);
  font: inherit; color: var(--charcoal);
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
}
.faq-search input:focus-visible { outline: 2px solid var(--forest); outline-offset: 2px; }
.faq-status { font-size: var(--step--1); color: #6C6560; margin-top: var(--s-2); min-height: 1.5em; }
.faq-empty { padding-top: var(--s-5); }

/* ---------- footer ---------- */
.foot { background: var(--charcoal); color: var(--ground); padding-block: var(--s-8) var(--s-8); }
.foot-in { display: grid; gap: var(--s-7); }
.foot .mark { width: 40px; color: var(--rose); margin-bottom: var(--s-4); }
.foot-tag { font-family: var(--serif); font-size: var(--step-1); line-height: 1.35; max-width: 22ch; }
.foot-col h2 {
  font-family: var(--sans); font-size: var(--step--1); font-weight: 500;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--rose);
  margin-bottom: var(--s-4);
}
.foot-col ul { display: grid; gap: var(--s-3); }
.foot-col li { font-size: var(--step--1); }
.foot-col a { color: var(--ground); text-decoration: none; border-bottom: 1px solid transparent; }
.foot-col a:hover { color: var(--rose); border-bottom-color: var(--rose); }
.tbd { color: #A9A29B; font-style: italic; }
.foot-social { margin-top: var(--s-5); font-size: var(--step--1); display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3) var(--s-5); }
.foot-social a {
  display: inline-flex; align-items: center; gap: var(--s-2);
  color: var(--ground); text-decoration: none; border-bottom: 1px solid transparent;
}
.foot-social a:hover { color: var(--rose); border-bottom-color: var(--rose); }
.foot-icon { flex: none; }
.foot-col a[aria-current="page"] { color: var(--rose); }
/* ---------- reveal ----------
   Gated on `scripting: enabled` rather than on a class set by JS: with JavaScript off the
   query is false, so everything renders in its final state with no flash and no fallback
   stylesheet. Browsers without the feature simply skip the animation. */
@media (scripting: enabled) {
  /* Armed by default: if app.js never runs — a 404, a blocked script, a file:// open — the
     animation fires and the page becomes readable anyway. app.js sets .js-ready as its first
     act, which disarms it and hands the reveal back to the scroll pass. Content that only
     JavaScript can un-hide is a page that can be invisible, and this page's only job is a
     form. */
  .reveal {
    opacity: 0; transform: translateY(12px);
    animation: reveal-failsafe 400ms ease-out 2s forwards;
  }
  html.js-ready .reveal { animation: none; }
  .reveal.is-in {
    opacity: 1; transform: none;
    transition: opacity 400ms ease-out, transform 400ms ease-out;
  }
}
@keyframes reveal-failsafe { to { opacity: 1; transform: none; } }

/* ---------- portfolio ----------
   Two case cards, each a photograph over a copy block on an opaque surface. The name and the
   description sit *under* the frame rather than on it: nothing is laid over a photograph to
   make type readable. The buttons are view only for now (`data-case` is the hook for the modal). */
.case-head { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: var(--s-5); margin-bottom: var(--s-7); }
.case-head .sec-head { margin-bottom: 0; }
.case-all {
  display: inline-flex; align-items: center; gap: 0.6em; padding: 0 0 var(--s-1);
  font: inherit; font-size: var(--step--1); font-weight: 500; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--charcoal); background: none; cursor: pointer;
  border: 0; border-bottom: 1px solid var(--charcoal);
}
.case-all .btn-arrow { width: 1em; height: 1em; transition: transform 200ms ease; }
.case-all:hover, .case-all:focus-visible { color: var(--burgundy); border-bottom-color: var(--burgundy); }
.case-all:hover .btn-arrow { transform: translateX(3px); }
.cases { display: grid; gap: var(--s-5); }
.case {
  display: flex; flex-direction: column;
  background: var(--ground-2);
  border: 1px solid color-mix(in srgb, var(--line) 65%, var(--glacier));
  border-radius: var(--radius-card);
  overflow: hidden;
}
.case-fig { aspect-ratio: 1 / 1; margin: 0; }
.case-fig img { width: 100%; height: 100%; object-fit: cover; }
.case-body, .case-meta, .case-thumbs, .case-cta { padding-inline: var(--s-5); }
.case-body { padding-top: var(--s-5); }
.case-body .eyebrow { margin-bottom: var(--s-2); }
.case-body h3 { font-size: var(--step-2); color: var(--burgundy); }
.case-sub {
  margin: var(--s-2) 0 var(--s-4); font-size: var(--step--1); font-weight: 500;
  letter-spacing: 0.14em; text-transform: uppercase;
}
.case-body p:last-child { max-width: 46ch; }
.case-meta {
  list-style: none; margin: var(--s-5) 0 0; padding-block: var(--s-4);
  display: flex; flex-wrap: wrap; gap: var(--s-3) var(--s-6);
  border-top: 1px solid var(--line); font-size: var(--step--1);
}
.case-meta li { display: flex; align-items: center; gap: var(--s-2); }
.case-meta svg { flex: none; color: var(--burgundy); }
.case-thumbs { list-style: none; margin: 0; padding-bottom: var(--s-5); display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s-3); }
.case-thumbs li { aspect-ratio: 4 / 3; border-radius: var(--radius); }
.case-thumbs img { width: 100%; height: 100%; object-fit: cover; }
/* The button is the card's floor, so both land on one baseline when the copy runs to
   different lengths. */
.case-cta { margin: auto 0 0; padding-bottom: var(--s-5); }
@media (min-width: 900px) {
  .cases { grid-template-columns: repeat(2, 1fr); gap: var(--s-6); }
}

/* ---------- case pages ----------
   `case-animo.html` and `case-nikkie.html`, opened from the portfolio's "Bekijk case". One
   layout, two sets of copy. The opening is copy on the page ground beside a photograph that
   bleeds off the right edge (a panel, like `.visie-band`) and starts *below* the header bar, so
   the transparent bar sits on ground and nothing is laid over the frame. */
.case-hero { padding-top: var(--head-h); background: var(--ground); }
.case-hero-copy { padding: var(--s-7) var(--s-5) var(--s-8); }
.case-back {
  display: inline-flex; align-items: center; gap: 0.6em; margin-bottom: var(--s-7);
  font-size: var(--step--1); font-weight: 500; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--charcoal); text-decoration: none;
}
.case-back .btn-arrow { width: 1em; height: 1em; transform: scaleX(-1); transition: transform 200ms ease; }
.case-back:hover, .case-back:focus-visible { color: var(--burgundy); }
.case-back:hover .btn-arrow { transform: scaleX(-1) translateX(3px); }
.case-hero h1 {
  font-size: min(var(--step-4), calc((100vw - 3rem) / 6.6));
  color: var(--burgundy); font-weight: 400; letter-spacing: -0.015em; line-height: 1.05;
}
.case-hero .lede { margin-top: var(--s-4); }
.case-intro { max-width: 46ch; margin-top: var(--s-5); }
.case-facts {
  list-style: none; margin: var(--s-7) 0 0; padding: var(--s-4) 0 0;
  display: flex; flex-wrap: wrap; gap: var(--s-3) var(--s-6);
  border-top: 1px solid var(--line); font-size: var(--step--1);
}
.case-facts li { display: flex; align-items: center; gap: var(--s-2); }
.case-facts svg { flex: none; color: var(--burgundy); }
.case-hero-fig { margin: 0; aspect-ratio: 4 / 5; max-height: none; }
.case-hero-fig img { width: 100%; height: 100%; object-fit: cover; }
@media (min-width: 900px) {
  .case-hero { display: grid; grid-template-columns: 1fr 1fr; min-height: 100vh; min-height: 100svh; }
  .case-hero-copy {
    align-self: center;
    padding: var(--s-8) var(--s-8) var(--s-8) max(var(--s-5), calc((100vw - var(--wrap)) / 2 + var(--s-5)));
  }
  .case-hero-fig { aspect-ratio: auto; height: calc(100vh - var(--head-h)); height: calc(100svh - var(--head-h)); min-height: 560px; }
}
/* The three-part story: numbered, one column on a phone, three across from 900px. */
.case-steps { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-8); }
.case-step .eyebrow { margin-bottom: var(--s-4); padding-bottom: var(--s-3); border-bottom: 1px solid var(--line); }
.case-step h2 { font-size: var(--step-2); color: var(--burgundy); margin-bottom: var(--s-4); }
.case-step p:last-child { max-width: 46ch; }
@media (min-width: 900px) { .case-steps { grid-template-columns: repeat(3, 1fr); gap: var(--s-7); } }
/* Sfeerimpressie. The track is a fixed-height row of frames, each as wide as its own ratio, so a
   portrait and a landscape sit side by side uncropped. It is the scroll box itself — nothing
   here moves it — and it bleeds off both page edges, its padding lining the first frame up
   with the content gutter. The buttons are on screen only under `html.js-gallery`, the late
   gate the quote dots use: without the script that drives them they would be dead controls. */
.case-gallery { padding-inline: 0; padding-bottom: var(--s-5); overflow: hidden; }
.gallery-head { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--s-5); margin-bottom: var(--s-6); }
.gallery-head .sec-head { margin-bottom: 0; }
.gallery-nav { display: none; gap: var(--s-2); }
html.js-gallery .gallery-nav { display: flex; }
.gallery-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; padding: 0; cursor: pointer;
  background: var(--ground-2); color: var(--burgundy);
  border: 1px solid var(--burgundy); border-radius: var(--radius-btn);
  transition: background 180ms ease, color 180ms ease, opacity 180ms ease;
}
.gallery-btn:hover, .gallery-btn:focus-visible { background: var(--burgundy); color: var(--ground-2); }
.gallery-btn:disabled { opacity: 0.35; cursor: default; background: var(--ground-2); color: var(--burgundy); }
.gallery-track {
  --gutter: max(var(--s-5), calc((100% - var(--wrap)) / 2 + var(--s-5)));
  list-style: none; margin: 0; padding: 0 0 0 var(--gutter);
  display: flex; gap: var(--s-3);
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x proximity; scroll-padding-inline: var(--gutter);
  scrollbar-width: thin; scrollbar-color: var(--rose) transparent;
  padding-bottom: var(--s-4);
}
.gallery-track > li {
  flex: none; height: clamp(380px, 90vw, 640px); scroll-snap-align: start;
  border-radius: var(--radius);
}
.gallery-track img { display: block; height: 100%; width: auto; }
/* The track is focusable so the arrow keys scroll it; its ring follows the frames' own corner. */
.gallery-track:focus-visible { outline-offset: -2px; }

/* ---------- photograph crops ----------
   One class per photograph, put on the <img>. Named for the frame rather than for the slot,
   so a photograph keeps its crop wherever it is used — two of these serve two slots each.
   Anything not listed here takes the browser's own 50% 50%, which is the right answer for a
   frame whose subject is already centred.

   `object-position` only bites on the axis the box actually crops, so most of these carry one
   meaningful number. The arithmetic behind every value, for re-deriving rather than nudging:
   with `cover`, the visible fraction f of the cropped axis is (box ratio ÷ frame ratio) for a
   vertical crop, and its inverse for a horizontal one; the visible band then runs from
   pos x (1 - f) to pos x (1 - f) + f. Change a box's shape and the number has to be worked
   out again — a value that looks right at one width is usually wrong at the next. */

/* The three page heroes, all 16:9 recompositions with their subjects small in the bottom
   quarter and open sky above. On a phone the box is far taller than the frame, so the crop is
   horizontal and f is about 0.26 (0.46 / 1.79): x picks which quarter of the width survives.
   On a window wider than 16:9 the crop turns vertical, and 85% keeps the subjects on the floor
   of the band — at 21:9, f is 0.76 and the frame shows from 21% to 96%. */
.crop-bergtafel { object-position: 50% 85%; }
.crop-panorama-groep { object-position: 50% 85%; }
/* The loaded chair sits right of centre (60–65% across), so 60% shows 45–70%: the rock's right
   half and the three skiers, with the empty chair just out of frame. */
.crop-skilift { object-position: 60% 85%; }
/* #samen, in the mirrored panel. The box is wider than this 0.671 frame, so the crop is
   vertical and f is about 0.77 — centred shows the frame from 12% to 88%, which is both skiers
   and both plumes. Nothing is being kept out of the copy's way here; the panel does that. */
.crop-poeder-duo { object-position: 50% 50%; }
/* Home steps backdrop. Anchored low so the skier is not cut off at the boots; the frame is
   motion-blurred and sits behind the card, so this is about composition, not detail. */
.crop-afdaling-snel { object-position: 50% 72%; }
/* Card grid. Three tall frames in a 16:10 box show half their height or less, so each needs
   saying where its subject is: the crowd, the table and the ridge all sit low. */
.crop-apres-ski { object-position: 50% 72%; }
.crop-lunch-uitzicht { object-position: 50% 72%; }
.crop-bergtop { object-position: 50% 55%; }
/* Panels. Stacked, the box is 3:2 and these two are near enough to it that the whole frame
   shows; the values that matter are in the 1250px block, where the panel reshapes the box. */
.crop-aperol-zon { object-position: 50% 50%; }
.crop-pistebord { object-position: 50% 38%; }
.crop-ligstoelen { object-position: 50% 62%; }
.crop-team { object-position: 50% 30%; }  /* 3:2 stacked: shows 15–65% of the height, helmets to smiles */
/* Two slots: the six-step backdrop and a wide panel. One value covers both, since neither
   slot has a subject the other loses. */
.crop-bergen-van-boven { object-position: 50% 52%; }
/* Voor merken. The header is a 736x920 portrait behind a 16:9 hero, so only the middle 45% of
   its height shows: anchored high, that is the pale sky and the ridge. The three panel frames
   are 2:3 portraits in a 3:2 stacked box (44% of the height): each anchor keeps the faces. */
.crop-header-voor-merken { object-position: 50% 0%; }
.crop-voor-merken-02 { object-position: 50% 60%; }
.crop-voor-merken-03 { object-position: 50% 40%; }
.crop-voor-merken-04 { object-position: 50% 48%; }
/* The portfolio cards' square frames, cropped from portrait masters: the faces sit in the
   upper-middle of the Animo frame, the hotel's logo in the upper half of the Nikkie one. */
.crop-portfolio-animo { object-position: 50% 30%; }
.crop-portfolio-nikkie { object-position: 50% 30%; }

/* ---------- breakpoints ---------- */
@media (min-width: 640px) {
  .usps { grid-template-columns: repeat(2, 1fr); gap: var(--s-5); }
  .quotes { padding-top: 0; padding-inline: clamp(var(--s-6), 7vw, var(--s-9)); }
  .quote-glyph-open { top: 0; left: 0; transform: none; }
  /* The closing mark hangs high in its own box, so bottom: 0 reads as sitting too low. The
     offset is optical, not geometric. */
  .quote-glyph-close { display: block; bottom: 0.35em; right: 0; }
  .row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-5); margin-bottom: var(--s-6); }
  .row .field { margin-bottom: 0; }
  .form-shell { padding: var(--s-8) var(--s-7); }
  html.js-steps .fs, html.js-steps .thanks { padding: var(--s-7); }
  /* A page title should outrank a section heading; below this the card is too narrow to
     carry the bigger step without the title running to four lines. */
  .page-hero h1 { font-size: var(--step-4); }
  .compare { grid-template-columns: 1fr 1fr; gap: var(--s-8); }
  /* The hairline is what sets the two lists against each other; on a phone they are stacked
     and the rule would read as a divider between unrelated blocks. */
  .compare-now { padding-left: var(--s-7); border-left: 1px solid var(--line); }
  /* 300px rather than a fixed column count: five items land as three-plus-two on a laptop and
     two-plus-two-plus-one on a tablet, and both read better than five stretched thin. */
  .points { grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); }
}
@media (min-width: 1100px) { .points-5 { grid-template-columns: repeat(5, 1fr); } }

/* Three full labels do not fit a phone row, so only the card you are on keeps its name —
   the numbers carry the rest. The tilt comes down too: a card is far taller than it is wide
   at this width, and the same angle would swing its corners off the side of the screen. */
@media (max-width: 560px) {
  html.js-steps .step-tab:not([aria-current]) .step-tab-t { display: none; }
  html.js-steps .steps-nav { gap: var(--s-2) var(--s-4); }
  .deck { --tilt: 0.9deg; --fan-room: 16px; }
  html.js-steps .deck-nav .btn { flex: 1 1 auto; }
}

@media (max-width: 899px) {
  .head-cta { display: none; }
  .burger { display: block; }
  .nav {
    position: fixed; inset: var(--head-h) 0 auto 0;
    background: var(--ground); border-bottom: 1px solid var(--line);
    padding: var(--s-5) var(--s-5) var(--s-7);
    transform: translateY(-8px); opacity: 0; visibility: hidden;
    /* visibility is deliberately NOT transitioned: a transitioned visibility is still
       `hidden` at progress 0, and an element that is not yet rendered cannot take focus —
       so moving focus into the drawer on open would silently no-op. */
    transition: opacity 180ms ease, transform 180ms ease;
    max-height: calc(100vh - var(--head-h)); overflow-y: auto;
  }
  .nav.is-open { transform: none; opacity: 1; visibility: visible; }
  .nav ul { flex-direction: column; gap: 0; }
  .nav ul a {
    display: block; padding: var(--s-4) 0; font-size: var(--step-0);
    border-bottom: 1px solid var(--line);
  }
  .nav-cta { display: inline-flex; margin-top: var(--s-5); }
}

@media (min-width: 900px) {
  /* The no-svh fallback for the headline (see .hero h1 below), set by the headline's longest
     line rather than by the scale — at 900px this is 67px, and "De wintersport waar je" at the
     0.49em advance in the token's own comment is 713px against the 852px the content column
     has left after its gutters. */
  :root { --head-h: 76px; --step-hero: clamp(4.20rem, 2.00rem + 4.20vw, 6.00rem); }
  .nav-cta { display: none; }
  /* The headline goes from three lines to two, and it is the *width* that buys it: the four
     clip boxes become flex items and pack two to a row, so it reads "De wintersport waar je /
     samen naar uitkijkt." — two lines of much the same length, which is why "samen" is a box
     of its own. The sizing is what holds that split: --step-hero-lg is deliberately too big
     for a third box to follow on the first row. One line fewer than the phone layout is a
     whole line of leading off the first screen, which is what was pushing the lede and the
     buttons down it.
     The gap is the space between two words that are now in separate boxes: fit-content trims
     a trailing space, so the row has to put it back, in em so it tracks the type. */
  .hero-title { display: flex; flex-wrap: wrap; column-gap: 0.26em; }
  /* Flex items, so the inline-block pairing that makes the phone's middle line is off and all
     four boxes pack by width instead. */
  .hero-title .hl:nth-of-type(2),
  .hero-title .hl:nth-of-type(3) { display: block; }
  /* Line grouping up here is 1+2 / 3+4, so the delays regroup with it. */
  .hero-title .hl:nth-of-type(2) > span { animation-delay: 80ms; }
  .hero-title .hl:nth-of-type(3) > span,
  .hero-title .hl:nth-of-type(4) > span { animation-delay: 220ms; }
  /* Its own token, because the line that has to fit is a different line up here. The svh
     ceiling stays: on a short laptop window two lines at the full size still push the lede and
     the buttons off the first screen, and the hero clips. */
  .hero h1 { font-size: min(var(--step-hero-lg), 26svh); }
  /* No shared measure on the block any more. The headline is sized so its long line fits the
     content column (see --step-hero-lg), and the lede gets the wider measure that a two-line
     headline leaves room for — at 30ch it ran to three lines under a block half as tall. */
  .hero-copy > * { max-width: none; }
  .hero .lede { max-width: 44ch; }
  .usps { grid-template-columns: repeat(4, 1fr); }
  /* Three cards, not four: at four tracks the third card would sit beside an empty column. */
  .usps-3 { grid-template-columns: repeat(3, 1fr); }
  .usp p { font-size: var(--step--1); }
  /* The band splits: the card holds the left of the content column, the photograph keeps the
     right. 560px is where the step titles stop wrapping. */
  .steps-col { max-width: 560px; }
  /* Taller than the copy needs. The photograph is a portrait behind a wide band, so height is
     the only room the scene has: at the rhythm's own height the four figures fill the frame
     top to bottom and the ridge behind them is gone. */
  .steps-band { min-height: 940px; display: grid; align-content: center; }
  .foot-in { grid-template-columns: 1.4fr 1fr 1fr 1fr; gap: var(--s-8); }
}

/* ---------- onze visie, the panel layout ----------
   The photograph takes the left half of the *viewport*, bleeds off the page edge and fills the
   band top to bottom; the copy takes the right half. The grid is deliberately not inside .wrap
   — a panel that stopped at the 1200px content cap would read as a large image in a column,
   which is the layout this replaced.

   1250px rather than the usual 900, and the reason is the fill: the panel is as tall as the
   band, the band is as tall as the copy and its padding make it, and half of a narrower
   desktop is a box square enough that `cover` closes in past the car's wheels. Below this the
   band keeps the stacked layout — the same photograph across the full width, with none of that
   arithmetic. The sum, measured at 1250: a 625x612 panel shows 57% of the frame's width — the
   wheels and the passengers, with both bumpers off the edges. It opens back up as the viewport
   widens (65% at 1440, 80% at 1920). Re-check it if this copy block or its padding grows. */
@media (min-width: 1250px) {
  /* The band's own block padding goes to the copy, so the photograph reaches the section's
     top and bottom edges rather than floating inside the rhythm. The band is then exactly as
     tall as the copy and its padding make it, and the panel is that whole height. */
  .visie-band { padding-block: 0; }
  .visie-grid {
    grid-template-columns: 1fr 1fr; align-items: stretch; gap: 0;
    /* A floor under the panel's shape for the case the copy is *shorter* than the frame wants,
       which without this would letterbox the photograph into a strip. The row stretches to it,
       and the figure stretches to the row. */
    min-height: calc(50vw / 1.7);
  }
  .visie-fig { aspect-ratio: auto; margin-bottom: 0; }
  .visie-copy {
    align-self: center;
    /* In px rather than vh, because this padding is also the panel's height: it decides the
       frame's shape, and a value tracking the window height would reshape the photograph on a
       laptop versus a desktop. Generous, and that has a cost worth knowing about — every 32px
       here squares the panel up by a few per cent, and a squarer box shows less of a 1.80
       frame. The sum is in this query's header. */
    padding-block: var(--s-9);
    /* Left pad off the fold, right edge on the content column: half the wrap as the max-width,
       so the copy still ends where every other section's does. */
    max-width: calc(var(--wrap) / 2);
    padding-inline: clamp(var(--s-7), 5vw, var(--s-9)) var(--s-5);
  }
  /* Mirrored: the frame takes the right half and bleeds off that page edge. Two panels running
     the same way down one page read as a template rather than as a layout. Placed explicitly
     rather than with `order`, so the figure stays first in the DOM — with CSS off both panels
     still read photo-then-copy, which is the whole reason for that source order. */
  .visie-flip .visie-fig { grid-column: 2; grid-row: 1; }
  .visie-flip .visie-copy {
    grid-column: 1; grid-row: 1; justify-self: end;
    padding-inline: var(--s-5) clamp(var(--s-7), 5vw, var(--s-9));
  }
  /* Panel crops. The panel is as tall as the copy, and `cover` on a box that much taller than
     a 1.5 frame crops it sideways instead of vertically — so these two swap which axis their
     number is on once the layout changes.
     aperol-zon: f is about 0.68 of the width, and the glass and the woman sit from 50% to 85%,
     so centred would cut her off. pistebord: the box is close to the frame's own ratio here, so
     nearly all of it shows and the stacked value's low anchor is no longer needed. */
  .crop-aperol-zon { object-position: 58% 50%; }
  .crop-pistebord { object-position: 50% 50%; }
}


/* ---------- motion ---------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important; animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important; scroll-behavior: auto !important;
  }
  .reveal { opacity: 1 !important; transform: none !important; animation: none !important; }
  .hero-fig, .page-hero-fig, .hero-title .hl > span, .hero .lede, .hero .hero-cta,
  .page-hero-card > * { animation: none !important; }
  /* the border and shadow still answer the hover; the movement is what goes */
  .usps .usp:hover, .usp:hover img { transform: none; }
  .btn:hover .btn-arrow, .btn:focus-visible .btn-arrow { transform: none; }
  /* `*` does not reach this pseudo-element, so the FAQ unfold needs naming on its own */
  .faq-item::details-content { transition: none !important; }
}

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

/* No photograph is taller than the screen it is read on. The panel and card frames take their
   height from a ratio or from the copy beside them, so on a short window (a landscape phone, a
   small laptop) they can outgrow it; the img already covers its frame, so capping the frame
   crops a little rather than scaling anything. */
.visie-fig, .case-fig, .usp figure, .ph {
  max-height: 100vh;
  max-height: 100svh;
}
.visie-fig { overflow: hidden; }
