/* base.css — "bold" template: loud and rounded.
 *
 * 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, which to leave alone).
 *
 * Character: one unit, repeated. An oversized statement headline — 93.6px at
 * 1440px, weight 800, tracked in to -0.035em — set over a single deeply-rounded
 * media slab, on a warm off-white ground, interrupted once per page by that same
 * slab shape filled near-black and inset from the page edges. Every section opens
 * at the same display size; that uniformity is the design. Controls are full
 * pills, the shadows are deep and far-offset, and the front page opens with a
 * split hero whose right-hand column holds a CSS-drawn presentation frame.
 *
 * Shares the BEM vocabulary of the "soft", "sharp", "fine", "lush", "airy" and
 * "grand" 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.
 *
 * Two things separate this template from "airy", which it otherwise resembles
 * (same warm ground, same floating navigation pill, same negatively-tracked sans
 * display), and both are load-bearing. The display treatment is a different
 * instrument — 93.6px/800 here against 68px/600 there, repeated in every section
 * rather than used once. And NOTHING LIFTS ON HOVER: controls drop to 0.88
 * opacity and cards tighten their shadow. A translateY on a hover state turns a
 * "bold" site into an "airy" one, which is why there is not a single one in this
 * file.
 *
 * 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. */

/* madefor */
@font-face {
  font-family: 'Wix Madefor Display';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/wix-madefor-display-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: 'Wix Madefor Display';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url('fonts/wix-madefor-display-normal-800.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: 'Wix Madefor Text';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/wix-madefor-text-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: 'Wix Madefor Text';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/wix-madefor-text-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: 'Wix Madefor Text';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/wix-madefor-text-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;
}

/* intertight-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: 'Inter Tight';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/inter-tight-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: 'Inter Tight';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url('fonts/inter-tight-normal-800.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;
}

/* onest */
@font-face {
  font-family: 'Onest';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/onest-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: 'Onest';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/onest-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: 'Onest';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/onest-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: 'Onest';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url('fonts/onest-normal-800.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;
}

/* schibsted */
@font-face {
  font-family: 'Schibsted Grotesk';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/schibsted-grotesk-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: 'Schibsted Grotesk';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/schibsted-grotesk-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: 'Schibsted Grotesk';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/schibsted-grotesk-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: 'Schibsted Grotesk';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url('fonts/schibsted-grotesk-normal-800.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-accent must hold >= 4.5:1
 * on --color-accent; --color-on-primary must hold >= 4.5:1 on
 * --color-ground-deep; --color-accent used as body-size text on the ground must
 * hold >= 4.5:1 itself.
 *
 * The accent is louder here than in the rest of the family: it fills the primary
 * control, marks a list, and carries the closing statement at display size. That
 * makes it load-bearing text, not decoration, so a brand hue that only clears 3:1
 * cannot be used at body size — put it on a fill or at display size instead. The
 * placement rule is in TEMPLATE.md and it is keyed to the ground beneath the
 * accent, not to a count of appearances.
 *
 * The last five properties are marked "template default": they are what the
 * design IS rather than what a brand is, and varying them per site is how the
 * design gets lost. They live in this block only so that a build reading the
 * brand contract sees every property in one place. */
:root {
  --color-primary: #2a1a10;          /* crust-brown near-black — ink, headings, focus ring */
  --color-primary-shade: #1b100a;    /* darker primary for pressed states (derive: primary ~12% darker) */
  --color-primary-tint: #e5ddd0;     /* pale wash for icon plates (derive: primary mixed ~92% ground) */
  --color-on-primary: #f7f2ea;       /* text and icons on an inverted slab — flour white on the espresso ground */
  --color-accent: #b8440d;           /* paprika red, hue ~19° — primary control, marks, the closing statement */
  --color-on-accent: #ffffff;        /* the primary control's label (5.12:1 on the default accent) */
  --color-surface: #f5f0e6;          /* flour-cream page ground — warm off-white, not grey and not white */
  --color-surface-alt: #ffffff;      /* the one plain-white slab */
  --color-text: #2a1a10;             /* body ink, the same crust brown as the primary */
  --color-muted: #6b6055;            /* secondary text and resting nav links, a warm mid grey-brown */
  --color-border: rgba(42, 26, 16, 0.1); /* the page's one hairline */
  --color-ground-deep: #1c1512;      /* espresso near-black — the inverted slab and the footer */

  --font-heading: 'Wix Madefor Display', 'Segoe UI', system-ui, -apple-system, 'Helvetica Neue', Arial, sans-serif;
  --font-body: 'Wix Madefor Text', 'Segoe UI', system-ui, -apple-system, 'Helvetica Neue', Arial, sans-serif;

  --radius-slab: 40px;    /* template default — the workhorse radius; it is the design */
  --display-leading: 1.19; /* template default — a FLOOR, not a starting point (see the note below) */
  --frame-bezel: 5%;     /* template default — the frame's edge, as a share of its own width */
}
/* ── end Brand parameters ──────────────────────────────────────────────────── */

/* ── Fixed tokens (the template character — do not edit per site) ──────────── */
:root {
  /* Type scale 1.25 (major third) from a 16px base, fluid via clamp. The display
     sizes are deliberately NOT on this ladder — they fall much more steeply than
     a ladder step between a desktop and a phone, so they get their own clamp
     below. The ladder covers the body cluster and supplies the sub-heading sizes
     an inner page needs. */
  --step--1: clamp(0.8125rem, 0.79rem + 0.1vw, 0.875rem);
  --step-0: clamp(1rem, 0.98rem + 0.1vw, 1rem);
  --step-1: clamp(1.125rem, 1.08rem + 0.2vw, 1.25rem);
  --step-2: clamp(1.375rem, 1.29rem + 0.36vw, 1.5625rem);
  --step-3: clamp(1.625rem, 1.45rem + 0.75vw, 1.9375rem);
  --step-4: clamp(2rem, 1.65rem + 1.5vw, 2.4375rem);
  --step-5: clamp(2.375rem, 1.8rem + 2.4vw, 3.0625rem);
  --step-6: clamp(2.75rem, 1.9rem + 3.6vw, 3.8125rem);

  /* The display size, on its own curve. It renders 46px in a 350px container on a
     390px phone and 93.6px at 1440px — the top of the range is the measured value
     exactly, and the bottom is two pixels under the measured 50px because a
     14-character Finnish compound needs the whole 350px measure at 50px and has
     no room left for a comma. See TEMPLATE.md, "Finnish word length", before
     touching either end. */
  --display-size: clamp(2.875rem, 1.72rem + 4.74vw, 5.85rem);
  --display-size-lead: clamp(1.75rem, 1.15rem + 2.46vw, 3.6875rem); /* the quieter 58-60px second level */
  --leading-body: 1.6;
  --tracking-display: -0.035em;
  --tracking-body: -0.01em;

  /* Spacing. The capture found no clean unit — the page is a few large hand-set
     numbers with small gaps filling in between them — so the small rungs step in
     8px multiples and the two large ones are the measured values: 40px of card
     padding and the 86-94px of the inverted slab. */
  --space-3xs: 0.25rem;
  --space-2xs: 0.5rem;
  --space-xs: 1rem;
  --space-s: 1.5rem;
  --space-m: 2.5rem;
  --space-l: 4.5rem;
  --space-xl: 5.85rem;
  --section-pad: clamp(4rem, 2.2rem + 7.2vw, 9rem);

  /* The inverted slab's vertical padding is exactly one display line — that
     relationship is measured, so it is expressed against the display step rather
     than pinned to a pixel value. Its horizontal padding is its own curve,
     landing on the measured 86.4px at 1440px. */
  --slab-pad-y: var(--display-size);
  --slab-pad-x: clamp(1.25rem, 0.6rem + 3.2vw, 5.4rem);

  /* Two measures: text is held to 1176px, and the slab that runs wider takes
     1296px. Both sit inside the same 132px desktop gutter. */
  --container-max: 73.5rem;
  --container-wide: 81rem;

  /* Radii. 40px is the mode and it belongs to ONE shape — a large rectangle
     holding a picture, a panel or a price card. 25px is what sits nested inside
     it, 12px is for small inputs, and everything interactive is a full pill. */
  --radius-s: 12px;
  --radius-m: 25px;
  --radius-l: var(--radius-slab);
  --radius-pill: 999px;
  --radius-frame: 34px;  /* the presentation frame's outer corner */
  --radius-aperture: 25px;  /* and the aperture inside it */

  /* Deep, warm, far-offset shadows with a large negative spread: the object sits
     well above the page and casts a long soft pool rather than a hard drop. The
     literal (38,34,28) is a warmed version of the page ink — rgba() cannot take a
     custom property directly. The accent glow is the primary control's own, and
     it is the only coloured shadow on the page. */
  --shadow-deep: 0 50px 110px -40px rgba(38, 34, 28, 0.42);
  --shadow-mid: 0 26px 60px -28px rgba(38, 34, 28, 0.34);
  --shadow-mid-tight: 0 26px 50px -22px rgba(38, 34, 28, 0.34);
  --shadow-near: 0 4px 16px -6px rgba(30, 28, 24, 0.16);
  --shadow-accent: 0 6px 20px -10px rgba(216, 30, 0, 0.45);

  --border-hair: 1px solid var(--color-border);

  /* How far a drifting slab picture travels across its pass through the
     viewport — about 4.5% of the scroll distance it is visible for. */
  --parallax-travel: clamp(1.5rem, 0.5rem + 3.6vw, 4.5rem);

  --ease-bold: cubic-bezier(0.16, 1, 0.3, 1);
  --dur-reveal: 900ms;
  --dur-hover: 150ms;

  --header-h: 4.375rem;  /* the floating pill's own height, 70px */
  --header-gap: 0.875rem; /* 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);
  letter-spacing: var(--tracking-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: the accent is reserved for controls and
   for the closing statement, so an inline link is distinguished by the underline
   rather than by colour. */
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(--display-leading);
  /* One weight for every headline on the page. The design's whole voice is the
     top weight and there is no second signal — no colour change, no italic — so a
     heading set at 600 reads as a mistake rather than as a variation. */
  font-weight: 800;
  color: var(--color-text);
  letter-spacing: var(--tracking-display);
}
h1 {
  font-size: var(--step-6);
}
h2 {
  font-size: var(--step-5);
}
h3 {
  font-size: var(--step-3);
}
h4 {
  font-size: var(--step-2);
}
ul,
ol {
  padding-left: 1.25em;
}

/* A visible focus ring on every interactive element, in the ink. It follows the
   element's own radius — a pill on a control, the slab radius on a slab — and it
   is never traded away for the hover treatment. */
:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
  border-radius: var(--radius-s);
}
/* On an inverted slab the ink ring would disappear into the ground, so it
   inverts with it. */
.slab--deep :focus-visible,
.slab--black :focus-visible,
.site-footer :focus-visible {
  outline-color: var(--color-on-primary);
}

/* ── Utilities ───────────────────────────────────────────────────────────── */
/* The gutter is expressed as a width rather than as padding, so a child column
   can be a percentage of the SAME box the measurement describes: 1176px at x=132
   on a 1440px screen, 350px at x=20 on a 390px phone, both measured. Padding
   would shrink the content box inside those numbers and every column fraction
   below would then be a fraction of the wrong thing. */
.container {
  width: min(100% - 2.5rem, var(--container-max));
  margin-inline: auto;
}
/* The wider measure, for a slab that runs past the text column: 1296px at x=72. */
.container--wide {
  width: min(100% - 2.5rem, var(--container-wide));
}
/* 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: 130; /* above the floating pill and above the grain */
  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);
}

/* ── Grain: texture, not motion ──────────────────────────────────────────── */
/* A fixed, non-interactive wash over the whole viewport. It never moves and never
   changes, so it is unaffected by a reduced-motion preference and needs no
   script. It is generated rather than loaded: a full-viewport texture image would
   be a real payload cost for a decorative effect.
   The layer's own opacity is 0.35, but the noise inside it is deliberately faint
   — the colour matrix multiplies the turbulence alpha down to 0.18 — so the ink
   this actually deposits over body text is a few percent. A grain strong enough
   to see clearly on a headline is strong enough to fail a contrast check. */
.grain {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0.35;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0.10 0 0 0 0 0.09 0 0 0 0 0.07 0 0 0 0.18 0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ── Buttons ─────────────────────────────────────────────────────────────── */
/* Every control on this page is a full pill; there are no rounded rectangles in
   the interactive vocabulary. --primary carries the accent and its glow;
   --ghost is the transparent one with a hairline, for use beside it. */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-height: 3.25rem; /* comfortable tap target */
  padding: 0.9rem 1.75rem;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--step-0);
  line-height: 1.2;
  letter-spacing: 0;
  text-decoration: none;
  cursor: pointer;
}
.button__label {
  display: inline-block;
}
.button--primary {
  background: var(--color-accent);
  color: var(--color-on-accent);
  border-color: var(--color-accent);
  box-shadow: var(--shadow-accent);
}
/* The whole control fades together — fill, label and glow — rather than changing
   colour. On a saturated fill a darkening step reads as a different brand colour;
   an opacity drop reads as a press. */
