/* ==========================================================================
   SITE CONSTRUCT WA — landing page
   --------------------------------------------------------------------------
   Colour contract (verified with WCAG matrix, do not improvise pairings):
     text  #f3efe6 on bg #121417 ......... 16.08:1  text-safe
     text  #f3efe6 on surface #1b1e24 .... 14.55:1  text-safe
     ochre #f43e04 on bg #121417 .........  4.87:1  text-safe
     ochre #f43e04 on surface #1b1e24 ....  4.40:1  UI-SAFE ONLY — not body text
     soft  #ff6a33 on surface #1b1e24 ....  5.85:1  text-safe  <-- use on cards
     ink   #121417 on ochre #f43e04 ......  4.87:1  text-safe  <-- button labels
   RULE: orange TEXT on a graphite surface must be --ochre-soft, never --ochre.

   Photography: Site Construct's own project archive (Benchmark, Atlas Pearls,
   Emergency Vet Wangara, Dental Circle, Spearwood Medical, VEOLIA, NBN, JDL).
   ========================================================================== */

@layer tokens, base, layout, motion, components, utils;

/* ========================================================================== */
@layer tokens {

  /* Typed custom properties — these are what make gradients/angles animatable */
  @property --dial      { syntax: "<percentage>"; inherits: true;  initial-value: 0%;   }
  @property --sweep     { syntax: "<percentage>"; inherits: false; initial-value: 0%;   }
  @property --glow      { syntax: "<percentage>"; inherits: false; initial-value: 0%;   }

  :root {
    /* Brand — exact values, sampled from the logo. Not derived, not "improved". */
    --ink:        #121417;
    --graphite:   #1b1e24;
    --graphite-2: #23272f;
    --bone:       #f3efe6;
    --bone-dim:   #c7c3ba;
    --muted:      #8b8f96;
    --ochre:      #f43e04;
    --ochre-soft: #ff6a33;

    /* Derived tints — color-mix keeps them locked to the brand hue */
    --line:        color-mix(in oklab, var(--bone) 12%, transparent);
    --line-strong: color-mix(in oklab, var(--bone) 26%, transparent);
    --ochre-wash:  color-mix(in oklab, var(--ochre) 14%, transparent);
    --ochre-edge:  color-mix(in oklab, var(--ochre) 42%, transparent);
    --surface-up:  color-mix(in oklab, var(--bone) 4%, var(--graphite));
    --scrim:       color-mix(in srgb, var(--ink) 82%, transparent);

    /* Sector accents — a hue per sector, held in oklch so lightness stays even */
    --sec-industrial: oklch(64% 0.14  58);
    --sec-retail:     oklch(64% 0.13  32);
    --sec-office:     oklch(66% 0.09 232);
    --sec-defence:    oklch(62% 0.07 150);
    --sec-medical:    oklch(68% 0.10 196);
    --sec-care:       oklch(66% 0.10 300);
    --sec-food:       oklch(70% 0.12  88);
    --sec-land:       oklch(63% 0.15  42);
    --sec-mixed:      oklch(66% 0.12 340);

    /* Type — industrial grotesque system. Archivo carries display AND body,
       with weight (700 vs 300) doing the contrast; Plex Mono holds the
       technical register. Replaces the earlier Fraunces serif. */
    --font-display:   "Archivo", "Helvetica Neue", Arial, sans-serif;
    --font-interface: "Archivo", system-ui, -apple-system, sans-serif;
    --font-technical: "IBM Plex Mono", ui-monospace, Consolas, monospace;
    --font-eyebrow:   "Archivo Expanded", "Archivo", var(--font-interface);

    /* Fluid type scale */
    --step--1: clamp(0.80rem, 0.77rem + 0.15vw, 0.88rem);
    --step-0:  clamp(1.00rem, 0.96rem + 0.20vw, 1.12rem);
    --step-1:  clamp(1.25rem, 1.15rem + 0.50vw, 1.55rem);
    --step-2:  clamp(1.60rem, 1.35rem + 1.20vw, 2.30rem);
    --step-3:  clamp(2.05rem, 1.55rem + 2.40vw, 3.40rem);
    --step-4:  clamp(2.60rem, 1.60rem + 4.60vw, 5.40rem);

    /* Spacing scale — pick from this, never improvise a padding */
    --space-1: 0.25rem;  --space-2: 0.5rem;   --space-3: 0.75rem;
    --space-4: 1rem;     --space-6: 1.5rem;   --space-8: 2rem;
    --space-12: 3rem;    --space-16: 4rem;    --space-24: 6rem;
    --space-32: 8rem;    --space-48: 12rem;

    --measure: 62ch;
    --max: 1280px;
    --radius: 2px;          /* commercial construction: square, not soft */

    /* Motion */
    --ease-out:    cubic-bezier(.22,.61,.36,1);
    --d-press: 120ms;
    --d-hover: 160ms;
    --d-panel: 240ms;

    color-scheme: dark;
  }
}

/* ========================================================================== */
@layer base {

  *, *::before, *::after { box-sizing: border-box; }

  html {
    -webkit-text-size-adjust: 100%;
    scroll-behavior: smooth;
    /* keep anchored sections clear of the fixed masthead */
    scroll-padding-top: 5.5rem;
  }
  @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

  body {
    margin: 0;
    background: var(--ink);
    color: var(--bone);
    font-family: var(--font-interface);
    font-size: var(--step-0);
    font-weight: 300;
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    overflow-x: hidden;
  }

  h1, h2, h3 {
    font-family: var(--font-display);
    font-weight: 700;
    line-height: 1.0;
    letter-spacing: -0.035em;
    margin: 0;
    text-wrap: balance;
  }
  h1 { font-size: var(--step-4); }
  h2 { font-size: var(--step-3); }
  h3 { font-size: var(--step-1); letter-spacing: -0.01em; }

  p { margin: 0; text-wrap: pretty; max-width: var(--measure); }

  a { color: inherit; text-decoration-color: var(--ochre-edge); text-underline-offset: 0.25em; }

  img, svg { display: block; max-width: 100%; }
  img { height: auto; }

  ul { margin: 0; padding: 0; list-style: none; }

  ::selection { background: var(--ochre); color: var(--ink); }

  :focus-visible {
    outline: 2px solid var(--ochre-soft);
    outline-offset: 3px;
    border-radius: var(--radius);
  }

  /* Screen-reader-only, still focusable */
  .sr {
    position: absolute; width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap;
  }
  .skip {
    position: absolute; top: var(--space-2); left: var(--space-2);
    z-index: 100; padding: var(--space-3) var(--space-4);
    background: var(--ochre); color: var(--ink);
    font-family: var(--font-technical); font-size: var(--step--1);
    transform: translateY(-200%);
  }
  .skip:focus-visible { transform: none; }
}

/* ========================================================================== */
@layer layout {

  .shell {
    width: min(100% - 2.5rem, var(--max));
    margin-inline: auto;
  }

  /* Section rhythm is weighted by role — pivotal sections get more air. Tightened 20/09/2026:
     two major sections back to back were putting 256-294px of empty dark between the last line
     of one and the first of the next, which the owner kept hitting as dead space. */
  .section        { padding-block: var(--space-16); }
  .section--major { padding-block: var(--space-24); }
  .section--tight { padding-block: var(--space-12); }

  .rule {
    height: 1px; border: 0; margin: 0;
    background: var(--line);
  }

  .eyebrow {
    font-family: var(--font-eyebrow);
    font-weight: 700;
    font-size: var(--step--1);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ochre-soft);
    margin: 0 0 var(--space-4);
    display: flex; align-items: center; gap: var(--space-3);

    &::before {
      content: ""; inline-size: var(--space-8); block-size: 2px;
      background: var(--ochre); flex: none;
    }
  }

  .lede {
    font-size: var(--step-1);
    color: var(--bone-dim);
    font-weight: 300;
    margin-top: var(--space-6);
  }

  .section-head { margin-bottom: var(--space-16); }
}

/* ==========================================================================
   MOTION — scroll-driven, running on the compositor, no JavaScript.
   Everything degrades to the finished state when unsupported or when the
   visitor asks for reduced motion. Nothing is hidden by default.
   ========================================================================== */
