/* base.css — "lush" template: cinematic.
 *
 * 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, the four contract
 * sections (section composition, motion, interaction/cursor, image treatment)
 * and the brand-parameter contract.
 *
 * Character: photography-carried and cinematic — a full-bleed photographic hero
 * and a dark photographic closing section, both under a fixed inset frame; a
 * header of three tabs hanging off that frame; a display serif that takes italic
 * emphasis inside the headline; 10px picture corners; pill buttons with an arrow
 * badge; and restrained scroll motion (staggered 10px reveals with a 40px
 * emphasis variant, a 0.02-0.03 parallax drift, sticky pins, an additive cursor
 * trail). Shares the BEM vocabulary of the "soft", "sharp" and "fine" templates
 * and adds its own blocks on top, so a page's shared markup renders under any of
 * them unchanged.
 *
 * A lush site loads TWO behaviour files: the shared `main.js` (reveals, nav,
 * sticky state) and this template's `motion.js` (parallax, cursor trail). Both
 * are optional at runtime — the page renders completely without either.
 *
 * Fonts are self-hosted from the site's own `fonts/` folder — see the generated
 * web-font section below.
 */

/* ── 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. */

/* instrument */
@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/instrument-sans-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: 'Instrument Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/instrument-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: 'Instrument Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/instrument-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: 'Instrument Serif';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/instrument-serif-italic-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: 'Instrument Serif';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/instrument-serif-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;
}

/* playfair-worksans */
@font-face {
  font-family: 'Playfair Display';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/playfair-display-italic-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: 'Playfair Display';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/playfair-display-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: 'Playfair Display';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/playfair-display-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: 'Work Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/work-sans-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: 'Work Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/work-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: 'Work Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/work-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;
}