.button--primary:hover {
  color: var(--color-on-accent);
  opacity: 0.88;
}
.button--ghost {
  background: transparent;
  color: var(--color-text);
  border-color: var(--color-border);
}
.button--ghost:hover {
  color: var(--color-text);
  opacity: 0.88;
}
/* Inside an inverted slab the ghost control has to invert with the ground. */
.slab--deep .button--ghost,
.slab--black .button--ghost,
.site-footer .button--ghost {
  color: var(--color-on-primary);
  border-color: rgba(244, 244, 246, 0.28);
}

/* ── Header: the floating navigation pill ────────────────────────────────── */
/* Fixed, inset from every edge, and completely inert: the reference's header was
   watched across sixty-five scroll positions and changed neither opacity, shape
   nor position. main.js still adds --stuck once the page scrolls, and this
   template deliberately gives it nothing to do — the pill is opaque and shadowed
   from the first frame, so there is no "now it needs a background" moment. The
   modifier stays in the vocabulary so a page's header markup is identical under
   every template's stylesheet. */
.site-header {
  position: fixed;
  top: var(--header-gap);
  left: 0;
  right: 0;
  z-index: 100;
  padding-inline: var(--header-gap);
}
.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-s);
  width: 100%;
  max-width: 82.5rem; /* 1320px — the widest panel on the page */
  min-height: var(--header-h);
  margin-inline: auto;
  padding: var(--space-3xs) var(--space-3xs) var(--space-3xs) var(--space-s);
  background: var(--color-surface);
  border: var(--border-hair);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-near);
}
.site-header__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  color: var(--color-text);
  text-decoration: none;
}
/* The wordmark is one of the two controls 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: 2.25rem;
  height: 2.25rem;
  border-radius: var(--radius-pill);
}
.site-header__wordmark {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: var(--step-1);
  letter-spacing: -0.02em;
  white-space: nowrap;
}
.nav__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  background: transparent;
  border: var(--border-hair);
  border-radius: var(--radius-pill);
  cursor: pointer;
}
.nav__toggle-bars {
  position: relative;
  width: 1.125rem;
  height: 2px;
  background: var(--color-text);
}
.nav__toggle-bars::before,
.nav__toggle-bars::after {
  content: '';
  position: absolute;
  left: 0;
  width: 1.125rem;
  height: 2px;
  background: var(--color-text);
}
.nav__toggle-bars::before {
  top: -6px;
}
.nav__toggle-bars::after {
  top: 6px;
}
.nav__menu {
  list-style: none;
  padding: 0;
  margin: 0;
}
.nav__item {
  margin: 0;
}
.nav__link {
  display: inline-flex;
  align-items: center;
  min-height: 3rem;
  padding: 0 var(--space-xs);
  border-radius: var(--radius-pill);
  color: var(--color-muted);
  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(--header-gap);
    right: var(--header-gap);
    top: calc(100% + var(--space-2xs));
    flex-direction: column;
    gap: var(--space-3xs);
    padding: var(--space-s);
    background: var(--color-surface);
    border: var(--border-hair);
    border-radius: var(--radius-m);
    box-shadow: var(--shadow-mid);
  }
  .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);
  }
}

/* ── The statement: the block that opens every section ───────────────────── */
/* This is the template. A 93.6px headline at weight 800, tracked in to -0.035em,
   opening every single section at the same size. It is a separate block from
   .section__head because it carries the display clamp and the raised leading, and
   neither must leak onto an ordinary heading.
   The leading is a FLOOR of 1.05, not a default. The measured value is 0.98 —
   below 1.0, so a line box is shorter than the type is tall — and in a language
   without marks above the capital line that reads as a solid mass. Finnish sets
   A-diaeresis, O-diaeresis and A-ring above cap height, and at 0.98 the mark on a
   capital A-diaeresis starting a second line lands inside the previous line's
   descenders. The tightness is carried by the tracking and the weight instead,
   both kept exactly as measured. Do not lower it. */