@layer motion {

  @keyframes reveal-in   { from { opacity: 0; transform: translateY(18px); } }
  @keyframes reveal-slide{ from { opacity: 0; transform: translateX(-24px); } }
  @keyframes wipe-in     { from { clip-path: inset(0 100% 0 0); } }
  /* grow-x / grow-y drive LINE DRAWS (the scroll-progress bar and the
     lifecycle spine), not element entrances. A rule drawing itself from
     zero length is the intended effect, and transform keeps it on the
     compositor. Deliberate override of the usual zero-scale rule. */
  @keyframes grow-x      { from { transform: scaleX(0); } }
  @keyframes grow-y      { from { transform: scaleY(0); } }
  @keyframes dial-round  { to   { --dial: 100%; } }
  @keyframes dial-ping   { 0% { transform: scale(.62); opacity: 0; }
                           35% { opacity: .85; }
                          100% { transform: scale(1.55); opacity: 0; } }
  @keyframes sweep-across{ to   { --sweep: 100%; } }
  @keyframes lift-glow   { to   { --glow: 100%; } }
  @keyframes hail-in     { to   { transform: none; } }

  @supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {

      /* --- reading-order reveals ------------------------------------- */
      .reveal {
        animation: reveal-in linear both;
        animation-timeline: view();
        animation-range: entry 14% cover 52%;
      }
      .reveal-x {
        animation: reveal-slide linear both;
        animation-timeline: view();
        animation-range: entry 14% cover 54%;
      }
      /* stagger children off a single timeline, no JS delays needed */
      .stagger > * {
        animation: reveal-in linear both;
        animation-timeline: view();
        animation-range: entry 10% cover 46%;
      }
      .stagger > :nth-child(2) { animation-range: entry 14% cover 50%; }
      .stagger > :nth-child(3) { animation-range: entry 18% cover 54%; }
      .stagger > :nth-child(4) { animation-range: entry 22% cover 58%; }
      .stagger > :nth-child(5) { animation-range: entry 26% cover 62%; }
      .stagger > :nth-child(6) { animation-range: entry 30% cover 66%; }

      /* --- rules that draw themselves -------------------------------- */
      .rule--draw {
        transform-origin: left;
        animation: grow-x linear both;
        animation-timeline: view();
        animation-range: entry 0% cover 24%;
      }

      /* --- masthead scroll progress ---------------------------------- */
      .progress {
        transform-origin: left;
        animation: grow-x linear both;
        animation-timeline: scroll(root block);
      }

      /* --- lifecycle spine draws as the pinned scene plays ------------ */
      .spine__fill {
        transform-origin: top;
        animation: grow-y linear both;
        animation-timeline: view(block);
        animation-range: contain 0% contain 100%;
      }

      /* --- stage dials fill on entry (animating a typed custom prop) -- */
      .stage__dial {
        animation: dial-round linear both;
        animation-timeline: view();
        animation-range: entry 20% cover 40%;
      }
      /* one ring pings outward as the dial completes — transform+opacity
         only, so it stays on the compositor */
      .stage__ping {
        animation: dial-ping linear both;
        animation-timeline: view();
        animation-range: entry 34% cover 52%;
      }

      /* --- image wipe reveals ---------------------------------------- */
      .wipe {
        animation: wipe-in linear both;
        animation-timeline: view();
        animation-range: entry 10% cover 40%;
      }

    }
  }

  /* Honest fallback: no scroll-timeline support, or reduced motion, means
     every element above is simply in its finished state — nothing is lost. */
}