/* fraunces-inter */
@font-face {
  font-family: 'Fraunces';
  font-style: italic;
  font-weight: 400 500;
  font-display: swap;
  src: url('fonts/fraunces-italic-400-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: 'Fraunces';
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url('fonts/fraunces-normal-400-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: 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;
}

/* cormorant-figtree */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/cormorant-garamond-italic-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: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/cormorant-garamond-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: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/cormorant-garamond-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: 400;
  font-display: swap;
  src: url('fonts/figtree-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: '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;
}

/* ── end web fonts ── */

/* ── Brand parameters (per-site) ─────────────────────────────────────────────
 * The ONLY block the build agent edits. Fill from the extracted brand (logo
 * colours, photography, 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-accent is DECORATIVE ONLY (corner marks, badges, the
 * hover sweep behind ink text) — never load-bearing text unless it independently
 * meets 4.5:1.
 * --color-scrim and --color-on-media are the two properties the other templates
 * do not have, and they exist because this one puts text on photographs.
 * AUTOMATED CONTRAST CHECKING CANNOT SEE THROUGH A PHOTOGRAPH — axe skips text
 * over a background image entirely, so the scrim depth is a human judgement:
 * deepen it until --color-on-media is comfortably legible over the LIGHTEST
 * region of the actual picture.
 * Fonts must be one of TEMPLATE.md's approved pairs, and the heading face must
 * have a TRUE italic — the italic-inside-the-headline device depends on it. */
:root {
  --color-primary: #2d4a3a;        /* deep forest green — links, buttons, ink blocks (9.42:1 on surface) */
  --color-primary-shade: #223a2d;  /* darker primary for hover/active */
  --color-primary-tint: #eef2ef;   /* pale green wash for icon plates / quiet sections */
  --color-on-primary: #ffffff;     /* text/icons on --color-primary blocks (9.76:1) */
  --color-accent: #cf9b52;         /* sunset amber — corner marks and the pill hover sweep, decorative only (2.40:1) */
  --color-surface: #fcfbf7;        /* warm white page background, and the colour the fixed frame is drawn in */
  --color-surface-alt: #f1f3eb;    /* pale sage for alternating sections */
  --color-text: #1d231f;           /* near-black green ink body text (15.45:1 / 14.29:1) */
  --color-muted: #535d57;          /* muted green-grey secondary text (6.60:1 / 6.11:1) */
  --color-border: #e0e3d9;         /* pale line — form fields and the footer divide */
  --color-scrim: rgba(10, 18, 12, 0.70); /* deepened for the bright foliage and the sunlit lake photographs */
  --color-on-media: #fcfbf7;       /* all text over photography — the surface's warm white, so axe (which cannot see the photograph) reads it as unresolved rather than as white text on the page surface */

  --font-heading: 'Instrument Serif', Georgia, 'Times New Roman', serif;
  --font-body: 'Instrument Sans', 'Segoe UI', system-ui, -apple-system, sans-serif;
}
/* ── end Brand parameters ──────────────────────────────────────────────────── */

/* ── Fixed tokens (the template character — do not edit per site) ──────────── */
:root {
  /* Type scale 1.333 (perfect fourth), fluid via clamp; body line-height 1.65.
     The top two steps are widened so --step-5 reaches 96px, the display size the
     reference capture actually sets its headlines in. The ratio is a design
     choice, not a measurement — the capture's own scale fits could not settle it,
     and TEMPLATE.md states the reasoning. */
  --step--1: clamp(0.8125rem, 0.79rem + 0.11vw, 0.875rem);
  --step-0: clamp(1rem, 0.98rem + 0.1vw, 1.0625rem);
  --step-1: clamp(1.333rem, 1.29rem + 0.21vw, 1.42rem);
  --step-2: clamp(1.777rem, 1.71rem + 0.33vw, 1.9rem);
  --step-3: clamp(2.369rem, 2.25rem + 0.6vw, 2.6rem);
  --step-4: clamp(2.75rem, 2.3rem + 2.2vw, 3.75rem);
  --step-5: clamp(3.25rem, 2.1rem + 5.75vw, 6rem);
  --leading-body: 1.65;
  --leading-display: 1.05;

  /* Spacing — a 4px sub-unit with a 1.5rem section rhythm. Every rung is a
     multiple of 4. This is a CHOSEN ladder: the capture found no spacing rhythm
     at all on the reference, so there was nothing to adopt (TEMPLATE.md states
     the reasoning). Wide container so photography can breathe. */
  --space-3xs: 0.25rem;
  --space-2xs: 0.5rem;
  --space-xs: 1rem;
  --space-s: 1.5rem;
  --space-m: 2.5rem;
  --space-l: 4rem;
  --space-xl: 6rem;
  --section-pad: clamp(4rem, 2.5rem + 7.5vw, 8rem);
  --container-max: 74rem;

  /* Radii — 10px is the workhorse (it held 85.7% of the reference's painted
     radius area); 20px is the large corner, including the frame's inner corners,
     which is where the hero's rounded read comes from; the panel's bottom
     corners take a much larger sweep; buttons are full pills. */
  --radius-s: 4px;
  --radius-m: 10px;
  --radius-l: 20px;
  --radius-panel: 48px;
  --radius-pill: 999px;

  /* Elevation is photographic, not linear: no hairline rules anywhere. Pictures
     cast a soft shadow, fixed overlays get a tighter one. */
  --shadow-media: 0 24px 60px rgba(32, 37, 39, 0.14);
  --shadow-float: 0 12px 32px rgba(32, 37, 39, 0.16);

  /* The fixed frame's band width. Halved on small viewports so it does not eat
     the reading measure. */
  --frame-inset: 16px;

  /* Motion. The two rise distances are measured: the reference's ordinary reveal
     rises 10px, and a handful of section openers rise 40px instead. Nothing in
     between is used, so the template models both rather than averaging them.
     The durations and the easing are chosen, not measured — the capture samples
     by scroll position rather than by time, so it cannot tell how long a
     transition runs. 720ms over 10px settles rather than slides. */
  --ease-lush: cubic-bezier(0.16, 1, 0.3, 1);
  --dur-reveal: 720ms;
  --dur-hover: 420ms;
  /* The two measured rises are held as named tokens and --reveal-rise selects
     between them, so a nested reveal inside a 40px one can be put back on the
     ordinary distance without repeating the number. */
  --reveal-rise-near: 10px;
  --reveal-rise-far: 40px;
  --reveal-rise: var(--reveal-rise-near);

  --header-h: 4rem;
}

/* ── Reset ───────────────────────────────────────────────────────────────── */
*,
*::before,
*::after {
  box-sizing: border-box;
}
* {
  margin: 0;
}
/* The page background lives on <html>, not <body>: the ambient backdrop sits at
   a negative z-index inside <body>, so a background painted on <body> would
   cover it completely. */
html {
  -webkit-text-size-adjust: 100%;
  background: var(--color-surface);
}
body {
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: var(--leading-body);
  color: var(--color-text);
  background: transparent;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
img,
picture,
svg,
video {
  display: block;
  max-width: 100%;
  height: auto;
}
input,
button,
textarea,
select {
  font: inherit;
  color: inherit;
}
/* Body-copy links stay underlined so affordance never rests on colour alone
   (WCAG 1.4.1) — the reset preserves the anchor underline; do not remove it. */
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);
  font-weight: 400; /* Instrument Serif ships one weight — the drama is size, not weight */
  color: var(--color-text);
  letter-spacing: -0.02em;
}
/* The signature device: emphasis INSIDE a headline, set in the heading serif's
   true italic. Every approved heading face has one — a synthesised oblique looks
   wrong at the display size, which is why the font pairs are constrained. */
h1 em,
h2 em,
h3 em {
  font-style: italic;
  font-weight: inherit;
}
h1 {
  font-size: var(--step-4);
}
h2 {
  font-size: var(--step-3);
}
h3 {
  font-size: var(--step-2);
}
ul,
ol {
  padding-left: 1.25em;
}

/* Visible, high-contrast focus ring for keyboard users on every interactive
   element — primary, never the sub-AA accent; never removed without an
   equal-or-better replacement. */
:focus-visible {
  outline: 3px solid var(--color-primary);
  outline-offset: 3px;
  border-radius: var(--radius-s);
}
/* Over photography the ink ring disappears — switch it to the on-media colour. */
.hero--media :focus-visible,
.media-panel :focus-visible {
  outline-color: var(--color-on-media);
}

/* Safety net for anything a site-specific composition adds. The template's own
   motion is declared inside the no-preference query below, so it does not exist
   to be overridden rather than merely being sped up to nothing. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* ── Motion, declared only where motion is welcome ────────────────────────── */
/* Everything in this block is absent for a visitor who asked for reduced motion:
   no reveal transition, no smooth anchor scroll, no compositing hint. main.js
   independently marks every [data-reveal] visible immediately in that case, and
   motion.js never attaches its parallax or cursor listeners. */
@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth; /* anchor navigation only — this template never hijacks the scroll */
  }
  /* Reveal targets are only hidden when JS is active (root .js), so a no-JS
     visitor keeps all content visible. main.js adds .is-visible on entry.

     Three things can want to move the same element — the reveal, parallax, and a
     static layout offset — so the reveal appends to `transform` rather than
     overwriting it. Parallax writes the separate `translate` property and
     composes on its own, needing nothing here. A static offset (the scattered
     collage's raised and lowered items are the only ones in this template) is a
     `transform`, and these two rules outrank the rule that sets it, so an offset
     element declares its offset as `--static-offset` and both rules append it.
     Without that the offset would be erased the moment the element settled.

     The fallback is deliberately empty: an element with no offset rises from
     `--reveal-rise` and settles at `transform: none` exactly as before, so a
     settled reveal never becomes a containing block for fixed or sticky
     descendants.

     `--reveal-delay` is what turns a group of siblings into a sequence: motion.js
     sets it per child so the members of a group arrive one after another instead
     of all at once. It defaults to 0ms, so an element nobody staggered behaves
     exactly as it always did. */
  .js [data-reveal] {
    opacity: 0;
    transform: translateY(var(--reveal-rise)) var(--static-offset, );
    transition: opacity var(--dur-reveal) var(--ease-lush) var(--reveal-delay, 0ms),
      transform var(--dur-reveal) var(--ease-lush) var(--reveal-delay, 0ms);
  }
  .js [data-reveal].is-visible {
    opacity: 1;
    transform: var(--static-offset, none);
  }
  /* The reference's second, rarer reveal: a longer rise for a section opener.
     Same duration and easing as the ordinary one, so it reads as emphasis
     rather than as a different animation. TEMPLATE.md caps it per page. */
  .js [data-reveal='far'] {
    --reveal-rise: var(--reveal-rise-far);
  }
  /* --reveal-rise inherits, so without this every revealed element inside an
     emphasised block would take the 40px rise too and the page would break the
     two-per-page cap on the long rise without anyone writing it. */
  .js [data-reveal='far'] [data-reveal] {
    --reveal-rise: var(--reveal-rise-near);
  }
  /* motion.js rewrites `translate` and `scale` on the hero photograph every
     frame; the hint keeps it on its own compositing layer so the drift never
     repaints the page. Only the hero moves, so only the hero is promoted —
     a layer per picture would cost memory for nothing. */
  .hero__photo,
  .hero__art,
  .hero__media img {
    will-change: translate, scale;
  }
}