.statement {
  max-width: 100%;
}
.statement__title {
  font-size: var(--display-size);
  line-height: var(--display-leading);
  letter-spacing: var(--tracking-display);
  font-weight: 800;
  /* At this size a wrapping failure is not subtle. A Finnish compound of 16
     characters or more breaks with a visible hyphen; the 13-14 character words a
     tagline is full of are never broken. 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;
}
/* The quieter second level: a long statement paragraph or a subordinate head, set
   in the display family at 600 rather than 800 so it reads as a different voice
   and not as a smaller version of the same one. */
.statement__lead {
  margin-top: var(--space-s);
  max-width: 46ch;
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: var(--display-size-lead);
  line-height: var(--display-leading);
  letter-spacing: -0.02em;
  color: var(--color-text);
  overflow-wrap: break-word;
  hyphens: auto;
  -webkit-hyphens: auto;
  hyphenate-limit-chars: 16 5 4;
}
/* On an inverted slab the statement inverts with the ground. */
.slab--deep .statement__title,
.slab--deep .statement__lead,
.slab--black .statement__title,
.slab--black .statement__lead {
  color: var(--color-on-primary);
}

/* ── The slab: the one rounded shape the design is made of ───────────────── */
/* A large rectangle at the 40px radius, clipping whatever is inside it. The
   picture within carries no radius of its own — the rounding lives on the
   wrapper, which is why a photograph never needs to know about it.
   There is never text set on a picture in this template, so there is no scrim
   anywhere in this file. Text sits beside the slab or above it. */
.slab {
  position: relative;
  border-radius: var(--radius-slab);
  overflow: hidden;
}
.slab__media {
  position: relative;
  border-radius: var(--radius-slab);
  overflow: hidden;
  box-shadow: var(--shadow-deep);
}
.slab__image {
  width: 100%;
  height: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: 0;
}
.slab__image--portrait {
  aspect-ratio: 4 / 7;
}
.slab__image--square {
  aspect-ratio: 4 / 3;
}
.slab__body {
  padding: var(--slab-pad-y) var(--slab-pad-x);
}
/* The inverted slabs. All three are inset from the page edge rather than
   full-bleed: the pale page shows through at all four corners, so the shape reads
   as a held breath rather than as a stripe. ONE of them appears per page — twice
   on a pale ground reads as stripes, and the footer's own does not count because
   it closes the page rather than interrupting it. */
.slab--deep {
  background: var(--color-ground-deep);
  color: var(--color-on-primary);
}
.slab--black {
  background: #000000;
  color: var(--color-on-primary);
}
.slab--light {
  background: var(--color-surface-alt);
  color: var(--color-text);
}
.slab--flush {
  /* The wider measure: 1296px against the text column's 1176px. */
  max-width: var(--container-wide);
  margin-inline: auto;
}
/* Secondary copy directly on a 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 slab's own text colour. Copy inside a pale card ON
   a dark slab keeps the ordinary muted ink, because its background is still
   pale. */
.slab--deep .section__lead,
.slab--deep .faq__answer,
.slab--deep .mark-list__item,
.slab--black .section__lead,
.slab--black .faq__answer,
.slab--black .mark-list__item {
  color: rgba(244, 244, 246, 0.82);
}
.slab--deep .section__title,
.slab--deep h2,
.slab--deep h3,
.slab--black .section__title,
.slab--black h2,
.slab--black h3 {
  color: var(--color-on-primary);
}

/* ── Hero media: three cases, one block ──────────────────────────────────── */
/* What goes beside the hero title is a per-site choice, not a fixed part of this
   template. There are three cases and a build picks one from what the business
   actually sells:
     1. .showcase--framed — the presentation frame. Right when the thing being
        shown is presented rather than merely photographed: something on a screen,
        a printed piece, a finished cabinet, a plated dish.
     2. .showcase--plain — an ordinary photograph, cropped to the 40px slab. Right
        for a workshop, a shopfront, a finished job on site.
     3. no showcase at all — see .hero--stated. Right for a service business whose
        opening is words and a call to action.
   Case 1 is the narrow case, not the default. A frame around a photograph of a
   workshop tells the visitor the business sells a screen product, and for a
   business without one that is a lie the layout tells before the copy can speak. */
.showcase {
  width: 100%;
  margin-inline: auto;
}
/* Both variants crop their picture to their own corner; the picture carries no
   radius of its own. Not a child selector: the aperture usually holds a
   <picture>, so the rule has to reach the <img> inside it. */
.showcase__aperture {
  overflow: hidden;
}
.showcase__aperture img,
.showcase__image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Case 1 — the presentation frame. Drawn geometry, not a picture of an object: a
   rounded outer frame in the deep ground with a bezel of 5% of its own width,
   holding an aperture at a smaller radius. Two rules keep it neutral. It is NEVER
   given a brand mark, a camera cutout, a notch, a button or a speaker slot —
   those are the parts that would make it a copy of somebody's product photograph.
   And at most ONE appears per page; a second turns a presentation into a layout
   habit. The aspect ratio is a custom property because a cabinet is not a phone:
   set --frame-aspect to 4 / 5, 1 / 1 or 3 / 2 and the frame still reads. */
.showcase--framed {
  --frame-aspect: 49 / 100;
  max-width: 26.875rem; /* 430px — the frame's measured width at desktop */
}
.showcase__frame {
  position: relative;
  aspect-ratio: var(--frame-aspect);
  padding: var(--frame-bezel);
  background: var(--color-ground-deep);
  border-radius: var(--radius-frame);
  box-shadow: var(--shadow-deep);
}
.showcase--framed .showcase__aperture {
  width: 100%;
  height: 100%;
  border-radius: var(--radius-aperture);
  background: var(--color-surface);
}
/* The wider orientations need a wider box to sit in, or the frame is drawn tall
   and then filled with a landscape aperture. */
.showcase--wide {
  max-width: 40rem;
}

/* Case 2 — the plain picture. No frame: the slab's own 40px corner and the deep
   shadow do all of the presenting, which is what the rest of the page does with
   every other picture. The ratio is a custom property so a portrait subject can
   set 4 / 5 without a new class. */
.showcase--plain {
  margin: 0; /* it is usually a <figure> */
}
.showcase--plain .showcase__aperture {
  aspect-ratio: var(--showcase-aspect, 4 / 3);
  border-radius: var(--radius-slab);
  box-shadow: var(--shadow-deep);
}

/* ── Split hero: the one-off opening ─────────────────────────────────────── */
/* Two equal columns with a small gap — measured at 47.5% each of a 1176px
   container, with a 4.9% gap between them. The title, its lead and its controls
   sit on the left; the right-hand stage holds either a presentation frame or a
   slab. This composition belongs to the FRONT PAGE only. An inner page opens with
   a statement-and-slab section instead, because a whole screen of picture is not
   what a visitor who has already arrived somewhere is looking for. */
.hero {
  padding-block: var(--section-pad);
  background: var(--color-surface);
}
.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,
.statement__kicker {
  display: inline-block;
  margin-bottom: var(--space-s);
  font-family: var(--font-body);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: var(--step--1);
  color: var(--color-muted);
}
/* The page title is the one headline that sits below the section size: 77.76px
   against 93.6px at desktop. It is a consequence of the column, not of the type —
   the title lives in half the container's width, so it needs the smaller size to
   hold its four lines without running away down the page. */
.hero__title {
  font-size: clamp(2.625rem, 1.7rem + 3.8vw, 4.86rem);
  line-height: var(--display-leading);
  letter-spacing: var(--tracking-display);
  font-weight: 800;
  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: 42ch;
}
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-top: var(--space-m);
}
/* The right-hand column. It is a stage rather than a media block because what it
   holds is a showcase or a slab, not a bare image — and because whether it is
   there at all is a per-site choice. */