/* ========================================================================== */
@layer components {

  /* --- masthead ---------------------------------------------------------- */
  .masthead {
    position: fixed; inset-block-start: 0; inset-inline: 0; z-index: 50;
    background: color-mix(in srgb, var(--ink) 72%, transparent);
    border-block-end: 1px solid var(--line);
    backdrop-filter: blur(14px) saturate(1.4);
    -webkit-backdrop-filter: blur(14px) saturate(1.4);
  }
  .masthead__bar {
    display: flex; align-items: center; gap: var(--space-8);
    padding-block: var(--space-4);
  }
  .masthead__logo { flex: none; }
  .masthead__logo img { inline-size: clamp(140px, 15vw, 190px); }

  .navlinks {
    margin-inline-start: auto;
    display: flex; gap: var(--space-6); align-items: center;
    font-size: var(--step--1); letter-spacing: 0.02em;
  }
  .navlinks a {
    text-decoration: none; color: var(--bone-dim);
    transition: color var(--d-hover) var(--ease-out);
    &:hover { color: var(--bone); }
  }
  @media (max-width: 860px) { .navlinks { display: none; } }

  /* Compact nav — a native <details> disclosure, so it works with no JS
     and is keyboard-operable out of the box. */
  .navmenu { display: none; margin-inline-start: auto; }
  @media (max-width: 860px) { .navmenu { display: block; } }

  .navmenu summary {
    list-style: none; cursor: pointer;
    font-family: var(--font-technical); font-size: var(--step--1);
    letter-spacing: 0.08em; text-transform: uppercase; color: var(--bone);
    border: 1px solid var(--line-strong);
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius);
    transition: border-color var(--d-hover) var(--ease-out);
  }
  .navmenu summary::-webkit-details-marker { display: none; }
  .navmenu summary::after { content: " +"; color: var(--ochre-soft); }
  .navmenu[open] summary { border-color: var(--ochre-edge); }
  .navmenu[open] summary::after { content: " ×"; }

  .navmenu__panel {
    position: absolute; inset-inline: 0; inset-block-start: 100%;
    background: var(--ink);
    border-block-end: 1px solid var(--line);
    display: grid; gap: var(--space-1);
    padding: var(--space-4) 1.25rem var(--space-6);
  }
  .navmenu__panel a {
    text-decoration: none; color: var(--bone-dim);
    padding: var(--space-3) 0;
    border-block-end: 1px solid var(--line);
    font-size: var(--step-0);
  }
  .navmenu__panel a:hover { color: var(--bone); }

  .progress {
    block-size: 2px; inline-size: 100%;
    background: linear-gradient(90deg, var(--ochre), var(--ochre-soft));
  }

  /* --- buttons ----------------------------------------------------------- */
  .btn {
    display: inline-flex; align-items: center; gap: var(--space-3);
    padding: var(--space-4) var(--space-8);
    font-family: var(--font-technical);
    font-size: var(--step--1); letter-spacing: 0.06em; text-transform: uppercase;
    text-decoration: none; border-radius: var(--radius);
    border: 1px solid transparent; cursor: pointer;
    transition: background var(--d-hover) var(--ease-out),
                border-color var(--d-hover) var(--ease-out),
                color var(--d-hover) var(--ease-out);
  }
  /* ink on ochre = 4.87:1, a legal text pairing */
  .btn--primary {
    background: var(--ochre); color: var(--ink); font-weight: 500;
    &:hover { background: var(--ochre-soft); }
    &:active { transform: translateY(1px); transition-duration: var(--d-press); }
  }
  .btn--ghost {
    border-color: var(--line-strong); color: var(--bone);
    &:hover { border-color: var(--bone); background: color-mix(in oklab, var(--bone) 6%, transparent); }
  }

  /* --- hero -------------------------------------------------------------- */
  /* Full-bleed hero: photograph behind, headline over the top. A gradient
     scrim guarantees the type sits on near-solid ink (16:1) rather than
     depending on what happens to be in the frame behind it. */
  .hero {
    position: relative;
    min-block-size: 100svh;
    display: grid; align-items: end;
    overflow: clip;
    isolation: isolate;
  }
  .hero__media {
    position: absolute; inset: 0; z-index: -2;
    overflow: clip;
  }
  .hero__img {
    inline-size: 100%; block-size: 100%;
    object-fit: cover;
    /* holds the lit doorway and figures in frame as the box narrows */
    object-position: 58% 58%;
  }
  .hero__scrim {
    position: absolute; inset: 0; z-index: -1;
    background:
      linear-gradient(100deg,
        var(--ink) 0%,
        var(--scrim) 36%,
        color-mix(in srgb, var(--ink) 30%, transparent) 64%,
        color-mix(in srgb, var(--ink) 58%, transparent) 100%),
      linear-gradient(to top, var(--ink) 2%, transparent 46%);
  }
  .hero__inner { padding-block: var(--space-24) var(--space-16); }
  .hero__title {
    max-inline-size: 17ch;
    & em {
      font-style: normal;
      color: var(--ochre);          /* 4.87:1 on ink — legal */
      display: block;
    }
  }
  .hero__lede { margin-top: var(--space-6); max-inline-size: 46ch; }
  .hero__actions {
    margin-top: var(--space-12);
    display: flex; flex-wrap: wrap; gap: var(--space-4);
  }

  /* credentials band — lifted out of the hero so the hero always fits the
     viewport and the aerial is not cropped by an over-tall box */
  .statband { border-block-end: 1px solid var(--line); }
  .hero__stats {
    display: grid; gap: var(--space-8);
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  }
  .stat__n {
    font-family: var(--font-display);
    font-weight: 700; letter-spacing: -0.03em;
    font-size: var(--step-2); line-height: 1;
    color: var(--bone);
  }
  .stat__l {
    font-family: var(--font-technical);
    font-size: var(--step--1); color: var(--muted);
    letter-spacing: 0.04em; margin-top: var(--space-2);
  }

  /* --- the gap / problem beat -------------------------------------------- */
  /* Two stacked images in one column of a split, each sliding in on its own (20/09/2026).
     The column used to hold one photograph and a lot of empty dark below it. */
  .split__stack { display: grid; gap: var(--space-6); align-content: start; }

  /* The lifecycle stages: every stage's name, body and tag arrives from the right as you come
     down the spine, and the type is a point larger throughout (owner, 20/09/2026). The dial and
     the spine keep their own entry animation - only the words move sideways. */
  .stage__name { font-size: calc(var(--step-2) + 0.085rem); }
  .stage__body { font-size: calc(var(--step-0) + 0.085rem); }
  .stage__tag  { font-size: calc(var(--step--1) + 0.085rem); }

  /* The cost benchmarks head: the eyebrow comes in from the left at the page's normal pace,
     the title from the right and deliberately slower than the ticker running beneath it
     (owner, 20/09/2026). Slower here means a longer range - the same travel spread over more
     scroll, so it is still arriving while the eyebrow above it has settled. */
  @supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
      .costs__title.slide-r { animation-range: entry 0% cover 88%; }
    }
  }

  /* "Built in Western Australia." - the two halves arrive from opposite sides, and they do it
     high up, in the band of space above the heading rather than down at the fold (owner,
     20/09/2026). "Built in" carries the brand orange, italic and a size up. */
  .built { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 0.34em; }
  .built__a {
    color: var(--ochre);              /* 4.87:1 on ink - large display text, legal */
    font-style: italic;
    font-size: 1.16em;
    /* the italic leans right and the last letter overhung the next word, which a flex gap
       alone does not clear because the slant sits outside the glyph box */
    padding-inline-end: 0.1em;
  }
  @supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
      /* earlier than the page default so the two halves meet while the heading is still
         in the upper part of the screen, where the owner pointed */
      .built__a, .built__b { animation-range: entry 6% cover 34%; }
    }
  }

  /* ---- setout: the hero's survey boundary, reused as a border -------------
     Four edges that draw themselves clockwise off the scroll, with a peg landing at each
     corner as the line reaches it. Same orange, same idea as the pegs and string line in the
     film. No script and nothing extra to load; with reduced motion or without scroll-driven
     animation support the boundary is simply drawn, which is the honest fallback. */
  .setout {
    position: relative;
    padding: var(--space-12) var(--space-8);
    /* One timeline for the whole boundary. Without this each edge gets its own view()
       timeline from its own box - and the bottom edge sits 800px below the top one, so it
       was running on a completely different clock and never drew. */
    view-timeline-name: --setout;
    view-timeline-axis: block;
  }
  .setout__edge {
    position: absolute; background: var(--ochre); pointer-events: none;
  }
  .setout__edge--t, .setout__edge--b { block-size: 2px; inset-inline: 0; }
  .setout__edge--l, .setout__edge--r { inline-size: 2px; inset-block: 0; }
  .setout__edge--t { inset-block-start: 0; transform-origin: left center; }
  .setout__edge--r { inset-inline-end: 0;  transform-origin: center top; }
  .setout__edge--b { inset-block-end: 0;   transform-origin: right center; }
  .setout__edge--l { inset-inline-start: 0; transform-origin: center bottom; }

  /* the pegs: a short stake with a bright head, like the corner pegs on the lot */
  .setout__peg {
    position: absolute; inline-size: 9px; block-size: 9px;
    background: var(--ochre);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--ochre) 22%, transparent);
    pointer-events: none;
  }
  .setout__peg--tl { inset-block-start: -4px; inset-inline-start: -4px; }
  .setout__peg--tr { inset-block-start: -4px; inset-inline-end: -4px; }
  .setout__peg--br { inset-block-end: -4px;   inset-inline-end: -4px; }
  .setout__peg--bl { inset-block-end: -4px;   inset-inline-start: -4px; }

  @media (max-width: 700px) { .setout { padding: var(--space-8) var(--space-4); } }

  @supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
      .setout__edge { transform: scale(0); }
      .setout__peg  { transform: scale(0); }
      .setout__edge, .setout__peg {
        animation: linear both;
        animation-timeline: --setout;
      }
      /* clockwise: top left-to-right, right down, bottom right-to-left, left up */
      .setout__edge--t { animation-name: setout-x; animation-range: cover  8% cover 22%; }
      .setout__edge--r { animation-name: setout-y; animation-range: cover 20% cover 32%; }
      .setout__edge--b { animation-name: setout-x; animation-range: cover 30% cover 42%; }
      .setout__edge--l { animation-name: setout-y; animation-range: cover 40% cover 52%; }
      /* each peg lands as the line arrives at its corner */
      .setout__peg--tl { animation-name: setout-peg; animation-range: cover  4% cover 10%; }
      .setout__peg--tr { animation-name: setout-peg; animation-range: cover 19% cover 25%; }
      .setout__peg--br { animation-name: setout-peg; animation-range: cover 29% cover 35%; }
      .setout__peg--bl { animation-name: setout-peg; animation-range: cover 39% cover 45%; }
    }
  }

  /* Worth knowing: sourced industry facts, alternating sides as they arrive (owner, 20/09/2026). */
  .facts {
    display: grid; gap: 1px;
    background: var(--line); border: 1px solid var(--line);
    margin-block-start: var(--space-8);
  }
  @media (min-width: 900px) { .facts { grid-template-columns: 1fr 1fr; } }
  .fact {
    background: var(--ink);
    padding: var(--space-8);
    display: grid; gap: var(--space-3); align-content: start;
  }
  .fact__n {
    font-family: var(--font-display); font-weight: 700;
    font-size: var(--step-2); letter-spacing: -0.03em; line-height: 1;
    color: var(--ochre-soft);
  }
  .fact__t { font-size: var(--step-0); line-height: 1.6; color: var(--bone-dim); max-width: none; }
  .fact__src {
    font-family: var(--font-technical); font-size: var(--step--1);
    letter-spacing: 0.06em; color: var(--muted);
  }

  .gap-diagram {
    margin-top: var(--space-12);
    display: grid; gap: var(--space-4);
    /* wider cards: the role names carry real weight now, 128px could not hold them */
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    counter-reset: link;
  }
  .gap-node {
    border: 1px solid var(--line);
    border-block-start: 2px solid var(--line-strong);
    padding: var(--space-6);
    background: var(--graphite);
    font-family: var(--font-interface);
    font-size: var(--step-0);
    color: var(--bone-dim);
    position: relative;
  }
  /* :has() — the node that owns the break styles itself and its neighbours */
  .gap-node:has(.gap-node__break) {
    border-block-start-color: var(--ochre);
    color: var(--ochre-soft);           /* 5.85:1 on graphite — legal */
    background: linear-gradient(var(--ochre-wash), transparent), var(--graphite);
  }
  /* marker drawn in CSS, not typed as a glyph — no font-coverage risk */
  .gap-node__break {
    display: flex; align-items: center; gap: var(--space-2);
    margin-top: var(--space-3);
    font-family: var(--font-technical);
    font-size: var(--step--1); color: var(--ochre-soft);

    &::before {
      content: ""; inline-size: 7px; block-size: 7px; flex: none;
      background: var(--ochre);
      transform: rotate(45deg);
    }
  }

  /* --- split band (photo + text) ----------------------------------------- */
  .split {
    display: grid; gap: var(--space-16);
    grid-template-columns: 1fr;
    align-items: center;
  }
  @media (min-width: 900px) {
    .split { grid-template-columns: 1.05fr 1fr; gap: var(--space-24); }
    .split--flip > :first-child { order: 2; }
  }
  /* full frame, no crop — the box takes the photograph's own aspect */
  .split__media {
    position: relative;
    border: 1px solid var(--line);
  }
  .split__media img {
    inline-size: 100%; block-size: auto;
  }

  /* --- lifecycle: the pinned scene --------------------------------------- */
  .lifecycle { position: relative; }
  .lifecycle__grid {
    display: grid; gap: var(--space-12);
    grid-template-columns: 1fr;
  }
  @media (min-width: 1000px) {
    .lifecycle__grid { grid-template-columns: 30ch 1fr; gap: var(--space-24); }
    .lifecycle__aside {
      position: sticky; inset-block-start: 8rem;
      align-self: start;
      block-size: fit-content;
    }
  }

  .stages {
    position: relative;
    counter-reset: stage;
    display: grid; gap: var(--space-4);
  }
  /* the spine the stages hang off */
  .spine {
    position: absolute; inset-block: 0; inset-inline-start: 27px;
    inline-size: 2px; background: var(--line);
  }
  .spine__fill {
    position: absolute; inset: 0;
    background: linear-gradient(var(--ochre), var(--ochre-soft));
  }

  .stage {
    counter-increment: stage;
    display: grid;
    grid-template-columns: 56px 1fr;
    gap: var(--space-6);
    align-items: start;
    padding-block: var(--space-6);
  }
  .stage__dial {
    inline-size: 56px; block-size: 56px; flex: none;
    border-radius: 50%;
    display: grid; place-items: center;
    position: relative;
    background:
      conic-gradient(var(--ochre) var(--dial), color-mix(in oklab, var(--bone) 10%, transparent) 0);
    transition: transform var(--d-panel) var(--ease-out);
  }
  /* expanding ring, sits outside the dial and never affects layout */
  .stage__ping {
    position: absolute; inset: -7px;
    border-radius: 50%;
    border: 1px solid var(--ochre);
    opacity: 0;
    pointer-events: none;
  }
  .stage__dial::before {
    content: ""; position: absolute; inset: 3px;
    border-radius: 50%; background: var(--ink);
  }
  .stage__dial::after {
    content: counter(stage, decimal-leading-zero);
    position: relative;
    font-family: var(--font-technical); font-size: var(--step--1);
    font-weight: 500;
    /* muted until the ring fills, then full accent — same driving value */
    color: color-mix(in oklab, var(--ochre-soft) var(--dial), var(--muted));
  }
  /* pointer response — gated to real pointers so touch never sticks */
  @media (hover: hover) and (pointer: fine) {
    .stage:hover .stage__dial { box-shadow: 0 0 0 5px var(--ochre-wash); }
    .stage:hover .stage__name { color: var(--ochre-soft); }
    @media (prefers-reduced-motion: no-preference) {
      .stage:hover .stage__dial { transform: scale(1.09); }
    }
  }
  .stage__name {
    transition: color var(--d-hover) var(--ease-out);
  }
  .stage__name {
    font-family: var(--font-display);
    font-size: var(--step-2);
    line-height: 1;
  }
  .stage__body { color: var(--bone-dim); margin-top: var(--space-3); }
  .stage__tag {
    font-family: var(--font-technical); font-size: var(--step--1);
    color: var(--muted); letter-spacing: 0.06em; text-transform: uppercase;
    margin-top: var(--space-3); display: block;
  }

  /* --- projects ---------------------------------------------------------- */
  .projects {
    display: grid; gap: var(--space-8);
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 330px), 1fr));
  }
  .project {
    container-type: inline-size;
    container-name: project;
    position: relative;
    border: 1px solid var(--line);
    background: var(--graphite);
    overflow: clip;
    /* the card itself is the 3D stage — a tilt-panel, fittingly */
    perspective: 900px;
    transition: border-color var(--d-panel) var(--ease-out);
    &:hover { border-color: var(--ochre-edge); }
  }
  .project__media {
    aspect-ratio: 3 / 2; overflow: clip;
    background: var(--ink);
    transform-style: preserve-3d;
  }
  .project__media img {
    inline-size: 100%; block-size: 100%; object-fit: cover;
    transition: transform var(--d-panel) var(--ease-out),
                filter var(--d-panel) var(--ease-out);
    filter: saturate(0.9) contrast(1.02);
  }
  /* hover motion gated to real pointers, per the motion rules */
  @media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
    .project:hover .project__media img {
      transform: scale(1.04) rotateX(1.2deg);
      filter: saturate(1) contrast(1.05);
    }
  }
  .project__body { padding: var(--space-6); }
  .project__name {
    font-family: var(--font-display); font-size: var(--step-1);
    line-height: 1.1;
  }
  .project__meta {
    margin-top: var(--space-3);
    font-family: var(--font-technical); font-size: var(--step--1);
    color: var(--muted);
    display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4);
  }
  .project__sector {
    display: inline-flex; align-items: center; gap: var(--space-2);
    color: var(--bone-dim);
    &::before {
      content: ""; inline-size: 8px; block-size: 8px;
      background: var(--sector, var(--ochre));
      flex: none;
    }
  }
  .project__flag {
    font-family: var(--font-technical); font-size: var(--step--1);
    letter-spacing: 0.06em; text-transform: uppercase;
    color: var(--ochre-soft);              /* 5.85:1 on graphite */
    border: 1px solid var(--ochre-edge);
    padding: 0 var(--space-2);
  }

  /* container query — the card responds to its own width, not the viewport */
  @container project (min-width: 420px) {
    .project__body { padding: var(--space-8); }
    .project__name { font-size: var(--step-2); }
  }

  /* --- capability: illustrated formats ----------------------------------- */
  /* Full frames, never cropped and never upscaled past their native width.
     subgrid aligns the captions even though the frames differ slightly. */
  .capstrip {
    display: grid; gap: var(--space-4);
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
    margin-block-end: var(--space-4);
  }
  @supports (grid-template-rows: subgrid) {
    .capstrip { grid-template-rows: repeat(2, auto); }
    .capshot { grid-row: span 2; grid-template-rows: subgrid; display: grid; }
  }
  .capshot {
    margin: 0;
    border: 1px solid var(--line);
    background: var(--graphite);
    align-content: start;
    transition: border-color var(--d-panel) var(--ease-out);
    &:hover { border-color: var(--ochre-edge); }
  }
  .capshot img { inline-size: 100%; block-size: auto; }
  .capshot figcaption {
    padding: var(--space-4) var(--space-6);
    /* same face and size as the capability descriptions (.cap__d), owner 19/09/2026 */
    font-family: var(--font-interface);
    font-size: var(--step--1); line-height: 1.55;
    color: var(--muted);
  }
  .capstrip__note {
    font-family: var(--font-interface); font-size: var(--step--1); line-height: 1.55;
    color: var(--muted);
    margin-block-end: var(--space-12);
  }

  /* The sectors used to be a loose grey sentence between the photographs and the capability
     grid and read as an orphan (owner, 20/09/2026). It is now the head of that grid: same
     1px rule, sharing its top edge, with the sectors as tags rather than a run-on list. */
  .sectors {
    border: 1px solid var(--ochre-edge); border-block-end: 0;
    background: var(--graphite);
    padding: var(--space-6);
    display: grid; gap: var(--space-4);
  }
  .sectors__label {
    font-family: var(--font-eyebrow); font-weight: 700;
    font-size: var(--step--1); letter-spacing: 0.14em; text-transform: uppercase;
    color: var(--ochre-soft);
    margin: 0; display: flex; align-items: center; gap: var(--space-3);
    &::before { content: ""; inline-size: var(--space-8); block-size: 2px; background: var(--ochre); flex: none; }
  }
  .sectors__list {
    display: flex; flex-wrap: wrap; gap: var(--space-2);
    font-family: var(--font-technical); font-size: var(--step--1); line-height: 1;
    & li {
      padding: var(--space-2) var(--space-3);
      border: 1px solid var(--line);
      color: var(--bone-dim);
      white-space: nowrap;
    }
  }

  /* --- capability grid (subgrid keeps rows aligned) ---------------------- */
  .capability {
    display: grid; gap: 1px;
    /* the 1px gaps ARE the dividers between tiles; in orange they border the nine line by line,
       inside the setout boundary that borders the block (owner, 20/09/2026). Muted so the
       boundary and its pegs stay the stronger line. */
    background: var(--ochre-edge);
    border: 1px solid var(--ochre-edge);
    /* Nine tiles, so the column count is pinned rather than auto-fit: three across fills every
       row exactly, and at two across the last tile takes the spare cell. An auto-fit grid left
       a hole in the last row at some widths (owner, 20/09/2026). */
    grid-template-columns: 1fr;
  }
  @media (min-width: 620px)  { .capability { grid-template-columns: repeat(2, 1fr); }
                               .cap:last-child { grid-column: span 2; } }
  @media (min-width: 1000px) { .capability { grid-template-columns: repeat(3, 1fr); }
                               .cap:last-child { grid-column: auto; } }
  .cap {
    background: var(--ink);
    padding: var(--space-8) var(--space-6);
    display: grid; gap: var(--space-3);
    align-content: start;
    position: relative;
    transition: background var(--d-panel) var(--ease-out),
                transform var(--d-panel) var(--ease-out),
                box-shadow var(--d-panel) var(--ease-out);
    &:hover, &:focus-within {
      background: var(--graphite);
      /* embossed: lifted, with a light top edge and a shadow under it */
      transform: translateY(-3px);
      box-shadow:
        inset 0 1px 0 color-mix(in srgb, var(--bone) 24%, transparent),
        inset 0 -1px 0 color-mix(in srgb, #000 45%, transparent),
        0 10px 22px color-mix(in srgb, #000 42%, transparent);
      z-index: 2;
    }
  }
  @media (prefers-reduced-motion: reduce) {
    .cap { transition: background var(--d-panel) var(--ease-out); }
    .cap:hover, .cap:focus-within { transform: none; }
  }
  @supports (grid-template-rows: subgrid) {
    .capability { grid-template-rows: repeat(3, auto); }
    .cap { grid-row: span 3; grid-template-rows: subgrid; }
  }
  .cap__n {
    font-family: var(--font-technical); font-size: var(--step--1);
    color: var(--ochre-soft); letter-spacing: 0.08em;
  }
  .cap__t { font-family: var(--font-display); font-weight: 700; letter-spacing: -0.025em; font-size: var(--step-1); }
  .cap__d { color: var(--muted); font-size: var(--step--1); line-height: 1.55; }

  /* --- partners statement (solution band) -------------------------------- */
  .partners {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: var(--step-2);
    line-height: 1.1;
    letter-spacing: -0.03em;
    color: var(--bone);
    & em { font-style: normal; color: var(--ochre); }   /* 4.87:1 on ink */
  }

  /* --- land / investment ledger ------------------------------------------ */
  .ledger {
    display: grid; gap: 1px; background: var(--line);
    border-block: 1px solid var(--line);
  }
  .ledger__row {
    background: var(--ink);
    display: grid; gap: var(--space-4);
    grid-template-columns: 1fr;
    padding: var(--space-8) 0;
  }
  @media (min-width: 820px) {
    .ledger__row { grid-template-columns: 12ch 1fr 1fr; gap: var(--space-12); align-items: baseline; }
  }
  .ledger__k {
    font-family: var(--font-technical); font-size: var(--step--1);
    color: var(--ochre-soft); letter-spacing: 0.08em; text-transform: uppercase;
  }
  .ledger__t { font-family: var(--font-display); font-weight: 700; letter-spacing: -0.025em; font-size: var(--step-1); }
  .ledger__d { color: var(--muted); font-size: var(--step--1); line-height: 1.6; }

  /* --- contact ----------------------------------------------------------- */
  .contact {
    border: 1px solid var(--line);
    background:
      radial-gradient(120% 140% at 8% 0%, var(--ochre-wash), transparent 58%),
      var(--graphite);
    padding: clamp(var(--space-12), 6vw, var(--space-32));
  }
  .contact__grid {
    display: grid; gap: var(--space-12);
    grid-template-columns: 1fr;
  }
  @media (min-width: 900px) { .contact__grid { grid-template-columns: 1.2fr 1fr; gap: var(--space-24); } }
  .contact__list { display: grid; gap: var(--space-6); }
  .contact__item {
    display: grid; gap: var(--space-1);
    padding-block-end: var(--space-6);
    border-block-end: 1px solid var(--line);
  }
  .contact__k {
    font-family: var(--font-technical); font-size: var(--step--1);
    color: var(--muted); letter-spacing: 0.08em; text-transform: uppercase;
  }
  .contact__v {
    font-size: var(--step-1); color: var(--bone);
    text-decoration: none;
    &:hover { color: var(--ochre-soft); }
  }
  /* an unconfirmed value is visibly marked, never quietly blank */
  .tbc {
    font-family: var(--font-technical); font-size: var(--step--1);
    color: var(--ochre-soft);
    border: 1px dashed var(--ochre-edge);
    padding: var(--space-1) var(--space-3);
    display: inline-block;
  }

  /* --- footer ------------------------------------------------------------ */
  .footer {
    border-block-start: 1px solid var(--line);
    padding-block: var(--space-12);
    color: var(--muted); font-size: var(--step--1);
  }
  .footer__bar {
    display: flex; flex-wrap: wrap; gap: var(--space-6);
    align-items: center; justify-content: space-between;
  }
  .footer__links { display: flex; flex-wrap: wrap; gap: var(--space-6); }
  .footer__links a { text-decoration: none; &:hover { color: var(--bone); } }

  /* ======================================================================
     PROJECT LIGHTBOX — a :target overlay. No JavaScript: the card links to
     an id, :target reveals it, and the close control links back to
     #projects. Each one holds a scroll-snap slideshow of real photography.
     ====================================================================== */

  /* stretched link: one real, labelled link makes the whole card clickable */
  .project__link {
    text-decoration: none; color: inherit;
    &::after { content: ""; position: absolute; inset: 0; z-index: 2; }
  }
  .project__more {
    margin-top: var(--space-4);
    font-family: var(--font-technical); font-size: var(--step--1);
    letter-spacing: 0.06em; text-transform: uppercase;
    color: var(--ochre-soft);
    display: flex; align-items: center; gap: var(--space-2);

    &::after {
      content: ""; inline-size: 6px; block-size: 6px; flex: none;
      border-block-start: 1px solid currentColor;
      border-inline-end: 1px solid currentColor;
      transform: rotate(45deg);
      transition: transform var(--d-hover) var(--ease-out);
    }
  }
  @media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
    .project:hover .project__more::after { transform: rotate(45deg) translate(2px, -2px); }
  }

  .lightbox {
    position: fixed; inset: 0; z-index: 80;
    display: grid; grid-template-rows: 1fr;
    place-items: center;
    padding: var(--space-4);
    background: color-mix(in srgb, var(--ink) 88%, transparent);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    /* visibility keeps the contents out of the tab order while closed */
    visibility: hidden; opacity: 0;
    transition: opacity var(--d-panel) var(--ease-out),
                visibility var(--d-panel) var(--ease-out);
  }
  .lightbox:target { visibility: visible; opacity: 1; }

  .lightbox__backdrop { position: absolute; inset: 0; cursor: zoom-out; }

  .lightbox__panel {
    position: relative;
    inline-size: min(100%, 1360px);
    max-block-size: 92svh;
    display: grid; grid-template-rows: auto 1fr;
    gap: var(--space-4);
    background: var(--graphite);
    border: 1px solid var(--line-strong);
    padding: var(--space-6);
    overflow: hidden;
  }
  .lightbox__head {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2) var(--space-6);
    padding-inline-end: var(--space-12);
  }
  .lightbox__title { font-size: var(--step-2); }
  .lightbox__meta {
    font-family: var(--font-technical); font-size: var(--step--1);
    color: var(--bone-dim); letter-spacing: 0.04em;
  }
  .lightbox__close {
    position: absolute; inset-block-start: var(--space-4); inset-inline-end: var(--space-4);
    z-index: 3;
    inline-size: 42px; block-size: 42px;
    display: grid; place-items: center;
    border: 1px solid var(--line-strong); border-radius: var(--radius);
    text-decoration: none; color: var(--bone);
    font-family: var(--font-technical); font-size: var(--step-1); line-height: 1;
    background: var(--ink);
    transition: border-color var(--d-hover) var(--ease-out),
                color var(--d-hover) var(--ease-out);
    &:hover { border-color: var(--ochre); color: var(--ochre-soft); }
  }

  /* the slideshow: CSS scroll-snap, swipe or drag, no script */
  .lightbox__rail {
    display: flex; gap: var(--space-4);
    overflow-x: auto; overflow-y: hidden;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    scrollbar-width: thin;
    scrollbar-color: var(--ochre) transparent;
    padding-block-end: var(--space-3);
  }
  @media (prefers-reduced-motion: reduce) { .lightbox__rail { scroll-behavior: auto; } }
  .lightbox__rail img {
    flex: 0 0 auto;
    max-block-size: min(66svh, 620px);
    max-inline-size: 100%;
    inline-size: auto; block-size: auto;
    object-fit: contain;
    scroll-snap-align: center;
    border: 1px solid var(--line);
  }
  .lightbox__hint {
    font-family: var(--font-technical); font-size: var(--step--1);
    color: var(--muted); letter-spacing: 0.06em; text-transform: uppercase;
  }

  /* ======================================================================
     HAIL — the scroll-triggered enquiry prompt. Slides in past the hero on
     browsers with scroll timelines; simply sits there on browsers without
     one (never hidden by a feature that might not exist).
     ====================================================================== */
  .hail-dismiss:checked ~ .hail { display: none; }

  .hail {
    position: fixed; z-index: 60;
    inset-block-end: var(--space-6);
    inset-inline-end: var(--space-6);
    inline-size: min(calc(100vw - 2 * var(--space-6)), 380px);
    display: flex; align-items: center; gap: var(--space-4);
    padding: var(--space-4) var(--space-6);
    background: var(--ochre);
    color: var(--ink);
    border-radius: var(--radius);
    box-shadow: 0 18px 50px color-mix(in srgb, #000 55%, transparent);
  }
  .hail__text { font-size: var(--step--1); font-weight: 500; line-height: 1.35; }
  .hail__cta {
    font-family: var(--font-technical); font-size: var(--step--1);
    font-weight: 500; text-transform: uppercase; letter-spacing: 0.06em;
    color: var(--ink); white-space: nowrap;
    text-decoration-color: color-mix(in srgb, var(--ink) 45%, transparent);
  }
  .hail__close {
    margin-inline-start: auto; flex: none;
    inline-size: 26px; block-size: 26px;
    display: grid; place-items: center; cursor: pointer;
    border: 1px solid color-mix(in srgb, var(--ink) 35%, transparent);
    border-radius: var(--radius);
    font-family: var(--font-technical); font-size: var(--step--1); line-height: 1;
    color: var(--ink);
    transition: background var(--d-hover) var(--ease-out);
    &:hover { background: color-mix(in srgb, var(--ink) 14%, transparent); }
  }
  .hail__close:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

  @supports (animation-timeline: scroll()) {
    @media (prefers-reduced-motion: no-preference) {
      .hail {
        transform: translateY(160%);
        animation: hail-in linear both;
        animation-timeline: scroll(root block);
        animation-range: 92svh 128svh;
      }
    }
  }
}

