/* base.css — "stark" template: a field report, monospace over documentary photography.
 *
 * A complete, quality-gated design system (hand-rolled BEM, framework-free).
 * The build agent copies this file verbatim into a site's `style.css` and
 * overwrites ONLY the delimited brand-parameter block below — every other rule
 * ships byte-identical, which is how the fixture's Lighthouse/axe pass transfers
 * to every real build. See TEMPLATE.md for the character spec and the
 * brand-parameter contract (which properties to fill, how tints/shades derive).
 *
 * Character: two type families in fixed roles. A MONOSPACE stamps everything
 * loud and technical — the uppercase page title set directly on a full-bleed
 * photograph, navigation, kickers, index rows, buttons and data lines — and a
 * neutral SANS carries everything meant for reading: sentence-case section
 * headings and body copy. Uppercase is always the mono; sentence case is always
 * the sans. Near-monochrome grounds, a quiet 4/8/12px radius ladder, hairline
 * rules instead of shadows, and one saturated accent that appears only on things
 * a visitor can click.
 *
 * Three properties of this template are load-bearing and easy to break:
 *
 *   1. Text is set ON photographs here, so every such surface carries a scrim.
 *      `--scrim` is the floor that makes white legible; it never goes below
 *      rgba(0, 0, 0, 0.45) and `--nav-fill` never goes lighter than
 *      rgba(0, 0, 0, 0.6). Lightening either is how this design fails an audit.
 *   2. The accent is interactive-only. A heading, a background or a run of body
 *      text in the accent breaks the one rule of colour this design has. Because
 *      the accent doubles as the link colour, body links stay underlined so the
 *      affordance never depends on colour alone.
 *   3. There is exactly one shadow token, `--shadow-float`, and it is for fixed
 *      overlays — the cookie banner and the mobile navigation sheet. Content
 *      never casts a shadow; separation comes from hairlines, ground changes and
 *      the rounded plates.
 *
 * Motion is opt-in, not opt-out: every transition and the one scroll reveal are
 * declared inside `@media (prefers-reduced-motion: no-preference)`, so a visitor
 * who asks for reduced motion is served a page with no motion to switch off
 * rather than a page that animates and is then patched back. There is no
 * parallax and no ambient layer; this template ships no second script.
 *
 * 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. */

/* geist-mono-geist */
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/geist-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: 'Geist';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/geist-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: 'Geist Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/geist-mono-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;
}

/* plex-mono-sans */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/ibm-plex-mono-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: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/ibm-plex-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: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/ibm-plex-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;
}

/* jetbrains-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: 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: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/jetbrains-mono-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;
}

/* redhat-mono-text */
@font-face {
  font-family: 'Red Hat Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/red-hat-mono-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: 'Red Hat Text';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/red-hat-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: 'Red Hat Text';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/red-hat-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;
}

/* ── 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 on
 *   --color-surface-alt; --color-on-primary must hold >= 4.5:1 on
 *   --color-primary and on --color-primary-shade; --color-primary used as link
 *   text must hold >= 4.5:1 on both surfaces.
 *   --color-on-media is legible ONLY inside a scrim: white text over an
 *   unscrimmed photograph has no measurable contrast at all.
 *
 * Two floors are not preferences. --scrim never drops below rgba(0, 0, 0, 0.45)
 * and --nav-fill never goes lighter than rgba(0, 0, 0, 0.6): they are what makes
 * the hero lead and the navigation legible wherever they cross a bright frame.
 * A build that lightens either has to re-measure the rendered page, not assume.
 *
 * Fonts must be one of TEMPLATE.md's approved pairs, and --font-heading must be
 * the MONOSPACE of that pair. Swapping the two slots inverts the whole design:
 * the register split — mono for uppercase and data, sans for reading — is the
 * template. The mono is used at ONE weight, 400, including at 62px. */
:root {
  --color-primary: #9e4409;          /* the source logo's orange (#f5791f), deepened: 6.4:1 under white, 5.7:1 on the surface, 5.0:1 on the alt */
  --color-primary-shade: #ad4b0b;    /* hover fill, a step lighter; white still holds 5.5:1 */
  --color-primary-tint: #efe3d9;     /* pale decorative wash */
  --color-on-primary: #ffffff;
  --color-accent: #9e4409;           /* the same value as --color-primary */
  --color-surface: #f4f1ee;          /* off-white tinted faintly toward the orange */
  --color-surface-alt: #e8e3de;
  --color-text: #1c1917;             /* 15.5:1 on the surface, 13.7:1 on the alt */
  --color-muted: #5b544e;            /* 6.6:1 on the surface, 5.8:1 on the alt */
  --color-border: #1c1917;

  --color-deep: #140a04;             /* near-black with the orange's cast */
  --color-on-media: #ffffff;
  --scrim: rgba(0, 0, 0, 0.65);      /* the hero photograph has a bright wall top left; 0.65 keeps the lead above 4.5:1 */
  --nav-fill: rgba(0, 0, 0, 0.6);

  --font-heading: 'JetBrains Mono', ui-monospace, 'SF Mono', 'Cascadia Mono', 'Courier New', monospace;
  --font-body: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
}
/* ── end Brand parameters ──────────────────────────────────────────────────── */