.hero__stage {
  min-width: 0;
}
/* Case 3 — no hero media. The title, its lead and its controls are the whole
   opening, so the body is no longer half of the container. It is widened, but not
   to the full 1176px measure: a 77.76px headline running the whole container
   reads as a banner rather than as a page title, and the lead beneath it would
   sit alone under a very long line. */
.hero--stated .hero__body {
  max-width: 46rem;
}
/* Below this width the two columns stack, title first and stage second — which is
   the order the reference sets on a phone, and the only order that makes sense
   when the picture is a presentation of something the title has not named yet. */
@media (min-width: 60rem) {
  .hero--split .hero__inner {
    /* auto-fit rather than a fixed 1fr 1fr: with both children present this is
       the measured 50/50 split, and if the media column is left out the row
       collapses to one column instead of leaving an empty half of the screen. */
    grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
    gap: 4.9%;
  }
}

/* ── Section scaffolding ─────────────────────────────────────────────────── */
/* A section is between 0.8 and 1.2 screens tall. Shorter and the display size
   overwhelms it; longer and the page becomes a product tour, which is the one
   thing this design must not be. Four to six sections on a front page; a
   four-section page is a correct page. */
.section {
  padding-block: var(--section-pad);
}
.section--alt {
  background: var(--color-surface-alt);
}
/* The ordinary section head, for a subsection inside a statement section. A
   section that needs a heading smaller than the display size is a subsection —
   that is what this block is for, and it is not an alternative to .statement. */