/* ========================================================================== */
@layer utils {
  .flow > * + * { margin-block-start: var(--space-6); }
  /* content-visibility: auto was here, with a 700px intrinsic size. It broke every link in the
     navigation: an unrendered section is assumed to be 700px tall, the real ones are far taller,
     so the anchor target kept moving and the page never arrived. Proved by disabling it alone -
     with it on, a click on "Contact" never landed; with it off, it landed in 1.25s. In-page
     navigation is a function of the site; skipping some offscreen rendering is not worth it. */
  .defer { }
  .nowrap { white-space: nowrap; }
}

/* ==========================================================================
   Added 19/09/2026: WA cost benchmark marquee and directional slide-ins
   ========================================================================== */
@layer components {
  /* --- WA cost benchmarks (Koste rates, CSS-only marquee) ---------------- */
  .costs__head {
    display: flex; flex-wrap: wrap; justify-content: space-between;
    align-items: end; gap: var(--space-6);
    margin-block-end: var(--space-12);
  }
  .costs__source {
    font-family: var(--font-technical); font-size: var(--step--1);
    color: var(--muted); letter-spacing: 0.04em;
    & a { color: var(--bone); }
    & a:hover { color: var(--ochre-soft); }
  }
  .costs__lane {
    overflow: hidden;
    border-block: 1px solid var(--line);
    padding-block: var(--space-4);
    &:focus-visible { outline: 2px solid var(--ochre-soft); outline-offset: -2px; }
  }
  .costs__track {
    display: flex; inline-size: max-content;
    gap: var(--space-3); padding-inline-end: var(--space-3);
  }
  .cost-card {
    flex: none;
    inline-size: clamp(270px, 26vw, 360px);
    padding: var(--space-6);
    border: 1px solid var(--line);
    border-block-start: 3px solid var(--sector, var(--ochre));
    background:
      linear-gradient(150deg,
        color-mix(in oklab, var(--sector, var(--ochre)) 9%, transparent),
        transparent 58%),
      var(--graphite);
  }
  .cost-card__type {
    font-family: var(--font-technical); font-size: 0.68rem;
    letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted);
  }
  .cost-card__name {
    font-size: var(--step-1); line-height: 1.15; letter-spacing: -0.02em;
    min-block-size: 2.3em;
    margin-block: var(--space-3) var(--space-6);
  }
  .cost-card__vals {
    display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-2);
    margin: 0; padding-block-start: var(--space-3);
    border-block-start: 1px solid var(--line);
    & dt {
      font-family: var(--font-technical); font-size: 0.66rem;
      letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted);
    }
    & dd {
      margin: var(--space-1) 0 0;
      font-family: var(--font-display); font-weight: 700;
      font-size: var(--step-0); letter-spacing: -0.01em;
      font-variant-numeric: tabular-nums; color: var(--bone);
    }
    /* mid rate is the headline figure; soft orange is text-safe on graphite */
    & .is-mid dd { color: var(--ochre-soft); }
  }
  @media (max-width: 640px) { .cost-card { inline-size: 78vw; } }
}