/* ── Fixed tokens (the template character — do not edit per site) ──────────── */
:root {
  /* Type scale, ratio 1.2 from a 14px base — the ratio that fits the design's own
     painted sizes (12, 13, 14, 16, 18, 21, 24, 50, 72) with the least residual.
     The two display steps are fluid; everything below 24px is held fixed, because
     scaling small type gains nothing and costs the page its density. */
  --step--2: 0.75rem;      /* 12px — legal line, image credits */
  --step--1: 0.8125rem;    /* 13px — metadata, the coordinates line */
  --step-0: 1rem;          /* 16px — body */
  --step-1: 1.125rem;      /* 18px — a slightly raised paragraph */
  --step-2: 1.3125rem;     /* 21px — the small end of an index label */
  --step-3: 1.5rem;        /* 24px — sub-headings and the testimonial quote */
  --step-label: 0.875rem;  /* 14px — navigation, kickers, buttons, tile labels; always mono uppercase */
  /* 30px on a 390px screen rising to the design's 50px at 1440px: the opening
     statement, and any sentence-case heading below the top level. */
  --step-4: clamp(1.875rem, 1.41rem + 1.9vw, 3.125rem);
  /* 34px rising to the design's 72px: the sans section headings. */
  --step-5: clamp(2.125rem, 1.24rem + 3.62vw, 4.5rem);
  /* The index label, 21px rising to the design's 24px. */
  --step-index: clamp(1.3125rem, 1.24rem + 0.29vw, 1.5rem);
  /* The hero title's own clamp: 32px at 390px, 62.4px at 1440px — both ends are
     the sizes the design renders at. Read TEMPLATE.md's "Finnish word length"
     before touching either end: the mono spends a full fixed advance per
     character, so this clamp is what decides whether a Finnish compound sets
     whole on a phone. */
  --step-hero: clamp(2rem, 1.3rem + 2.9vw, 3.9rem);

  --leading-body: 1.5;       /* a half-step more than the design's 1.41 — Finnish compounds break ragged */
  --leading-display: 1.05;   /* the sans section headings */
  /* The mono title is set solid. Ä and Ö carry a diaeresis above the cap line, so
     the title block must never be given a fixed height or `overflow: hidden`. */
  --leading-title: 1;
  --tracking-title: -0.0625em;  /* the title's negative tracking; the width comes from the mono's own advance */
  --tracking-label: 0.06em;     /* the 13–14px mono uppercase labels */

  /* Spacing on a 4px base. */
  --space-3xs: 0.25rem;    /* 4px */
  --space-2xs: 0.5rem;     /* 8px */
  --space-xs: 0.75rem;     /* 12px */
  --space-s: 1rem;         /* 16px — also the tile grid's gap */
  --space-m: 1.5rem;       /* 24px */
  --space-l: 2.5rem;       /* 40px */
  --space-xl: 4rem;        /* 64px */
  /* 56px between sections on a phone rising to the design's 120px at 1440px. */
  --section-pad: clamp(3.5rem, 2.01rem + 6.1vw, 7.5rem);

  --container-max: 84rem;     /* 1344px — the content container */
  --container-narrow: 70rem;  /* 1120px — the navigation bar and the hero's text column */
  /* 20px on a phone widening to the design's 48px at 1440px. */
  --gutter: clamp(1.25rem, 0.6rem + 2.67vw, 3rem);
  --measure: 62ch;            /* running copy never uses the full container width */

  --radius-s: 4px;   /* photographs, the testimonial plate */
  --radius-m: 8px;   /* the navigation bar, the dark slab, the footer plate — the dominant radius */
  --radius-l: 12px;  /* small squarish media */
  /* The photo tiles are the deliberate exception at 0 and take no token: a square
     corner is what separates the tile grid from every other picture on the page. */

  /* The ONLY shadow in this file, and it is for chrome that floats over the page:
     the cookie banner and the open mobile navigation sheet. Content is flat. */
  --shadow-float: 0 8px 24px rgba(0, 0, 0, 0.24);

  /* Hairlines are 1px throughout, in two registers: ink on a light ground, and a
     pale stone on the plates, derived so it follows a brand's own tint. */
  --border-hair: 1px solid var(--color-border);
  --border-soft: 1px solid color-mix(in srgb, var(--color-text) 28%, var(--color-surface-alt));
  /* The tile-label gradient's deep end. It is deeper than --scrim and it is fixed,
     because the tile promises something the hero does not: a white label holding
     4.5:1 over ANY photograph, including a blown-out one. At 0.72 the worst case
     — a pure white frame — composites to rgb(71, 71, 71), which white clears at
     7.6:1. The gradient reaches this value above the label zone and holds it, so
     the label never sits on the ramp. */
  --tile-foot: rgba(0, 0, 0, 0.72);

  /* The hairline on the translucent navigation bar. It is a dark line by design —
     over a bright photograph it is nearly invisible, which is the intent: the bar
     is read by its fill, not by its edge. */
  --nav-border: #303130;

  /* The one ground brighter than the page surface: the statement band. Mixed from
     the surface rather than set to white, so it keeps a brand's tint. */
  --ground-bright: color-mix(in srgb, var(--color-surface) 30%, #ffffff);
  /* The hero lead and other secondary text over a photograph. */
  --color-on-media-soft: color-mix(in srgb, var(--color-on-media) 80%, transparent);

  /* The floating bar: how far it is inset from the top and the page edges, and how
     tall it stands. Both viewports the design was measured at are represented. */
  --header-inset: 1rem;   /* 16px on a phone; 24px from tablet width up */
  --header-h: 3.875rem;   /* 62px on a phone; 57px from tablet width up */

  --dur-hover: 150ms;    /* every hover here is a colour or opacity change; easing one reads as lag */
  --dur-reveal: 600ms;
  --reveal-rise: 24px;   /* the measured ~80px travel reads as the layout jumping, not content arriving */
}

@media (min-width: 48rem) {
  :root {
    --header-inset: 1.5rem;  /* 24px */
    --header-h: 3.5625rem;   /* 57px */
  }
}

/* ── Reset ───────────────────────────────────────────────────────────────── */
*,
*::before,
*::after {
  box-sizing: border-box;
}
* {
  margin: 0;
}
html {
  -webkit-text-size-adjust: 100%;
  /* The navigation bar is fixed, so an in-page anchor must stop below it. */
  scroll-padding-top: calc(var(--header-inset) + var(--header-h) + var(--space-s));
  /* Reserve the scrollbar's width at all times. The lightbox hides the page's
     overflow while it is open, which would otherwise take the bar away and shift
     the whole page sideways behind the dialog. */
  scrollbar-gutter: stable;
}
body {
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: var(--leading-body);
  color: var(--color-text);
  background: var(--color-surface);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
img,
picture,
svg,
video {
  display: block;
  max-width: 100%;
  height: auto;
}
input,
button,
textarea,
select {
  font: inherit;
  color: inherit;
}
/* Links carry the accent, and they stay underlined: the accent is the only colour
   on the page, so colour alone cannot be the affordance. */
a {
  color: var(--color-primary);
  text-underline-offset: 0.18em;
}
a:hover {
  color: var(--color-primary-shade);
}
/* Sentence-case headings are the sans, at the regular weight. The contrast in
   this design comes from size and from the register split, not from boldness. */
h1,
h2,
h3,
h4 {
  font-family: var(--font-body);
  font-weight: 400;
  line-height: var(--leading-display);
  color: var(--color-text);
}
h1,
h2 {
  font-size: var(--step-5);
}
h3 {
  font-size: var(--step-4);
}
h4 {
  font-size: var(--step-3);
}
strong,
b {
  font-weight: 600;
}
ul,
ol {
  padding-left: 1.25em;
}

/* Focus is not hover. Every interactive element takes a visible ring in the
   accent, and nothing ever trades it away for the hover treatment. */
:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
/* On a photograph the violet ring can sink into the scrim, so it gains a thin
   white outer line and stays visible against any frame. */
.hero :focus-visible,
.tile:focus-within,
.site-header :focus-visible {
  outline-color: var(--color-primary);
  box-shadow: 0 0 0 1px var(--color-on-media);
}

/* ── Utilities ───────────────────────────────────────────────────────────── */
.container {
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
/* 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 fixed navigation bar, the only other stacked chrome */
  padding: var(--space-2xs) var(--space-s);
  border-radius: var(--radius-s);
  background: var(--color-primary);
  color: var(--color-on-primary);
  font-family: var(--font-heading);
  font-size: var(--step-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  text-decoration: none;
}
.skip-link:focus {
  top: var(--space-2xs);
  color: var(--color-on-primary);
}

/* Long Finnish compounds break only when they are genuinely too long for their
   column, and when they do they show a hyphen rather than splitting silently.
   Words of about fourteen characters — which an ordinary Finnish tagline and
   nearly every business name is full of — stay whole. Both need
   `<html lang="fi">` to hyphenate at all. */
.hyphenate {
  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;
}

/* ── Buttons ─────────────────────────────────────────────────────────────── */
/* Mono uppercase labels on a solid fill, at the small radius. Hover lightens the
   fill and nothing moves — no lift, no shadow, no scale. */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-height: 3rem;
  padding: 0.75rem 1.5rem;
  border: 1px solid transparent;
  border-radius: var(--radius-s);
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: var(--step-label);
  line-height: 1.2;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  text-decoration: none;
  cursor: pointer;
}
.button__label {
  display: inline-block;
}
.button--primary {
  background: var(--color-primary);
  color: var(--color-on-primary);
}
.button--primary:hover {
  background: var(--color-primary-shade);
  color: var(--color-on-primary);
}
/* The quiet button is a neutral fill, not an outline: this design has no outlined
   control. It lightens one step under the pointer, like the primary. */
.button--ghost {
  background: var(--color-text);
  color: var(--color-on-media);
}
.button--ghost:hover {
  background: color-mix(in srgb, var(--color-text) 88%, #ffffff);
  color: var(--color-on-media);
}

/* ── Header: a floating translucent bar over the page ────────────────────── */
/* Fixed from load, inset from the top and both edges, rounded, blurred, and it
   never unpins or changes size. It is dark everywhere: one theme, no scroll-linked
   switching, because at its fill floor it already holds contrast over every ground
   this template allows. main.js still sets `.site-header--stuck` once the page
   scrolls, and this template deliberately gives that class no rules — the bar is
   the same object at the top of the page and at the bottom. */
.site-header {
  position: fixed;
  top: var(--header-inset);
  left: 0;
  right: 0;
  z-index: 100;
  padding-inline: var(--header-inset);
}
.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-s);
  width: 100%;
  max-width: var(--container-narrow);
  min-height: var(--header-h);
  margin-inline: auto;
  padding-inline: var(--space-s);
  border: 1px solid var(--nav-border);
  border-radius: var(--radius-m);
  background: var(--nav-fill);
  -webkit-backdrop-filter: blur(17px);
  backdrop-filter: blur(17px);
}
.site-header__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  color: var(--color-on-media);
  text-decoration: none;
}
.site-header__logo {
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--radius-s);
}
.site-header__wordmark {
  font-family: var(--font-heading);
  font-size: var(--step-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  white-space: nowrap;
}
.nav__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  background: transparent;
  border: 1px solid var(--nav-border);
  border-radius: var(--radius-s);
  cursor: pointer;
}
.nav__toggle-bars {
  position: relative;
  width: 1.125rem;
  height: 1.5px;
  background: var(--color-on-media);
}
.nav__toggle-bars::before,
.nav__toggle-bars::after {
  content: '';
  position: absolute;
  left: 0;
  width: 1.125rem;
  height: 1.5px;
  background: var(--color-on-media);
}
.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 links rest at white and dim under the pointer. Opacity is the whole hover
   language here — nothing else about the bar changes. */