/* ── Fixed overlay layers ────────────────────────────────────────────────── */
/* All three are pointer-events: none, so none of them can swallow a click.

   .ambient-bg — a still wash below all content. It NEVER animates: a page that
   never settles is tiring, and an infinite loop is the hardest motion to make
   honest under a reduced-motion preference. */
.ambient-bg {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: var(--color-surface);
}
.ambient-bg__wash {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(58% 46% at 12% 0%, var(--color-primary-tint), transparent 62%),
    radial-gradient(52% 44% at 88% 100%, var(--color-accent), transparent 64%);
  opacity: 0.55;
}

/* .viewport-frame — the fixed inset band that makes the page read as a card
   floating on the surface colour. The band is painted as an enormous outward
   box-shadow from a rounded inner rectangle, which is what gives the content
   ROUNDED inner corners (a plain border would leave them square); the parent
   clips the overspill. The frame is decoration only: aria-hidden in the markup,
   pointer-events: none here. */
.viewport-frame {
  position: fixed;
  inset: 0;
  z-index: 10;
  overflow: hidden;
  pointer-events: none;
}
.viewport-frame::before {
  content: '';
  position: absolute;
  inset: var(--frame-inset);
  border-radius: var(--radius-l);
  box-shadow: 0 0 0 100vmax var(--color-surface);
}
/* A small cross in each corner, just inside the band — the reference's corner
   ornaments. Decorative, so the sub-AA accent is the right colour for them. */
.viewport-frame__corner {
  position: absolute;
  width: 10px;
  height: 10px;
}
.viewport-frame__corner::before,
.viewport-frame__corner::after {
  content: '';
  position: absolute;
  background: var(--color-accent);
}
.viewport-frame__corner::before {
  left: 0;
  top: 50%;
  width: 100%;
  height: 1px;
}
.viewport-frame__corner::after {
  top: 0;
  left: 50%;
  width: 1px;
  height: 100%;
}
.viewport-frame__corner--tl {
  top: calc(var(--frame-inset) + 6px);
  left: calc(var(--frame-inset) + 6px);
}
.viewport-frame__corner--tr {
  top: calc(var(--frame-inset) + 6px);
  right: calc(var(--frame-inset) + 6px);
}
.viewport-frame__corner--bl {
  bottom: calc(var(--frame-inset) + 6px);
  left: calc(var(--frame-inset) + 6px);
}
.viewport-frame__corner--br {
  bottom: calc(var(--frame-inset) + 6px);
  right: calc(var(--frame-inset) + 6px);
}

/* motion.js builds the SVG structure and names the gradient; the colour and
   weight of the line live here, so the trail follows the site brand like every
   other painted thing in the template. */

@media (max-width: 47.99rem) {
  :root {
    --frame-inset: 8px;
  }
}

/* ── Utilities ───────────────────────────────────────────────────────────── */
.container {
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--space-s);
}
/* 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: 100; /* above the frame and the floating header */
  padding: var(--space-2xs) var(--space-xs);
  background: var(--color-primary);
  color: var(--color-on-primary);
  border-radius: var(--radius-pill);
  transition: top var(--dur-hover) var(--ease-lush);
}
.skip-link:focus {
  top: var(--space-s);
  color: var(--color-on-primary);
}

/* ── Kickers (shared hero/section eyebrow — uppercase, letterspaced sans) ─── */
.hero__kicker,
.section__kicker {
  display: inline-block;
  margin-bottom: var(--space-s);
  font-family: var(--font-body); /* sans eyebrow, distinct from the serif headings */
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-size: var(--step--1);
  color: var(--color-muted);
}
.hero--media .hero__kicker {
  color: var(--color-on-media);
}