@layer motion {
  @keyframes cost-scroll   { to { transform: translateX(-50%); } }
  /* explicit inset(0) end state: inset() does not interpolate to `none`, so
     without it the wipe snaps open halfway instead of drawing across */
  @keyframes slide-from-l  { from { opacity: 0; transform: translateX(-10vw); clip-path: inset(0 100% 0 0); }
                             to   { clip-path: inset(0); } }
  @keyframes slide-from-r  { from { opacity: 0; transform: translateX(10vw);  clip-path: inset(0 0 0 100%); }
                             to   { clip-path: inset(0); } }
  @keyframes image-settle  { from { transform: scale(1.18); } }
  /* the survey boundary drawing itself, and a peg landing */
  @keyframes setout-x   { to { transform: scaleX(1); } }
  @keyframes setout-y   { to { transform: scaleY(1); } }
  @keyframes setout-peg { to { transform: scale(1); } }

  /* the slide-ins start off-canvas; clip stops them adding a sideways scroll */
  main { overflow-x: clip; }

  @media (prefers-reduced-motion: no-preference) {
    /* marquee runs on time, not scroll; hovering or focusing it pauses it */
    .costs__track { animation: cost-scroll 110s linear infinite; }
    .costs__lane:hover .costs__track,
    .costs__lane:focus-within .costs__track { animation-play-state: paused; }
  }
  @media (prefers-reduced-motion: reduce) {
    .costs__lane { overflow-x: auto; }
    .costs__track [aria-hidden="true"] { display: none; }
  }

  @supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
      /* image blocks slide and wipe in from their own side, and the
         photograph inside settles from a slight zoom as it lands */
      .slide-l, .slide-r {
        animation: slide-from-l linear both;
        animation-timeline: view();
        animation-range: entry 8% cover 56%;
      }
      .slide-r { animation-name: slide-from-r; }
      :is(.slide-l, .slide-r) img {
        animation: image-settle linear both;
        animation-timeline: view();
        animation-range: entry 0% cover 50%;
      }
    }
  }
}