.nav__link {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  padding: 0 var(--space-xs);
  font-family: var(--font-heading);
  font-size: var(--step-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--color-on-media);
  text-decoration: none;
}
.nav__link:hover {
  color: var(--color-on-media);
  opacity: 0.6;
}
.nav__link--active {
  opacity: 1;
}

/* Mobile-first: the menu collapses behind the toggle and opens as a rounded sheet
   under the bar, in the same fill and blur so it reads as part of the same object.
   It is fixed chrome floating over the page, which is what earns it the one
   shadow token in this file. */
@media (max-width: 47.99rem) {
  .nav__menu {
    display: none;
    position: absolute;
    left: var(--header-inset);
    right: var(--header-inset);
    top: calc(100% + var(--space-2xs));
    flex-direction: column;
    gap: var(--space-3xs);
    padding: var(--space-s);
    border: 1px solid var(--nav-border);
    border-radius: var(--radius-m);
    background: var(--nav-fill);
    -webkit-backdrop-filter: blur(17px);
    backdrop-filter: blur(17px);
    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-2xs);
  }
}

/* ── Hero: the title set directly on a full-bleed photograph ─────────────── */
/* The template's whole identity. One photograph fills the viewport behind a
   scrim; the mono uppercase title, its hairline rule and the sans lead sit on
   top of it. The scrim is not decoration — it is the only thing giving the white
   text contrast, which is why `--scrim` has a floor.
   The block deliberately has no fixed height and no `overflow: hidden` on its
   text: an uppercase Finnish Ä or Ö carries a diaeresis above the cap line, and a
   clipped title is the failure this design invites. */