.section__head {
  max-width: 52ch;
  margin-bottom: var(--space-l);
}
.section__title {
  font-size: var(--step-4);
}
.section__lead {
  margin-top: var(--space-s);
  color: var(--color-muted);
  font-size: var(--step-1);
  max-width: 56ch;
}
/* The repeating unit: a statement across the full container, then a picture slab
   and a text column beneath it. --reverse puts the picture on the other side, and
   alternating them down the page is what keeps five identical sections from
   reading as five identical sections. */
.feature {
  display: grid;
  gap: var(--space-m);
  align-items: center;
  margin-top: var(--space-l);
}
.feature__body {
  min-width: 0;
}
.feature__text {
  color: var(--color-muted);
  max-width: 52ch;
}
@media (min-width: 60rem) {
  .feature {
    grid-template-columns: 1.1fr 0.9fr;
    gap: var(--space-l);
  }
  .feature--reverse .feature__body {
    order: 2;
  }
}

/* ── Marks: the short list with an accent dot ────────────────────────────── */
/* The mark is drawn in CSS and is never an image. It is one of the accent's
   permitted roles on a pale ground. */
.mark-list {
  list-style: none;
  padding: 0;
  margin-top: var(--space-s);
  display: grid;
  gap: var(--space-xs);
}
.mark-list__item {
  display: flex;
  align-items: baseline;
  gap: var(--space-2xs);
  color: var(--color-text);
}
.mark-list__mark {
  flex: 0 0 auto;
  width: 0.625rem;
  height: 0.625rem;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  /* A resting alignment nudge, not a hover behaviour: it lifts the dot onto the
     optical centre of the first line of text beside it. */
  transform: translateY(-0.05em);
}

