/* base.css — "airy" template: centred and weightless.
 *
 * A complete, quality-gated design system (hand-rolled BEM, framework-free).
 * The build agent copies this file verbatim into a site's `style.css` and
 * overwrites ONLY the delimited brand-parameter block below — every other rule
 * ships byte-identical, which is how the fixture's Lighthouse/axe pass transfers
 * to every real build. See TEMPLATE.md for the character spec and the
 * brand-parameter contract (which properties to fill, how tints/shades derive).
 *
 * Character: calm, centred, render-led. One sans family in two weights; a
 * navigation pill floating over the page; headings split into a muted half and a
 * full-ink half of the same colour family; a large product image that is cut off
 * by the bottom edge of the section it sits in; soft two-layer shadows and a
 * 12/20/40/pill radius scale; one near-black band per page and nothing else
 * loud. Shares the BEM vocabulary of the "soft", "sharp", "fine" and "lush"
 * templates so a site's HTML renders under any of them unchanged. Fonts are
 * self-hosted from the site's own `fonts/` folder — see the generated web-font
 * section below.
 *
 * Motion is opt-in, not opt-out: every transition, animation and smooth scroll in
 * this file is declared inside `@media (prefers-reduced-motion: no-preference)`.
 * A visitor who asks for reduced motion gets a page with no motion to switch off,
 * rather than a page that animates and is then patched back. Nothing in the
 * layout depends on motion having run.
 */

/* ── Web fonts (self-hosted, generated) ──────────────────────────────────
 * Regenerate with `node scripts/fetch-template-fonts.mjs`. The files live in
 * the site's own `fonts/` folder, so a page load reaches no third party.
 * Rules for every approved pair are listed; only the pair named in the brand
 * block is ever requested, and a build copies only that pair's files. */