.hero {
  position: relative;
  display: flex;
  align-items: flex-end;
  min-height: 100svh;
  padding-block: calc(var(--header-inset) + var(--header-h) + var(--space-xl)) var(--space-xl);
  background: var(--color-deep);
  isolation: isolate;
}
.hero__media {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--color-deep); /* what the visitor sees while the photograph decodes */
}
.hero__media picture {
  /* The base reset leaves a picture at auto height, and a percentage height on the
     image inside then has nothing to resolve against — the photograph falls back to
     its own aspect ratio and covers only the top of a tall, narrow hero. */
  height: 100%;
}
.hero__art {
  width: 100%;
  height: 100%;
  /* Plain: no filter, no blend mode, no clip path. The photographs are asked to
     be good, not to be styled. */
  object-fit: cover;
}
.hero__media::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--scrim);
}
.hero__inner {
  width: 100%;
  max-width: var(--container-narrow);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.hero__body {
  min-width: 0; /* keep a long compound from blowing the column out */
  color: var(--color-on-media);
}
/* The small square that opens the hero, before the title. */
.hero__mark {
  display: block;
  width: 0.75rem;
  height: 0.75rem;
  margin-bottom: var(--space-m);
  background: var(--color-on-media);
}
.hero__kicker {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  margin-bottom: var(--space-s);
  font-family: var(--font-heading);
  font-size: var(--step-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--color-on-media);
}
/* Kickers open with a small filled square in the accent — a mark, not a dot. */
.hero__kicker::before,
.section__kicker::before,
.slab__kicker::before {
  content: '';
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  background: var(--color-primary);
}
.hero__title {
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: var(--step-hero);
  line-height: var(--leading-title);
  letter-spacing: var(--tracking-title);
  text-transform: uppercase;
  color: var(--color-on-media);
}
/* The hairline under the title, running the full column, with the coordinates
   sitting at its right end. */
.hero__rule {
  display: flex;
  justify-content: flex-end;
  margin-top: var(--space-m);
  padding-top: var(--space-2xs);
  border-top: 1px solid color-mix(in srgb, var(--color-on-media) 55%, transparent);
}
.hero__coords {
  font-family: var(--font-heading);
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--color-on-media-soft);
}
.hero__text {
  max-width: 46ch;
  margin-top: var(--space-m);
  color: var(--color-on-media-soft);
}
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-s);
  margin-top: var(--space-l);
}
/* An optional row of small monochrome marks along the hero's bottom edge —
   certifications or memberships the business actually holds, never another
   brand's logo without permission. */
.hero__strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-m);
  margin-top: var(--space-xl);
  padding-top: var(--space-s);
  border-top: 1px solid color-mix(in srgb, var(--color-on-media) 30%, transparent);
  list-style: none;
  padding-left: 0;
  font-family: var(--font-heading);
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--color-on-media-soft);
}
/* Inner pages open with the same treatment at half height, and without the strip. */
.hero--compact {
  min-height: 50svh;
}

/* ── Section scaffolding ─────────────────────────────────────────────────── */
.section {
  padding-block: var(--section-pad);
}
.section--alt {
  background: var(--color-surface-alt);
}
/* The statement band: the one ground brighter than the page surface, carrying a
   kicker and a single sentence about what the business does. */
.section--statement {
  background: var(--ground-bright);
}
.section--statement .section__lead {
  max-width: 28ch;
  font-size: var(--step-4);
  line-height: var(--leading-display);
}
.section__head {
  max-width: var(--measure);
  margin-bottom: var(--space-l);
}
/* The centred head used by the detail and tile sections. */
.section__head--centred {
  max-width: 56ch;
  margin-inline: auto;
  text-align: center;
}
.section__head--centred .section__kicker {
  justify-content: center;
}
.section__kicker {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  margin-bottom: var(--space-s);
  font-family: var(--font-heading);
  font-size: var(--step-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--color-muted);
}
.section__title {
  font-size: var(--step-5);
}
/* A small mono number after a section title, stating how many items follow. It is
   ornament and carries aria-hidden in the markup. */
.section__count {
  margin-left: var(--space-2xs);
  font-family: var(--font-heading);
  font-size: var(--step-label);
  letter-spacing: 0;
  vertical-align: super;
  color: var(--color-muted);
}
.section__lead {
  max-width: var(--measure);
  margin-top: var(--space-s);
}
/* A row of buttons closing a section. The hero has its own; this is the same row
   for every block below it, so no page has to borrow the hero's element. */