/* ==========================================================================
   Added 19/09/2026: Site Camera hero film (hero-film.js, see BRIEF.md).
   Without JS or WebGL none of the .is-live rules apply and the hero is the
   original photograph + headline at 100svh.
   ========================================================================== */
@layer components {
  .film { display: block; }
  .film__stage {
    position: relative;
    min-block-size: 100svh;
    display: grid; align-items: end;
    overflow: clip; isolation: isolate;
  }
  .film__stage > .film__beat { grid-area: 1 / 1; }
  .film__beat--later { display: none; }
  .film__beat--later h2 { font-size: var(--step-4); max-inline-size: 16ch; }
  .film__beat--later .lede { max-inline-size: 44ch; }
  .film__beat--later { padding-block: var(--space-24) var(--space-16); }

  .film__canvas {
    position: absolute; inset: 0;
    inline-size: 100%; block-size: 100%;
    opacity: 0; transition: opacity 0.6s var(--ease-out);
  }
  .film.is-ready .film__canvas { opacity: 1; }

  .film.is-live { block-size: 1020svh; }  /* 46-week programme; lengthened again 20/09 so the pieces fly in slower */
  .film.is-live .film__stage { position: sticky; inset-block-start: 0; block-size: 100svh; }
  .film.is-live .film__beat { opacity: 0; will-change: opacity, transform; }
  .film.is-live .film__beat--later { display: block; }

  /* programme readout + skip link, bottom right over the scene */
  .film__readout, .film__skip { display: none; }
  .film.is-live .film__readout {
    display: grid; grid-template-columns: auto auto; gap: var(--space-2) var(--space-6);
    position: absolute; z-index: 2;
    inset-block-end: var(--space-8); inset-inline-end: max(1.25rem, 4vw);
    font-family: var(--font-technical); font-size: var(--step--1);
    letter-spacing: 0.1em; text-transform: uppercase;
    color: var(--bone);
    /* no shadow anywhere on this page (owner, 20/09/2026); this one sits over open photograph,
       so it takes the same quiet backing as the skip link instead */
    padding: var(--space-2) var(--space-4);
    background: color-mix(in srgb, var(--ink) 72%, transparent);
    border: 1px solid var(--line);
    backdrop-filter: blur(3px);
  }
  .film__readout [data-film-week] { color: var(--ochre-soft); text-align: end; }
  .film__bar {
    grid-column: 1 / -1; inline-size: min(220px, 40vw); block-size: 2px;
    background: color-mix(in oklab, var(--bone) 25%, transparent);
    & i { display: block; block-size: 100%; background: var(--ochre); transform-origin: left; transform: scaleX(0); }
  }
  .film.is-live .film__skip {
    display: block; position: absolute; z-index: 2;
    inset-block-start: calc(var(--space-24) + var(--space-2)); inset-inline-end: max(1.25rem, 4vw);
    font-family: var(--font-technical); font-size: var(--step--1);
    letter-spacing: 0.08em; text-transform: uppercase;
    /* Now the photograph is no longer washed down, this link can land on bright sky, where a
       text shadow alone measured 2.5:1. It carries its own backing instead (owner, 20/09/2026). */
    color: var(--bone);
    padding: var(--space-2) var(--space-3);
    background: color-mix(in srgb, var(--ink) 74%, transparent);
    border: 1px solid var(--line);
    backdrop-filter: blur(3px);
    &:hover { color: var(--ochre-soft); border-color: var(--line-strong); }
  }

  @media (max-width: 700px) {
    .film.is-live { block-size: 820svh; }
    .film.is-live .film__readout { inset-block-end: auto; inset-block-start: calc(var(--space-24) - var(--space-2)); inset-inline: 1.25rem auto; }
    .film.is-live .film__skip { inset-block-start: calc(var(--space-24) - var(--space-2)); }
  }

  /* the hail prompt waits until the film has handed over to the page */
  :root:has(.film.is-live) { --film-h: 1020svh; }
  @media (max-width: 700px) { :root:has(.film.is-live) { --film-h: 820svh; } }
  @supports (animation-timeline: scroll()) {
    @media (prefers-reduced-motion: no-preference) {
      :root:has(.film.is-live) .hail {
        animation-range: calc(var(--film-h) - 90svh) calc(var(--film-h) - 54svh);
      }
    }
  }
}

/* ==========================================================================
   Added 20/09/2026: enquiry form with document upload (#enquiry, enquiry-form.js)
   ========================================================================== */
@layer components {
  .enquiry {
    margin-block-start: var(--space-16);
    padding-block-start: var(--space-12);
    border-block-start: 1px solid var(--line);
  }
  .enquiry__title { font-size: var(--step-2); margin-block-end: var(--space-8); }
  .enquiry__grid {
    display: grid; gap: var(--space-6);
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  }
  .field { display: grid; gap: var(--space-2); }
  .field--wide { grid-column: 1 / -1; }
  .field > span, .field > span:first-child {
    font-family: var(--font-technical); font-size: var(--step--1);
    letter-spacing: 0.06em; text-transform: uppercase; color: var(--bone-dim);
    & b { color: var(--ochre-soft); font-weight: 500; }
  }
  .field input, .field select, .field textarea {
    font: inherit; font-size: var(--step-0); color: var(--bone);
    background: var(--graphite); border: 1px solid var(--line-strong);
    border-radius: var(--radius); padding: var(--space-3) var(--space-4);
    inline-size: 100%;
    &:focus-visible { outline: 2px solid var(--ochre-soft); outline-offset: 1px; border-color: transparent; }
  }
  .field textarea { resize: vertical; min-block-size: 8rem; }
  .field select option { background: var(--graphite); }

  .drop {
    position: relative; display: grid; gap: var(--space-2); place-items: center; text-align: center;
    padding: var(--space-8) var(--space-6);
    border: 1px dashed var(--line-strong); border-radius: var(--radius);
    background: var(--graphite); cursor: pointer;
    transition: border-color var(--d-hover) var(--ease-out), background var(--d-hover) var(--ease-out);
    &:hover, &.is-over { border-color: var(--ochre-soft); background: var(--surface-up); }
    &:focus-within { outline: 2px solid var(--ochre-soft); outline-offset: 1px; }
    & input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
  }
  .drop__cta { color: var(--bone); font-weight: 500; }
  .drop__hint { color: var(--muted); font-size: var(--step--1); line-height: 1.55; max-inline-size: 60ch; }
  .drop__list {
    list-style: none; margin: var(--space-3) 0 0; padding: 0; display: grid; gap: var(--space-1);
    font-size: var(--step--1); color: var(--bone-dim);
    & li::before { content: ""; display: inline-block; inline-size: 8px; block-size: 8px; margin-inline-end: var(--space-2); background: var(--ochre); }
    & li.is-bad { color: var(--ochre-soft); }
  }
  .enquiry__hp { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); }
  .consent {
    display: flex; gap: var(--space-3); align-items: start;
    margin-block: var(--space-8) var(--space-6);
    font-size: var(--step--1); line-height: 1.55; color: var(--muted); max-inline-size: 80ch;
    & input { inline-size: 18px; block-size: 18px; margin-block-start: 2px; accent-color: var(--ochre); flex: none; }
    & a { color: var(--bone); }
  }
  .enquiry__actions { display: flex; flex-wrap: wrap; gap: var(--space-4); align-items: center; margin-block-start: var(--space-6); }
  .enquiry__actions .btn[disabled] { opacity: 0.6; cursor: progress; }
  .enquiry__status { font-size: var(--step--1); color: var(--bone-dim); &.is-bad { color: var(--ochre-soft); } }
  .enquiry__progress {
    margin-block-start: var(--space-4); block-size: 3px; background: var(--line);
    & i { display: block; block-size: 100%; background: var(--ochre); transform-origin: left; transform: scaleX(0); }
  }
  .enquiry__done h3 { font-size: var(--step-2); }
  .enquiry__done p { margin-block-start: var(--space-4); color: var(--bone-dim); }
}