/* manrope-inter */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/inter-normal-400.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/inter-normal-500.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/inter-normal-600.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Manrope';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/manrope-normal-500.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Manrope';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/manrope-normal-600.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Manrope';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('fonts/manrope-normal-700.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* jakarta-inter */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/inter-normal-400.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/inter-normal-500.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/inter-normal-600.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/plus-jakarta-sans-normal-500.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/plus-jakarta-sans-normal-600.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('fonts/plus-jakarta-sans-normal-700.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* figtree-sourcesans */
@font-face {
  font-family: 'Figtree';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/figtree-normal-500.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Figtree';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/figtree-normal-600.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Figtree';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('fonts/figtree-normal-700.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Source Sans 3';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/source-sans-3-normal-400.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Source Sans 3';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/source-sans-3-normal-500.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Source Sans 3';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/source-sans-3-normal-600.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* outfit-karla */
@font-face {
  font-family: 'Karla';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/karla-normal-400.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Karla';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/karla-normal-500.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Karla';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/karla-normal-600.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/outfit-normal-500.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/outfit-normal-600.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('fonts/outfit-normal-700.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ── end web fonts ── */

/* ── Brand parameters (per-site) ─────────────────────────────────────────────
 * The ONLY block the build agent edits. Fill from the extracted brand (logo
 * colours, screenshot palette, tone). Keep every property; adjust its value.
 * Contrast contract: --color-text and --color-muted must hold >= 4.5:1 on
 * --color-surface and --color-surface-alt; --color-on-primary must hold >= 4.5:1
 * on --color-primary; --color-primary used as link text must hold >= 4.5:1 on
 * --color-surface; --color-on-ground-deep must hold >= 4.5:1 on
 * --color-ground-deep. --color-accent is DECORATIVE ONLY — never load-bearing
 * text unless it independently meets 4.5:1.
 * --color-muted is load-bearing in this template in a way it is not in the
 * others: half of every heading is set in it, at display size. A grey that only
 * just passes as small print will fail the largest text on the page, so pick the
 * nearest brand tone that clears 4.5:1 rather than the lightest one available.
 * Fonts must be one of TEMPLATE.md's approved sans pairs, and the pair must have
 * a visibly heavier 600 than 400 — the two-tone heading only reads as a colour
 * change if the whole headline is unmistakably one weight. */
:root {
  --color-primary: #33291f;          /* deep bark brown — links, primary pill, ink blocks */
  --color-primary-shade: #241c15;    /* darker primary for hover/active (derive: primary ~12% darker) */
  --color-primary-tint: #f0ece5;     /* pale wash for icon plates (derive: primary mixed ~92% white) */
  --color-on-primary: #fcfaf6;       /* text/icons on --color-primary blocks */
  --color-accent: #a8703c;           /* warm tan — marks and rules, decorative only */
  --color-surface: #fcfbf9;          /* warm off-white page background */
  --color-surface-alt: #f5f2ec;      /* a barely-deeper warm tone for alternating sections */
  --color-text: #14120f;             /* near-black ink */
  --color-muted: #6a665e;            /* the quiet half of a heading and resting nav links (5.6:1 on surface) */
  --color-border: #eae5db;           /* pale warm hairline */

  --color-chrome: #fcfbf9;           /* fill of the floating navigation pill — opaque over pale AND dark sections */
  --color-quiet-fill: #f2efe8;       /* fill of the secondary (ghost) pill button */
  --color-ground-deep: #14120f;      /* the one inverted band per page */
  --color-on-ground-deep: #f7f5f1;   /* all text inside that band */

  --font-heading: 'Manrope', 'Segoe UI', system-ui, -apple-system, 'Helvetica Neue', Arial, sans-serif;
  --font-body: 'Inter', 'Segoe UI', system-ui, -apple-system, 'Helvetica Neue', Arial, sans-serif;
}
/* ── end Brand parameters ──────────────────────────────────────────────────── */

/* ── Fixed tokens (the template character — do not edit per site) ──────────── */
:root {
  /* Type scale 1.25 (major third) from a 17px base, fluid via clamp. Five of the
     seven steps land within a couple of pixels of a size the reference actually
     paints; the two in between exist so inner pages have a sub-heading size to
     use. The hero title deliberately sits OUTSIDE this ladder — see .stage__title. */
  --step--1: clamp(0.875rem, 0.85rem + 0.1vw, 0.9375rem);
  --step-0: clamp(1rem, 0.96rem + 0.18vw, 1.0625rem);
  --step-1: clamp(1.25rem, 1.2rem + 0.22vw, 1.328rem);
  --step-2: clamp(1.5rem, 1.4rem + 0.43vw, 1.663rem);
  --step-3: clamp(1.75rem, 1.55rem + 0.86vw, 2.078rem);
  --step-4: clamp(2.0625rem, 1.75rem + 1.36vw, 2.598rem);
  --step-5: clamp(2.5rem, 1.57rem + 3.98vw, 4.056rem);
  --leading-body: 1.6;
  --leading-display: 1.03; /* the measured display line-height: lines nearly touch */
  --tracking-display: -0.025em;

  /* Spacing steps in multiples of the 4px base unit the capture reported. The
     rhythm BETWEEN sections is a separate clamp, because section height there is
     driven by content, not by a repeating vertical unit. */
  --space-3xs: 0.25rem;
  --space-2xs: 0.5rem;
  --space-xs: 0.75rem;
  --space-s: 1.25rem;
  --space-m: 2rem;
  --space-l: 3.5rem;
  --space-xl: 5.5rem;
  --section-pad: clamp(4rem, 2.6rem + 5.6vw, 7rem);

  /* Two measures: display text is held narrower than the page band so a centred
     headline never runs the full width of a wide screen. */
  --container-max: 80rem;
  --measure-display: 64rem;

  /* Radii. 20px is the dominant value; 12px is what sits nested inside it; 40px
     is reserved for the one inverted band; everything interactive is a full pill. */
  --radius-s: 12px;
  --radius-m: 20px;
  --radius-l: 40px;
  --radius-pill: 999px;

  /* Two-layer soft shadows, never a hard drop. The ink literal (17,17,16) is the
     default --color-text: a shadow tinted with the page's own ink reads as depth
     rather than as grey haze, and rgba() cannot take a custom property directly. */
  --shadow-rest: 0 1px 2px rgba(17, 17, 16, 0.04), 0 8px 24px rgba(17, 17, 16, 0.06);
  --shadow-lift: 0 2px 8px rgba(17, 17, 16, 0.08), 0 16px 34px rgba(17, 17, 16, 0.1);
  /* The dark pill carries a hairline of light along its top edge — an inset
     highlight, not a border — which is what stops a near-black pill reading as a
     flat hole in a pale page. */
  --shadow-pill: inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 1px 2px rgba(17, 17, 16, 0.2),
    0 6px 16px rgba(17, 17, 16, 0.16);
  --shadow-pill-lift: inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 2px 8px rgba(17, 17, 16, 0.14),
    0 16px 34px rgba(17, 17, 16, 0.2);
  /* The quiet pill sits closer to the page and casts a shorter shadow than the
     dark one; both deepen to the same value on hover. */
  --shadow-quiet-pill: 0 2px 6px rgba(17, 17, 16, 0.04), 0 6px 16px rgba(17, 17, 16, 0.1);
  --shadow-quiet-pill-lift: 0 2px 8px rgba(17, 17, 16, 0.14), 0 16px 34px rgba(17, 17, 16, 0.2);
  /* How far the stage render travels across its pass through the viewport. */
  --render-drift: clamp(2rem, 1rem + 3vw, 5rem);
  --border-hair: 1px solid var(--color-border);

  /* A decelerating curve: motion arrives quickly and settles slowly, which is what
     a page whose product is calm should feel like. */
  --ease-airy: cubic-bezier(0.22, 1, 0.36, 1);
  --dur-reveal: 780ms;
  --dur-hover: 200ms;

  --header-h: 3.875rem;   /* the floating pill's own height */
  --header-gap: 1rem;     /* the air between the pill and the top of the viewport */
}

/* ── Reset ───────────────────────────────────────────────────────────────── */
*,
*::before,
*::after {
  box-sizing: border-box;
}
* {
  margin: 0;
}
html {
  -webkit-text-size-adjust: 100%;
}
body {
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: var(--leading-body);
  color: var(--color-text);
  background: var(--color-surface);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* The navigation pill is fixed, so the first section has to start below it or
     the page opens with its own headline hidden under the chrome. */
  padding-top: calc(var(--header-h) + var(--header-gap) * 2);
}
img,
picture,
svg,
video {
  display: block;
  max-width: 100%;
  height: auto;
}
input,
button,
textarea,
select {
  font: inherit;
  color: inherit;
}
/* Body-copy links keep their underline: in a near-monochrome scheme the primary
   sits close to the ink, so the underline and not the colour is the affordance. */
a {
  color: var(--color-primary);
  text-underline-offset: 0.18em;
}
a:hover {
  color: var(--color-primary-shade);
}
h1,
h2,
h3,
h4 {
  font-family: var(--font-heading);
  line-height: var(--leading-display);
  /* One weight for every heading on the page. The two-tone device is a colour
     change, and it only reads as one if nothing else in the headline moves. */
  font-weight: 600;
  color: var(--color-text);
  letter-spacing: var(--tracking-display);
}
h1 {
  font-size: var(--step-4);
}
h2 {
  font-size: var(--step-3);
}
h3 {
  font-size: var(--step-2);
}
ul,
ol {
  padding-left: 1.25em;
}

/* A visible focus ring on every interactive element, in the ink rather than in a
   brand colour, so it is legible on the pale surface, on the quiet fill and on
   the inverted band. Focus is never traded away for the hover treatment. */
:focus-visible {
  outline: 2px solid var(--color-text);
  outline-offset: 2px;
  border-radius: var(--radius-s);
}
/* Inside the inverted band the ink ring would disappear, so it inverts with it. */
.band--deep :focus-visible {
  outline-color: var(--color-on-ground-deep);
}

/* ── Utilities ───────────────────────────────────────────────────────────── */
.container {
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  /* 24px on a phone widening to the measured 85px gutter on a large screen. */
  padding-inline: clamp(1.5rem, 0.6rem + 3.6vw, 5.3125rem);
}
/* Accessible visually-hidden text (labels for icon-only controls, etc.). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
/* Skip link: off-screen until focused, then pinned top-left for keyboard users. */
.skip-link {
  position: absolute;
  left: var(--space-s);
  top: -4rem;
  z-index: 120; /* above the floating pill, which is the only other fixed layer */
  padding: var(--space-2xs) var(--space-s);
  background: var(--color-primary);
  color: var(--color-on-primary);
  border-radius: var(--radius-pill);
  text-decoration: none;
}
.skip-link:focus {
  top: var(--space-2xs);
  color: var(--color-on-primary);
}

/* ── Two-tone heading ────────────────────────────────────────────────────── */
/* A standalone block wrapped INSIDE a heading element, so the same device works
   in a stage title and in a section title without chaining elements onto either,
   and so a screen reader still reads one continuous heading string. Both halves
   keep the heading's size and weight; only the colour differs. Which half is
   quiet alternates down the page — that is a composition decision made in the
   markup, which is why there is no "quiet is always first" rule here. */
.duotone {
  display: inline;
}
.duotone__quiet {
  color: var(--color-muted);
}
.duotone__loud {
  color: var(--color-text);
}
/* On the inverted band the same two roles have to be re-pointed: the pale ink
   becomes loud, and the quiet half is a dimmed version of it. */
.band--deep .duotone__quiet {
  color: rgba(247, 246, 243, 0.62);
}
.band--deep .duotone__loud {
  color: var(--color-on-ground-deep);
}

/* ── Word cascade ────────────────────────────────────────────────────────── */
/* The statement block's text arrives a word at a time. This is the page's most
   numerous piece of motion and it is not the same thing as a block reveal: the
   words do not move at all, they only come up from a dim tone to full ink.
   Without JavaScript, and under reduced motion, the words are simply at full ink
   from the first frame — the block below is the complete resting state, and the
   animated version lives in the motion section at the end of this file. */
.cascade__word {
  display: inline;
}

/* ── Buttons ─────────────────────────────────────────────────────────────── */
/* Every interactive element in this template is a full pill; there are no
   rounded rectangles. --primary/--ghost are the shared vocabulary, --pill/--quiet
   are the template's own pair and are what pages should use. */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-height: 3rem; /* comfortable tap target */
  padding: 0.8rem 1.5rem;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: var(--step-0);
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
}
.button__label {
  display: inline-block;
}
.button--primary,
.button--pill {
  background: var(--color-primary);
  color: var(--color-on-primary);
  border-color: var(--color-primary);
  box-shadow: var(--shadow-pill);
}
/* The dark pill LIGHTENS under the pointer rather than darkening. On a near-black
   fill there is no room to go darker, so the lift has to come from the fill moving
   a few points towards its own label colour. The first pair of declarations is the
   fallback for a browser without color-mix(), where the fill simply holds. */
.button--primary:hover,
.button--pill:hover {
  background: var(--color-primary);
  border-color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 94%, var(--color-on-primary));
  border-color: color-mix(in srgb, var(--color-primary) 94%, var(--color-on-primary));
  color: var(--color-on-primary);
  box-shadow: var(--shadow-pill-lift);
}
/* The secondary pill: an off-white fill with a hairline, for use beside the dark
   one and on top of a pale ground. */
.button--ghost,
.button--quiet {
  background: var(--color-quiet-fill);
  color: var(--color-text);
  border-color: rgba(17, 17, 16, 0.05);
  box-shadow: var(--shadow-quiet-pill);
}
.button--ghost:hover,
.button--quiet:hover {
  color: var(--color-text);
  border-color: rgba(17, 17, 16, 0.1);
  box-shadow: var(--shadow-quiet-pill-lift);
}

/* ── Header: the floating navigation pill ────────────────────────────────── */
/* The pill IS the site header in a different shape, so it is a modifier on the
   header block rather than a second navigation block. It is fixed and is the only
   floating layer on the page; it scrolls over both the pale sections and the
   inverted band, which is why its fill stays opaque and keeps its shadow instead
   of going translucent and inverting over one of them. */
.site-header {
  position: fixed;
  top: var(--header-gap);
  left: 0;
  right: 0;
  z-index: 100;
  padding-inline: var(--space-s);
}
.site-header--pill .site-header__inner,
.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-s);
  width: 100%;
  max-width: 60rem;
  min-height: var(--header-h);
  margin-inline: auto;
  padding: var(--space-3xs) var(--space-3xs) var(--space-3xs) var(--space-s);
  background: var(--color-chrome);
  border: 1px solid rgba(17, 17, 16, 0.05);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-rest);
}
/* main.js adds --stuck once the page has scrolled off the top, and this template
   deliberately gives it nothing to do: the reference's floating chrome was watched
   across twenty scroll positions and changed neither opacity nor shape. The pill is
   opaque and shadowed from the first frame, so there is no "now it needs a
   background" moment to answer. The modifier stays in the vocabulary so a page's
   header markup is identical under every template's stylesheet. */