/* ── Buttons ─────────────────────────────────────────────────────────────── */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-height: 3rem; /* comfortable tap target */
  padding: 0.75rem 1.75rem;
  border: 1px solid transparent;
  border-radius: var(--radius-pill); /* every button in this template is a pill */
  font-weight: 500;
  font-size: var(--step-0);
  letter-spacing: 0.01em;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--dur-hover) var(--ease-lush), color var(--dur-hover) var(--ease-lush),
    border-color var(--dur-hover) var(--ease-lush);
}
.button--primary {
  background: var(--color-primary);
  color: var(--color-on-primary);
  border-color: var(--color-primary);
}
.button--primary:hover {
  background: var(--color-primary-shade);
  color: var(--color-on-primary);
  border-color: var(--color-primary-shade);
}
/* Ghost = a hairline outline that fills with ink on hover. */
.button--ghost {
  background: transparent;
  color: var(--color-primary);
  border-color: var(--color-primary);
}
.button--ghost:hover {
  background: var(--color-primary);
  color: var(--color-on-primary);
}

/* .button--pill: the template's headline call to action. Its hover changes NO
   colour on the control itself — a fill sweeps across from the left and the
   badge steps aside, so what the visitor sees is motion, not a tint. That is what
   the reference does: hovered and unhovered, its call to action's own computed
   style was identical while its interior moved. The choreography and the
   distances below are this template's own — the capture could not measure them
   reliably.
   .button--quiet is the pill for use over photography: a near-white fill with
   ink text, sweeping to the warm accent. Both colours are contract-guaranteed
   against --color-text, so it stays legible whatever the picture does. */
.button--pill {
  position: relative;
  overflow: hidden;
  gap: var(--space-xs);
  padding: 0.5rem 0.5rem 0.5rem 1.75rem;
}
.button--pill::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--color-primary-shade);
  transform: translateX(-101%);
  transition: transform var(--dur-hover) var(--ease-lush);
}
.button--pill:hover::before,
.button--pill:focus-visible::before {
  transform: translateX(0);
}
.button--quiet {
  background: var(--color-surface);
  color: var(--color-text);
  border-color: var(--color-surface);
}
.button--quiet::before {
  background: var(--color-accent);
}
.button--quiet:hover {
  color: var(--color-text); /* the sweep carries the change; the text must not move register */
}
/* Both label and badge sit above the sweeping fill. */
.button__label,
.button__badge {
  position: relative;
  transition: transform var(--dur-hover) var(--ease-lush);
}
.button__badge {
  display: inline-grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  flex: none;
  border-radius: var(--radius-pill);
  background: var(--color-on-primary);
  color: var(--color-primary);
}
.button--quiet .button__badge {
  background: var(--color-primary);
  color: var(--color-on-primary);
}
.button--pill:hover .button__label,
.button--pill:focus-visible .button__label {
  transform: translateX(-0.375rem);
}
.button--pill:hover .button__badge,
.button--pill:focus-visible .button__badge {
  transform: translateX(0.375rem);
}

/* ── Header / navigation ─────────────────────────────────────────────────── */
.site-header {
  position: sticky;
  top: 0;
  z-index: 20; /* above content and the frame band, below the skip link */
  background: var(--color-surface);
}
.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-s);
  min-height: var(--header-h);
}
.site-header__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  font-family: var(--font-heading);
  font-size: var(--step-1);
  color: var(--color-text);
  text-decoration: none;
}
.site-header__logo {
  width: 2rem;
  height: 2rem;
  border-radius: var(--radius-s);
}
/* The letterspaced wordmark eases open on hover — the reference's wordmark moved
   on hover without changing a single computed colour. */
.site-header__wordmark {
  text-transform: uppercase;
  letter-spacing: 0.18em;
  font-family: var(--font-body);
  font-size: var(--step--1);
  font-weight: 500;
  transition: letter-spacing var(--dur-hover) var(--ease-lush);
}
.site-header__brand:hover .site-header__wordmark {
  letter-spacing: 0.26em;
}

/* .site-header--tabs: three separate boxes hanging from the top edge of the fixed
   frame — a wordmark flush to the frame's left inset, the navigation centred on
   the viewport, and a call to action flush right. Each box is SQUARED where it
   meets the frame and ROUNDED where it leaves it, which is what makes the three
   read as tabs cut out of the frame rather than as a bar laid over the page. The
   capture measured exactly this arrangement on the reference: three boxes at the
   same top offset, with `0 0 20px 0`, `0 0 20px 20px` and `0 0 0 20px` radii.

   It is a MODIFIER on the header block, not a separate nav block — the tabs ARE
   the site header in a different shape, so a lush page's header markup is still a
   header containing a brand link, a nav and a call to action.

   The header box itself takes no pointer events: the gaps between the tabs are
   page, and a click there must reach the photograph underneath, not a transparent
   header spanning the whole width. Each tab turns them back on.

   The backdrop blur is deliberate here even though `fine` refuses it on its sticky
   bar: fine's header is a full-width element blurring the whole viewport width on
   every scroll frame, while these are three small fixed boxes. Browsers without
   backdrop-filter get opaque tabs. */
.site-header--tabs {
  position: fixed;
  top: var(--frame-inset);
  left: var(--frame-inset);
  right: var(--frame-inset);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: stretch; /* the three tabs share one depth, as the capture measured */
  column-gap: var(--space-2xs);
  background: transparent;
  pointer-events: none;
}
.site-header__tab {
  display: flex;
  align-items: center;
  gap: var(--space-3xs);
  min-height: 3.5rem;
  padding: var(--space-3xs) var(--space-s);
  pointer-events: auto;
  color: var(--color-text);
  text-decoration: none;
  background: color-mix(in srgb, var(--color-surface) 92%, transparent);
}
@supports (backdrop-filter: blur(1px)) {
  .site-header__tab {
    backdrop-filter: blur(12px);
  }
}
@supports not (backdrop-filter: blur(1px)) {
  .site-header__tab {
    background: var(--color-surface);
  }
}
/* Squared against the frame, rounded away from it — see the block comment. */
.site-header__tab--brand {
  justify-self: start;
  border-radius: 0 0 var(--radius-l) 0;
}
.site-header__tab--nav {
  position: relative; /* the mobile menu sheet hangs from this tab */
  padding-inline: var(--space-3xs);
  border-radius: 0 0 var(--radius-l) var(--radius-l);
}
.site-header__tab--action {
  justify-self: end;
  padding: var(--space-3xs);
  border-radius: 0 0 0 var(--radius-l);
}
/* The call-to-action pill is compacted to the tab's own height. At its section size
   it makes one tab a third taller than the other two and the three stop reading as
   one row hanging off the frame. It stays a 48px tap target. */