/* ── Cards ───────────────────────────────────────────────────────────────── */
/* Rounded at the nested radius rather than the slab's, because a card is one of
   the many small units the design does NOT round at 40px — that value belongs to
   the few very large shapes. */
.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-alt);
  border: var(--border-hair);
  border-radius: var(--radius-m);
}
.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-3);
  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-near);
}
.gallery__image {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

/* ── Testimonial ─────────────────────────────────────────────────────────── */
.testimonial {
  padding: var(--space-m);
  background: var(--color-surface-alt);
  border-radius: var(--radius-m);
  color: var(--color-text);
}
.testimonial__quote {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: var(--step-2);
  line-height: 1.35;
  letter-spacing: -0.02em;
}
.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-2);
  margin-bottom: var(--space-2xs);
}
.faq__answer {
  color: var(--color-muted);
  max-width: 60ch;
}

/* ── Price cards ─────────────────────────────────────────────────────────── */
/* 40px radius and 40px of padding — both measured — with a hairline outline and
   no shadow at rest. These exist for a business that publishes prices for its
   SERVICES. A business that does not publish prices drops the whole section
   rather than filling it with something else. */
.plan-grid {
  display: grid;
  gap: var(--space-s);
  grid-template-columns: 1fr;
  margin-top: var(--space-l);
}
@media (min-width: 52rem) {
  .plan-grid {
    /* 18rem rather than 16: at three columns on a 900px screen a card's text box
       is too narrow for a display-size price phrase, so the grid drops to two
       columns before that happens. */
    grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  }
}
.plan {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-m);
  background: var(--color-surface-alt);
  border: var(--border-hair);
  border-radius: var(--radius-slab);
}
.plan--featured {
  background: var(--color-ground-deep);
  color: var(--color-on-primary);
  border-color: transparent;
  box-shadow: var(--shadow-mid);
}
.plan__name {
  font-size: var(--step-2);
}
.plan--featured .plan__name {
  color: var(--color-on-primary);
}
.plan__price {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: var(--step-5);
  letter-spacing: var(--tracking-display);
  line-height: var(--display-leading);
  /* A price is a phrase as often as it is a number ("sopimuksen mukaan"), and at
     display size one long word in a narrow card widens the whole document rather
     than wrapping. */
  overflow-wrap: break-word;
  hyphens: auto;
  -webkit-hyphens: auto;
}
.plan__list {
  list-style: none;
  padding: 0;
  display: grid;
  gap: var(--space-2xs);
  color: var(--color-muted);
}
.plan--featured .plan__list {
  color: rgba(244, 244, 246, 0.82);
}
.plan__action {
  margin-top: auto;
  padding-top: var(--space-xs);
}