.section__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-s);
  margin-top: var(--space-l);
}
/* Ordinary running copy inside a section. */
.section__text {
  max-width: var(--measure);
  margin-top: var(--space-s);
}
/* Copy followed by a grid needs the section rung between them, or the row of
   pictures reads as part of the paragraph. */
.section__text + .tile-grid,
.section__text + .card-grid,
.section__text + .gallery,
.section__head + .tile-grid,
.section__head + .card-grid,
.section__head + .index,
.card-grid + .gallery,
.gallery + .card-grid {
  margin-top: var(--space-l);
}

/* ── Numbered index: the template's signature list ───────────────────────── */
/* Two-digit mono numbers, mono uppercase labels, one ink hairline under each row.
   Rows may be links; when they are, the whole row is the target and the label
   takes the accent under the pointer while the hairline stays put. */
.index {
  border-top: var(--border-hair);
}
.index__row {
  display: flex;
  align-items: baseline;
  gap: var(--space-m);
  min-height: 5.0625rem; /* 81px */
  padding-block: var(--space-m);
  border-bottom: var(--border-hair);
  color: inherit;
  text-decoration: none;
}
.index__number {
  flex: none;
  font-family: var(--font-heading);
  font-size: var(--step-label);
  font-variant-numeric: tabular-nums;
  color: var(--color-muted);
}
.index__label {
  min-width: 0;
  font-family: var(--font-heading);
  font-size: var(--step-index);
  line-height: 1.15;
  letter-spacing: -0.02em;
  text-transform: uppercase;
}
a.index__row:hover .index__label {
  color: var(--color-primary);
}

/* ── Dark slab: the one dark panel inside the light run ──────────────────── */
/* The ink ground is --color-text, so the slab and the body copy are the same
   colour playing two roles. At most one per page: twice and the page stripes.
   The accent is NOT placed on this ground without being checked against the ink
   rather than against the surface. */
.slab {
  overflow: hidden;
  border-radius: var(--radius-m);
  background: var(--color-text);
  color: var(--color-on-media);
}
.slab__inner {
  display: grid;
  gap: var(--space-l);
  padding: var(--space-l);
}
.slab__kicker {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  font-family: var(--font-heading);
  font-size: var(--step-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--color-on-media);
}
.slab__title {
  margin-top: var(--space-s);
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: var(--step-3);
  line-height: 1.2;
  text-transform: uppercase;
  color: var(--color-on-media);
}
.slab__text {
  max-width: 46ch;
  margin-top: var(--space-s);
  color: color-mix(in srgb, var(--color-on-media) 84%, var(--color-text));
}
/* The oversized two-digit number in the panel's corner: ornament, aria-hidden. */
.slab__number {
  display: block;
  margin-bottom: var(--space-s);
  font-family: var(--font-heading);
  font-size: var(--step-5);
  line-height: 1;
  letter-spacing: var(--tracking-title);
  color: color-mix(in srgb, var(--color-on-media) 30%, var(--color-text));
}
.slab__media {
  overflow: hidden;
  border-radius: var(--radius-s);
}
.slab__media img {
  width: 100%;
  aspect-ratio: 12 / 5;
  object-fit: cover;
}
@media (min-width: 56.25rem) {
  .slab__inner {
    padding: var(--space-xl);
  }
}

/* ── Photo tiles: square, sharp-cornered, labelled on a gradient foot ────── */
/* The one place in this template with no radius at all. Each tile carries its own
   gradient behind the label zone, so a white label holds 4.5:1 over any
   photograph rather than depending on the picture being dark where the text sits.
   Fill with real photographs of the business's own work; drop the grid below four
   usable images rather than padding it. */
.tile-grid {
  display: grid;
  gap: var(--space-s);
  grid-template-columns: 1fr;
}
@media (min-width: 30rem) {
  .tile-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 56.25rem) {
  .tile-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}
.tile {
  position: relative;
  min-width: 0;
  overflow: hidden;
  background: var(--color-text);
}
.tile__image {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
}
.tile__scrim {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 60%;
  background: linear-gradient(to bottom, transparent, var(--tile-foot) 58%, var(--tile-foot));
  pointer-events: none;
}
.tile__label {
  position: absolute;
  left: var(--space-s);
  right: var(--space-s);
  bottom: var(--space-s);
  font-family: var(--font-heading);
  font-size: var(--step-label);
  line-height: 1.2;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--color-on-media);
}

/* ── Cards ───────────────────────────────────────────────────────────────── */
/* The plain content card of the shared vocabulary: a hairline box at the small
   radius. It does not change under the pointer — nothing here lifts. */
.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 {
  min-width: 0;
  padding: var(--space-m);
  border: var(--border-soft);
  border-radius: var(--radius-s);
}
.card__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  margin-bottom: var(--space-s);
  border-radius: var(--radius-s);
  background: var(--color-primary-tint);
  color: var(--color-primary);
}
.card__title {
  margin-bottom: var(--space-2xs);
  font-size: var(--step-3);
}
.card__text {
  color: var(--color-text);
}
/* The reset at the top of this file removes every margin, so a card holding more
   than a title and one paragraph (a photo, a second paragraph, a line of links)
   would stack its pieces flush against each other. This spaces each piece from the
   one above it. The gap equals the title's own bottom margin, and the two overlap
   rather than add up, so the space under a title stays as it was. */
.card > * + * {
  margin-top: var(--space-2xs);
}
/* The icon is an inline box, so its bottom margin does not overlap the next
   piece's top margin. It already sets the space beneath it, so the piece that
   follows adds none. */
.card__icon + * {
  margin-top: 0;
}

/* ── Gallery ─────────────────────────────────────────────────────────────── */
/* The column count follows the number of pictures rather than a breakpoint
   ladder, so a gallery holding one photograph is one full-width photograph
   instead of a third of a row with two empty thirds beside it. */