.site-header__tab--action .button--pill {
  padding: var(--space-3xs) var(--space-3xs) var(--space-3xs) var(--space-s);
}
.site-header__tab--action .button__badge {
  width: 2.5rem;
  height: 2.5rem;
}
/* main.js adds --stuck once the page has left the top. lush deliberately styles
   nothing for it: the reference's header is byte-identical at the top of the page
   and 3,000px down it, and a header that restyles itself mid-scroll is a second
   motion device competing with the reveals for the reader's attention. The class
   is still added — it is shared behaviour — it simply paints nothing here. */
.nav__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  background: var(--color-primary);
  color: var(--color-on-primary);
  border: 0;
  border-radius: var(--radius-pill);
  cursor: pointer;
}
.nav__toggle-bars {
  position: relative;
  width: 1.125rem;
  height: 1px;
  background: currentColor;
}
.nav__toggle-bars::before,
.nav__toggle-bars::after {
  content: '';
  position: absolute;
  left: 0;
  width: 1.125rem;
  height: 1px;
  background: currentColor;
}
.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 hover: a tint to the alternate surface plus a small lift. The tint is what
   the reference does — a direct read of its nav links' computed style. The lift is
   this template's addition. */
.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-text);
  text-decoration: none;
  font-weight: 500;
  transition: background-color var(--dur-hover) var(--ease-lush), transform var(--dur-hover) var(--ease-lush);
}
.nav__link:hover,
.nav__link--active {
  background: var(--color-surface-alt);
  color: var(--color-text);
}
.nav__link:hover {
  transform: translateY(-1px);
}

/* The three tabs do not fit across a phone, so below 48rem lush keeps the device
   and drops to TWO: the wordmark still hangs flush left, and the nav toggle and
   the call to action sit together as one tab flush right (they touch, so the pair
   reads as a single box rounded only on its bottom-left corner). Nothing is lost —
   the same three things are reachable, and the call to action survives, which
   matters more on a phone than a centred menu does.

   Mobile-first for the nav itself: it collapses behind the toggle and expands to a
   row at >= 48rem. */
@media (max-width: 47.99rem) {
  .site-header--tabs {
    grid-template-columns: auto 1fr auto;
    column-gap: 0;
  }
  /* A wordmark as well as three tabs does not fit across a phone: the row fills
     edge to edge and the gaps that make them read as separate tabs disappear. The
     mark alone carries the brand tab, and the wordmark stays in the accessibility
     tree so the link keeps its name. */
  .site-header--tabs .site-header__wordmark {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    white-space: nowrap;
    clip-path: inset(50%);
  }
  .site-header__tab--brand {
    padding-inline: var(--space-2xs);
  }
  .site-header__tab--nav {
    justify-self: end;
    border-radius: 0 0 0 var(--radius-l);
  }
  .site-header__tab--action {
    border-radius: 0;
  }
  /* The sheet hangs from the right-hand tab rather than spanning the frame, so it
     opens under the control that opened it. */
  .site-header--tabs .nav__menu {
    left: auto;
    right: 0;
    min-width: 12rem;
  }
  .nav__menu {
    display: none;
    position: absolute;
    left: 0;
    right: 0;
    top: calc(100% + var(--space-2xs));
    flex-direction: column;
    gap: var(--space-3xs);
    padding: var(--space-xs);
    background: var(--color-surface);
    border-radius: var(--radius-l);
    /* The open sheet floats over content — a shadow is warranted here. */
    box-shadow: var(--shadow-float);
  }
  .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);
  }
}

/* ── Hero ────────────────────────────────────────────────────────────────── */
/* The pill header is fixed rather than in flow, so the first block on every page
   has to leave room for it — otherwise the capsule lands on top of the kicker. */
.hero {
  padding-block: calc(var(--section-pad) + var(--header-h)) var(--section-pad);
}
.hero__inner {
  display: grid;
  gap: var(--space-l);
  align-items: center;
}
.hero__body {
  min-width: 0; /* grid child: keep a long word from blowing out the text column */
  /* In the centred .hero--media the text is sized to its content, so a long Finnish
     compound made this block wider than the screen and, because that hero clips what
     overflows, both edges of the headline and the body copy were cut off. */
  max-width: 100%;
}
.hero__title {
  /* Its own clamp rather than --step-5: it keeps the 96px display size at desktop, but
     rises more slowly below that, so a 13-character Finnish word ("painopalvelua") still
     fits the column on a phone instead of breaking. --step-5 itself is unchanged. */
  font-size: clamp(3.1rem, 1.8rem + 6.4vw, 6rem);
  overflow-wrap: break-word;
  /* Finnish compounds routinely run past 20 characters ("ympäristöystävällistä"). One cannot
     fit unbroken in a hero column at display size — it would take body-copy size to fit. Left
     unhyphenated the browser still breaks the word, it just breaks it silently, which reads
     worse than a hyphen. So hyphenate, but only from 16 characters up, leaving at least 5
     letters before and 4 after the break. The 13-14 character words that a Finnish tagline
     is full of ("painopalvelua") are kept whole by the title size instead, so they never get
     the hyphen that made the first live site look broken. A 16+ character business name will
     be hyphenated; a visible hyphen is still the better of the two possible breaks. Safari
     before 17 ignores the 16-character floor and applies only the 5/4 limits. */
  hyphens: auto;
  -webkit-hyphens: auto;
  hyphenate-limit-chars: 16 5 4;
  -webkit-hyphenate-limit-before: 5;
  -webkit-hyphenate-limit-after: 4;
  max-width: 18ch; /* a ch cap limits swap reflow (CLS) at the display size */
}
.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-l);
  overflow: hidden;
  box-shadow: var(--shadow-media);
}
.hero__art {
  width: 100%;
  height: auto;
}
@media (min-width: 56rem) {
  .hero__inner {
    grid-template-columns: 1.05fr 0.95fr;
  }
}