/* ── Closing statement ───────────────────────────────────────────────────── */
/* The one centred block on the page, and the one place a heading is painted in
   the accent. It sits on the deep ground, where the accent holds 3.59:1 — enough
   for a graphical object and for text this large, and not enough for body copy,
   which is why the note beneath it is not in the accent. */
.closing {
  padding-block: var(--space-xl);
  padding-inline: var(--slab-pad-x);
  text-align: center;
  background: var(--color-ground-deep);
  color: var(--color-on-primary);
  border-radius: var(--radius-slab);
}
.closing__title {
  font-size: var(--display-size);
  line-height: var(--display-leading);
  letter-spacing: var(--tracking-display);
  font-weight: 800;
  color: var(--color-accent);
  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;
}
.closing__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-xs);
  margin-top: var(--space-m);
}
.closing__note {
  margin-top: var(--space-s);
  font-size: var(--step--1);
  color: rgba(244, 244, 246, 0.82);
}

/* ── 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.9rem 1.1rem;
  background: var(--color-surface-alt);
  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: 9rem;
  resize: vertical;
}

/* ── Footer ──────────────────────────────────────────────────────────────── */
/* The footer is an inverted slab in its own right, inset from the page edges like
   every other one. It closes the page rather than interrupting it, so it does not
   count against the one-inverted-slab-per-page rule. */
.site-footer {
  padding-block: var(--space-l) var(--space-m);
  padding-inline: var(--slab-pad-x);
  margin: var(--space-s) clamp(0rem, -1rem + 4vw, 3rem) var(--space-s);
  background: var(--color-ground-deep);
  color: var(--color-on-primary);
  border-radius: var(--radius-slab);
}
.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-1);
  color: var(--color-on-primary);
  margin-bottom: var(--space-2xs);
}
.site-footer__text {
  color: rgba(244, 244, 246, 0.82);
}
.site-footer__link {
  display: block;
  padding: var(--space-3xs) 0;
  color: rgba(244, 244, 246, 0.82);
  text-decoration: none;
  /* An email address is one unbreakable word. In a narrow footer column it would
     widen the whole document rather than wrap, which is invisible at the two
     widths a template is usually checked at and shows up at tablet width. */
  overflow-wrap: anywhere;
}
.site-footer__link:hover {
  color: var(--color-on-primary);
}
.site-footer__legal {
  margin-top: var(--space-m);
  padding-top: var(--space-s);
  border-top: 1px solid rgba(244, 244, 246, 0.18);
  color: rgba(244, 244, 246, 0.82);
  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);
  font-size: var(--step-6);
}
.legal__body > * + * {
  margin-top: var(--space-s);
}
.legal__body h2 {
  font-size: var(--step-3);
  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-surface);
  border: var(--border-hair);
  border-radius: var(--radius-m);
  box-shadow: var(--shadow-mid);
}
.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);
}