.gallery {
  display: grid;
  gap: var(--space-s);
  grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr) /* never wider than the container, so a 320px phone gets one full-width column */);
}
.gallery__item {
  overflow: hidden;
  border-radius: var(--radius-l);
  background: var(--color-primary-tint);
}
/* The link wraps the whole picture, so it has to be a block. An inline link keeps
   the space a text line reserves under its letters, which would make the item
   taller than the photograph inside it. */
.gallery__link {
  display: block;
}
/* Only when the script is running does a click open the picture, so only then
   does the pointer promise it. */
.js .gallery__link {
  cursor: zoom-in;
}
.gallery__image {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}
/* Mono uppercase under the picture, like every other label on the page. This
   template's own pages carry no caption, but the slot is styled so a site can add
   one without writing new CSS. */
.gallery__caption {
  padding: var(--space-xs) var(--space-s);
  font-family: var(--font-heading);
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--color-muted);
}
/* A gallery holding one picture runs the full row and takes the wide-panel ratio:
   at 3:2 a full-width photograph would be nearly a screen tall. Written as a class
   rather than inferred from the picture being the item's only sibling, so the
   markup says what it is — a site with
   a one-picture gallery adds the class when it takes this version. */
.gallery--single .gallery__item {
  grid-column: 1 / -1;
}
.gallery--single .gallery__image {
  aspect-ratio: 12 / 5;
}

/* ── Lightbox ────────────────────────────────────────────────────────────── */
/* main.js builds this dialog the first time a visitor opens a gallery picture.
   The dialog carries no frame of its own: the photograph is the content and the
   darkened page behind it is the frame. Every control is a visible word, so the
   template needs no icon font and the labels follow the site's own language. */
.lightbox {
  width: 100vw;
  max-width: 100vw;
  height: 100vh;
  max-height: 100vh;
  padding: 0;
  border: 0;
  /* The dialog paints the dark ground itself rather than leaving it to the
     backdrop alone: it fills the viewport, and a browser that does not paint
     ::backdrop would otherwise show the light page straight under the caption. */
  background: rgba(10, 0, 23, 0.9);
  color: var(--color-on-media);
  overflow: hidden;
}
.lightbox::backdrop {
  background: rgba(10, 0, 23, 0.9);
}
.lightbox__figure {
  margin: 0;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  /* The picture keeps clear of the chrome around it: the close button above, and
     the counter with the previous/next pair below. */
  padding: 4rem var(--space-s) 7.5rem;
}
.lightbox__image {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  min-height: 0; /* lets the picture shrink so the caption always fits under it */
  object-fit: contain;
  border-radius: var(--radius-s);
}
.lightbox__caption {
  flex: 0 0 auto;
  max-width: 60ch;
  text-align: center;
  font-size: var(--step--1);
  color: var(--color-on-media);
}
.lightbox__close,
.lightbox__prev,
.lightbox__next {
  position: fixed;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 3rem;
  min-height: 3rem; /* the same comfortable tap target the page's buttons keep */
  padding: var(--space-3xs) var(--space-xs);
  font-family: var(--font-heading);
  font-size: var(--step-label);
  line-height: 1.2;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--color-text);
  background: var(--color-surface);
  border: 1px solid transparent;
  border-radius: var(--radius-s);
  cursor: pointer;
}
.lightbox__close {
  top: var(--space-s);
  right: var(--space-s);
}
.lightbox__prev,
.lightbox__next {
  top: 50%;
  transform: translateY(-50%);
}
.lightbox__prev {
  left: var(--space-s);
}
.lightbox__next {
  right: var(--space-s);
}
.lightbox__count {
  position: fixed;
  left: 50%;
  bottom: var(--space-s);
  transform: translateX(-50%);
  font-family: var(--font-heading);
  font-size: var(--step-label);
  letter-spacing: var(--tracking-label);
  color: var(--color-on-media);
}
/* On a phone there is no room beside the photograph, so the pair moves under it,
   side by side, and the counter rises above them. */
@media (max-width: 39.99em) {
  .lightbox__prev,
  .lightbox__next {
    top: auto;
    bottom: var(--space-s);
    transform: none;
  }
  .lightbox__prev {
    left: auto;
    right: 50%;
    margin-right: var(--space-2xs);
  }
  .lightbox__next {
    right: auto;
    left: 50%;
    margin-left: var(--space-2xs);
  }
  .lightbox__count {
    bottom: calc(var(--space-s) + 3.5rem);
  }
}
@media (min-width: 40em) {
  .lightbox__figure {
    /* Previous and next sit at the sides on a wide screen, so the picture leaves
       them a column of their own. */
    padding: 4rem 9rem 5rem;
  }
}
@media (prefers-reduced-motion: no-preference) {
  .lightbox[open] {
    animation: lightbox-in var(--dur-hover) ease-out;
  }
  @keyframes lightbox-in {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }
}
/* The only thing holding the page still behind the open dialog — the script
   deliberately scrolls nothing itself. It is paired with the stable scrollbar
   gutter in the reset, so the page does not shift sideways as the bar goes. */
html.lightbox-open {
  overflow: hidden;
}

/* ── Price cards ─────────────────────────────────────────────────────────── */
/* One card per package, for a business that publishes prices; a business that
   does not leaves the section out rather than filling it with vague cards. Each
   card ends with exactly one action pinned to the bottom, so the buttons line up
   across cards whose lists are of different lengths. */
.plan-grid {
  display: grid;
  gap: var(--space-s);
  grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr) /* never wider than the container, so a 320px phone gets one full-width column */);
  margin-top: var(--space-l);
}
.plan {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-m);
  background: var(--color-surface);
  border: var(--border-soft);
  border-radius: var(--radius-s);
}
/* The highlighted card is the pale brand wash inside an ink-weight brand frame.
   The accent is never body text on a light ground in this template, so the
   highlight lives in the surface and the border, and the badge names it in
   words. */