/* .hero--media: the full-bleed photographic hero. It runs to the viewport edge
   and the fixed frame draws over its corners, which is where the rounded read
   comes from — the frame's inner radius IS --radius-l, the 20px the capture
   measured on the reference's hero. The photograph must never be lazy and never
   revealed: it is the Largest Contentful Paint element on nearly every page. */
.hero--media {
  position: relative;
  display: grid;
  place-items: center;
  min-height: 100svh;
  padding-block: calc(var(--section-pad) + var(--header-h)) var(--section-pad);
  overflow: hidden;
  isolation: isolate;
}
.hero__backdrop {
  position: absolute;
  inset: 0;
  z-index: -1;
}
/* <picture> has no class of its own to hook, and it must fill the backdrop: left
   at its intrinsic height the photograph stops part-way down and the rest of the
   hero shows the page background instead. */
.hero__backdrop picture {
  display: block;
  height: 100%;
}
.hero__photo {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* The only treatment a photograph gets in this template. Darkest at top and
   bottom, where the headline and the call to action sit. */
.hero__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, var(--color-scrim), transparent 38%, transparent 55%, var(--color-scrim));
}
/* The two-column rule further down belongs to the side-by-side hero; the
   full-bleed one is a single centred column at every width. */
.hero--media .hero__inner {
  grid-template-columns: 1fr;
  justify-items: center;
  text-align: center;
}
.hero--media .hero__title,
.hero--media .hero__text {
  color: var(--color-on-media);
}
.hero--media .hero__title {
  max-width: 22ch;
}
.hero--media .hero__text {
  margin-inline: auto;
}
.hero--media .hero__actions {
  justify-content: center;
}

/* ── Section scaffolding ─────────────────────────────────────────────────── */
/* .section has no background of its own, so the ambient backdrop shows through. */
.section {
  padding-block: var(--section-pad);
}
.section--alt {
  background: var(--color-surface-alt);
}
.section__head {
  max-width: 54ch;
  margin-bottom: var(--space-l);
}
.section__head--centred {
  margin-inline: auto;
  text-align: center;
}
.section__title {
  font-size: var(--step-3);
}
.section__lead {
  margin-top: var(--space-s);
  color: var(--color-muted);
  font-size: var(--step-1);
}

/* ── Cards ───────────────────────────────────────────────────────────────── */
.card-grid {
  display: grid;
  gap: var(--space-m);
  grid-template-columns: 1fr;
}
@media (min-width: 40rem) {
  .card-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 60rem) {
  .card-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}
.card {
  padding: var(--space-m) var(--space-s);
  background: var(--color-surface);
  border-radius: var(--radius-l);
  transition: box-shadow var(--dur-hover) var(--ease-lush), transform var(--dur-hover) var(--ease-lush);
}
.card:hover {
  box-shadow: var(--shadow-media);
  transform: translateY(-2px);
}
.card__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  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);
}

/* ── Collage (the scattered asymmetric photo group) ───────────────────────── */
/* Pictures at varying sizes around centred text, offset from one another so the
   group reads as scattered rather than gridded. The offsets are STATIC — they are
   composition, not motion — and they are dropped below 48rem, where a single
   column has no room to scatter. Never put [data-parallax] on an offset item:
   the offset is a `transform` and the drift is `translate`, so they would both
   apply and the item would sit in neither place. The offsets travel through the
   `--static-offset` custom property because these items are also reveal targets;
   see the reveal rules for why a bare `transform` here would be wiped out. */
.collage {
  display: grid;
  gap: var(--space-s);
  grid-template-columns: 1fr;
}
.collage__item {
  margin: 0;
}
.collage__image {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: var(--radius-m);
  box-shadow: var(--shadow-media);
}
.collage__item--wide .collage__image {
  aspect-ratio: 3 / 2;
}
.collage__item--tall .collage__image {
  aspect-ratio: 2 / 3;
}
.collage__caption {
  margin-top: var(--space-2xs);
  color: var(--color-muted);
  font-size: var(--step--1);
}
@media (min-width: 48rem) {
  .collage {
    grid-template-columns: repeat(12, 1fr);
    align-items: start;
    gap: var(--space-m);
  }
  .collage__item {
    grid-column: span 4;
  }
  .collage__item--wide {
    grid-column: span 5;
  }
  .collage__item--tall {
    grid-column: span 3;
  }
  /* Each offset is both declared as `--static-offset` and applied here. Applying
     it is what keeps the scatter under a reduced-motion preference, where the
     reveal rules do not exist; declaring it is what lets the reveal rules append
     the offset instead of erasing it, since every collage item is also a reveal
     target and the reveal rule wins on `transform`. */
  .collage__item--raised {
    --static-offset: translateY(-12%);
    transform: var(--static-offset);
  }
  .collage__item--lowered {
    --static-offset: translateY(14%);
    transform: var(--static-offset);
  }
}