/* 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;
  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-weight: 800;
  font-size: var(--display-size);
  line-height: var(--display-leading);
  letter-spacing: var(--tracking-display);
  color: var(--color-muted);
}
.error-page__title {
  font-size: var(--step-4);
}
.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-slab);
}
@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: revealed blocks are at full opacity from the first
   frame, the drifting picture sits still, and hover changes happen instantly. The
   fixed header and the grain are unaffected, because neither is animation.
   The whole budget is three behaviours and there is no fourth. The reference
   registered thirty reveals, and they are ONE effect rather than thirty: every
   single one starts at exactly zero opacity, takes 600ms, and twenty-two of the
   thirty travel within a fifth of a pixel of 39.6px. So there is one reveal here,
   at 40px over 600ms, applied to section-level blocks and to nothing smaller —
   revealing individual paragraphs and list items produces a page where something
   is always arriving, which on a four-section brochure site reads as fussy. */
@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }

  /* Block reveal: a fade from zero with a 40px rise. Only hidden when JavaScript
     is running (the root .js class), so a page without it shows everything.

     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. The step
     is long — a part travels 40px here, and a short step on that much travel
     makes the parts overlap into one blur instead of reading as an order. The
     number of steps is capped so a long list still arrives as one thing. An
     element nobody numbered has an index of 0 and starts immediately, exactly as
     it did before. */
  .js [data-reveal] {
    --reveal-step: 100ms;
    --reveal-delay: calc(min(var(--reveal-index, 0), 6) * var(--reveal-step));
    opacity: 0;
    transform: translateY(40px);
    transition: opacity var(--dur-reveal) var(--ease-bold) var(--reveal-delay),
      transform var(--dur-reveal) var(--ease-bold) var(--reveal-delay);
  }
  .js [data-reveal].is-visible {
    opacity: 1;
    transform: none;
  }

  /* Hover. Short and linear, because the change is an opacity drop and an eased
     300ms fade on a button press reads as lag.
     NOTHING MOVES. The reference's controls report a transform on hover and every
     one of them is sub-pixel — the largest displacement across all of them is
     0.13px, with no measurable shift on any control. That is a transition caught
     mid-flight, not a lift. The nearest template in this family does lift its
     buttons by a real 2px, and a site built from this file that starts lifting
     has quietly become that template instead. Do not add a translateY here. */
  .button,
  .nav__link,
  .site-footer__link,
  .skip-link {
    transition: opacity var(--dur-hover) linear, color var(--dur-hover) linear,
      border-color var(--dur-hover) linear, top var(--dur-hover) linear;
  }
  /* A card or slab link settles rather than lifts: the shadow tightens and its
     negative spread shrinks, so the object appears to come closer to the page. */
  .slab__media,
  .card {
    transition: box-shadow var(--dur-hover) linear;
  }
  a:hover > .slab__media,
  a:focus-visible > .slab__media {
    box-shadow: var(--shadow-mid-tight);
  }

  /* The drifting picture: one slab travels with the page as its section passes
     the viewport, at about 4.5% of the scroll distance — gentle enough to read as
     depth rather than as an effect. Driven by a scroll-driven animation rather
     than by a script, so this template ships no second behaviour file. A browser
     without scroll-driven animations ignores the timeline and paints the picture
     at its resting position, which is the correct static composition.
     ONE of these per page. The reference ran two on a fifteen-section page; on a
     four-section page two would be half the pictures on the site moving. */
  @supports (animation-timeline: view()) {
    .slab--drift .slab__image {
      animation: slab-drift linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
    }
  }
}

/* slab-drift: the picture rises through its slab as the section passes the
   viewport, ending at its resting position and never travelling past it, so the
   crop can never expose an edge. The slab clips, and the picture is scaled just
   past the frame so the travel has somewhere to come from. Transform-only, so the
   image itself is never repainted. */
@keyframes slab-drift {
  from {
    transform: translateY(var(--parallax-travel)) scale(1.08);
  }
  to {
    transform: translateY(0) scale(1.08);
  }
}

/* ── 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.
 *
 * Three additions are specifically out of bounds, because they are the ones this
 * design invites and cannot survive: a second radius value on the slab, a
 * translateY on any hover state, and a third inverted section. Each of them
 * individually turns this into a different template. */