.site-header__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  color: var(--color-text);
  text-decoration: none;
}
/* The wordmark is the one control that does nothing under the pointer, so the
   document-wide link hover has to be cancelled on it. */
.site-header__brand:hover {
  color: var(--color-text);
}
.site-header__logo {
  width: 2rem;
  height: 2rem;
  border-radius: var(--radius-pill);
}
.site-header__wordmark {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: var(--step-0);
  letter-spacing: -0.01em;
  white-space: nowrap;
}
.nav__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  background: var(--color-quiet-fill);
  border: 1px solid rgba(17, 17, 16, 0.05);
  border-radius: var(--radius-pill);
  cursor: pointer;
}
.nav__toggle-bars {
  position: relative;
  width: 1.125rem;
  height: 1.5px;
  background: var(--color-text);
}
.nav__toggle-bars::before,
.nav__toggle-bars::after {
  content: '';
  position: absolute;
  left: 0;
  width: 1.125rem;
  height: 1.5px;
  background: var(--color-text);
}
.nav__toggle-bars::before {
  top: -5px;
}
.nav__toggle-bars::after {
  top: 5px;
}
.nav__menu {
  list-style: none;
  padding: 0;
  margin: 0;
}
.nav__item {
  margin: 0;
}
.nav__link {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  padding: 0 var(--space-xs);
  border-radius: var(--radius-pill);
  color: var(--color-muted); /* resting nav links share the muted role with the quiet half of a heading */
  text-decoration: none;
  font-weight: 500;
}
.nav__link:hover,
.nav__link--active {
  color: var(--color-text);
}