.plan--featured {
  background: var(--color-primary-tint);
  border: 1px solid var(--color-primary);
}
.plan__badge {
  align-self: flex-start;
  padding: var(--space-3xs) var(--space-xs);
  font-family: var(--font-heading);
  font-size: var(--step-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--color-on-primary);
  background: var(--color-primary);
  border-radius: var(--radius-s);
}
.plan__name {
  font-family: var(--font-body);
  font-size: var(--step-2);
  line-height: var(--leading-display);
}
.plan__price {
  /* The figure is set in the mono, like every other number on the page, and at
     the weight the page already loads: a heavier weight would pull a third font
     file onto a page whose largest paint is already its photograph. The opening
     statement step is too wide for a card this narrow — a five-figure sum pushed
     the period onto a line of its own — so the figure takes the step below it. */
  font-family: var(--font-heading);
  font-size: var(--step-3);
  line-height: var(--leading-display);
  /* A price is as often a phrase as a figure ("Sopimuksen mukaan"), and at
     display size one long word in a narrow card would widen the whole page
     instead of wrapping inside the card. */
  overflow-wrap: break-word;
  hyphens: auto;
  -webkit-hyphens: auto;
}
/* The prefix and the period read as one phrase with the figure. Kept from
   breaking inside themselves, so a narrow card wraps "/ kk" as a unit instead of
   stranding the slash at the end of the line above. */
.plan__prefix,
.plan__period {
  white-space: nowrap;
  font-family: var(--font-heading);
  font-size: var(--step-0);
  letter-spacing: var(--tracking-label);
  color: var(--color-muted);
}
.plan__list {
  list-style: none;
  padding: 0;
  display: grid;
  gap: var(--space-3xs);
  color: var(--color-muted);
}
.plan__action {
  margin-top: auto;
  padding-top: var(--space-xs);
}

/* ── Testimonial: the pale plate ─────────────────────────────────────────── */
/* Photograph on the left, the quote on the right in the mono — the one
   sentence-case mono setting on the page — with the quote mark in the accent and
   a mono uppercase attribution under it. */
.testimonial {
  display: grid;
  gap: var(--space-l);
  padding: var(--space-l);
  border-radius: var(--radius-s);
  background: var(--color-surface-alt);
}
.testimonial__media {
  overflow: hidden;
  border-radius: var(--radius-s);
  background: var(--color-primary-tint);
}
.testimonial__image {
  width: 100%;
  aspect-ratio: 9 / 10;
  object-fit: cover;
}
.testimonial__quote {
  font-family: var(--font-heading);
  font-size: var(--step-3);
  line-height: 1.4;
}
/* The one non-interactive use of the accent this template allows, alongside the
   kicker squares. */
.testimonial__quote::before {
  content: '\201C';
  display: block;
  font-size: var(--step-5);
  line-height: 0.8;
  color: var(--color-primary);
}
.testimonial__author {
  display: block;
  margin-top: var(--space-m);
  font-family: var(--font-heading);
  font-style: normal;
  font-size: var(--step-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--color-muted);
}
@media (min-width: 56.25rem) {
  .testimonial {
    grid-template-columns: 0.6fr 1fr;
    align-items: center;
    padding: var(--space-xl);
  }
}

/* ── FAQ ─────────────────────────────────────────────────────────────────── */
.faq {
  max-width: 52rem;
  border-top: var(--border-hair);
}
.faq__item {
  padding-block: var(--space-m);
  border-bottom: var(--border-hair);
}
.faq__question {
  margin-bottom: var(--space-2xs);
  font-size: var(--step-3);
}
.faq__answer {
  max-width: var(--measure);
}
/* An answer longer than one paragraph (two paragraphs, or a paragraph and a list)
   needs the same spacing as a card: with every margin reset, its pieces would run
   together. */
.faq__answer > * + * {
  margin-top: var(--space-2xs);
}

/* ── 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-family: var(--font-heading);
  font-size: var(--step-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
}
.contact-form__input,
.contact-form__textarea {
  width: 100%;
  padding: 0.75rem 1rem;
  border: var(--border-hair);
  border-radius: var(--radius-s);
  background: var(--ground-bright);
}
.contact-form__input:focus,
.contact-form__textarea:focus {
  outline: 2px solid var(--color-primary);
  outline-offset: 0;
  border-color: var(--color-primary);
}
.contact-form__textarea {
  min-height: 8rem;
  resize: vertical;
}

/* ── Footer: an inset rounded plate ──────────────────────────────────────── */
/* The plate is inset from the page edges rather than running to them, so the page
   closes as an object instead of bleeding off the screen. */
.site-footer {
  padding: var(--space-s);
}
.site-footer__inner {
  display: grid;
  gap: var(--space-l);
  padding: var(--space-l) var(--gutter);
  border-radius: var(--radius-m);
  background: var(--color-surface-alt);
}
.site-footer__heading {
  margin-bottom: var(--space-s);
  font-family: var(--font-heading);
  font-size: var(--step-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--color-muted);
}
.site-footer__text {
  color: var(--color-text);
}
.site-footer__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-s);
  margin-top: var(--space-m);
}
/* The link directory, capped at three columns — a small firm's whole site fits. */
.site-footer__directory {
  display: grid;
  gap: var(--space-m);
  grid-template-columns: 1fr;
}
@media (min-width: 30rem) {
  .site-footer__directory {
    grid-template-columns: repeat(3, 1fr);
  }
}
.site-footer__column {
  min-width: 0;
}
.site-footer__link {
  display: block;
  padding: var(--space-3xs) 0;
  color: var(--color-text);
  text-decoration: none;
  /* An email address is one unbreakable word and a directory column is narrow. */
  overflow-wrap: anywhere;
}
.site-footer__link:hover {
  color: var(--color-primary);
}
.site-footer__legal {
  margin-top: var(--space-l);
  padding-top: var(--space-m);
  border-top: var(--border-soft);
  font-family: var(--font-heading);
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--color-muted);
}
@media (min-width: 48rem) {
  .site-footer__inner {
    grid-template-columns: 1fr 1.4fr;
    align-items: start;
    padding: var(--space-xl);
  }
  .site-footer__legal {
    grid-column: 1 / -1;
  }
}