/* ── Marquee (the photograph band that moves on its own) ──────────────────── */
/* A full-bleed strip of pictures travelling steadily leftward and looping without
   a seam. Measured on the reference: cards 400px wide at a 948/1422 portrait
   ratio, a 10px gap, a 10px corner radius, and 60px of travel per second driven
   from script rather than a CSS keyframe.

   The layout here is the whole device minus its movement: with no script, or
   under a reduced-motion preference, this stays a readable row of photographs
   rather than an empty box. motion.js clones items until the track is wide enough
   to wrap seamlessly and then owns `transform` on the TRACK only, so each card is
   still free to carry its own.

   The band leans because the stage is tilted in 3D. The reference's own matrix
   works out to about 25 degrees of X rotation with a couple of degrees of Y and Z;
   lush takes a third of that. At the reference's angle the cards are squashed to
   90% height and the lean starts reading as a gimmick against this template's
   much calmer page. The smaller angle is a CHOICE, not a measurement — the same
   kind of departure as the zoom range. */
.marquee {
  position: relative;
  overflow: hidden; /* the stage is wider than the page and must not scroll it */
}
.marquee__stage {
  /* Tilting the stage rather than the track keeps the travel axis horizontal:
     a transform on the track itself would send the cards off along the tilted
     axis instead of straight across. */
  transform: rotateX(8deg) rotateZ(-1deg);
  transform-origin: 50% 50%;
}
.marquee__track {
  display: flex;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
  will-change: transform;
}
.marquee__item {
  flex: 0 0 auto;
  width: 400px;
  margin: 0;
}
.marquee__image {
  display: block;
  width: 100%;
  aspect-ratio: 948 / 1422;
  object-fit: cover;
  border-radius: var(--radius-m);
  box-shadow: var(--shadow-media);
}
.marquee__controls {
  display: flex;
  justify-content: flex-end;
  padding-top: var(--space-xs);
}
/* Content that starts moving on its own and runs longer than five seconds needs a
   way to stop it. The reference offers none; this template promises an
   accessibility floor the reference does not, so the control is not optional. */
.marquee__toggle {
  padding: var(--space-3xs) var(--space-xs);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-muted);
  font: inherit;
  font-size: var(--step--1);
  cursor: pointer;
}
.marquee__toggle:hover {
  color: var(--color-text);
}
/* Script absent: no control can work, so it is never shown as a dead button. */
.marquee__controls {
  display: none;
}
.marquee--live .marquee__controls {
  display: flex;
}
@media (max-width: 48rem) {
  .marquee__item {
    width: 260px;
  }
  .marquee__stage {
    transform: none; /* a lean costs more than it gives on a narrow screen */
  }
}
@media (prefers-reduced-motion: reduce) {
  .marquee__stage {
    transform: none;
  }
}

/* ── Media panel (a photograph with a large bottom radius) ────────────────── */
/* The picture and the text share ONE grid cell, which is what keeps the overlay
   honest: the cell is as tall as whichever of the two is taller, so a headline
   that outgrows the picture makes the panel taller instead of being clipped away
   by the panel's own overflow. (Laying the text over the picture with absolute
   positioning does lose it — at 390px this panel is about 260px tall and its text
   about 515px, so the heading and most of the lead disappear.) The picture then
   fills the cell and crops. */
.media-panel {
  position: relative;
  display: grid;
  overflow: hidden;
  border-radius: var(--radius-l) var(--radius-l) var(--radius-panel) var(--radius-panel);
  isolation: isolate;
}
.media-panel__image {
  grid-area: 1 / 1;
  width: 100%;
  height: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}
/* This picture used to drift, and overscanned its clipped panel by 16% so the
   drift could never pull an empty edge into view. Only the hero moves now, so the
   overscan has nothing to cover for and is gone: the image fills the panel by
   being sized to it and cropped, which is sharper at the same file size. */
.media-panel__body {
  grid-area: 1 / 1;
  align-self: end;
  padding: var(--space-xl) var(--space-s) var(--space-m);
  text-align: center;
  color: var(--color-on-media);
  background: linear-gradient(180deg, transparent, var(--color-scrim) 55%);
}
.media-panel__body .section__title,
.media-panel__body .section__lead {
  color: var(--color-on-media);
}
@media (min-width: 48rem) {
  .media-panel__image {
    aspect-ratio: 16 / 9;
  }
}

/* ── Pinned section (pure CSS — no JavaScript at all) ─────────────────────── */
/* A panel that holds its place while a taller companion column scrolls past it.
   Travel is the flow column's height minus the panel's, which lands inside the
   200-1800px range measured on the reference. TEMPLATE.md caps a page at three
   pinned sections, which is what the reference uses. */
.pin-section {
  position: relative;
}
.pin-section__flow > * + * {
  margin-top: var(--space-xl);
}
@media (min-width: 60rem) {
  .pin-section {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-l);
    align-items: start;
  }
  .pin-section__panel {
    position: sticky;
    top: calc(var(--header-h) + var(--space-l));
  }
}

/* ── 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: 60rem) {
  .gallery {
    grid-template-columns: repeat(3, 1fr);
  }
}
.gallery__item {
  overflow: hidden;
  border-radius: var(--radius-m);
}
.gallery__image {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  transition: transform var(--dur-hover) var(--ease-lush);
}
.gallery__item:hover .gallery__image {
  transform: scale(1.04);
}

/* ── Testimonial (an oversized serif quote, no box) ───────────────────────── */
/* Measured in rem, not ch: the wrapper is set in the body sans while the quote
   inside it is set in the display serif at --step-3, so a ch cap here would be
   computed from the wrong font and squeeze the quote into a narrow ribbon. */