/* Mobile-first: the menu collapses behind the toggle and opens as its own rounded
   sheet under the pill, rather than stretching the pill itself out of shape. */
@media (max-width: 47.99rem) {
  .nav__menu {
    display: none;
    position: absolute;
    left: var(--space-s);
    right: var(--space-s);
    top: calc(100% + var(--space-2xs));
    flex-direction: column;
    gap: var(--space-3xs);
    padding: var(--space-s);
    background: var(--color-chrome);
    border: 1px solid rgba(17, 17, 16, 0.05);
    border-radius: var(--radius-m);
    box-shadow: var(--shadow-lift);
  }
  .nav__menu--open {
    display: flex;
  }
  .nav__link {
    width: 100%;
  }
}
@media (min-width: 48rem) {
  .nav__toggle {
    display: none;
  }
  .nav__menu {
    display: flex;
    align-items: center;
    gap: var(--space-3xs);
  }
}

/* ── Stage: the centred render device that opens and closes the page ─────── */
/* Centred text over a warm ground, with one large image below it whose bottom is
   cut off by the section's own edge. The cut is the device: it has to land on the
   object, so the picture must be a single centred subject on an empty ground.
   There is never text on the image, so there is no scrim in this template. */
.stage {
  position: relative;
  padding-top: var(--section-pad);
  background: var(--color-surface);
  /* The clip that makes the bleed. It also guarantees the render can never widen
     the document: a full-width child cannot produce a horizontal scrollbar if its
     parent clips. Vertical scrolling is unaffected because .stage is in flow. */
  overflow: hidden;
}
.stage__inner {
  max-width: var(--measure-display);
  margin-inline: auto;
  text-align: center;
}
.stage__title {
  /* Its own clamp, outside the type ladder: the measured display size rises more
     shallowly than a ladder step would, and the small end of it has to hold a
     Finnish compound whole on a 390px screen. See TEMPLATE.md, "Finnish word
     length", before changing either end of this. */
  font-size: clamp(2.5rem, 1.85rem + 2.67vw, 4.25rem);
  line-height: var(--leading-display);
  letter-spacing: var(--tracking-display);
  /* Centred display text has no slack: an overflowing word pushes out of both
     edges at once. Break long compounds visibly rather than silently, but only
     from 16 characters up, so the 13-14 character words a Finnish tagline is full
     of are never hyphenated. Safari before 17 ignores the 16-character floor and
     applies only the 5/4 limits. */
  overflow-wrap: break-word;
  hyphens: auto;
  -webkit-hyphens: auto;
  hyphenate-limit-chars: 16 5 4;
  -webkit-hyphenate-limit-before: 5;
  -webkit-hyphenate-limit-after: 4;
  max-width: 100%;
}
.stage__text {
  margin: var(--space-s) auto 0;
  max-width: 46ch;
  font-size: var(--step-1);
  color: var(--color-muted);
}
.stage__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-xs);
  margin-top: var(--space-m);
}
/* ── The stage's media: three cases, one element ──────────────────────────── */
/* What sits under the centred text is decided per site, not by the template:
     1. .stage__media--render — one large subject on an empty ground, running the
        full width and cut off by the stage's bottom edge. The template's own
        opening device, and right only when the business really does have a single
        object to show.
     2. .stage__media--picture — an ordinary photograph inside the text measure,
        rounded and complete rather than cut. Right for a workshop, a shopfront or
        a finished job, none of which survive being cropped by the section edge.
     3. no media at all — see .stage--stated.
   The cut in case 1 has to land on the object, so it needs a picture composed for
   it. Handing an ordinary photograph to the render variant is what makes a stage
   look broken: the cut falls somewhere arbitrary and reads as a mistake. */