/* ── Legal page ──────────────────────────────────────────────────────────── */
/* Pages without a hero still have to clear the fixed bar, so they open below it. */
.legal {
  max-width: 68ch;
  padding-block: calc(var(--header-inset) + var(--header-h) + var(--space-xl)) var(--section-pad);
}
.legal__title {
  margin-bottom: var(--space-l);
}
.legal__body > * + * {
  margin-top: var(--space-s);
}
.legal__body h2 {
  margin-top: var(--space-l);
  font-size: var(--step-4);
}

/* ── 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-m);
  border-radius: var(--radius-m);
  background: var(--color-text);
  color: var(--color-on-media);
  box-shadow: var(--shadow-float);
}
.cookie-banner__text {
  flex: 1 1 16rem;
  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-family: var(--font-heading);
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
}

/* 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;
  gap: var(--space-s);
  min-height: 60vh;
  align-content: center;
  padding-block: calc(var(--header-inset) + var(--header-h) + var(--space-xl)) var(--section-pad);
}
.error-page__code {
  font-family: var(--font-heading);
  font-size: var(--step-5);
  line-height: 1;
  letter-spacing: var(--tracking-title);
  color: var(--color-muted);
}
.error-page__title {
  font-size: var(--step-4);
}
.error-page__text {
  max-width: 52ch;
}
.error-page__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-s);
  margin-top: var(--space-m);
}

/* ── 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;
  border: var(--border-soft);
  border-radius: var(--radius-s);
  background: var(--color-surface-alt);
}
@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;
  font-size: var(--step--1);
  color: var(--color-muted);
}
/* 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 ──────────────────────────────────────────────────────────────── */
/* The entire motion budget is here, and it is two things: a colour or opacity
   change on hover, and one below-fold reveal — a fade with a small rise. There is
   no parallax and no ambient layer, and this template ships no second script.
   Under a reduced-motion preference none of these rules is delivered: revealed
   blocks are at their final opacity from the first frame, and hover colours change
   instantly. Nothing in the layout depends on motion having run.
   The reveal never goes above the fold: the hero photograph is the Largest
   Contentful Paint element on every page and must paint complete. */
@media (prefers-reduced-motion: no-preference) {
  .js [data-reveal] {
    opacity: 0;
    transform: translateY(var(--reveal-rise));
    transition: opacity var(--dur-reveal) ease-out, transform var(--dur-reveal) ease-out;
  }
  .js [data-reveal].is-visible {
    opacity: 1;
    transform: none;
  }

  a,
  .button,
  .nav__link,
  .index__label,
  .site-footer__link {
    transition: color var(--dur-hover) linear, background-color var(--dur-hover) linear,
      opacity var(--dur-hover) linear;
  }
}

/* ── 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.
 * Two additions are specifically out of bounds: a second saturated colour
 * anywhere, and a box-shadow on content. The only shadow token is
 * --shadow-float, and it belongs to fixed overlays. */

/* Detect disabled scripting before first paint; a delayed script must not collapse a tall header. */
@media (max-width: 47.99rem) and (scripting: none) {
  html:not(.js) .site-header { position: relative; }
  html:not(.js) .site-header__inner { flex-wrap: wrap; }
  html:not(.js) .site-header nav { width: 100%; }
  html:not(.js) .nav__toggle { display: none; }
  html:not(.js) .nav__menu {
    display: flex;
    position: static;
    flex-direction: column;
    align-items: stretch;
    width: 100%;
    min-width: 0;
    box-shadow: none;
  }
  html:not(.js) .nav__link { white-space: normal; overflow-wrap: anywhere; }
}

/* ── Site additions (not part of the template) ── */
/* Every label here carries the source's own wording; the template's uppercase
   setting would change the rendered words, so they keep the source's case. */
.skip-link,
.button,
.nav__link,
.hero__kicker,
.hero__title,
.section__kicker,
.index__label,
.testimonial__author,
.site-footer__heading,
.site-footer__legal,
.demo-banner {
  text-transform: none;
}
/* The source's own wide logo (439×93, light lettering on transparent) on the dark bar,
   at the bar's logo height and its own proportions. */
.site-header__logo--wide {
  width: auto;
  height: 1.75rem;
  border-radius: 0;
}
/* Each service row carries the source's description under its label. */
.index__body {
  min-width: 0;
}
.index__body > p {
  margin-top: var(--space-2xs);
  color: var(--color-muted);
}
/* The source states that no photographs have been published yet: its notice boxes
   keep their place in the gallery on a solid ground. */
.placeholder-media {
  display: grid;
  place-items: center;
  aspect-ratio: 3 / 2;
  padding: var(--space-m);
  background: var(--color-surface);
  color: var(--color-muted);
  text-align: center;
}
/* No customer quote has been published: the plate carries the source's notice alone. */
@media (min-width: 56.25rem) {
  .testimonial--text {
    grid-template-columns: 1fr;
  }
}
.testimonial--text .section__lead {
  margin: 0;
}
.contact-lines {
  margin-top: var(--space-m);
  display: grid;
  gap: var(--space-3xs);
}
/* The demo banner is fixed to the bottom edge; reserve its height so it never covers
   the footer's last line. */
body:has(> .demo-banner) {
  padding-bottom: 5rem;
}