.testimonial {
  max-width: 34rem;
  margin-inline: auto;
  text-align: center;
}
.testimonial__quote {
  font-family: var(--font-heading);
  font-size: var(--step-3);
  line-height: 1.2;
}
.testimonial__author {
  display: block;
  margin-top: var(--space-s);
  font-family: var(--font-body);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: var(--step--1);
  color: var(--color-muted);
}

/* ── FAQ ─────────────────────────────────────────────────────────────────── */
.faq {
  max-width: 52rem;
  margin-inline: auto;
}
.faq__item {
  padding: var(--space-s);
  background: var(--color-surface);
  border-radius: var(--radius-l);
}
.faq__item + .faq__item {
  margin-top: var(--space-xs);
}
.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: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: var(--step--1);
}
.contact-form__input,
.contact-form__textarea {
  width: 100%;
  padding: 0.85rem 1.125rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-m);
}
.contact-form__input:focus,
.contact-form__textarea:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px var(--color-primary-tint);
}
.contact-form__textarea {
  min-height: 8rem;
  resize: vertical;
}

/* ── Footer ──────────────────────────────────────────────────────────────── */
.site-footer {
  padding-block: var(--space-xl) var(--space-l);
  background: var(--color-surface-alt);
  border-radius: var(--radius-panel) var(--radius-panel) 0 0;
}
/* .site-footer--media: the dark photographic closing section — the page's last
   word said over a picture rather than on a quiet tinted block. A photograph
   fills the whole footer, one wash of --color-scrim goes over it, and every line
   of type reverses out in --color-on-media.

   THE SCRIM IS THE ONLY THING KEEPING THIS LEGIBLE, and axe cannot check it: it
   scores text against a computed background colour and skips text over a picture
   entirely. Text sits over the WHOLE area here, not just a corner, so choose the
   closing photograph dark and measure the worst case — composite --color-scrim
   over the picture's lightest region and check --color-on-media against the
   result. TEMPLATE.md records the measurement for the shipped fixture.

   The footer's own background-color is removed rather than darkened, so the
   photograph is what paints the block. */
.site-footer--media {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  background: transparent;
  color: var(--color-on-media);
}
.site-footer__backdrop {
  position: absolute;
  inset: 0;
  z-index: -1;
}
/* The photograph overscans its box by 8% in each direction so a parallax drift
   can never pull an edge into view. 8% of a footer is many times the furthest the
   0.03 drift ceiling can move it. */
.site-footer__photo {
  position: absolute;
  inset: -8% 0;
  width: 100%;
  height: 116%;
  object-fit: cover;
}
.site-footer__scrim {
  position: absolute;
  inset: 0;
  background: var(--color-scrim);
}
/* The closing statement: the display serif, one line of intent, above the
   practical columns. It is the "last word" half of the device — the identity and
   the contact details below it are the other half. */
.site-footer__closing {
  margin-bottom: var(--space-l);
}
.site-footer__statement {
  max-width: 22ch; /* the closing line breaks as a shape, not as a paragraph */
  font-family: var(--font-heading);
  font-size: var(--step-4);
  line-height: var(--leading-display);
  letter-spacing: -0.02em;
}
/* A footer line that is text rather than a link (an address, an opening time).
   It shares the link's rhythm without claiming to be actionable. */
.site-footer__text {
  padding: var(--space-3xs) 0;
}
.site-footer--media .site-footer__heading,
.site-footer--media .site-footer__link,
.site-footer--media .site-footer__text {
  color: var(--color-on-media);
}
/* Hover reverses the relationship instead of changing hue: the link picks up a
   quiet plate of its own. A colour shift would have to come from a brand slot
   that has no contrast guarantee over a photograph. */
.site-footer--media .site-footer__link {
  width: fit-content; /* the plate hugs the words rather than the whole column */
  padding-inline: var(--space-2xs);
  margin-inline: calc(var(--space-2xs) * -1);
  border-radius: var(--radius-s);
  transition: background-color var(--dur-hover) var(--ease-lush);
}
.site-footer--media .site-footer__link:hover {
  color: var(--color-on-media);
  background: color-mix(in srgb, var(--color-on-media) 16%, transparent);
}
/* The fine print steps back without losing the contrast floor: 78% of the
   on-media colour over the scrimmed picture still clears 4.5:1 by a wide margin,
   which a muted grey would not. */
.site-footer--media .site-footer__legal {
  color: color-mix(in srgb, var(--color-on-media) 78%, transparent);
  border-top-color: color-mix(in srgb, var(--color-on-media) 24%, transparent);
}
.site-footer__inner {
  display: grid;
  gap: var(--space-l);
  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-1);
  margin-bottom: var(--space-s);
}
.site-footer__link {
  display: block;
  padding: var(--space-3xs) 0;
  color: var(--color-text);
  text-decoration: none;
}
.site-footer__link:hover {
  color: var(--color-primary);
}
.site-footer__legal {
  margin-top: var(--space-l);
  padding-top: var(--space-s);
  border-top: 1px solid var(--color-border);
  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; /* above content and the frame, below the skip link */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-s);
  padding: var(--space-s) var(--space-m);
  background: var(--color-surface);
  border-radius: var(--radius-l);
  box-shadow: var(--shadow-float);
}
.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; /* above the cookie banner */
  padding: var(--space-2xs) var(--space-s);
  text-align: center;
  background: var(--color-primary); /* ink block carries --color-on-primary at >= 4.5:1 */
  color: var(--color-on-primary);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: var(--step--1);
}

/* The global `a` rule sets the link colour directly, which beats the colour the
   banner merely passes down by inheritance — so without this the CTA renders in
   --color-primary on a --color-primary background at 1:1 and disappears. */
.demo-banner a {
  color: inherit;
}


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

/* ── 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: 1px solid var(--color-border);
  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;
}