.stage__media {
  position: relative;
  width: 100%;
  margin-top: var(--space-l);
  overflow: hidden;
}
/* Case 1 — the bleed. Its height is what performs the cut: the picture keeps its
   own aspect ratio at full width, and whatever falls past this height is clipped
   by .stage. object-fit stays `contain` so the subject is never cropped at the
   sides. */
.stage__media--render {
  height: clamp(13rem, 2rem + 38vw, 30rem);
}
.stage__media--render img {
  width: 100%;
  height: auto;
  object-fit: contain;
  border-radius: 0; /* the render carries no frame, no radius and no filter */
}
/* Case 2 — the complete picture. It stays inside the display measure so the stage
   still reads as centred, and it is cropped by its own box rather than by the
   section edge. */
.stage__media--picture {
  max-width: var(--measure-display);
  margin-inline: auto;
  aspect-ratio: var(--stage-media-aspect, 16 / 9);
  border-radius: var(--radius-m);
  box-shadow: var(--shadow-rest);
}
.stage__media--picture img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Case 3 — no media. The stage has no bottom padding of its own, because in the
   other two cases the picture runs to the section's edge; with nothing there the
   padding has to come back or the text sits on the next section. */
.stage--stated {
  padding-bottom: var(--section-pad);
}
/* The short stage: the same device with less in it. It closes the front page, so
   the page ends on the note it opened on without spending a second full screen on
   an image, and it is also what inner pages open with — a page that is not the
   front page should not give a whole screen to a picture. */
.stage--closing {
  padding-bottom: 0;
}
.stage--closing .stage__media--render {
  height: clamp(10rem, 2rem + 26vw, 22rem);
}
.stage--closing .stage__media--picture {
  aspect-ratio: var(--stage-media-aspect, 21 / 9);
}

/* ── Hero (shared vocabulary — inner pages that do not use a stage) ──────── */
.hero {
  padding-block: var(--section-pad);
  background: var(--color-surface);
  overflow: hidden;
}
.hero__inner {
  display: grid;
  gap: var(--space-l);
  align-items: center;
}
.hero__body {
  min-width: 0; /* grid child: keep a long compound from blowing out the text column */
}
.hero__kicker,
.section__kicker,
.stage__kicker {
  display: inline-block;
  margin-bottom: var(--space-s);
  font-family: var(--font-heading);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: var(--step--1);
  color: var(--color-muted);
}
.hero__title {
  font-size: var(--step-4);
  overflow-wrap: break-word;
  hyphens: auto;
  -webkit-hyphens: auto;
  hyphenate-limit-chars: 16 5 4;
  -webkit-hyphenate-limit-before: 5;
  -webkit-hyphenate-limit-after: 4;
}
.hero__text {
  margin-top: var(--space-s);
  font-size: var(--step-1);
  color: var(--color-muted);
  max-width: 46ch;
}
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-top: var(--space-m);
}
.hero__media {
  border-radius: var(--radius-m);
  overflow: hidden;
  box-shadow: var(--shadow-rest);
}
.hero__art {
  width: 100%;
  height: auto;
}
@media (min-width: 56rem) {
  .hero__inner {
    grid-template-columns: 1.05fr 0.95fr;
  }
}

/* ── Section scaffolding ─────────────────────────────────────────────────── */
/* Everything between the two stages is left-aligned. Centring a middle section
   breaks the frame the opening and closing stages make. */
.section {
  padding-block: var(--section-pad);
}
.section--alt {
  background: var(--color-surface-alt);
}
.section__head {
  max-width: 52ch;
  margin-bottom: var(--space-l);
}
.section__title {
  font-size: var(--step-3);
}
.section__lead {
  margin-top: var(--space-s);
  color: var(--color-muted);
  font-size: var(--step-1);
}

