/* ═══════════════════════════════════════════════════════════════════════════
   OLIMPIA ACTIVE WEAR — "PRECISION AT SCALE"
   Implementation of _team/design-system.md v1.0

   The ledger and the flame: every page is a ruled technical document, and the
   only thing permitted to break the ledger is heat.

   Layer order is load-bearing. Components never reach past the semantic
   tokens into a primitive — that is what makes the register inversion total.
   ═══════════════════════════════════════════════════════════════════════════ */

@layer reset, tokens, base, devices, components, utilities;

/* ═══════════════════════════════════════════════════════════════════════════
   RESET
   ═══════════════════════════════════════════════════════════════════════════ */
@layer reset {
  *, *::before, *::after { box-sizing: border-box; }

  html { -webkit-text-size-adjust: 100%; }

  body, h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd, ol, ul {
    margin: 0;
  }

  ul[role='list'], ol[role='list'] { list-style: none; padding: 0; }

  img, picture, svg, video, canvas {
    display: block;
    max-width: 100%;
  }

  input, button, textarea, select { font: inherit; color: inherit; }

  button { background: none; border: 0; }

  /* Long unbroken strings (URLs, part numbers) must not blow out the grid —
     but headings are excluded on purpose. At display sizes `break-word` will
     snap a word mid-stem ("Producim / os"), which is far uglier than a little
     overflow. Headings are kept in range by the type scale instead. */
  p, li, dd { overflow-wrap: break-word; }
  h1, h2, h3, h4, h5, h6 { overflow-wrap: normal; hyphens: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   TOKENS
   ═══════════════════════════════════════════════════════════════════════════ */
@layer tokens {

  /* ── PRIMITIVES · never referenced by a component ────────────────────── */
  :root {
    /* FOREST · sampled off the reference photograph's shadowed leaves. */
    --pine-950: #060B06;
    --pine-900: #0D1710;   /* FOUNDRY page ground */
    --pine-800: #16241A;   /* raised surface / card */
    --pine-700: #1F3123;   /* ambient rule */
    --pine-600: #2C3F2E;
    --pine-550: #47603F;   /* SPEC micro-label — 6.56:1 on cream-000 */
    --pine-500: #3B5340;
    --pine-450: #5E7A5F;   /* UI border, dark — 3.86:1 */
    --pine-400: #6E8A6C;
    --pine-300: #94AC90;
    --pine-200: #B9CDB4;
    --pine-100: #D9E6D5;

    /* CREAM · warm paper, the light register. */
    --cream-000: #FAF8F1;
    --cream-100: #F3EFE3;
    --cream-200: #E7E1D0;
    --cream-300: #D3CAB4;
    --cream-400: #B3A98D;
    --cream-500: #8A8168;

    /* AMBER · the berries. Warm signal against cold forest — the one
       chromatic voice, rationed exactly as the flame was. */
    --amber-300: #F0C07E;  /* dark-register hover */
    --amber-400: #E3A45C;  /* THE BRAND AMBER — 8.48:1 on pine-900 */
    --amber-500: #D08A3C;
    --amber-600: #A55F1B;
    --amber-700: #8A4E14;  /* light-register text — 6.21:1 on cream-000 */

    /* OLIVE · diagrams only. */
    --olive-400: #9BAA63;
    --olive-500: #5F6B3B;

    --error-400: #F0846F;
    --error-600: #A83A22;
    --ok-400:    #9BAA63;
    --ok-600:    #5F6B3B;

    /* RADII · the site should feel continuous, not cut from sheet metal. */
    --r-sm: 0.5rem;
    --r-md: 0.875rem;
    --r-lg: 1.25rem;
    --r-xl: 1.75rem;
    --r-pill: 999px;
  }

  /* ── REGISTER: FOUNDRY (dark, default) — brand and stadium ───────────── */
  :root,
  [data-register='foundry'] {
    --bg:           var(--pine-900);
    --bg-raised:    var(--pine-800);
    --bg-sunken:    var(--pine-950);
    --text-strong:  var(--cream-000);
    --text:         var(--cream-100);
    --text-2:       var(--pine-200);
    --text-3:       var(--pine-300);
    --edge:         var(--pine-450);
    --rule-ambient: var(--pine-700);
    --accent:       var(--amber-400);
    --accent-hover: var(--amber-300);
    --accent-fill:  var(--amber-400);
    --on-accent:    var(--pine-950);
    --focus:        var(--amber-400);
    --danger:       var(--error-400);
    --ok:           var(--ok-400);
    --diagram:      var(--olive-400);
    color-scheme: dark;
  }

  /* ── REGISTER: SPEC (light) — document and datasheet ─────────────────── */
  [data-register='spec'] {
    --bg:           var(--cream-000);
    --bg-raised:    var(--cream-200);
    --bg-sunken:    var(--cream-100);
    --text-strong:  var(--pine-950);
    --text:         var(--pine-900);
    --text-2:       var(--pine-500);
    --text-3:       var(--pine-550);
    --edge:         var(--cream-500);
    --rule-ambient: var(--cream-300);
    --accent:       var(--amber-700);
    --accent-hover: var(--amber-600);
    --accent-fill:  var(--amber-700);
    --on-accent:    var(--cream-000);
    --focus:        var(--amber-700);
    --danger:       var(--error-600);
    --ok:           var(--ok-600);
    --diagram:      var(--olive-500);
    color-scheme: light;
  }

  /* ── RULES · three weights make the structure legible ────────────────── */
  :root {
    --rule-ghost: color-mix(in oklab, var(--rule-ambient) 70%, transparent);
    --rule-hair:  var(--rule-ambient);
    --rule-edge:  var(--edge);
  }

  /* ── TYPE ────────────────────────────────────────────────────────────── */
  :root {
    /* Bricolage Grotesque carries an optical-size axis, so headlines get
       tighter fitting at display sizes and open up at small ones on their own.
       Instrument Sans has a wider default sidebearing than Figtree, which is
       most of why the page reads less cramped. JetBrains Mono replaces Martian
       Mono: Martian is drawn very wide, and it was what pushed micro-labels
       out of their columns and broke the spec rows. */
    --ff-display: 'Bricolage', 'Instrument-fallback', system-ui, sans-serif;
    --ff-text:    'Instrument', 'Instrument-fallback', system-ui, sans-serif;
    --ff-mono:    'JetBrains', ui-monospace, 'SFMono-Regular', Menlo, monospace;

    /* Every clamp carries a rem component — a pure-vw scale fails WCAG 1.4.4. */
    --fs-micro:   clamp(0.6875rem, 0.6674rem + 0.0826vw, 0.7500rem);
    --fs-caption: clamp(0.7500rem, 0.7299rem + 0.0826vw, 0.8125rem);
    --fs-body-s:  clamp(0.8750rem, 0.8549rem + 0.0826vw, 0.9375rem);
    --fs-body:    clamp(1.0000rem, 0.9597rem + 0.1653vw, 1.1250rem);
    --fs-body-l:  clamp(1.1250rem, 1.0646rem + 0.2479vw, 1.3125rem);
    --fs-lead:    clamp(1.3125rem, 1.1916rem + 0.4959vw, 1.6875rem);
    --fs-h6:      clamp(1.1250rem, 1.0847rem + 0.1653vw, 1.2500rem);
    --fs-h5:      clamp(1.2500rem, 1.1493rem + 0.4132vw, 1.5625rem);
    --fs-h4:      clamp(1.5000rem, 1.3187rem + 0.7438vw, 2.0625rem);
    --fs-h3:      clamp(1.8750rem, 1.5728rem + 1.2397vw, 2.8125rem);
    --fs-h2:      clamp(2.3750rem, 1.8110rem + 2.3140vw, 4.1250rem);
    --fs-h1:      clamp(3.0000rem, 1.9525rem + 4.2975vw, 6.2500rem);
    /* Floors are set by the longest single word the display face has to hold
       at 375px — "Producimos" — so the headline never has to break mid-word.
       The upper bounds are unchanged; only the small end comes down. */
    --fs-display: clamp(2.8750rem, 1.7000rem + 4.8200vw, 7.2500rem);
    --fs-mega:    clamp(3.5rem, 2.2rem + 13vw, 17rem);
  }

  /* ── SPACE ───────────────────────────────────────────────────────────── */
  :root {
    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 0.75rem;
    --space-4: 1rem;
    --space-5: 1.5rem;
    --space-6: 2rem;
    --space-7: 3rem;
    --space-8: 4rem;
    --space-9: 6rem;
    --space-10: 8rem;
    --space-11: 12rem;
    --space-12: 16rem;

    --pad-card:    var(--space-5);
    --pad-card-lg: var(--space-7);
    --gap-grid:    var(--space-6);
    --stack-tight: var(--space-4);
    --stack-base:  var(--space-6);
    --stack-loose: var(--space-8);
    --section-y:   clamp(var(--space-10), 12vw, var(--space-12));
  }

  /* ── GRID ────────────────────────────────────────────────────────────── */
  :root {
    --grid-cols: 12;
    --grid-gutter: 1.5rem;
    --grid-margin: 3rem;
    --grid-max: 90rem;
    --frame-max: 105rem;
  }
  @media (max-width: 1023px) { :root { --grid-cols: 8; --grid-gutter: 1.25rem; --grid-margin: 2rem; } }
  @media (max-width: 767px)  { :root { --grid-cols: 4; --grid-gutter: 1rem;    --grid-margin: 1.5rem; } }
  @media (max-width: 479px)  { :root { --grid-margin: 1.25rem; } }
  @media (min-width: 1280px) { :root { --grid-margin: 3.5rem; } }
  @media (min-width: 1600px) { :root { --grid-gutter: 2rem; --grid-margin: 4rem; } }

  /* ── MOTION · mechanical. Nothing here uses a spring. ────────────────── */
  :root {
    --ease-precision: cubic-bezier(0.16, 1, 0.30, 1);
    --ease-machine:   cubic-bezier(0.65, 0, 0.35, 1);
    --ease-cut:       cubic-bezier(0.55, 0, 1, 0.45);
    --ease-flame:     cubic-bezier(0.22, 1, 0.36, 1);
    --ease-belt:      linear;

    --dur-instant: 80ms;
    --dur-fast: 160ms;
    --dur-base: 280ms;
    --dur-slow: 480ms;
    --dur-deliberate: 720ms;
    --dur-cinematic: 1200ms;
    --stagger: 60ms;

    /* The bar carries the brand, so it gets room to do it. 80px was tight
       against a 40px wordmark; 104px lets the logo read at full size with
       real air above and below it. */
    --nav-h: 104px;
  }
  /* A 104px bar eats too much of a phone screen. Must come after the
     declaration above — same specificity, so source order decides. */
  @media (max-width: 767px) { :root { --nav-h: 84px; } }

  /* ── INCREASED CONTRAST ──────────────────────────────────────────────── */
  @media (prefers-contrast: more) {
    :root, [data-register='foundry'] {
      --text-3: var(--pine-200);
      --edge: var(--pine-300);
      --rule-ambient: var(--pine-500);
    }
    [data-register='spec'] {
      --text-3: var(--pine-500);
      --edge: var(--pine-500);
      --rule-ambient: var(--cream-500);
    }
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   BASE
   ═══════════════════════════════════════════════════════════════════════════ */
@layer base {

  /* Metric-matched fallback kills CLS while Instrument Sans swaps in. */
  @font-face {
    font-family: 'Instrument-fallback';
    src: local('Helvetica Neue'), local('Arial');
    size-adjust: 103%;
    ascent-override: 94%;
    descent-override: 24%;
    line-gap-override: 0%;
  }

  html { scroll-behavior: smooth; }

  body {
    font-optical-sizing: auto;
    background: var(--bg);
    color: var(--text);
    font-family: var(--ff-text);
    font-size: var(--fs-body);
    line-height: 1.75;
    font-synthesis-weight: none;
    -webkit-font-smoothing: antialiased;
  }

  /* Guard against a flash of Spanish before the English swap lands.
     main.js clears this; the inline head script also clears it on a timer
     so a JS failure can never leave the page blank. */
  .i18n-pending body { visibility: hidden; }

  [data-register] { background: var(--bg); color: var(--text); }

  h1, h2, h3, h4 {
    font-family: var(--ff-display);
    color: var(--text-strong);
    text-wrap: balance;
    /* A display headline should hold one or two lines. Anything past that is
       the container being too narrow for the size, not a wrapping problem. */
    max-width: 18ch;
  }
  h3, h4 { max-width: 24ch; }
  .t-display, .t-mega { max-width: 16ch; }
  h1 { font-size: var(--fs-h1); font-weight: 700; font-stretch: 100%; line-height: 0.98; letter-spacing: -0.025em; }
  h2 { font-size: var(--fs-h2); font-weight: 700; font-stretch: 100%; line-height: 1.02; letter-spacing: -0.022em; }
  h3 { font-size: var(--fs-h3); font-weight: 600; font-stretch: 100%; line-height: 1.12; letter-spacing: -0.018em; }
  h4 { font-size: var(--fs-h4); font-weight: 600; font-stretch: 100%; line-height: 1.2; letter-spacing: -0.012em; }
  h5 { font-family: var(--ff-text); font-size: var(--fs-h5); font-weight: 600; line-height: 1.25; letter-spacing: -0.01em; color: var(--text-strong); }
  h6 { font-family: var(--ff-text); font-size: var(--fs-h6); font-weight: 600; line-height: 1.35; color: var(--text-strong); }

  p { text-wrap: pretty; max-width: 62ch; }

  a {
    color: inherit;
    text-decoration: underline;
    text-decoration-color: var(--edge);
    text-underline-offset: 0.18em;
    text-decoration-thickness: 1px;
    transition: text-decoration-thickness var(--dur-fast) var(--ease-machine),
                text-underline-offset var(--dur-fast) var(--ease-machine),
                text-decoration-color var(--dur-fast) var(--ease-machine);
  }
  /* No colour change on hover — this is what keeps flame rationed. */
  a:hover { text-decoration-thickness: 2px; text-underline-offset: 0.12em; text-decoration-color: var(--text); }

  :focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 3px;
    border-radius: var(--r-sm);
  }

  ::selection { background: var(--accent); color: var(--on-accent); }

  /* ── Type utilities ───────────────────────────────────────────────────── */
  .t-mono  { font-family: var(--ff-mono); font-variant-numeric: tabular-nums; font-stretch: 87.5%; }
  .t-stat  { font-family: var(--ff-mono); font-weight: 700; font-stretch: 100%;
             font-variant-numeric: tabular-nums; letter-spacing: -0.01em; line-height: 0.9;
             color: var(--text-strong); }
  .t-lead  { font-size: var(--fs-lead); line-height: 1.6; letter-spacing: -0.01em; color: var(--text-2); }
  .t-body-l{ font-size: var(--fs-body-l); line-height: 1.72; }
  .t-body-s{ font-size: var(--fs-body-s); line-height: 1.72; }
  .t-caption{ font-size: var(--fs-caption); line-height: 1.62; letter-spacing: 0.005em; color: var(--text-3); }

  .t-display { font-family: var(--ff-display); font-size: var(--fs-display); font-weight: 700;
               font-stretch: 100%; line-height: 0.95; letter-spacing: -0.03em;
               color: var(--text-strong); text-wrap: balance; }
  .t-mega    { font-family: var(--ff-display); font-size: var(--fs-mega); font-weight: 800;
               font-stretch: 100%; line-height: 0.92; letter-spacing: -0.035em;
               color: var(--text-strong); }

  /* The one sanctioned flame word inside a headline — once per page. */
  .t-flame { color: var(--accent); }

  /* ── The eyebrow: a drawing annotation, never a marketing kicker ──────── */
  .eyebrow {
    font-family: var(--ff-mono);
    font-size: var(--fs-micro);
    font-weight: 500;
    font-stretch: 87.5%;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    line-height: 1.35;
    color: var(--text-3);          /* NEVER --accent */
    display: flex;
    align-items: center;
    gap: var(--space-3);
  }
  .eyebrow__index { opacity: 0.7; font-variant-numeric: tabular-nums; flex: none; }
  .eyebrow::after { content: ''; flex: 1 1 auto; height: 1px; background: var(--rule-ambient); }
  .eyebrow--plain::after { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   DEVICES  (design-system.md §3.4, §4)
   ═══════════════════════════════════════════════════════════════════════════ */
@layer devices {

  /* ── Persistent column field · one fixed layer, zero scroll work ─────── */
  .column-field {
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    max-width: var(--frame-max);
    margin-inline: auto;
    padding-inline: var(--grid-margin);
    display: grid;
    grid-template-columns: repeat(var(--grid-cols), minmax(0, 1fr));
    gap: var(--grid-gutter);
  }
  .column-field span { border-left: 1px solid var(--rule-ghost); }
  .column-field span:last-child { border-right: 1px solid var(--rule-ghost); }
  @media (max-width: 767px) { .column-field span:nth-child(n+3) { display: none; } }

  /* ── The plate · a technical drawing sheet with registration ticks ───── */
  .plate {
    position: relative;
    border: 1px solid var(--rule-hair);
    border-radius: var(--r-lg);
    padding: var(--pad-card-lg);
    background: var(--bg-raised);
  }
    /* Registration ticks were drawn for square plates; with rounded corners
     they read as artefacts, so the plate now states itself through the
     radius and the hairline alone. */
  .plate--flat { background: transparent; }

  /* ── CAD-style dimension annotation ──────────────────────────────────── */
  .dim {
    display: flex; align-items: center; gap: var(--space-3);
    font-family: var(--ff-mono); font-size: var(--fs-micro);
    letter-spacing: 0.14em; color: var(--cream-300); text-transform: uppercase;
  }
  [data-register='spec'] .dim { color: var(--pine-550); }
  .dim::before, .dim::after { content: ''; flex: 1; height: 1px; background: var(--rule-hair); }

  /* ── 4.1 THE APERTURE · the logo oval as universal viewport ────────────
     This was an SVG-ellipse `mask-image` stretched to the box. It is now
     `border-radius: 50%` + `overflow: hidden`, which inscribes exactly the
     same ellipse — including the non-uniform stretch on the 3/4 and 21/9
     variants, since a percentage radius is resolved per-axis. Identical
     result, and no CSS mask anywhere on the site: WebKit did not paint the
     masked brand marks at all, and a photograph is not worth that risk.
     The .aperture-ring sibling already draws its hairline this way. */
  .aperture {
    position: relative;
    aspect-ratio: 4 / 3;
    overflow: hidden;
    border-radius: 50%;
    background: var(--bg-raised);
  }
  .aperture > img {
    width: 100%; height: 100%;
    object-fit: cover;
    transition: transform var(--dur-slow) var(--ease-precision);
  }
  .aperture--portrait { aspect-ratio: 3 / 4; }
  .aperture--stadium  { aspect-ratio: 21 / 9; }

  /* The ring sits above the masked media as a sibling. */
  .aperture-frame { position: relative; }
  .aperture-ring {
    position: absolute; inset: 0;
    pointer-events: none;
    border-radius: 50%;
    box-shadow: inset 0 0 0 1px var(--rule-edge);
  }

  /* ── 4.2 THE FLAME INDEX · the mark as a counting system ─────────────────
     NO CSS MASKS. These were `-webkit-mask` / `mask` shorthands with a
     `center / contain` slash-size, painted over a background-colour. WebKit
     does not paint that reliably — the flames and the wordmark disappeared
     entirely in Safari while rendering correctly in Chrome and Firefox. A
     mask is also the wrong tool for a brand mark: when it fails, the brand
     does not degrade, it vanishes.

     So the artwork is now a real `background-image`, pre-tinted per colour
     token into `assets/logo/flame-*.png` (see _team/build-logos.py). Plain
     background-image + background-size has no browser caveats anywhere.
     Colour changes swap the file instead of recolouring a mask. */
  .flame-bullet {
    display: inline-block;
    width: 0.72em; height: 0.75em;
    flex: none;
    background-color: transparent;
    background-image: url('../logo/flame-pine300.png');
    background-size: contain;
    background-position: center;
    background-repeat: no-repeat;
  }
  [data-register='spec'] .flame-bullet { background-image: url('../logo/flame-pine550.png'); }

  /* The gauge: the real Olimpia mark, filling from the base with scroll.
     This is the only place flame colour animates, and it always represents
     genuine progress. The amber fill is a second, identically-sized copy of
     the artwork revealed bottom-up with clip-path — so the filled portion is
     flame-shaped, not a rectangle, without any mask involved. */
  .flame-gauge {
    --p: 0;
    position: relative;
    width: 18px; height: 19px;
    flex: none;
    background-color: transparent;
    background-image: url('../logo/flame-pine300.png');
    background-size: contain;
    background-position: center;
    background-repeat: no-repeat;
  }
  [data-register='spec'] .flame-gauge { background-image: url('../logo/flame-pine550.png'); }
  .flame-gauge::after {
    content: '';
    position: absolute; inset: 0;
    background-image: url('../logo/flame-amber400.png');
    background-size: contain;
    background-position: center;
    background-repeat: no-repeat;
    /* Reveal from the base upward. inset() is the well-supported shape. */
    -webkit-clip-path: inset(calc((1 - var(--p)) * 100%) 0 0 0);
            clip-path: inset(calc((1 - var(--p)) * 100%) 0 0 0);
    transition: -webkit-clip-path var(--dur-fast) linear,
                        clip-path var(--dur-fast) linear;
  }
  [data-register='spec'] .flame-gauge::after { background-image: url('../logo/flame-amber700.png'); }

  /* ── 4.3 THE TESSELLATION FIELD · 400,000 made felt ──────────────────── */
  .tessellation {
    --unit: 6px;
    position: absolute; inset: 0;
    pointer-events: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12'%3E%3Crect x='4' y='2' width='4' height='7' rx='1' fill='%237C8996'/%3E%3C/svg%3E");
    background-size: calc(var(--unit) * 2) calc(var(--unit) * 2);
    background-repeat: repeat;
    opacity: 0.5;
  }
  .tessellation--footer { opacity: 0.05; }

  /* The field hosts both builds: the CSS tile always, the canvas only when
     desktop + motion allowed. It never intercepts pointer events — the
     canvas reads the pointer from the section instead. */
  .tess-section { position: relative; }
  .tess-field {
    position: absolute; inset: 0;
    pointer-events: none;
    overflow: hidden;
  }
  .tessellation-canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
  /* When the canvas is live the static tile steps aside. */
  .tess-field:has(.tessellation-canvas:not([hidden])) .tessellation-static { opacity: 0.12; }

  /* ── THE LOOM · a live woven surface ─────────────────────────────────
     Warp and weft drawn as real threads on canvas, undulating slowly and
     displacing under the pointer like fabric under a hand. This is the
     "hi-tech textile" register of the site. The CSS weave underneath is
     always present, so the section is never empty if the canvas is gated
     off (mobile, reduced motion, save-data). */
  .loom { position: absolute; inset: 0; pointer-events: none; overflow: hidden; }
  .loom__canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
  .loom__fallback { position: absolute; inset: 0; opacity: 0.35; }

  /* ── TECHNICAL ICONS · one drawing system, injected by sprite.js ────── */
  .icon {
    width: 2rem; height: 2rem;
    flex: none;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.25;
    stroke-linecap: round;
    stroke-linejoin: round;
    color: var(--diagram);
    overflow: visible;
  }
  .icon--lg { width: 2.75rem; height: 2.75rem; }
  .icon--sm { width: 1.25rem; height: 1.25rem; stroke-width: 1.5; }

  /* Icons draw themselves in, like a plotter laying down thread. The stroke
     length is arbitrary but larger than any path in the set, so every icon
     completes within the same window. */
  .icon--draw path, .icon--draw rect, .icon--draw circle {
    stroke-dasharray: 160;
    stroke-dashoffset: 160;
    transition: stroke-dashoffset var(--dur-cinematic) var(--ease-belt);
    transition-delay: var(--reveal-delay, 0ms);
  }
  .is-in .icon--draw path,
  .is-in .icon--draw rect,
  .is-in .icon--draw circle,
  .icon--draw.is-in path, .icon--draw.is-in rect, .icon--draw.is-in circle {
    stroke-dashoffset: 0;
  }

  .spec-card:hover .icon, .cap-card:hover .icon, .sector:hover .icon { color: var(--text-2); }
  .icon { transition: color var(--dur-base) var(--ease-machine); }

  /* ── 4.4 THE SEAM · "Calidad en Cada Hebra" rendered as structure ────── */
  .seam {
    position: absolute;
    top: 0; bottom: 0;
    left: calc(var(--grid-margin) - 1px);
    width: 2px;
    pointer-events: none;
    z-index: 1;
    overflow: hidden;
  }
  .seam__track, .seam__drawn {
    position: absolute; inset: 0;
    background-image: repeating-linear-gradient(
      to bottom, currentColor 0 6px, transparent 6px 10px);
  }
  .seam__track { color: var(--rule-ghost); }
  .seam__drawn {
    color: var(--text-3);
    /* --seam-p (0→1) is written once per frame by the shared scroll handler. */
    clip-path: inset(0 0 calc((1 - var(--seam-p, 0)) * 100%) 0);
  }
  /* The needle: a 2px advancing tip, and the only flame in the device. */
  .seam__tip {
    position: absolute;
    left: 0; width: 2px; height: 18px;
    background: var(--accent);
    top: calc(var(--seam-p, 0) * 100%);
    transform: translateY(-100%);
    opacity: 0;
  }
  .seam.is-active .seam__tip { opacity: 1; }
  @media (max-width: 767px) { .seam { display: none; } }

  /* ── 4.5 THE SPEC PLATE · the workhorse. Repetition is the argument. ─── */
  /* The leader-line row is designed for terse values ("24 mm"), but Olimpia's
     specs are full sentences. Wrapping is therefore mandatory: the label keeps
     its leader on the first line and a long value drops beneath it at full
     width, instead of running out of the plate. */
  .spec-row {
    display: grid;
    grid-template-columns: minmax(5rem, 7.5rem) minmax(1rem, 0.4fr) minmax(0, 3.2fr);
    gap: var(--space-3);
    align-items: start;
    font-size: var(--fs-caption);
    line-height: 1.7;
  }
  /* The label is the technical annotation and stays mono; the value is prose
     and moves to the text face. JetBrains is far wider than Instrument, and
     setting sentences in it was what forced every value onto three lines. */
  .spec-row__label {
    font-family: var(--ff-mono);
    color: var(--text-3); text-transform: uppercase; letter-spacing: 0.06em;
    text-wrap: balance;
  }
  /* The leader belongs on the first line, beside the label. Aligning it to
     the end of the grid row dropped it to the bottom of a wrapped value. */
  .spec-row__lead {
    height: 0; align-self: start; margin-top: 0.85em;
    border-bottom: 1px dotted var(--rule-hair);
  }
  .spec-row__value {
    font-family: var(--ff-text);
    font-size: var(--fs-body-s);
    color: var(--text-strong); font-weight: 500;
    font-variant-numeric: tabular-nums;
    text-wrap: pretty;
    overflow-wrap: break-word;
  }
  /* Values that are genuinely data, not prose, keep the mono. */
  .spec-row__value.t-mono { font-family: var(--ff-mono); font-size: var(--fs-caption); }
  .spec-card__rows { display: grid; gap: var(--space-4); }

  /* Ledger variant: a long descriptive label against a short value. The
     default row is the opposite shape (short label, long value), and putting
     a sentence in that narrow track makes it wrap three times. */
  .spec-row--stat {
    grid-template-columns: minmax(0, 1fr) minmax(2rem, 4rem) auto;
    align-items: baseline;
  }
  .spec-row--stat .spec-row__lead { align-self: end; margin-top: 0; margin-bottom: 0.45em; }
  .spec-row--stat .spec-row__label { text-transform: none; letter-spacing: 0.02em; }
  .spec-row--stat .spec-row__value {
    font-size: var(--fs-body-l); font-weight: 600; text-align: right;
    white-space: nowrap;
  }
  @media (max-width: 599px) {
    .spec-row { grid-template-columns: 1fr; gap: var(--space-1); }
    .spec-row__lead { display: none; }
  }

  /* Isometric weave diagram — steel, the diagrammatic voice only. */
  .weave-diagram { color: var(--diagram); }
  .weave-diagram path, .weave-diagram line { transition: transform var(--dur-base) var(--ease-precision); }

  /* ── 4.6 THE OVAL TRACK · used once, which is what makes it land ─────── */
  .oval-track { width: 100%; height: auto; color: var(--rule-hair); }
  .oval-track .lane { fill: none; stroke: currentColor; stroke-width: 1; }
  .oval-track .lane-label { fill: var(--text-3); font-family: var(--ff-mono); font-size: 13px; letter-spacing: 0.22em; text-transform: uppercase; }
  .track-runner { fill: var(--accent); }

  /* ── 4.7 HALFTONE WEAVE · what fills an aperture with no photograph ──── */
  .weave {
    --w: 6px;
    background-color: var(--bg-raised);
    background-image:
      repeating-linear-gradient(90deg, color-mix(in oklab, var(--text-3) 22%, transparent) 0 1px, transparent 1px var(--w)),
      repeating-linear-gradient(0deg,  color-mix(in oklab, var(--text-3) 14%, transparent) 0 1px, transparent 1px var(--w)),
      repeating-linear-gradient(45deg, color-mix(in oklab, var(--text-3) 6%,  transparent) 0 1px, transparent 1px calc(var(--w) * 1.5));
  }
  .weave--ambient  { position: absolute; inset: 0; opacity: 0.05; pointer-events: none; background-color: transparent; }
  .weave--specimen { --w: 14px; width: 100%; height: 100%; }

  /* ── Scroll reveals · four behaviours, fired once each ────────────────── */
  .reveal {
    opacity: 0;
    transform: translateY(24px);
    transition: opacity var(--dur-slow) var(--ease-precision),
                transform var(--dur-slow) var(--ease-precision);
    transition-delay: var(--reveal-delay, 0ms);
  }
  .reveal.is-in { opacity: 1; transform: none; }

  .reveal-wipe {
    clip-path: inset(0 100% 0 0);
    transition: clip-path var(--dur-deliberate) var(--ease-precision);
    transition-delay: var(--reveal-delay, 0ms);
  }
  .reveal-wipe.is-in { clip-path: inset(0); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   COMPONENTS
   ═══════════════════════════════════════════════════════════════════════════ */
@layer components {

  /* ── Layout scaffolding ──────────────────────────────────────────────── */
  .shell { position: relative; z-index: 1; }

  .grid {
    display: grid;
    grid-template-columns: repeat(var(--grid-cols), minmax(0, 1fr));
    gap: var(--grid-gutter);
    max-width: var(--grid-max);
    margin-inline: auto;
    padding-inline: var(--grid-margin);
  }
  .wrap {
    max-width: var(--grid-max);
    margin-inline: auto;
    padding-inline: var(--grid-margin);
  }
  .section {
    position: relative;
    padding-block: var(--section-y);
    overflow: clip;
  }
  .section__head { display: grid; gap: var(--space-6); margin-bottom: var(--space-9); }
  .stack { display: grid; gap: var(--stack-base); align-content: start; }
  .stack--tight { gap: var(--stack-tight); }
  .stack--loose { gap: var(--stack-loose); }

  /* ── Skip link ───────────────────────────────────────────────────────── */
  .skip-link {
    position: absolute; top: 0; left: 0; z-index: 200;
    padding: var(--space-3) var(--space-5);
    background: var(--accent-fill); color: var(--on-accent);
    font-family: var(--ff-mono); font-size: var(--fs-micro);
    text-transform: uppercase; letter-spacing: 0.12em; text-decoration: none;
    transform: translateY(-120%);
  }
  .skip-link:focus-visible { transform: translateY(0); }

  /* ── Buttons · mono label is what stops these reading as generic ─────── */
  .btn {
    display: inline-flex; align-items: center; justify-content: center; gap: var(--space-3);
    min-height: 52px; padding: 0 var(--space-7);
    font-family: var(--ff-mono); font-size: var(--fs-micro); font-weight: 500;
    font-stretch: 87.5%; text-transform: uppercase; letter-spacing: 0.12em;
    border-radius: 2px; border: 1px solid transparent;
    text-decoration: none; cursor: pointer; text-align: center;
    transition: background-color var(--dur-fast) var(--ease-machine),
                border-color var(--dur-fast) var(--ease-machine),
                color var(--dur-fast) var(--ease-machine),
                transform var(--dur-instant) var(--ease-machine);
  }
  .btn:hover  { transform: translateY(-1px); text-decoration: none; }
  .btn:active { transform: translateY(0); }
  .btn:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }
  .btn--lg { min-height: 60px; padding: 0 var(--space-8); font-size: var(--fs-caption); }
  .btn--sm { min-height: 44px; padding: 0 var(--space-6); }
  .btn--block { width: 100%; }

  .btn--primary { background: var(--accent-fill); color: var(--on-accent); }
  .btn--primary:hover { background: var(--accent-hover); }
  /* On the amber fill the flame reads as the dark ink of --on-accent. */
  .btn--primary .flame-bullet {
    width: 10px; height: 11px;
    background-image: url('../logo/flame-pine950.png');
  }
  [data-register='spec'] .btn--primary .flame-bullet { background-image: url('../logo/flame-cream.png'); }

  .btn--secondary { border-color: var(--rule-edge); color: var(--text); }
  .btn--secondary:hover { border-color: var(--text); background: color-mix(in oklab, var(--text) 8%, transparent); }
  .btn--secondary:active { background: color-mix(in oklab, var(--text) 14%, transparent); }

  .btn--ghost { color: var(--text-2); padding: 0; min-height: 0; border-radius: 0; text-decoration: underline;
                text-decoration-thickness: 1px; text-underline-offset: 0.4em; }
  .btn--ghost:hover { color: var(--text); text-decoration-thickness: 2px; transform: none; }

  /* ── Nav ─────────────────────────────────────────────────────────────── */
  .nav {
    position: fixed; top: 0; left: 0; right: 0; z-index: 100;
    height: var(--nav-h);
    display: flex; align-items: center;
    border-bottom: 1px solid var(--rule-ghost);
    transition: height var(--dur-base) var(--ease-machine),
                background-color var(--dur-base) var(--ease-machine),
                border-color var(--dur-base) var(--ease-machine);
  }
  .nav.is-scrolled {
    --nav-h: 76px;
    background: color-mix(in oklab, var(--bg) 88%, transparent);
    backdrop-filter: blur(12px) saturate(1.2);
    -webkit-backdrop-filter: blur(12px) saturate(1.2);
    border-bottom-color: var(--rule-hair);
  }
  .nav__inner {
    width: 100%; max-width: var(--frame-max); margin-inline: auto;
    padding-inline: var(--grid-margin);
    display: flex; align-items: center; gap: var(--space-7);
  }
  /* Wordmark as a real background-image, not a mask. See §4.2 for why.
     Sized from HEIGHT, with the box matching the artwork's own 1120:220
     ratio. The old 132x32 box was 4.13:1 against 5.09:1 artwork, so
     background-size:contain fitted by width and the wordmark actually drew
     only ~26px tall inside a 32px box — it was letterboxed, which is why it
     read small. Driving the box off --logo-h means the number below is the
     real rendered height, and there is no dead space. */
  .nav__logo {
    --logo-h: 44px;
    display: block; flex: none;
    height: var(--logo-h);
    width: calc(var(--logo-h) * 1120 / 220);
    background-color: transparent;
    background-image: url('../logo/wordmark-cream.png');
    background-size: contain;
    background-position: left center;
    background-repeat: no-repeat;
    text-decoration: none;
    transition: height var(--dur-base) var(--ease-machine),
                width var(--dur-base) var(--ease-machine);
  }
  [data-register='spec'] .nav__logo { background-image: url('../logo/wordmark-pine950.png'); }
  /* The links are a <ul> inside a <nav aria-label="Principal"> wrapper, so the
     flex children of .nav__inner are [logo, nav, actions] — the <ul> is not a
     flex item. `margin-left: auto` therefore has to sit on the WRAPPER. It was
     on the <ul>, where it did nothing at all: the whole right side of the bar
     sat empty at every width, the quote button never reached the margin, and
     on mobile the burger floated in the middle instead of the right edge.
     With the links hidden below 1200px the wrapper collapses to zero width and
     this same rule pushes the actions cluster right, which is what we want. */
  .nav__inner > nav { margin-left: auto; }
  .nav__links {
    display: flex; align-items: center; gap: var(--space-7);
    list-style: none; padding: 0; margin: 0;
  }
  .nav__link {
    font-family: var(--ff-mono); font-size: var(--fs-micro); font-weight: 500;
    font-stretch: 87.5%; text-transform: uppercase; letter-spacing: 0.14em;
    color: var(--text-2); text-decoration: none; white-space: nowrap;
    padding-block: var(--space-2);
    transition: color var(--dur-fast) var(--ease-machine);
  }
  .nav__link:hover { color: var(--text-strong); text-decoration: none; }
  .nav__link[aria-current='page'] {
    color: var(--text-strong);
    border-bottom: 1px solid var(--accent);   /* sanctioned flame use */
    padding-bottom: 3px;
  }
  .nav__actions { display: flex; align-items: center; gap: var(--space-5); flex: none; }

  /* ── Language toggle · weight and ground, never flame ────────────────── */
  .lang {
    display: flex; align-items: stretch;
    height: 36px; border: 1px solid var(--rule-edge); border-radius: var(--r-pill);
    overflow: hidden;
  }
  .lang__opt {
    display: inline-flex; align-items: center; justify-content: center;
    width: 36px;
    font-family: var(--ff-mono); font-size: var(--fs-micro); font-weight: 500;
    font-stretch: 87.5%; letter-spacing: 0.1em;
    color: var(--text-3); background: transparent; cursor: pointer;
    text-decoration: none;
    transition: color var(--dur-fast) var(--ease-machine),
                background-color var(--dur-fast) var(--ease-machine);
  }
  .lang__opt + .lang__opt { border-left: 1px solid var(--rule-edge); }
  .lang__opt:hover { color: var(--text-2); text-decoration: none; }
  .lang__opt.is-active {
    color: var(--text-strong);
    background: color-mix(in oklab, var(--text) 12%, transparent);
  }

  /* ── Mobile nav ──────────────────────────────────────────────────────── */
  .nav__burger {
    display: none; flex: none;
    width: 44px; height: 44px;
    align-items: center; justify-content: center;
    cursor: pointer;
  }
  .nav__burger span {
    display: block; width: 20px; height: 1px; background: var(--text);
    position: relative;
    transition: transform var(--dur-base) var(--ease-machine), opacity var(--dur-fast) linear;
  }
  .nav__burger span::before, .nav__burger span::after {
    content: ''; position: absolute; left: 0; width: 20px; height: 1px; background: var(--text);
    transition: transform var(--dur-base) var(--ease-machine);
  }
  .nav__burger span::before { top: -6px; }
  .nav__burger span::after  { top: 6px; }
  .nav__burger[aria-expanded='true'] span { background: transparent; }
  .nav__burger[aria-expanded='true'] span::before { transform: translateY(6px) rotate(45deg); }
  .nav__burger[aria-expanded='true'] span::after  { transform: translateY(-6px) rotate(-45deg); }

  .nav__panel {
    position: fixed; inset: 0; z-index: 99;
    background: var(--bg);
    padding: calc(var(--nav-h) + var(--space-7)) var(--grid-margin) var(--space-7);
    display: flex; flex-direction: column; gap: var(--space-6);
    overflow-y: auto;
    visibility: hidden; opacity: 0;
    transition: opacity var(--dur-base) var(--ease-machine), visibility var(--dur-base);
  }
  .nav__panel.is-open { visibility: visible; opacity: 1; }
  .nav__panel-list { list-style: none; padding: 0; margin: 0; display: grid; }
  .nav__panel-link {
    display: flex; align-items: baseline; gap: var(--space-4);
    padding-block: var(--space-4);
    border-bottom: 1px solid var(--rule-hair);
    font-family: var(--ff-display); font-size: var(--fs-h4); font-weight: 600;
    color: var(--text-strong); text-decoration: none;
  }
  .nav__panel-index {
    font-family: var(--ff-mono); font-size: var(--fs-micro); font-stretch: 87.5%;
    color: var(--text-3); letter-spacing: 0.16em;
  }
  .nav__panel-foot { margin-top: auto; display: grid; gap: var(--space-5); }

  /* Collapse to the burger at 1200px, not 1023px. The Spanish labels are the
     constraint — "SOLICITAR COTIZACIÓN" and "TECNOLOGÍA" are materially longer
     than their English counterparts, and Spanish is the default language. */
  @media (max-width: 1199px) {
    .nav__links { display: none; }
    .nav__burger { display: inline-flex; }
    .nav__actions .btn { display: none; }
  }
  /* Below 480px the logo + gauge + language toggle + burger no longer fit
     across the bar. The gauge is the least load-bearing of the four, so it
     goes first, and the burger keeps its full 44px touch target. */
  /* Logo size ladder. The wordmark is the widest fixed object in the bar, so
     it steps down as the bar narrows rather than being clamped at two hard
     breakpoints — 44px at full width overflowed everything under ~550px.
     Every step below was measured against the actual right edge, in Spanish
     (the longer language), not guessed. */
  @media (max-width: 767px) { .nav__logo { --logo-h: 38px; } }
  @media (max-width: 599px) { .nav__logo { --logo-h: 34px; } }

  /* Below 600px the logo + gauge + language toggle + burger no longer fit
     across the bar. The gauge is the least load-bearing of the four, so it
     goes first, and the burger keeps its full 44px touch target. */
  @media (max-width: 599px) {
    .nav__actions .flame-gauge { display: none; }
    .nav__inner { gap: var(--space-3); }
    .nav__actions { gap: var(--space-3); }
    .nav__burger { margin-right: calc(var(--space-2) * -1); }
  }
  /* 320-375px hardware still exists (SE-class phones). */
  @media (max-width: 379px) { .nav__logo { --logo-h: 28px; } }
  @media (max-width: 339px) { .nav__logo { --logo-h: 24px; } }

  /* The condensed bar is 76px, so the full 44px wordmark crowds it. Step down
     one notch on scroll — the width/height transition on .nav__logo makes it
     move with the bar rather than snapping. Scoped to >=768px on purpose: this
     selector outranks the max-width ladder above, so applying it unscoped
     would push the 24-28px phone sizes back up to 36px and overflow the bar. */
  @media (min-width: 768px) {
    .nav.is-scrolled .nav__logo { --logo-h: 36px; }
  }
  /* Between collapse and comfortable, buy back space before hiding anything.
     This band used to clamp gaps all the way down to --space-4 and squeeze the
     link tracking to 0.1em, which is what made the bar look bunched on a
     ~1300px laptop — the most common viewport there is. The links are hidden
     below 1200px anyway, so the only real constraint here is the Spanish CTA.
     Give the tracking back and keep a genuine gap; the logo steps down one
     notch rather than the whole bar collapsing. */
  @media (min-width: 1200px) and (max-width: 1439px) {
    .nav__inner { gap: var(--space-5); }
    .nav__links { gap: var(--space-5); }
  }
  /* The genuinely tight sub-band: at 1200px the Spanish CTA plus five Spanish
     links leaves the least room of any width. One notch off the logo buys it,
     which is cheaper than dropping the quote button — that is the commercial
     path of the site and it stays visible. Above 1300px the wordmark runs at
     full size, which covers the common 1300-1440 laptop viewports. */
  @media (min-width: 1200px) and (max-width: 1299px) {
    .nav__inner { gap: var(--space-4); }
    .nav__logo { --logo-h: 36px; }
  }

  /* ── Brand lockup ─────────────────────────────────────────────────────
     The nav and footer marks are background images, which means the brand
     disappears entirely if that PNG ever fails. This lockup states the company
     name as live text next to the flame, so the name is always present and
     readable. Keep it that way. */
  .brand-hero {
    display: flex; align-items: center; gap: var(--space-4);
    margin-bottom: var(--space-7);
  }
  .brand-hero__mark {
    width: 2.5rem; height: 2.6rem; flex: none;
    background-color: transparent;
    background-image: url('../logo/mark-amber400.png');
    background-size: contain;
    background-position: center;
    background-repeat: no-repeat;
  }
  [data-register='spec'] .brand-hero__mark { background-image: url('../logo/mark-amber700.png'); }
  .brand-hero__name {
    font-family: var(--ff-mono); font-size: var(--fs-caption);
    font-weight: 500; text-transform: uppercase; letter-spacing: 0.26em;
    color: var(--text-2); line-height: 1.4;
  }
  @media (max-width: 479px) {
    .brand-hero__mark { width: 2rem; height: 2.1rem; }
    .brand-hero__name { letter-spacing: 0.18em; font-size: var(--fs-micro); }
  }

  /* ── Hero ────────────────────────────────────────────────────────────── */
  /* Spacing here is tuned so the primary CTA still lands above the fold on a
     720px-tall laptop viewport — the hero is the one place where a below-the-
     fold button would cost real money. */
  .hero {
    position: relative;
    min-height: min(100svh, 900px);
    display: flex; align-items: flex-end;
    padding-top: calc(var(--nav-h) + var(--space-6));
    padding-bottom: var(--space-7);
    overflow: clip;
  }
  .hero__inner { position: relative; z-index: 2; width: 100%; }
  .hero__headline { margin-block: var(--space-5) var(--space-7); }
  .hero__sub {
    max-width: 46ch;
    color: var(--text-2);
    font-size: var(--fs-body-l);
    line-height: 1.68;
  }
  .hero__actions { display: flex; flex-wrap: wrap; gap: var(--space-4); margin-top: var(--space-7); }
  .hero__meta {
    margin-top: var(--space-7);
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-4) var(--space-7);
  }
  @media (min-height: 900px) {
    .hero { padding-top: calc(var(--nav-h) + var(--space-8)); padding-bottom: var(--space-9); }
    .hero__meta { margin-top: var(--space-9); }
  }

  /* ── Stat block ──────────────────────────────────────────────────────── */
  .stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .stat { padding: var(--space-6) var(--space-5); border-left: 1px solid var(--rule-hair); }
  .stat:first-child { border-left: 0; padding-left: 0; }
    /* Sized against a quarter column of the page grid, not the page. Martian
     Mono is drawn wide, so --fs-h1 pushes "+400k" into the neighbouring stat
     at desktop widths. */
  .stat__value {
    font-size: clamp(1.85rem, 3.9vw, 4rem);
    font-stretch: 87.5%;
    white-space: nowrap;
    letter-spacing: -0.02em;
  }
  .stat { min-width: 0; overflow: hidden; }
  .stat__rule { height: 1px; background: var(--rule-hair); margin-block: var(--space-4); }
  /* Warm cream rather than --text-3. The muted greens read as part of the
     background on the dark register and made these labels hard to pick out. */
  .stat__label { font-size: var(--fs-caption); color: var(--cream-300); line-height: 1.6; }
  [data-register='spec'] .stat__label { color: var(--pine-550); }
  @media (max-width: 1023px) { .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .stat:nth-child(3) { border-left: 0; padding-left: 0; } }
  @media (max-width: 479px) { .stats { grid-template-columns: 1fr; }
    .stat { border-left: 0; padding-left: 0; border-top: 1px solid var(--rule-hair); }
    .stat:first-child { border-top: 0; } }

  /* ── Capability card · the frame stays rigid, only contents respond ──── */
  .cap-card {
    position: relative;
    display: grid; gap: var(--space-5);
    align-content: start;
    padding: var(--pad-card-lg);
    border: 1px solid var(--rule-hair);
    border-radius: var(--r-lg);
    background: var(--bg-raised);
    text-decoration: none;
    overflow: hidden;
    transition: border-color var(--dur-base) var(--ease-machine);
  }
  .cap-card::before {
    content: ''; position: absolute; top: 0; left: 0; right: 0; height: 1px;
    background: var(--text-3);
    clip-path: inset(0 100% 0 0);
    transition: clip-path var(--dur-base) var(--ease-precision);
  }
  .cap-card:hover { border-color: var(--rule-edge); text-decoration: none; }
  .cap-card:hover::before { clip-path: inset(0); }
  .cap-card:hover .flame-bullet { background-image: url('../logo/flame-amber400.png'); }
  [data-register='spec'] .cap-card:hover .flame-bullet { background-image: url('../logo/flame-amber700.png'); }
  .cap-card__title { font-size: var(--fs-h4); }
  .cap-card__desc { font-size: var(--fs-body-s); color: var(--text-2); max-width: none; }
  .cap-card__foot {
    margin-top: auto; padding-top: var(--space-5);
    border-top: 1px solid var(--rule-hair);
    font-family: var(--ff-mono); font-size: var(--fs-micro); font-stretch: 87.5%;
    text-transform: uppercase; letter-spacing: 0.14em; color: var(--text-3);
    display: flex; align-items: center; gap: var(--space-3);
  }

  /* ── Sector tile ─────────────────────────────────────────────────────── */
  .sector { display: grid; gap: var(--space-5); align-content: start; text-decoration: none; }
  .sector:hover { text-decoration: none; }
  .sector:hover .aperture > img,
  .sector:hover .weave--specimen { transform: scale(1.03); }
  .weave--specimen { transition: transform var(--dur-slow) var(--ease-precision); }
  .sector__name { font-size: var(--fs-h4); }
  .sector__desc { font-size: var(--fs-body-s); color: var(--text-2); max-width: 42ch; }

  /* ── Technology spec card ────────────────────────────────────────────── */
  .spec-card { display: grid; gap: var(--space-5); align-content: start; }
  .spec-card__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-4); }
  .spec-card__pn {
    font-family: var(--ff-mono); font-size: var(--fs-caption); font-weight: 500;
    font-stretch: 87.5%; letter-spacing: 0.08em; color: var(--text-3);
  }
  .spec-card__name { font-size: var(--fs-h5); font-family: var(--ff-display); font-weight: 600; }
  .spec-card__fn { font-size: var(--fs-body-s); color: var(--text-2); max-width: none; }
  .spec-card__rows { display: grid; gap: var(--space-3); }
  .spec-card:hover .weave-diagram .warp { transform: translate(2px, -1px); }

  /* ── Quote form · a form is a document, so it lives in SPEC ──────────── */
  .field { display: grid; gap: var(--space-2); }
  .field__label {
    font-family: var(--ff-mono); font-size: var(--fs-micro); font-weight: 500;
    text-transform: uppercase; letter-spacing: 0.11em;
    color: var(--text-3);
    line-height: 1.5;
    overflow-wrap: break-word;
  }
  .field { min-width: 0; }
  .field__input, .field__select, .field__textarea { width: 100%; min-width: 0; }
  .field__req { color: var(--danger); }
  .field__input, .field__select, .field__textarea {
    min-height: 52px;
    padding: 0 var(--space-4);
    background: var(--bg-sunken);
    border: 1px solid var(--rule-edge);
    border-radius: var(--r-md);
    color: var(--text);
    font-family: var(--ff-text); font-size: var(--fs-body);
    transition: border-color var(--dur-fast) var(--ease-machine);
  }
  .field__textarea { min-height: 140px; padding: var(--space-4); line-height: 1.6; resize: vertical; }
  .field__input:hover, .field__select:hover, .field__textarea:hover { border-color: var(--text-3); }
  .field__input:focus, .field__select:focus, .field__textarea:focus { border-color: var(--focus); }
  .field__input::placeholder, .field__textarea::placeholder { color: var(--text-3); }
  .field__input--num { font-family: var(--ff-mono); font-variant-numeric: tabular-nums; }
  .field__error {
    display: none;
    font-size: var(--fs-caption); color: var(--danger);
    align-items: center; gap: var(--space-2);
  }
  /* Colour is never the only error signal — weight, glyph and text too. */
  .field.has-error .field__input,
  .field.has-error .field__select,
  .field.has-error .field__textarea { border: 2px solid var(--danger); }
  .field.has-error .field__error { display: flex; }

  .form-grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: var(--space-5); }
  .col-4  { grid-column: auto/span 4; }
  .col-6  { grid-column: auto/span 6; }
  .col-12 { grid-column: auto/span 12; }
  @media (max-width: 767px) { .col-4, .col-6 { grid-column: auto/span 12; } }

  /* ── Footer ──────────────────────────────────────────────────────────── */
  .cta-band {
    position: relative;
    background: var(--accent-fill);
    color: var(--on-accent);
    padding-block: var(--space-10);
  }
  .cta-band h2 { color: var(--on-accent); }
  .cta-band .btn--secondary { border-color: var(--on-accent); color: var(--on-accent); }
  .cta-band .btn--secondary:hover { background: color-mix(in oklab, var(--on-accent) 14%, transparent); }

  .footer { position: relative; padding-block: var(--space-9) var(--space-6); overflow: clip; }
  .footer__grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: var(--space-7) var(--grid-gutter); }
  .footer__brand { grid-column: span 4; display: grid; gap: var(--space-5); align-content: start; }
  .footer__col   { grid-column: span 2; display: grid; gap: var(--space-3); align-content: start; }
  .footer__contact { grid-column: span 3; display: grid; gap: var(--space-3); align-content: start; }
  .footer__logo {
    width: 150px; height: 36px;
    background-color: transparent;
    background-image: url('../logo/wordmark-cream.png');
    background-size: contain;
    background-position: left center;
    background-repeat: no-repeat;
  }
  [data-register='spec'] .footer__logo { background-image: url('../logo/wordmark-pine950.png'); }
  .footer__tagline { font-style: italic; color: var(--text-2); }
  .footer__link { font-size: var(--fs-body-s); color: var(--text-2); text-decoration: none; }
  .footer__link:hover { color: var(--text-strong); text-decoration: underline; }
  .footer__base {
    margin-top: var(--space-8); padding-top: var(--space-5);
    border-top: 1px solid var(--rule-hair);
    display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
    gap: var(--space-4);
    font-family: var(--ff-mono); font-size: var(--fs-micro); font-stretch: 87.5%;
    color: var(--text-3); letter-spacing: 0.08em;
  }
  @media (max-width: 1023px) {
    .footer__brand { grid-column: span 12; }
    .footer__col { grid-column: span 4; }
    .footer__contact { grid-column: span 12; }
  }
  @media (max-width: 767px) { .footer__col { grid-column: span 12; } }

  /* ── Newsletter ──────────────────────────────────────────────────────── */
  .newsletter { display: flex; gap: var(--space-3); flex-wrap: wrap; }
  .newsletter .field__input { flex: 1 1 12rem; min-width: 0; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   UTILITIES
   ═══════════════════════════════════════════════════════════════════════════ */
@layer utilities {
  .visually-hidden {
    position: absolute !important; width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden;
    clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
  }
  .u-center { margin-inline: auto; }
  .u-max-46 { max-width: 46ch; }
  .u-max-56 { max-width: 56ch; }
  .u-rule { height: 1px; background: var(--rule-hair); border: 0; }

  /* Column spans on the 12-col page grid */
  .c-1{grid-column:auto/span 1}.c-2{grid-column:auto/span 2}.c-3{grid-column:auto/span 3}
  .c-4{grid-column:auto/span 4}.c-5{grid-column:auto/span 5}.c-6{grid-column:auto/span 6}
  .c-7{grid-column:auto/span 7}.c-8{grid-column:auto/span 8}.c-9{grid-column:auto/span 9}
  .c-10{grid-column:auto/span 10}.c-11{grid-column:auto/span 11}.c-12{grid-column:auto/span 12}

  /* Column offsets. Separate from the span so the two compose. */
  .at-7 { grid-column-start: 7; }
  .at-8 { grid-column-start: 8; }
  .at-9 { grid-column-start: 9; }

  @media (max-width: 1023px) {
    .c-4,.c-5,.c-6,.c-7 { grid-column: auto/span 8; }
    .c-3 { grid-column: auto/span 4; }
    .c-8,.c-9,.c-10,.c-11 { grid-column: auto/span 8; }
    .at-7, .at-8, .at-9 { grid-column-start: auto; }
  }
  @media (max-width: 767px) {
    [class*='c-'] { grid-column: auto/span 4; }
    .at-7, .at-8, .at-9 { grid-column-start: auto; }
  }

  /* Auto-fit card grids */
  .cards-3 { display: grid; gap: var(--gap-grid); grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .cards-2 { display: grid; gap: var(--gap-grid); grid-template-columns: repeat(2, minmax(0, 1fr)); }
  @media (max-width: 1023px) { .cards-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
  @media (max-width: 767px)  { .cards-3, .cards-2 { grid-template-columns: 1fr; } }
}

/* ═══════════════════════════════════════════════════════════════════════════
   REDUCED MOTION — removes movement, never information (§5.5)
   ═══════════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  html { scroll-behavior: auto; }
  .reveal { opacity: 1; transform: none; transition: opacity var(--dur-fast) linear; }
  .reveal-wipe { clip-path: inset(0); }
  .seam__drawn { clip-path: inset(0); }
  .seam__tip { display: none; }
  .flame-gauge::after { transition: none; }
  .btn:hover { transform: none; }
  .sector:hover .aperture > img, .sector:hover .weave--specimen { transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   VIEW TRANSITIONS — progressive enhancement, nothing breaks without it
   ═══════════════════════════════════════════════════════════════════════════ */
@view-transition { navigation: auto; }
::view-transition-old(root) { animation: vt-out var(--dur-base) var(--ease-cut) both; }
::view-transition-new(root) { animation: vt-in var(--dur-slow) var(--ease-precision) both; }
@keyframes vt-out { to { opacity: 0; transform: translateY(-8px); } }
@keyframes vt-in { from { opacity: 0; transform: translateY(12px); } }
@media (prefers-reduced-motion: reduce) { @view-transition { navigation: none; } }

@media print {
  .nav, .column-field, .seam, .tessellation, .cta-band { display: none !important; }
  body { background: #fff; color: #000; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   ADDITIONS — cloth physics, garment configurator, interactive cards
   ═══════════════════════════════════════════════════════════════════════════ */
@layer components {

  /* ── The counter ──────────────────────────────────────────────────────
     "+400,000" is eight glyphs of a deliberately WIDE monospace, so it needs
     its own scale — --fs-mega is sized for short words and overflows here. */
  .t-counter {
    font-family: var(--ff-mono);
    font-weight: 700;
    font-stretch: 100%;
    font-variant-numeric: tabular-nums;
    font-size: clamp(2.75rem, 0.9rem + 8.6vw, 10.5rem);
    line-height: 0.9;
    letter-spacing: -0.02em;
    color: var(--text-strong);
    white-space: nowrap;
  }

  /* ── Garment configurator ─────────────────────────────────────────────
     A live cloth mesh you can grab, plus the colour decision a uniform buyer
     is actually making. */
  .garment {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 22rem);
    gap: var(--space-8);
    align-items: center;
  }
  @media (max-width: 1023px) { .garment { grid-template-columns: minmax(0, 1fr); } }

  .garment__stage {
    position: relative;
    aspect-ratio: 4 / 5;
    max-height: 38rem;
    width: 100%;
    border-radius: var(--r-xl);
    border: 1px solid var(--rule-hair);
    background: var(--bg-sunken);
    cursor: grab;
    touch-action: none;
    overflow: hidden;
  }
  .garment__stage canvas { position: absolute; inset: 0; }
  /* Registration ticks so the stage reads as a plate, not a widget. */


  .garment__hint {
    position: absolute; left: var(--space-4); bottom: var(--space-4); z-index: 2;
    font-family: var(--ff-mono); font-size: var(--fs-micro); font-stretch: 87.5%;
    text-transform: uppercase; letter-spacing: 0.16em; color: var(--text-3);
    pointer-events: none;
  }

  /* ── DELIVERED GARMENTS · real product, photographed ──────────────────
     A four-up plate spread of garments that actually left the plant. Runs on
     its OWN grid rather than the 12-column one on purpose: `.c-3` plus the
     `.at-N` offsets is the pattern that silently collapsed three pages once
     (see HANDOFF), and this needs nothing from the main grid but its gutter.

     Lives in the SPEC (light) register because the source photography is
     white-seamless studio work — on the dark FOUNDRY ground each shot would
     read as a glaring white rectangle. The hairline border is what gives a
     near-white photo an edge against near-white paper. */
  /* NAMED `lookbook`, NOT `garment`. `.garment` is already taken, 40 lines up,
     by the configurator wrapper — `grid-template-columns: 1fr 22rem`. Reusing
     the name meant every card silently inherited that two-column 22rem track
     and the four-up spread collapsed to a single column. Different component,
     different name. */
  .lookbook {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--grid-gutter);
  }
  @media (max-width: 1023px) { .lookbook { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
  @media (max-width: 479px)  { .lookbook { grid-template-columns: minmax(0, 1fr); } }

  .lookbook__item { display: grid; gap: var(--space-4); align-content: start; min-width: 0; margin: 0; }
  .lookbook__media {
    position: relative;
    aspect-ratio: 4 / 5;
    border-radius: var(--r-lg);
    border: 1px solid var(--rule-edge);
    background: var(--bg-raised);
    overflow: hidden;
  }
  /* The source frames are already cropped to 4:5, so `cover` never actually
     crops — it is here so a future replacement at another ratio degrades to a
     sensible fill instead of distorting. */
  .lookbook__media img {
    width: 100%; height: 100%;
    object-fit: cover;
    object-position: center top;
    display: block;
  }
  .lookbook__line {
    font-family: var(--ff-mono); font-size: var(--fs-micro); font-stretch: 87.5%;
    text-transform: uppercase; letter-spacing: 0.16em; color: var(--text-3);
  }
  .lookbook__name {
    font-family: var(--ff-text); font-size: var(--fs-body); font-weight: 600;
    color: var(--text-strong); line-height: 1.25;
    overflow-wrap: break-word;
  }
  .lookbook__note {
    font-size: var(--fs-body-s); color: var(--text-2); line-height: 1.4;
    overflow-wrap: break-word;
  }

  .swatches { display: flex; flex-wrap: wrap; gap: var(--space-3); }
  .swatch {
    width: 2.75rem; height: 2.75rem;
    border-radius: var(--r-pill);
    border: 1px solid var(--rule-edge);
    cursor: pointer;
    position: relative;
    transition: transform var(--dur-fast) var(--ease-machine),
                border-color var(--dur-fast) var(--ease-machine);
  }
  .swatch:hover { transform: translateY(-2px); border-color: var(--text); }
  .swatch[aria-pressed='true'] { border-color: var(--text-strong); }
  .swatch[aria-pressed='true']::after {
    content: ''; position: absolute; inset: -5px;
    border: 1px solid var(--accent); border-radius: 3px;
  }

  /* ── Sector swatch panel ──────────────────────────────────────────────
     Replaces the empty oval that appeared wherever no photograph exists.
     A woven colour field carrying the sector's icon reads as a fabric
     specimen — deliberate, rather than a missing image. */
  /* The swatch always sits on a dark tinted ground, whichever register the
     section is in. Letting it follow --bg-raised put a pale icon on pale
     paper in the SPEC sections and the hospitality mark all but vanished. */
  .specimen {
    position: relative;
    aspect-ratio: 4 / 3;
    border-radius: var(--r-lg);
    border: 1px solid color-mix(in oklab, var(--sw) 30%, transparent);
    background: color-mix(in oklab, var(--sw) 14%, #0D1710);
    display: grid;
    place-items: center;
    overflow: hidden;
  }
  .specimen__weave {
    position: absolute; inset: 0;
    background-image:
      repeating-linear-gradient(90deg, color-mix(in oklab, var(--sw) 26%, transparent) 0 1px, transparent 1px 8px),
      repeating-linear-gradient(0deg,  color-mix(in oklab, var(--sw) 18%, transparent) 0 1px, transparent 1px 8px);
    transition: transform var(--dur-slow) var(--ease-precision);
  }
  .specimen .icon {
    position: relative; z-index: 1;
    width: 40%; height: auto; aspect-ratio: 1;
    color: color-mix(in oklab, var(--sw) 78%, #FAF8F1);
    stroke-width: 1.05;
  }
  .specimen__tag {
    position: absolute; left: var(--space-5); bottom: var(--space-4); z-index: 1;
    font-family: var(--ff-mono); font-size: var(--fs-micro);
    text-transform: uppercase; letter-spacing: 0.11em;
    color: color-mix(in oklab, var(--sw) 55%, #FAF8F1);
  }
  
  .sector:hover .specimen__weave { transform: scale(1.06); }

  /* ── Interactive cards ────────────────────────────────────────────────
     The pointer lifts a soft sheen across the plate and tilts it a degree or
     two — enough to feel like a physical sample being turned to the light,
     not a bouncing web card. --mx/--my are written by main.js. */
  .plate, .cap-card, .spec-card, .sector {
    --mx: 50%; --my: 50%; --tilt: 0deg; --tiltx: 0deg;
    transform: perspective(900px) rotateY(var(--tilt)) rotateX(var(--tiltx));
    transform-style: preserve-3d;
    transition: transform var(--dur-base) var(--ease-precision),
                border-color var(--dur-base) var(--ease-machine);
    will-change: transform;
  }
  .plate::before, .cap-card::before { z-index: 3; }
  .is-live { position: relative; }
  .is-live::after {
    content: '';
    position: absolute; inset: 0;
    pointer-events: none;
    background: radial-gradient(
      22rem circle at var(--mx) var(--my),
      color-mix(in oklab, var(--accent) 13%, transparent),
      transparent 65%);
    opacity: 0;
    transition: opacity var(--dur-base) var(--ease-machine);
    z-index: 1;
  }
  .is-live:hover::after { opacity: 1; }
  .is-live > * { position: relative; z-index: 2; }

  @media (hover: none) {
    .plate, .cap-card, .spec-card, .sector { transform: none; }
    .is-live::after { display: none; }
  }
}

@media (prefers-reduced-motion: reduce) {
  .plate, .cap-card, .spec-card, .sector { transform: none !important; }
  .is-live::after { display: none; }
  .swatch:hover { transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE FABRIC LAB — the homepage centrepiece.
   The six technologies are the product story, so they get the largest, most
   interactive block on the site, wired directly to the cloth simulation.
   ═══════════════════════════════════════════════════════════════════════════ */
@layer components {

  .lab {
    display: grid;
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    gap: var(--space-8);
    align-items: start;
  }
  @media (max-width: 1023px) { .lab { grid-template-columns: minmax(0, 1fr); } }

  .lab__stage { position: sticky; top: calc(var(--nav-h) + var(--space-5)); }
  @media (max-width: 1023px) { .lab__stage { position: static; } }

  /* Technology rows, styled after the client's own icon language:
     a white plate carrying black line art. */
  .tech-list { display: grid; gap: var(--space-3); }

  .tech-row {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: var(--space-5);
    align-items: center;
    width: 100%;
    padding: var(--space-4);
    border: 1px solid transparent;
    border-radius: var(--r-lg);
    background: transparent;
    text-align: left;
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-machine),
                border-color var(--dur-fast) var(--ease-machine);
  }
  .tech-row:hover { background: color-mix(in oklab, var(--text) 6%, transparent); }
  .tech-row[aria-pressed='true'] {
    border-color: var(--accent);
    background: color-mix(in oklab, var(--accent) 10%, transparent);
  }

  .tech-tile {
    width: 5.25rem; height: 3.75rem;
    flex: none;
    display: grid; place-items: center;
    background: var(--cream-000);
    border-radius: var(--r-md);
    transition: transform var(--dur-base) var(--ease-precision);
  }
  .tech-row:hover .tech-tile { transform: translateY(-2px); }
  .tech-row[aria-pressed='true'] .tech-tile { transform: translateY(-2px); }
  .tech-tile .icon {
    width: 2.6rem; height: 2.6rem;
    color: var(--pine-950);
    stroke-width: 1.4;
  }

  .tech-row__name {
    font-family: var(--ff-display); font-weight: 700; font-stretch: 100%;
    font-size: var(--fs-h6); line-height: 1.1;
    text-transform: uppercase; letter-spacing: -0.005em;
    color: var(--text-strong);
  }
  .tech-row__desc {
    font-size: var(--fs-body-s); color: var(--text-2);
    margin-top: var(--space-2); max-width: 46ch;
  }
  .tech-row[aria-pressed='true'] .tech-row__name { color: var(--accent); }

  .lab__legend {
    margin-top: var(--space-5);
    font-family: var(--ff-mono); font-size: var(--fs-micro); font-stretch: 87.5%;
    text-transform: uppercase; letter-spacing: 0.16em; color: var(--text-3);
    display: flex; align-items: center; gap: var(--space-3);
    min-width: 0;
  }
  .lab__legend > * { min-width: 0; overflow-wrap: break-word; }

  /* ── Selected-colour readout ───────────────────────────────────────────
     Replaces the bare inline legend under the swatches. That legend was a
     single mono, uppercase, 0.16em-tracked line in a flex row: a flex item
     defaults to min-width:auto, so it could not shrink below its own
     min-content and long names ("Azul Puerto Rico", "Verde bosque") pushed
     straight out of the sticky left column. This is a bounded plate instead —
     the name has its own field, wraps, and is anchored by a colour chip that
     confirms the selection visually as well as in words. */
  .colour-readout {
    margin-top: var(--space-5);
    display: flex; align-items: center; gap: var(--space-4);
    padding: var(--space-4);
    border: 1px solid var(--rule-edge);
    border-radius: var(--r-md);
    background: var(--bg-raised);
    max-width: 100%;
  }
  .colour-readout__chip {
    flex: none;
    width: 2rem; height: 2rem;
    border-radius: var(--r-pill);
    border: 1px solid var(--rule-edge);
    box-shadow: inset 0 0 0 2px color-mix(in oklab, var(--bg-raised) 100%, transparent);
    transition: background-color var(--dur-base) var(--ease-machine);
  }
  /* min-width:0 is what actually lets the text field shrink and wrap. */
  .colour-readout__text {
    min-width: 0;
    display: grid; gap: 2px;
  }
  .colour-readout__label {
    font-family: var(--ff-mono); font-size: var(--fs-micro); font-stretch: 87.5%;
    text-transform: uppercase; letter-spacing: 0.14em; color: var(--text-3);
  }
  .colour-readout__name {
    font-family: var(--ff-text); font-size: var(--fs-body-s); font-weight: 600;
    color: var(--text-strong);
    line-height: 1.25;
    overflow-wrap: break-word; word-break: break-word; hyphens: auto;
    text-transform: none; letter-spacing: 0;
  }
  @media (max-width: 479px) {
    .colour-readout { padding: var(--space-3); gap: var(--space-3); }
    .colour-readout__chip { width: 1.75rem; height: 1.75rem; }
  }

  @media (max-width: 479px) {
    .tech-row { grid-template-columns: auto minmax(0, 1fr); gap: var(--space-4); }
    .tech-tile { width: 4rem; height: 3rem; border-radius: 8px; }
    .tech-tile .icon { width: 2rem; height: 2rem; }
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   DISCIPLINES — the throwable ball, and its sport selector.
   ═══════════════════════════════════════════════════════════════════════════ */
@layer components {

  .disciplines {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 24rem);
    gap: var(--space-8);
    align-items: center;
  }
  @media (max-width: 1023px) { .disciplines { grid-template-columns: minmax(0, 1fr); } }

  .ball-stage {
    position: relative;
    aspect-ratio: 16 / 10;
    width: 100%;
    border-radius: var(--r-xl);
    border: 1px solid var(--rule-hair);
    background:
      radial-gradient(120% 80% at 50% 0%,
        color-mix(in oklab, var(--accent) 7%, transparent), transparent 62%),
      var(--bg-sunken);
    overflow: hidden;
    cursor: grab;
    touch-action: none;
  }
  .ball-stage canvas { position: absolute; inset: 0; }
  /* The floor line is drawn on the canvas at the exact physics height —
     a CSS rule here drifted from the simulation and the ball crossed it. */
  .ball-stage__hint {
    position: absolute; left: var(--space-5); top: var(--space-5); z-index: 2;
    font-family: var(--ff-mono); font-size: var(--fs-micro); font-stretch: 87.5%;
    text-transform: uppercase; letter-spacing: 0.16em; color: var(--text-3);
    pointer-events: none;
  }

  .sports { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-3); }
  .sport {
    display: grid; gap: var(--space-3); justify-items: center;
    padding: var(--space-4) var(--space-3);
    border: 1px solid var(--rule-hair);
    border-radius: var(--r-lg);
    background: transparent;
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-machine),
                background-color var(--dur-fast) var(--ease-machine),
                transform var(--dur-fast) var(--ease-machine);
  }
  .sport:hover { border-color: var(--rule-edge); transform: translateY(-2px); }
  .sport[aria-pressed='true'] {
    border-color: var(--accent);
    background: color-mix(in oklab, var(--accent) 12%, transparent);
  }
  .sport__dot {
    width: 2rem; height: 2rem; border-radius: var(--r-pill);
    border: 1px solid rgba(0, 0, 0, 0.28);
  }
  .sport__name {
    font-family: var(--ff-mono); font-size: var(--fs-micro); font-stretch: 87.5%;
    text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-2);
    text-align: center; line-height: 1.3;
  }
  .sport[aria-pressed='true'] .sport__name { color: var(--text-strong); }

  @media (prefers-reduced-motion: reduce) { .sport:hover { transform: none; } }
}

/* ═══════════════════════════════════════════════════════════════════════════
   WOVEN TEXT — the manifesto assembles itself out of thread.
   ═══════════════════════════════════════════════════════════════════════════ */
@layer components {
  .woven { position: relative; }
  /* The real text stays in the DOM for screen readers, search engines and
     any visitor without JS — it is only made invisible when we know scripting
     is available, and from the very first paint, so the plain sentence never
     flashes before the weave takes over. */
  .woven__source { position: relative; z-index: 1; }
  .has-js .woven .woven__source { visibility: hidden; }
  /* If the canvas cannot be used, or the weave stalls, the block flips back
     to plain text rather than sitting empty. */
  .has-js .woven.woven--fallback .woven__source { visibility: visible; }
  .woven__canvas {
    position: absolute; inset: 0; z-index: 2;
    width: 100%; height: 100%;
    pointer-events: none;
  }
}