/* ==========================================================================
   Added 20/09/2026: earthworks photograph behind the problem section
   ========================================================================== */
@layer components {
  /* Same treatment as the old hero: full-bleed, one screen tall, cover crop,
     hero scrim for the type. The photo's top edge is the section's top edge. */
  .landbg {
    position: relative; isolation: isolate; overflow: clip;
    min-block-size: 100svh;
    /* Centred content inside a full-screen box left slack at the foot, and the section's own
       bottom padding then met the next section's top padding: three dark bands in a row with
       nothing in them (owner, 20/09/2026). The content now sits toward the top of the frame
       and the junction carries one section's worth of air, not two and a half. */
    display: grid; align-content: start;
    padding-block: var(--space-24) var(--space-12);
  }
  .landbg + .section { padding-block-start: var(--space-16); }

  /* This section kept the original full-strength scrim while the hero above it was lightened,
     so it read as a much darker page (owner, 20/09/2026). Same treatment as the hero now. */
  .landbg .hero__scrim {
    background:
      /* the heading runs across the sky and the horizon, which is the brightest band in the
         frame - measured at 2.31:1 without this. The sky carries nothing worth keeping, so it
         takes the ink and the earthworks below stay open. */
      linear-gradient(to bottom,
        color-mix(in srgb, var(--ink) 80%, transparent) 0%,
        color-mix(in srgb, var(--ink) 62%, transparent) 14%,
        color-mix(in srgb, var(--ink) 22%, transparent) 26%,
        transparent 38%),
      linear-gradient(to right,
        color-mix(in srgb, var(--ink) 80%, transparent) 0%,
        color-mix(in srgb, var(--ink) 58%, transparent) 20%,
        color-mix(in srgb, var(--ink) 24%, transparent) 38%,
        transparent 52%),
      linear-gradient(to top, color-mix(in srgb, var(--ink) 42%, transparent) 0%, transparent 22%);
  }
  /* over a photograph the dimmed body grey is not white enough to read comfortably, and it
     was a touch small for the measure it runs to */
  .landbg .lede, .film .film__beat .lede {
    color: var(--bone);
    font-size: calc(var(--step-1) + 0.1rem);
  }

  /* The backdrop is a full-screen element, so the shared slide range (which runs to 56% of the
     cover phase) would still be wiping it in while the heading over it was already readable.
     It gets its own range instead: in by the time the section is. */
  @supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
      .landbg .hero__media.slide-r { animation-range: entry 0% entry 90%; }
      .landbg .hero__media.slide-r img { animation-range: entry 0% entry 100%; }
    }
  }
}

/* ==========================================================================
   Added 20/09/2026: the hero film renders over the aerial photograph
   ========================================================================== */
@layer components {
  /* The photo stays behind the transparent 3D render. It used to darken toward dusk as you
     scrolled; the owner asked for that removed (20/09/2026), so the light on the land holds
     from the first screen to the last. */
  .film .hero__img { object-position: 60% 55%; }

  /* Lighter again (owner, 20/09/2026). Rather than wash the whole frame down, the ink is
     pooled where the words actually sit - bottom left - and the rest of the land is left open.
     The headline still clears AA against the pool; checked on the rendered page, not assumed. */
  .film .hero__scrim {
    background:
      /* Shortened 20/09/2026 on the owner's mark: the shading is gone by about a fifth across,
         instead of washing halfway over the land. The headline runs well past that point, so
         the words carry their own contrast now (see .film__beat below) rather than leaning on
         a scrim that has to be wide enough to sit under them. */
      linear-gradient(to right,
        color-mix(in srgb, var(--ink) 84%, transparent) 0%,
        color-mix(in srgb, var(--ink) 66%, transparent) 18%,
        color-mix(in srgb, var(--ink) 30%, transparent) 34%,
        transparent 46%),
      /* just enough at the foot for the stage and week readout on the right */
      linear-gradient(to top, color-mix(in srgb, var(--ink) 42%, transparent) 0%, transparent 22%);
  }
  /* The house rule at the top of this file: orange TEXT on anything other than flat ink must be
     --ochre-soft. Over a photograph that goes double. Measured at 3.03:1 in --ochre, which only
     just clears the large-text threshold; --ochre-soft gives it a real margin. */
  .film .film__beat :is(h1, h2) em { color: var(--ochre-soft); }

  /* The beat copy sat on the page's standard 1280px measure, which on a wide screen starts
     well inside the frame. The owner wants it out at the left, level with the survey line.
     The hero gets its own wider measure; below that width it falls back to the normal gutter,
     so nothing changes on a laptop or a phone. */
  .film .film__beat.shell { inline-size: min(100% - 2.5rem, 1520px); }

  .film .film__canvas { z-index: 1; }
}

/* ==========================================================================
   Added 20/09/2026: the five-party chain opens on hover, focus or tap
   ========================================================================== */