/* ── Inverted band ───────────────────────────────────────────────────────── */
/* The one near-black section per page. Its large corner radius lets the pale page
   show through at all four corners, so it reads as a held breath rather than as a
   stripe. Used twice on one page it stops being an event — see TEMPLATE.md. */
.band {
  padding-block: var(--section-pad);
}
.band--deep {
  margin-inline: clamp(0rem, -1rem + 4vw, 3rem);
  padding-inline: var(--space-s);
  background: var(--color-ground-deep);
  color: var(--color-on-ground-deep);
  border-radius: var(--radius-l);
}
.band__inner {
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: clamp(1rem, 0.2rem + 3.2vw, 4rem);
}
.band--deep .section__title,
.band--deep h2,
.band--deep h3 {
  color: var(--color-on-ground-deep);
}
/* Secondary copy directly on the dark ground: the muted ink is a role defined
   against the pale surface and would fall under 4.5:1 here, so it is re-pointed
   to a dimmed version of the band's own text colour. Copy inside a pale card ON
   the band keeps the ordinary muted ink, because its background is still pale. */
.band--deep .section__lead,
.band--deep .faq__answer {
  color: rgba(247, 246, 243, 0.78);
}

/* ── Work tiles ──────────────────────────────────────────────────────────── */
/* A smaller radius nested inside a larger one — the relationship the capture
   found between its 20px and 12px rounded elements. */
.tile-grid {
  display: grid;
  gap: var(--space-s);
  grid-template-columns: 1fr;
}
@media (min-width: 40rem) {
  .tile-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 64rem) {
  .tile-grid {
    grid-template-columns: repeat(3, 1fr);
  }
  .tile--wide {
    grid-column: span 2;
  }
}
.tile {
  padding: var(--space-3xs);
  background: var(--color-surface);
  border: var(--border-hair);
  border-radius: var(--radius-m);
  box-shadow: var(--shadow-rest);
}
.tile__media {
  overflow: hidden;
  border-radius: var(--radius-s);
}
.tile__image {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}
.tile__label {
  display: block;
  padding: var(--space-s) var(--space-xs) var(--space-xs);
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: var(--step-0);
}
.tile__text {
  display: block;
  padding: 0 var(--space-xs) var(--space-s);
  color: var(--color-muted);
  font-size: var(--step--1);
}

/* ── Cards ───────────────────────────────────────────────────────────────── */
.card-grid {
  display: grid;
  gap: var(--space-s);
  grid-template-columns: 1fr;
}
@media (min-width: 40rem) {
  .card-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 64rem) {
  .card-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}
.card {
  padding: var(--space-m);
  background: var(--color-surface);
  border: var(--border-hair);
  border-radius: var(--radius-m);
  box-shadow: var(--shadow-rest);
}
.card__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  margin-bottom: var(--space-s);
  background: var(--color-primary-tint);
  color: var(--color-primary);
  border-radius: var(--radius-pill);
}
.card__title {
  font-size: var(--step-2);
  margin-bottom: var(--space-2xs);
}
.card__text {
  color: var(--color-muted);
}

/* ── Gallery ─────────────────────────────────────────────────────────────── */
.gallery {
  display: grid;
  gap: var(--space-s);
  grid-template-columns: 1fr;
}
@media (min-width: 40rem) {
  .gallery {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 64rem) {
  .gallery {
    grid-template-columns: repeat(3, 1fr);
  }
}
.gallery__item {
  overflow: hidden;
  border-radius: var(--radius-m);
  box-shadow: var(--shadow-rest);
}
.gallery__image {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

/* ── Testimonial (pale card, sized to sit on the inverted band) ──────────── */
.testimonial {
  padding: var(--space-m);
  background: var(--color-surface);
  border-radius: var(--radius-m);
  color: var(--color-text);
}
.testimonial__quote {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: var(--step-1);
  line-height: 1.45;
}
.testimonial__author {
  display: block;
  margin-top: var(--space-s);
  font-family: var(--font-body);
  font-style: normal;
  font-size: var(--step--1);
  color: var(--color-muted);
}

/* ── FAQ ─────────────────────────────────────────────────────────────────── */
.faq {
  max-width: 52rem;
  border-top: var(--border-hair);
}
.faq__item {
  padding-block: var(--space-s);
  border-bottom: var(--border-hair);
}
.faq__question {
  font-size: var(--step-1);
  margin-bottom: var(--space-2xs);
}
.faq__answer {
  color: var(--color-muted);
  max-width: 60ch;
}

/* ── Contact form (posts via mailto — no runtime dependency) ─────────────── */
.contact-form {
  display: grid;
  gap: var(--space-s);
  max-width: 40rem;
}
.contact-form__field {
  display: grid;
  gap: var(--space-3xs);
}
.contact-form__label {
  font-weight: 600;
  font-size: var(--step--1);
}
.contact-form__input,
.contact-form__textarea {
  width: 100%;
  padding: 0.8rem 1rem;
  background: var(--color-surface);
  border: var(--border-hair);
  border-radius: var(--radius-s);
}
.contact-form__input:focus,
.contact-form__textarea:focus {
  outline: none;
  border-color: var(--color-text);
  box-shadow: 0 0 0 2px var(--color-primary-tint);
}
.contact-form__textarea {
  min-height: 8rem;
  resize: vertical;
}

/* ── Footer ──────────────────────────────────────────────────────────────── */
/* The footer sits under the closing stage on the same surface, so it reads as the
   quiet last line of that section rather than as a separate slab. */
.site-footer {
  padding-block: var(--space-l) var(--space-m);
  background: var(--color-surface);
}
.site-footer__inner {
  display: grid;
  gap: var(--space-m);
  grid-template-columns: 1fr;
}
@media (min-width: 48rem) {
  .site-footer__inner {
    grid-template-columns: repeat(3, 1fr);
  }
}
.site-footer__heading {
  font-family: var(--font-heading);
  font-size: var(--step-0);
  margin-bottom: var(--space-2xs);
}
.site-footer__text {
  color: var(--color-muted);
}
.site-footer__link {
  display: block;
  padding: var(--space-3xs) 0;
  color: var(--color-muted);
  text-decoration: none;
}
.site-footer__link:hover {
  color: var(--color-text);
}
.site-footer__legal {
  margin-top: var(--space-m);
  padding-top: var(--space-s);
  border-top: var(--border-hair);
  color: var(--color-muted);
  font-size: var(--step--1);
}

/* ── Legal page ──────────────────────────────────────────────────────────── */
.legal {
  max-width: 62ch;
  margin-inline: auto;
  padding-block: var(--section-pad);
}
.legal__title {
  margin-bottom: var(--space-m);
}
.legal__body > * + * {
  margin-top: var(--space-s);
}
.legal__body h2 {
  font-size: var(--step-2);
  margin-top: var(--space-l);
}

/* ── Cookie banner (used only when a site sets cookies; main.js dismisses) ── */
.cookie-banner {
  position: fixed;
  left: var(--space-s);
  right: var(--space-s);
  bottom: var(--space-s);
  z-index: 80;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-s);
  padding: var(--space-s) var(--space-m);
  background: var(--color-chrome);
  border: var(--border-hair);
  border-radius: var(--radius-m);
  box-shadow: var(--shadow-lift);
}
.cookie-banner__text {
  flex: 1 1 16rem;
  color: var(--color-muted);
  font-size: var(--step--1);
}
.cookie-banner__actions {
  display: flex;
  gap: var(--space-2xs);
}

/* ── Demo banner (pre-contract preview marker) ───────────────────────────── */
.demo-banner {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 90;
  padding: var(--space-2xs) var(--space-s);
  text-align: center;
  background: var(--color-primary);
  color: var(--color-on-primary);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: var(--step--1);
}

/* ── Error (404) page ────────────────────────────────────────────────────── */
.error-page {
  display: grid;
  place-content: center;
  gap: var(--space-s);
  min-height: 60vh;
  padding: var(--section-pad) var(--space-s);
  text-align: center;
}
.error-page__code {
  font-family: var(--font-heading);
  font-size: var(--step-5);
  color: var(--color-muted);
}
.error-page__title {
  font-size: var(--step-3);
}
.error-page__text {
  color: var(--color-muted);
  max-width: 40ch;
  margin-inline: auto;
}

/* ── Map, click-to-load (only on a page with a confirmed street address) ──── */
/* The block reserves its space up front and holds only plain markup: nothing is
   requested from Google until the visitor presses the button, at which point
   main.js swaps the placeholder for the map frame. */
.map-embed {
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--color-surface-alt);
  border: var(--border-hair);
  border-radius: var(--radius-m);
}
@media (min-width: 45rem) {
  .map-embed {
    aspect-ratio: 16 / 9;
  }
}
.map-embed__placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  padding: var(--space-s);
  text-align: center;
}
/* The small print that tells the visitor what pressing the button will load. */
.map-embed__note {
  max-width: 44ch;
  color: var(--color-muted);
  font-size: var(--step--1);
}
/* Hidden without JavaScript, where pressing it could only do nothing; the link
   below it opens the same location in that case. */
.map-embed__load {
  display: none;
}
.js .map-embed__load {
  display: inline-flex;
}
.map-embed__link {
  font-size: var(--step--1);
}
.map-embed__frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* ── Motion ──────────────────────────────────────────────────────────────── */
/* Everything that moves on this page is declared here and nowhere else, inside a
   single no-preference query. A visitor who has asked for reduced motion never
   receives these rules at all: reveals are simply visible, the render does not
   drift, and hover changes only the shadow — which the resting rules above
   already do on their own.
   The whole budget is three behaviours and there is no fourth, because the page
   this template is derived from registered 27 identical reveals, one drift and no
   self-animating elements at all. If a build wants a scripted effect here, that is
   a sign the design is being pushed somewhere it does not go. */