@layer components {
  .gap-node { cursor: default; transition: border-color var(--d-hover) var(--ease-out), background var(--d-hover) var(--ease-out); }
  .gap-node::after {                       /* "there is more here" marker, drawn in CSS */
    content: "+"; position: absolute; inset-block-start: var(--space-3); inset-inline-end: var(--space-3);
    color: var(--muted); font-size: var(--step--1); line-height: 1;
    transition: transform var(--d-hover) var(--ease-out), color var(--d-hover) var(--ease-out);
  }
  .gap-node:hover, .gap-node:focus-within { z-index: 5; border-color: var(--line-strong); background-color: var(--surface-up); }
  .gap-node:hover::after, .gap-node:focus-within::after { transform: rotate(45deg); color: var(--ochre-soft); }
  .gap-node:focus-visible { outline: 2px solid var(--ochre-soft); outline-offset: 2px; }

  /* The number and the role (owner, 20/09/2026: the type was too small to read comfortably,
     in the card and in the panel below it). The number carries the sequence so the role can
     be set in the display face at reading size rather than as a mono label. */
  .gap-node__n {
    display: block;
    font-family: var(--font-technical); font-size: var(--step--1);
    letter-spacing: 0.1em; color: var(--muted);
    margin-block-end: var(--space-2);
  }
  .gap-node__role {
    display: block;
    font-family: var(--font-display); font-weight: 700;
    font-size: var(--step-1); letter-spacing: -0.02em; line-height: 1.05;
    color: var(--bone);
  }
  .gap-node:has(.gap-node__break) .gap-node__role { color: var(--ochre-soft); }

  .gap-node__more {
    position: absolute; inset-block-start: calc(100% + var(--space-2)); inset-inline-start: -1px;
    inline-size: max(100%, 340px);
    padding: var(--space-6);
    background: var(--ink); border: 1px solid var(--line-strong); border-block-start: 2px solid var(--ochre);
    box-shadow: 0 18px 40px color-mix(in srgb, #000 55%, transparent);
    font-family: var(--font-interface); font-size: var(--step-0); line-height: 1.6;
    color: var(--bone); text-transform: none; letter-spacing: 0;
    opacity: 0; visibility: hidden; transform: translateY(-6px);
    transition: opacity var(--d-panel) var(--ease-out), transform var(--d-panel) var(--ease-out), visibility var(--d-panel);
  }
  .gap-node:last-child .gap-node__more { inset-inline: auto -1px; }   /* keep the last one on screen */
  .gap-node:hover .gap-node__more, .gap-node:focus-within .gap-node__more, .gap-node:focus .gap-node__more {
    opacity: 1; visibility: visible; transform: none;
  }
  @media (prefers-reduced-motion: reduce) { .gap-node__more { transition: none; } }

  /* phones: the boxes wrap, so the text opens inside the box instead of floating off-screen */
  @media (max-width: 700px) {
    .gap-node__more {
      position: static; inline-size: auto; margin-block-start: 0; max-block-size: 0; padding: 0 0;
      border: 0; box-shadow: none; background: none; overflow: hidden; transform: none;
      transition: max-block-size var(--d-panel) var(--ease-out), padding var(--d-panel), opacity var(--d-panel);
    }
    .gap-node:hover .gap-node__more, .gap-node:focus-within .gap-node__more, .gap-node:focus .gap-node__more {
      max-block-size: 16rem; padding-block-start: var(--space-3);
    }
    .gap-node:last-child .gap-node__more { inset-inline: auto; }
  }
}

/* ==========================================================================
   Added 20/09/2026, reworked the same day to the owner's four-panel layout:
   tools down the left, enquiry form top right, our details and the office map
   beneath it. One 1px rule between panels, the same language as the capability
   grid. The two tall panels are capped and scroll inside themselves on a wide
   screen so the block reads as one page; on a phone everything stacks and
   scrolls with the page, because a scroll box inside a scrolling page is a
   trap on touch.
   ========================================================================== */
@layer components {
  .closing {
    display: grid; gap: 1px;
    background: var(--line);
    border: 1px solid var(--line);
  }
  .closing__left, .closing__right { display: grid; gap: 1px; background: var(--line); }
  .closing__panel {
    background: var(--ink);
    padding: var(--space-8);
    min-inline-size: 0;              /* lets a panel shrink instead of forcing the grid wide */
  }
  .closing__h {
    font-family: var(--font-display); font-weight: 700;
    font-size: var(--step-1); letter-spacing: -0.025em;
    margin: 0 0 var(--space-3);
  }
  .closing__sub {
    font-size: var(--step--1); line-height: 1.55; color: var(--muted);
    margin-block-end: var(--space-8);
  }
  .closing__address {
    font-style: normal; line-height: 1.5;
    color: var(--bone);
    margin-block-end: var(--space-8);
  }
  .closing__actions { display: flex; flex-wrap: wrap; gap: var(--space-4); margin-block-start: var(--space-8); }

  /* the panels already carry their own heading, so the parts inside drop theirs */
  .closing__tools .tools { border: 0; background: transparent; }
  .closing__form .enquiry { margin-block-start: 0; padding-block-start: 0; border-block-start: 0; }
  /* The details panel is the shortest of the four, so it earns its height back: the phone and
     the email keep a full row, the two registrations share one, and the address sits tight. */
  .closing__details .contact__list { margin-block-start: 0; gap: var(--space-4) var(--space-6); grid-template-columns: 1fr 1fr; }
  .closing__details .contact__item:nth-child(-n+2) { grid-column: 1 / -1; }
  .closing__details .contact__v { overflow-wrap: anywhere; }
  .closing__details .closing__address { margin-block-end: var(--space-6); }
  .closing__details, .closing__map { padding: var(--space-6); }
  /* the full-page treatment (big values, a rule under each) does not fit a quarter panel */
  .closing__details .contact__item { padding-block-end: var(--space-2); border-block-end: 0; }
  .closing__details .contact__v { font-size: var(--step-0); }
  .closing__details .closing__h { margin-block-end: var(--space-2); }
  .closing__details .closing__address { margin-block-end: var(--space-4); }
  .closing__details .contact__list { gap: var(--space-3) var(--space-6); }

  /* --- the map ---------------------------------------------------------- */
  .closing__mapframe {
    position: relative;
    aspect-ratio: 4 / 3;
    border: 1px solid var(--line);
    background: var(--graphite);
    & iframe { position: absolute; inset: 0; inline-size: 100%; block-size: 100%; border: 0; }
  }
  .closing__maplink {
    margin-block-start: var(--space-4);
    font-family: var(--font-technical); font-size: var(--step--1); line-height: 1.5;
    color: var(--muted);
    & a { color: var(--bone); }
    & a:hover { color: var(--ochre-soft); }
  }

  /* The tools and the form were built to run the full page width, so their own breakpoints
     watch the viewport. In a half-width panel that reads cramped, so inside the panel they
     answer to the PANEL's width instead and stack their result under their inputs. */
  .closing__tools { container-type: inline-size; }
  .closing__tools .tool { grid-template-columns: 1fr; padding: 0; gap: var(--space-8); }
  .closing__tools .tool__form { grid-template-columns: 1fr; }
  @container (min-width: 440px) {
    .closing__tools .tool__form { grid-template-columns: 1fr 1fr; }
  }

  @media (min-width: 1020px) {
    /* The panels were capped to one screen and scrolled inside themselves. Four little
       scrollbars is worse than a taller block (owner, 20/09/2026), so the block now grows to
       its content and nothing scrolls within it. The enquiry form lost its site, suburb and
       timeframe boxes to the message, which is most of what the extra height was paying for. */
    .closing { grid-template-columns: 1fr 1fr; align-items: stretch; }
    /* The left column ran short under the tools and left a hole; our details fill it. The map
       takes the whole lower right, which squares the two columns up (owner, 20/09/2026). */
    .closing__left  { grid-template-rows: auto 1fr; }
    .closing__right { grid-template-rows: auto 1fr; }
    /* the map fills whatever is left of its panel rather than holding a fixed 4:3 */
    .closing__map { display: flex; flex-direction: column; }
    .closing__map .closing__mapframe { aspect-ratio: auto; flex: 1 1 auto; min-block-size: 200px; }
  }
}

/* ==========================================================================
   Added 20/09/2026: free tools (build cost, concrete volume, working days)
   ========================================================================== */
@layer components {
  .tools { border: 1px solid var(--line); background: var(--graphite); }
  .tools__tabs { display: flex; flex-wrap: wrap; border-block-end: 1px solid var(--line); }
  .tools__tab {
    appearance: none; background: none; border: 0; cursor: pointer;
    padding: var(--space-4) var(--space-8);
    font-family: var(--font-technical); font-size: var(--step--1);
    letter-spacing: 0.06em; text-transform: uppercase; color: var(--bone-dim);
    border-block-end: 2px solid transparent; margin-block-end: -1px;
    transition: color var(--d-hover) var(--ease-out), border-color var(--d-hover) var(--ease-out);
    &:hover { color: var(--bone); }
  }
  .tools__tab[aria-selected="true"] { color: var(--bone); border-block-end-color: var(--ochre); }
  .tool { display: grid; gap: var(--space-8); padding: clamp(var(--space-6), 3vw, var(--space-12)); }
  .tool[hidden] { display: none; }   /* display:grid would otherwise beat the hidden attribute */
  @media (min-width: 860px) { .tool { grid-template-columns: 1.15fr 1fr; gap: var(--space-12); align-items: start; } }
  .tool__form { display: grid; gap: var(--space-6); align-content: start; }
  @media (min-width: 560px) { .tool__form { grid-template-columns: 1fr 1fr; } .tool__form > .btn, .tool__hint { grid-column: 1 / -1; } }
  .tool__form .btn { justify-self: start; }
  .tool__hint { color: var(--muted); font-size: var(--step--1); line-height: 1.55; }

  .tool__result {
    display: grid; gap: var(--space-4); align-content: start;
    padding: var(--space-8); background: var(--ink); border: 1px solid var(--line);
  }
  .tool__out {
    display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-4);
    padding-block-end: var(--space-3); border-block-end: 1px solid var(--line);
    & span { font-family: var(--font-technical); font-size: var(--step--1); letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }
    & b { font-family: var(--font-display); font-weight: 700; font-size: var(--step-1); letter-spacing: -0.02em; color: var(--bone); font-variant-numeric: tabular-nums; }
  }
  .tool__out.is-lead b { font-size: var(--step-2); color: var(--ochre-soft); }   /* 5.85:1 on ink */
  .tool__out:last-of-type { border-block-end: 0; padding-block-end: 0; }
  .tool__note { color: var(--muted); font-size: var(--step--1); line-height: 1.55; }
}

/* ==========================================================================
   Added 20/09/2026: the enquiry prompt steps aside at the tools and contact
   ========================================================================== */
@layer components {
  .hail { transition: opacity var(--d-panel) var(--ease-out), transform var(--d-panel) var(--ease-out); }
  .hail.is-away { opacity: 0; transform: translateY(140%); pointer-events: none; }
  @media (prefers-reduced-motion: reduce) { .hail.is-away { transform: none; visibility: hidden; } }
}

/* ==========================================================================
   Added 20/09/2026: touch sizing. Last in the file on purpose - these rules carry the same
   specificity as the ones they override, so document order is what decides.
   ========================================================================== */
@layer components {
  /* ---- touch sizing ------------------------------------------------------
     Measured on a 390x844 phone with touch emulation: the phone and email links, the map link
     and the rates source were 17-26px tall, well under a comfortable thumb. Scoped to coarse
     pointers, so nothing about the desktop layout moves. The project cards are already fine -
     their link spreads a ::after over the whole card. */
  @media (pointer: coarse) {
    .contact__v,
    .closing__maplink a,
    .costs__source a,
    .footer__links a,
    .hail__cta {
      display: inline-block;
      padding-block: var(--space-2);
      min-block-size: 44px;
      align-content: center;
    }
    /* 0.68rem is 10.9px - too small to read on a phone */
    .cost-card__type { font-size: 0.78rem; }
  }
}