@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }

  /* Block reveal on scroll: a fade with a 12px rise. Only hidden when JS is
     running (the root .js class), so a page without JavaScript shows everything.
     A cascade block is excluded because its words carry the reveal themselves —
     fading the paragraph as a block on top of that would show the sweep twice.

     A block marked `data-reveal-group` in the markup arrives as a sequence
     instead of as one piece: main.js numbers each part with `--reveal-index` and
     the delay below turns that number into the wait before it starts. 40ms is
     the same step the word cascade uses, so the page has one rhythm rather than
     two. The number of steps is capped so a long list still reads as one arrival
     and its last item is not still coming in after the reader has passed it. An
     element nobody numbered has an index of 0 and starts immediately, exactly as
     it did before. */
  .js [data-reveal]:not(.cascade) {
    --reveal-step: 40ms;
    --reveal-delay: calc(min(var(--reveal-index, 0), 6) * var(--reveal-step));
    opacity: 0;
    transform: translateY(12px);
    transition: opacity var(--dur-reveal) var(--ease-airy) var(--reveal-delay),
      transform var(--dur-reveal) var(--ease-airy) var(--reveal-delay);
  }
  .js [data-reveal]:not(.cascade).is-visible {
    opacity: 1;
    transform: none;
  }

  /* The word cascade. The endpoints are measured — a word starts at 16% of its
     final opacity, ends at full, takes 600ms and never moves. The step BETWEEN
     words is not measurable from a still capture, so 40ms is a choice: slow
     enough to read as a sweep across the sentence, fast enough that the last word
     of a long paragraph is not still arriving after the reader reaches it. Driven
     by the shared [data-reveal] hook, so this template still ships no second
     behaviour file. */
  .js .cascade .cascade__word {
    opacity: 0.16;
    transition: opacity var(--dur-reveal) var(--ease-airy);
  }
  .js .cascade.is-visible .cascade__word {
    opacity: 1;
  }
  .js .cascade.is-visible .cascade__word:nth-child(2) {
    transition-delay: 40ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(3) {
    transition-delay: 80ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(4) {
    transition-delay: 120ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(5) {
    transition-delay: 160ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(6) {
    transition-delay: 200ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(7) {
    transition-delay: 240ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(8) {
    transition-delay: 280ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(9) {
    transition-delay: 320ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(10) {
    transition-delay: 360ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(11) {
    transition-delay: 400ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(12) {
    transition-delay: 440ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(13) {
    transition-delay: 480ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(14) {
    transition-delay: 520ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(15) {
    transition-delay: 560ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(16) {
    transition-delay: 600ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(17) {
    transition-delay: 640ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(18) {
    transition-delay: 680ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(19) {
    transition-delay: 720ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(20) {
    transition-delay: 760ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(21) {
    transition-delay: 800ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(22) {
    transition-delay: 840ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(23) {
    transition-delay: 880ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(24) {
    transition-delay: 920ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(25) {
    transition-delay: 960ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(26) {
    transition-delay: 1000ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(27) {
    transition-delay: 1040ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(28) {
    transition-delay: 1080ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(29) {
    transition-delay: 1120ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(30) {
    transition-delay: 1160ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(31) {
    transition-delay: 1200ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(32) {
    transition-delay: 1240ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(33) {
    transition-delay: 1280ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(34) {
    transition-delay: 1320ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(35) {
    transition-delay: 1360ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(36) {
    transition-delay: 1400ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(37) {
    transition-delay: 1440ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(38) {
    transition-delay: 1480ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(39) {
    transition-delay: 1520ms;
  }
  .js .cascade.is-visible .cascade__word:nth-child(40) {
    transition-delay: 1560ms;
  }

  /* Hover lift: 2px, on everything liftable. */
  .button,
  .tile,
  .site-header__inner {
    transition: background-color var(--dur-hover) var(--ease-airy), border-color var(--dur-hover) var(--ease-airy),
      box-shadow var(--dur-hover) var(--ease-airy), transform var(--dur-hover) var(--ease-airy);
  }
  .button:hover,
  .button:focus-visible,
  .tile:hover {
    transform: translateY(-2px);
  }
  .nav__link,
  .site-footer__link,
  .skip-link {
    transition: color var(--dur-hover) var(--ease-airy), top var(--dur-hover) var(--ease-airy);
  }
  .tile:hover {
    box-shadow: var(--shadow-lift);
  }

  /* The render drifts down slightly as the section passes the viewport — the one
     parallax the reference performs. Driven by a scroll-driven animation rather
     than by a script, so this template still ships no second behaviour file.
     Browsers without scroll-driven animations ignore the timeline and paint the
     render at its resting position, which is the correct static composition. */
  @supports (animation-timeline: view()) {
    .stage__media--render img {
      animation: render-drift linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
    }
    /* The still frame the drift ends on is the composed one, so a browser that
       ignores the timeline sees the correct picture. */
  }
}

/* render-drift: the render travels downward as its section passes the viewport,
   at roughly 6.7% of the scroll distance. It only ever travels DOWN to its resting
   position, never past it, so the top of the clipped band can never be exposed.
   Transform-only, so the image itself is never repainted. */
@keyframes render-drift {
  from {
    transform: translateY(calc(var(--render-drift) * -1));
  }
  to {
    transform: translateY(0);
  }
}

/* ── Site-specific composition ───────────────────────────────────────────────
 * A brochure build should need nothing here. When a page genuinely needs
 * composition glue, append rules ONLY below this line, using existing tokens and
 * components — no new colours, fonts, radii, shadows, borders or component
 * blocks, and layout-only (grid/flex/spacing). Anything more is a template gap:
 * flag it for the template backlog rather than free-forming around it. */
