/* ============================================================
   WeatherSens — website · light editorial grade
   Canvas #f8fafc · ink #0f172a · the colour in the page is the
   blue of the ambient orbs; everything else is rationed to data.
   Continuous scroll: variable section heights, one ambient canvas,
   hairline surfaces, small fast motion.

   Inverted from the original dark grade on 2026-08-06 (founder
   call). The grade is a TOKEN FLIP, not a rewrite: every rule below
   still means what it meant on black — "strong" is still the loudest
   ink, the card dissolve is still brightest at the top, glows still
   bleed. Only the direction of the light changed. See DESIGN.md §2.
   ============================================================ */

:root{
  --canvas:#f8fafc; --surface:#ffffff;
  --text:#020617;   /* the ink extreme — hero + close statements only */
  --strong:#0f172a; --body:#334155; --muted:#475569; --faint:#64748b;
  --hair:#e2e8f0; --hair-strong:#cbd5e1; --plate:#eef2f7;

  /* surface ladder — cool-tinted ink alpha (black-alpha reads muddy on a
     blue-white canvas, so the raise is tinted toward the orbs' blue) */
  --line:rgba(15,40,80,.10); --line-strong:rgba(15,40,80,.20);
  --window:#eef3f9; /* recessed stage inside a card */

  /* semantic data colours — never decorative. Same two meanings as always
     (green = measured/correct, coral = drift/wrong); both are deepened from
     the dark-grade mint #9fe8cd / coral #c96a5c so they survive on white.
     Each has an -ink step for TEXT (≥4.5:1); the base is for ≥2px strokes. */
  --measured:#0f9d76; --measured-ink:#047857;
  --drift:#d1523c;    --drift-ink:#b2402a;

  --display:"General Sans","Aeonik Pro","Inter Tight",Inter,system-ui,sans-serif;
  --ui:"Inter",system-ui,-apple-system,sans-serif;

  /* ONE scale for every scene: header · subheader · small. Unified 2026-08-07
     (founder call) — before this the curtain beats rendered SMALLER than the
     section headers (34 vs 54 cap) and the history turn was a third size (32),
     so no two scenes agreed. The two .rise blooms (hero s-h72, close s-h96)
     stay outside this scale on purpose; they are the reserved extremes.
     --fs-head's numbers are dictated by the curtain, the tightest header on
     the page: its longest authored line is 35 characters and it has to fit the
     left column of a two-column pin. The 2.9vw slope tracks that column across
     the 768→1310px band, the 38px cap is where the column stops growing, and
     the 23px floor is the largest that still fits at 768px (the narrowest
     viewport that is still two-column — below 760px the pin stacks and the
     beat takes its own min(), see the phone block in §2). */
  --fs-head:clamp(23px,2.9vw,38px);
  --fs-sub:clamp(16px,1.7vw,21px);
  --fs-small:clamp(12px,1.05vw,15px);

  --e-enter:cubic-bezier(0.2,0,0,1);
  --e-move:cubic-bezier(0.65,0,0.35,1);
  --e-mask:cubic-bezier(0.16,1,0.3,1);
  --e-pop:cubic-bezier(0.34,1.3,0.4,1);
  --e-draw:cubic-bezier(0.6,0,0.2,1);
}

*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
body{
  background:var(--canvas);
  color:var(--body);
  font-family:var(--ui);
  line-height:1.5;
  overflow-x:hidden;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}
img,svg,video,canvas{display:block;max-width:100%}
a{color:inherit}

/* ---------- film stock: over everything ---------- */
/* PERF: no mix-blend-mode. A fixed, full-viewport blended layer forces the
   browser to re-blend everything beneath it on every scroll frame. The tile is
   generated with a varying ALPHA over black instead (see makeGrain()), so plain
   compositing produces the same paper tooth for free. */
#grain{
  position:fixed;inset:0;z-index:100;pointer-events:none;
  opacity:.5;
  background-repeat:repeat;background-size:170px 170px;
}
/* the vignette inverts to a cool edge-shade — a black vignette on white
   reads as dirt, so it is barely-there and blue */
#vig{
  position:fixed;inset:0;z-index:99;pointer-events:none;
  background:radial-gradient(ellipse at center, transparent 58%, rgba(15,45,90,.05) 100%);
}

/* the curtain blackout (see §2 of the sections below). Sits ABOVE the grain,
   vignette and cursor glow but BELOW the nav — the nav is faded out separately
   by the driver, because ink-coloured nav type on a black veil is unreadable.
   opacity is driven entirely by JS; the 0 default is what no-JS keeps. */
#veil{
  position:fixed;inset:0;z-index:105;pointer-events:none;
  background:#05070a;opacity:0;
}

/* soft blue bloom that trails the cursor (desktop pointers only).
   `screen` with white was invisible on a light canvas — it MULTIPLIES a
   pale blue instead, so the cursor still carries a little atmosphere.
   Anchored at -230,-230 so its UNTRANSFORMED box never overflows the
   viewport: a fixed element escapes body's overflow clip, and one wider
   than the screen expands the mobile layout viewport (broke the phone
   layout once). JS translates it by the raw cursor position. */
#cursorglow{
  position:fixed;top:-230px;left:-230px;width:460px;height:460px;border-radius:50%;
  pointer-events:none;z-index:104;opacity:0;transition:opacity .5s ease;will-change:transform;
  /* PERF: plain compositing, no blend mode — the tint is baked into the alpha */
  background:radial-gradient(circle closest-side, rgba(120,168,224,.20), rgba(120,168,224,.07) 44%, transparent 72%);
}
#cursorglow.on{opacity:1}
@media (pointer:coarse){#cursorglow{display:none}}

/* ---------- fixed header ---------- */
.nav{
  position:fixed;top:0;left:0;right:0;z-index:110;
  border-bottom:1px solid transparent;
  opacity:0;
  transition:opacity .7s ease .15s, background-color .25s ease, border-color .25s ease;
}
.nav.in{opacity:1}
/* must follow .nav.in — same specificity, so source order decides. The curtain
   driver adds this while the page is dark. */
.nav.veiled{opacity:0;pointer-events:none}
.nav.scrolled{
  background:rgba(248,250,252,.74);
  backdrop-filter:blur(10px) saturate(140%);-webkit-backdrop-filter:blur(10px) saturate(140%);
  border-bottom-color:var(--line);
}
.nav__inner{
  max-width:1200px;margin-inline:auto;padding-inline:clamp(22px,5vw,64px);
  height:clamp(60px,7vh,72px);
  display:flex;align-items:center;gap:clamp(20px,3vw,40px);
}
.nav__brand{
  display:flex;align-items:center;gap:.42em;margin-right:auto;
  font-family:var(--display);font-weight:500;font-size:clamp(17px,1.8vw,19px);
  letter-spacing:-.02em;color:var(--text);text-decoration:none;
}
/* flex:0 0 auto is load-bearing. The mark is a flex item with width:auto, so at
   the default flex-shrink:1 an overflowing bar squeezes it to nothing — which is
   exactly what happened on phones once the burger joined the row: the propeller
   silently vanished and left a bare "WeatherSens" (founder, 2026-08-07). */
.nav__brand .prop{height:.95em;width:auto;flex:0 0 auto}
.nav__links{display:flex;gap:clamp(18px,2.4vw,32px)}
.nav__links a{
  font-family:var(--ui);font-weight:500;font-size:13.5px;letter-spacing:.01em;
  color:var(--muted);text-decoration:none;transition:color .15s ease;
}
.nav__links a:hover,.nav__links a:focus-visible{color:var(--text)}
/* .btn.nav__cta, not .nav__cta: `.btn` is defined further down the file with
   the same specificity, so a bare .nav__cta loses the cascade to it on every
   property they share. This rule had been inert since it was written — the
   pill has been rendering at .btn's 12px/24px/14px, which is a good part of
   why the phone bar ran out of room. */
.btn.nav__cta{padding:9px 18px;font-size:13px}
@media (max-width:720px){.nav__links{display:none}}

/* ---------- the phone menu (≤720px) ----------
   Below 720px .nav__links disappears and this replaces it: a black panel that
   slides in from the left edge. The panel lives INSIDE <header class="nav">,
   which is the whole trick — the header is a stacking context at z-index 110,
   so the panel (z-index 1) can cover the header's own glass background while
   .nav__inner (z-index 2) keeps the toggle clickable on top of it. A panel
   outside the header would have to out-rank the entire nav, and would then
   cover the toggle too, forcing a second close button. */
.nav__inner{position:relative;z-index:2}
.nav__toggle{
  display:none;                       /* phones only */
  width:40px;height:40px;flex:0 0 auto;margin-left:-9px;margin-right:2px;
  align-items:center;justify-content:center;
  padding:0;border:0;background:none;cursor:pointer;color:var(--strong);
}
/* the bar has three things on a phone now (burger · brand · Contact) and at
   320px the desktop sizing did not fit them: the pill was pushed off the right
   edge and the brand ate the propeller to make room. Everything here is just
   buying that row back about 60px. */
@media (max-width:720px){
  .nav__toggle{display:inline-flex;width:34px;height:34px;margin-left:-7px;margin-right:0}
  .nav__inner{gap:8px;padding-inline:14px}
  /* THE LOCKUP NEVER GIVES. flex:0 0 auto and nowrap together mean the mark and
     the word are the one thing in this row that cannot be shrunk, ellipsed or
     dropped, whatever else has to (founder, 2026-08-07: "it is very important
     that our logo and WeatherSens text shows"). Anything added to this bar in
     future takes its space from the pill, not from here. */
  .nav__brand{font-size:15.5px;gap:.34em;flex:0 0 auto}
  .nav__brand span{white-space:nowrap}
  /* so the pill is the one that yields: very small, and the only flexible item */
  .btn.nav__cta{padding:6px 12px;font-size:11px;flex:0 1 auto}
}
/* two hairlines that rotate into an X. Transform only, per hard rule 6 —
   which is also why this is NOT three bars: fading a middle bar out would
   need opacity on a parent and would take the other two with it. */
.nav__burger{position:relative;display:block;width:20px;height:12px}
.nav__burger::before,.nav__burger::after{
  content:"";position:absolute;left:0;width:20px;height:1.5px;border-radius:1px;
  background:currentColor;
  transition:transform .34s var(--e-mask);
}
.nav__burger::before{top:0}
.nav__burger::after{top:10.5px}
.nav--open .nav__burger::before{transform:translateY(5.25px) rotate(45deg)}
.nav--open .nav__burger::after{transform:translateY(-5.25px) rotate(-45deg)}

.nav__panel{
  position:fixed;inset:0;z-index:1;
  background:#05070a;                 /* the curtain's blackout, same ink */
  display:flex;align-items:center;
  padding:clamp(78px,15vh,124px) clamp(22px,7vw,48px) clamp(34px,8vh,64px);
  /* visibility (not display) so the slide can animate, delayed on the way out
     so the panel stays hittable until it has finished leaving */
  transform:translateX(-100%);visibility:hidden;
  transition:transform .42s var(--e-mask),visibility 0s linear .42s;
}
.nav--open .nav__panel{
  transform:none;visibility:visible;
  transition:transform .42s var(--e-mask),visibility 0s;
}
/* above the breakpoint it must not exist at all, even mid-transition */
@media (min-width:721px){.nav__panel{display:none}}

.nav__menu{display:flex;flex-direction:column;gap:clamp(14px,3.2vh,26px);width:100%}
.nav__menu a{
  font-family:var(--display);font-weight:500;font-size:var(--fs-head);
  line-height:1.14;letter-spacing:-.018em;color:#f4f7fa;text-decoration:none;
  opacity:0;transform:translateX(-14px);
  transition:opacity .3s ease,transform .42s var(--e-mask);
}
.nav--open .nav__menu a{
  opacity:1;transform:none;
  transition-delay:calc(120ms + var(--i,0)*55ms);   /* JS stamps --i per link */
}
.nav__menu a[aria-current]{color:#8db6e4}
/* opening the menu moves focus to the first link, so this ring is on screen
   often — the UA default is a hairline box that reads as a rendering fault here */
.nav__menu a:focus-visible{outline:2px solid #8db6e4;outline-offset:6px;border-radius:3px}
/* the bar's own contents now sit on black, so they flip to paper. The Contact
   pill goes entirely: it is ink-on-ink there, and the panel already lists it. */
.nav--open .nav__brand,.nav--open .nav__toggle{color:#f4f7fa}
.nav--open .nav__cta{opacity:0;visibility:hidden}
html.nav-locked{overflow:hidden}
/* LOAD-BEARING. `backdrop-filter` makes an element the containing block for its
   position:fixed descendants, exactly as `filter` does. Leave .nav.scrolled's
   glass on while the menu is open and .nav__panel{position:fixed;inset:0} stops
   meaning "the viewport" and starts meaning "the 66px-tall bar" — the panel
   collapses into the header the moment the visitor has scrolled at all, which
   is nearly always. Dropping the glass costs nothing: the panel is covering
   that strip anyway. */
.nav--open.scrolled{
  background:transparent;border-bottom-color:transparent;
  backdrop-filter:none;-webkit-backdrop-filter:none;
}

/* ---------- buttons · arrow links ---------- */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:.5em;
  padding:12px 24px;border-radius:999px;border:1px solid transparent;
  font-family:var(--ui);font-weight:600;font-size:14px;letter-spacing:.02em;line-height:1;
  text-decoration:none;cursor:pointer;
  transition:transform .2s var(--e-enter),background-color .2s ease,border-color .2s ease,color .2s ease;
}
.btn:active{transform:scale(.98)}
/* primary is still "the loudest surface": an ink pill on paper, where on
   black it was a paper pill on ink */
.btn--primary{background:var(--strong);color:#fff}
.btn--primary:hover,.btn--primary:focus-visible{background:var(--text);transform:translateY(-1px)}
.btn--ghost{border-color:var(--line-strong);color:var(--body)}
.btn--ghost:hover,.btn--ghost:focus-visible{border-color:rgba(15,40,80,.34);color:var(--text);transform:translateY(-1px)}
.btn--lg{padding:15px 30px;font-size:15px}

.arrowlink{
  display:inline-flex;align-items:baseline;gap:.35em;
  font-family:var(--ui);font-weight:600;font-size:clamp(13.5px,1.2vw,15px);
  color:var(--strong);text-decoration:none;
  transition:color .15s ease;
}
.arrowlink span{transition:transform .2s var(--e-enter);display:inline-block}
.arrowlink:hover,.arrowlink:focus-visible{color:var(--text)}
.arrowlink:hover span{transform:translateX(3px)}

/* ---------- layout ---------- */
.wrap{
  position:relative;z-index:2;width:100%;max-width:1200px;margin-inline:auto;
  padding-inline:clamp(22px,5vw,64px);
}
/* overflow-x:CLIP, not hidden: kills the horizontal scrollable overflow of
   the huge unclipped glows (which expanded the mobile layout viewport and
   squeezed the whole page) WITHOUT creating a scroll container — so any
   position:sticky still works and vertical glow bleed stays free */
main{position:relative;overflow-x:clip}
section{position:relative;scroll-margin-top:88px}

/* section rhythm: heights come from content, not the viewport.
   Only the hero gets a min-height; everything else is a band. */
.hero{min-height:92svh;display:flex;align-items:center;justify-content:center}
/* the top gap was a tight 24–64px when the flight sat directly under the hero
   scene; it now follows the archival plate and gets the full section rhythm */
.flight{padding-block:clamp(70px,8vw,120px) clamp(84px,10vw,150px)}
.rail{padding-block:clamp(84px,10vw,150px)}
.globe{padding-block:clamp(96px,12vw,170px)}
.close{padding-block:clamp(150px,18vw,250px)}

/* faint chapter seams — connective tissue, not slide boundaries */
.rail>.wrap{
  border-top:1px solid var(--hair);padding-top:clamp(40px,5.5vw,72px);
}

/* ---------- type scale ---------- */
.s-h96{font-family:var(--display);font-weight:500;font-size:clamp(46px,8.4vw,96px);line-height:1.0;letter-spacing:-.03em;color:var(--text)}
.s-h72{font-family:var(--display);font-weight:500;font-size:clamp(33px,6vw,72px);line-height:1.05;letter-spacing:-.02em;color:var(--text)}
/* every section header on the page — history, rail, Windwall, globe, the
   statement over the footage, and the curtain beats. One class, one size.
   (Replaced .s-h54 / .s-h44 on 2026-08-07.) */
.s-head{font-family:var(--display);font-weight:500;font-size:var(--fs-head);line-height:1.14;letter-spacing:-.018em;color:var(--strong)}
/* the supporting line under a header — hero subline, the curtain's .l--sub */
.s-sub{font-family:var(--ui);font-weight:500;font-size:var(--fs-sub);line-height:1.45;color:var(--muted)}

.label{
  font-family:var(--ui);font-weight:600;font-size:var(--fs-small);
  letter-spacing:.16em;text-transform:uppercase;color:var(--muted);
}
.label.faint{color:var(--faint)}

/* section eyebrow. The numbered "coherent tour" index (01 Problem → 06 Vision)
   was removed from index.html on 2026-08-06 at the founder's call — sections now
   open on their headline. Only privacy.html still uses this, unnumbered. */
.eyebrow{
  display:inline-flex;align-items:center;gap:.95em;margin:0;
  font-family:var(--ui);font-weight:600;font-size:clamp(11px,1vw,13px);
  letter-spacing:.2em;text-transform:uppercase;color:var(--muted);
}

/* ---------- reveal system: small, fast, once ---------- */
.reveal-up{
  opacity:0;transform:translateY(20px);
  transition:opacity .65s var(--e-mask),transform .65s var(--e-mask);
  transition-delay:var(--d,0ms);
}
.is-in .reveal-up{opacity:1;transform:none}

.statement{margin:0}
.statement .line{display:block}

/* signature statements (.rise = hero + close): masked word-rise — the one
   theatrical move, reserved for the two pure-white blooms */
.word{display:inline-block;overflow:hidden;vertical-align:top;padding-bottom:.16em;margin-bottom:-.16em}
.word__i{
  display:inline-block;transform:translateY(114%);opacity:0;
  transition:transform 1.05s var(--e-mask),opacity .7s ease;
  transition-delay:calc(var(--wi,0)*80ms + var(--wbase,0ms));
}
.is-in .word__i{transform:none;opacity:1}
.hero .statement{--wbase:150ms}

/* every other statement: a quiet per-line fade-rise */
.statement:not(.rise) .line{
  opacity:0;transform:translateY(14px);
  transition:opacity .7s var(--e-mask),transform .7s var(--e-mask);
  transition-delay:calc(var(--li,0)*100ms);
}
.is-in .statement:not(.rise) .line{opacity:1;transform:none}

/* ---------- one continuous atmosphere ---------- */
.ambient{position:absolute;inset:0;overflow:hidden;pointer-events:none;z-index:0}
/* the drift is two long, slow, out-of-phase keyframe loops instead of a JS rAF
   loop (see driftOrbs() in main.js). Compositor-driven: zero main-thread cost,
   and it keeps running smoothly while the page is scrolling. JS only stamps a
   negative animation-delay per orb to spread the phases. */
/* centring moved from transform to negative margins on purpose: it leaves
   `transform` entirely to the keyframes. (Two animations on one property do
   NOT compose — the later one wins outright — so the drift has to be a single
   multi-stop animation.) */
.orb{
  position:absolute;width:var(--d);height:var(--d);left:var(--x);top:var(--y);
  margin-left:calc(var(--d) / -2);margin-top:calc(var(--d) / -2);
  border-radius:50%;opacity:var(--o);
  background:radial-gradient(closest-side, var(--c) 0%, var(--c) 10%, transparent 66%);
  animation:orbdrift 34s ease-in-out infinite alternate;
}
@keyframes orbdrift{
  0%   {transform:translate(-8px,-5px) scale(.985)}
  34%  {transform:translate(6px,4px)   scale(1.015)}
  67%  {transform:translate(-4px,6px)  scale(1.005)}
  100% {transform:translate(8px,-4px)  scale(1.03)}
}
@media (prefers-reduced-motion:reduce){.orb{animation:none}}

/* ---------- shared card surface: the dissolve ----------
   No plate, no outline. The cool tint and its 1px hairline exist only
   where light hits — the top — and fade to nothing before the bottom,
   so the card melts into the canvas instead of sitting on it as an
   outlined box. Hover = small rise + cursor spotlight (--x/--y from JS).
   On the light grade the tint is a blue SHADE rather than a warm raise
   (on paper the top catches shadow, not light) — the gradient direction,
   the masked ring and the fade-out point are all unchanged. */
.card{
  position:relative;overflow:hidden;
  background:linear-gradient(180deg,
    rgba(37,86,152,.055), rgba(37,86,152,.016) 48%, rgba(37,86,152,0) 88%);
  border-radius:20px;transition:transform .25s var(--e-enter);
}
/* recessed stage window — the cooler, deeper "screen" inside any card */
.step__stage{
  background:var(--window);
  border:1px solid rgba(15,40,80,.07);
  border-radius:10px;overflow:hidden;
}
/* dissolving hairline: drawn as a masked 1px ring, strongest at top,
   gone by ~3/4 down the sides — never a full outline */
.card::after{
  content:"";position:absolute;inset:0;border-radius:inherit;padding:1px;pointer-events:none;
  background:linear-gradient(180deg,
    rgba(15,40,80,.20), rgba(15,40,80,.07) 40%, rgba(15,40,80,0) 78%);
  -webkit-mask:linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude;
  opacity:.75;transition:opacity .3s ease;
}
.card::before{
  content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  background:radial-gradient(560px circle at var(--x,-999px) var(--y,-999px),
             rgba(56,120,200,.085), transparent 45%);
  opacity:0;transition:opacity .4s ease;
}
@media (hover:hover){
  .card:hover{transform:translateY(-2px)}
  .card:hover::before{opacity:1}
  .card:hover::after{opacity:1}
}

/* ============================================================
   1 · HERO / PROBLEM
   ============================================================ */
/* the wind field: transparent canvas over the orbs, under the type.
   It runs 32% PAST the hero's bottom edge so strands dissolve across the
   section boundary (glow law: never a hard seam), and wakes up AFTER the
   text is already readable. */
.hero__wind{
  /* width MUST be explicit: an absolutely-positioned canvas with only
     left+right resolves the over-constraint to its INTRINSIC bitmap width,
     which blew the layout viewport on DPR>1 screens (mobile squeeze bug) */
  position:absolute;top:0;left:0;width:100%;height:132%;z-index:1;
  pointer-events:none;opacity:0;transition:opacity 1.6s ease .4s;
  will-change:transform,opacity;
}
.hero.is-in .hero__wind{opacity:1}
/* PHONES AND TABLETS GET NO WIND FIELD (2026-08-07). On device it glitched hard
   — the whole screen flashed blank and tore into colour bands (founder). This is
   a full-viewport `will-change`d canvas the JS opens `desynchronized` (the
   low-latency path), which is the combination mobile GPU compositors handle
   worst. `display:none`, not `opacity:0`: opacity leaves the compositor layer
   alive, and the layer is the suspect. The hero still has its ambient orbs.
   This query is byte-identical to MOBILE_Q in js/wind.js, which bails out on the
   same condition — change one and you must change the other. */
@media (max-width:720px), (pointer:coarse) and (max-width:1024px){
  .hero__wind{display:none}
}

.hero__inner{padding-block:16vh 6vh;text-align:center;will-change:transform,opacity}
/* the claim — pure white, centered, word-rise */
.hero__stmt{color:var(--text);margin-inline:auto}
/* .s-sub carries the type; this only places it */
.subline{margin:clamp(22px,3vw,34px) auto 0;max-width:52ch}
.hero__cta{
  margin-top:clamp(28px,3.6vw,42px);
  display:flex;justify-content:center;gap:12px;flex-wrap:wrap;
}

.scrollcue{position:absolute;left:50%;bottom:clamp(20px,4vh,44px);transform:translateX(-50%);width:1px;height:clamp(72px,11vh,104px);z-index:2}
.scrollcue__line{display:block;width:1px;height:100%;background:linear-gradient(to bottom,transparent,var(--hair-strong))}
.scrollcue::after{
  content:'';position:absolute;left:50%;top:0;width:6px;height:6px;border-radius:50%;
  background:var(--strong);box-shadow:0 0 10px rgba(15,40,80,.28);
  transform:translateX(-50%);animation:cue 3s var(--e-move) infinite;
}
@keyframes cue{0%{top:0;opacity:0}18%{opacity:1}82%{opacity:1}100%{top:100%;opacity:0}}

/* ============================================================
   2 · THE CURTAIN — pinned blackout, two statements, then the farm
   ============================================================ */
/* Tall section, one sticky viewport. curtain() in main.js maps scroll onto:
   veil in (pre-roll, over the hero's last screen) → beat A → beat B + veil out
   + farm draw-in. Every value it writes is opacity/transform/colour only.
   The section keeps id="insight" so the nav's #insight link still lands here. */
.curtain{height:320vh}
.curtain__pin{
  position:sticky;top:0;height:100svh;
  /* above #veil (105) so the statements read on the black; the section and
     main are both position:relative/z-auto, so this competes with the veil
     in the root stacking context rather than being trapped under them */
  z-index:106;
  display:grid;align-content:center;align-items:center;
  /* 1.2/.8, widened from 1.08/.92 on 2026-08-07: the beats now render at the
     page-wide --fs-head instead of their own smaller scale, and a 35-character
     line at 38px needs ~660px. The farm gives up ~65px for it. */
  grid-template-columns:minmax(0,1.2fr) minmax(0,.8fr);
  gap:clamp(24px,4vw,64px);
  max-width:1280px;margin-inline:auto;
  padding:clamp(74px,10vh,116px) clamp(22px,5vw,64px) clamp(34px,5vh,64px);
  box-sizing:border-box;
}
/* the statements own the left column and stay optically centred there */
.curtain__stage{display:grid;justify-items:start;align-self:center}
/* JS-live only: the two statements share one cell so B lands exactly where A
   was. Without JS they stay stacked and readable. */
.curtain-live .curtain__beat{grid-area:1/1;align-self:center;pointer-events:none}
.curtain-live .curtain__stage{min-height:clamp(190px,34vh,330px);align-items:center}
/* the beats are section headers like any other: same --fs-head, whose slope
   and cap are derived from THIS column (see the token in :root). They used to
   run on a private clamp(18px,2.1vw,34px), which is what made them read small
   next to the history header. */
.curtain__beat{
  margin:0;text-align:left;
  font-family:var(--display);font-weight:500;
  font-size:var(--fs-head);line-height:1.2;letter-spacing:-.018em;
  color:var(--strong);will-change:opacity,transform;
}
.curtain__beat .l{display:block;text-wrap:balance}
/* .l--turn ("That changes now" on its own line, at header size) was retired on
   2026-08-07 — the founder folded the turn into the sub-line, so beat B now
   runs header + subheader and nothing else. */
/* beat A only ever exists on the black — near-paper type on the veil, the same
   "different surface" case as .flight__stmt over footage (hard rule 8).
   Beat B is born on black too but has to survive the return to white, so its
   colour is lerped by the driver across exactly the veil's lift window. */
.curtain__beat--a{color:#f4f7fa}
/* the subheader tier — .s-sub in the markup carries the type; the em-relative
   .52em it used to run on made it a fourth size nobody had chosen.
   colour:inherit is load-bearing — .s-sub's --muted slate is invisible on the
   blackout, and this line has to ride the beat colour the driver is lerping */
.curtain__beat .l--sub{display:block;margin-top:.75em;color:inherit;opacity:.78}
/* the farm holds the right column. Width is capped off the viewport height too
   (viewBox is 1440×1020, so height ≈ width/1.41) — otherwise a short window
   crops the turbines. */
.curtain__farm{
  width:100%;max-width:min(560px,72svh);justify-self:center;
  display:flex;flex-direction:column;align-items:center;gap:clamp(8px,1.4vh,18px);
  will-change:opacity;
}
.curtain-live .curtain__farm{opacity:0}
/* one column on phones: statements first, farm under them */
@media (max-width:760px){
  .curtain__pin{grid-template-columns:1fr;gap:clamp(14px,2.6vh,30px)}
  .curtain__stage{justify-items:center}
  /* the one place the beats leave --fs-head: stacked, they get the full 90vw
     column, and 35 characters only fit it below ~5.3vw. Letting the shared
     23px floor through wraps every line into a ragged two-row block on a
     360px phone. Nothing sits beside them here, so the divergence is unseen. */
  .curtain__beat{text-align:center;font-size:min(var(--fs-head),5.3vw)}
  /* the sub-line HAS to come down with it. --fs-sub bottoms out at a flat 16px
     while the beat keeps shrinking on 5.3vw, so the two curves crossed at
     ~302px and sat within a pixel of each other all the way to 340 — the
     subheader read as large as the header it sits under (founder, 2026-08-07).
     4vw is 5.3 × .75, so the pair keeps a fixed ratio down to the narrowest
     phone instead of converging. */
  .curtain__beat .l--sub{font-size:min(var(--fs-sub),4vw)}
  .curtain-live .curtain__stage{min-height:clamp(150px,26vh,240px)}
  .curtain__farm{max-width:min(420px,44svh)}
}

/* line-art wind farm (viewBox cropped to the survey square) */
.site__farm{width:100%;height:auto;display:block;overflow:hidden}
/* dashed survey grid: crisp 1.5px dashes (non-scaling), fading in cell by cell */
.site__grid line{
  stroke:#a9b8c8;stroke-width:1.5;stroke-dasharray:5 10;vector-effect:non-scaling-stroke;
  opacity:0;transition:opacity .9s var(--e-enter);transition-delay:calc(var(--gi,0)*45ms);
}
.curtain.farm-in .site__grid line{opacity:.7}
/* the one highlighted cell = the site */
.site__cell{
  fill:rgba(37,99,235,.055);stroke:#8ea2b6;stroke-width:1.5;stroke-dasharray:5 10;
  vector-effect:non-scaling-stroke;opacity:0;transition:opacity 1s var(--e-enter) .55s;
}
.curtain.farm-in .site__cell{opacity:1}
/* the perimeter of the survey grid — the region assigned to the turbines */
.site__perim{
  fill:none;stroke:#334155;stroke-width:2;stroke-dasharray:5 10;
  vector-effect:non-scaling-stroke;opacity:0;transition:opacity 1s var(--e-enter) .7s;
}
.curtain.farm-in .site__perim{opacity:.9}
.rotor{transform-box:fill-box;transform-origin:center}
.curtain.drawn .rotor{animation:spin var(--spin,9s) linear infinite}
.site__labels{text-align:center;line-height:1.75}
.site__labels .label{display:block;color:var(--text)}

/* shared line-art primitives */
.dp{fill:none;stroke-linecap:round;stroke-linejoin:round}
.pp{transform-box:fill-box;transform-origin:center}
@keyframes spin{to{transform:rotate(360deg)}}

/* ============================================================
   3 · THE HUNDRED YEARS — archival plate + the turn
   ============================================================ */
.history{padding-block:clamp(84px,10vw,150px) clamp(70px,8vw,120px)}
/* the scene spans exactly the curtain pin's bounds (1280 / the same inline
   padding), not the narrower 1200px .wrap — so the plate's left edge and the
   copy's right edge line up with the grid scene above it. Founder call,
   2026-08-07: same boundaries as the grid section, and the width the headline
   gave back when it dropped to --fs-head becomes air between the two. */
.history>.wrap{max-width:1280px}
.history__grid{
  /* the plate holds a proportion of the row so the pairing keeps its balance
     as the window narrows, but caps at 440px: the delivered scan is 490px
     wide, and past that the browser would be upscaling a halftone print */
  display:grid;grid-template-columns:minmax(0,min(440px,38%)) minmax(0,1fr);
  gap:clamp(32px,7vw,100px);align-items:center;
}
/* The copy was centred in its track until 2026-08-07, which put ~180px between
   it and the plate and ~120px to its right, with its left edge landing on
   nothing — the founder read it as text dropped at a random spot. It is
   left-aligned on the track's own start line now: one fixed edge, a generous
   but deliberate gap, and the slack falls off the right the way slack falls
   off any ragged-right text block. Tried and rejected: justify-self:end (both
   edges on the container boundary, but a 300px hole in the middle) and
   align-items:start (the void moves to the bottom right and gets louder). */
.history__copy{justify-self:start;max-width:100%}
/* SQUARE corners on purpose. The video below is a rounded frame; keeping this
   one hard-edged is what separates "an archive document" from "our footage". */
.history__plate{
  margin:0;position:relative;
  border-radius:0;overflow:hidden;
  border:1px solid rgba(15,40,80,.16);
  background:var(--window);
  box-shadow:0 1px 0 rgba(15,40,80,.05);
}
/* no forced aspect-ratio: the scan's own ratio (490x620, declared on the tag)
   sets the box, so nothing of the plate is cropped away */
.history__img{
  width:100%;height:auto;display:block;
  /* the source is a scan of a printed page: a hair of contrast keeps it from
     going flat-grey against the blue-white canvas */
  filter:contrast(1.06) saturate(.9);
}
.history__cap{
  padding:clamp(9px,1vw,13px) clamp(11px,1.2vw,15px);
  border-top:1px solid rgba(15,40,80,.10);
  font-family:var(--ui);font-weight:500;font-size:var(--fs-small);
  letter-spacing:.04em;line-height:1.45;color:var(--faint);
  /* at --fs-small the credit is a hair too long for a 440px plate; balanced
     into two even lines it reads as set, not as an accidental orphan */
  text-wrap:balance;
}
/* the turn is a header line, not a third size — set off from the headline by a
   gap alone. (The curtain's equivalent line was folded into its subtext on
   2026-08-07; this one stayed.) */
.history__turn{
  margin-top:clamp(20px,2.6vw,32px);
  font-family:var(--display);font-weight:500;
  font-size:var(--fs-head);line-height:1.2;letter-spacing:-.018em;
  color:var(--strong);
}
@media (max-width:820px){
  .history__grid{grid-template-columns:1fr;gap:clamp(28px,6vw,42px)}
  .history__plate{max-width:440px}
  .history__copy{justify-self:start}
}

/* ============================================================
   4 · INTRODUCING — copy left, field collage right (2026-08-07)
   ============================================================ */
.intro{padding-block:clamp(70px,8vw,120px)}
/* same 1280px bounds as the curtain pin and the history plate, so the
   collage's right edge lands exactly where the farm's does */
.intro>.wrap{max-width:1280px}
.intro__grid{
  display:grid;grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr);
  gap:clamp(30px,4.5vw,64px);align-items:center;
}
.intro__sub{margin-top:clamp(16px,2vw,24px);max-width:40ch}
.intro__cta{margin-top:clamp(22px,2.6vw,32px)}

/* THE COLLAGE — the four field photographs, in the arrangement the founder
   supplied: one portrait holding the full height on the left, one letterbox
   across the top right, two squares under it. The fr values and the
   aspect-ratio are the source layout's own proportions (a 2875x1562.5 box
   with 25-unit gutters), so every cell matches its photo's native ratio and
   `cover` never actually has to crop anything. Keep them in step if a photo
   is ever swapped. */
.collage{
  display:grid;
  grid-template-columns:1.2635fr 1fr 1fr;
  grid-template-rows:1fr 1fr;
  gap:clamp(5px,.62vw,9px);
  aspect-ratio:2875/1562.5;
}
/* rounded + hairline, matching .flight__frame: these are OUR footage. The
   history plate is square-cornered precisely so it does NOT look like this. */
.collage__cell{
  margin:0;overflow:hidden;background:var(--plate);
  border-radius:clamp(8px,1vw,14px);border:1px solid rgba(15,40,80,.10);
}
.collage__cell img{width:100%;height:100%;object-fit:cover;display:block}
.collage__cell--tall{grid-area:1/1/3/2}
.collage__cell--wide{grid-area:1/2/2/4}

@media (max-width:820px){
  .intro__grid{grid-template-columns:1fr;gap:clamp(28px,6vw,42px)}
}
/* phones: at 1.84:1 across a 350px column every photo becomes a thumbnail.
   Re-flow to letterbox banner on top, portrait beside the two squares under
   it. `align-items:start` is load-bearing — it lets the two squares size
   themselves off their own images so rows 2 and 3 become definite, which is
   what then gives the row-spanning portrait a height to fill. Stretch on all
   four would make row height and image height depend on each other. */
@media (max-width:640px){
  .collage{
    grid-template-columns:1fr 1fr;grid-template-rows:auto auto auto;
    aspect-ratio:auto;align-items:start;
  }
  .collage__cell--wide{grid-area:1/1/2/3}
  .collage__cell--tall{grid-area:2/1/4/2;align-self:stretch}
}

/* ============================================================
   5 · THE FLIGHT — framed footage
   ============================================================ */
/* the figure only establishes the positioning context; .flight__frame is the
   cropped window. Split apart 2026-08-07 so the caption can drop OUT of the
   frame on phones instead of being squeezed inside it. */
.flight__media{position:relative;margin:0}
.flight__frame{
  position:relative;overflow:hidden;
  border-radius:clamp(14px,1.8vw,22px);border:1px solid rgba(15,40,80,.10);
  aspect-ratio:16/9;min-height:340px;background:#000;
}
.flight__video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.flight__scrim{
  position:absolute;inset:0;
  background:linear-gradient(to top, rgba(8,7,7,.72) 0%, rgba(8,7,7,.30) 32%, rgba(8,7,7,0) 55%);
}
.flight__text{
  position:absolute;z-index:1;
  left:clamp(20px,3.5vw,44px);right:clamp(20px,3.5vw,44px);bottom:clamp(20px,3.2vw,40px);
}
/* the ONLY white type left on the light grade: this sits on top of dark
   footage, not on the page, so it keeps the dark-grade treatment. It is not
   an exception to hard rule 8 — the rule is about type on the canvas. */
.flight__stmt{color:#fff;text-shadow:0 2px 28px rgba(5,4,4,.8)}
.flight__label{margin-top:clamp(12px,1.6vw,20px);color:rgba(255,255,255,.82);text-shadow:0 1px 16px rgba(5,4,4,.85)}

/* ============================================================
   6 · MISSION RAIL — three cards
   ============================================================ */
/* the lede sits under the title as its subtext */
.rail__lede{
  margin-top:clamp(16px,2vw,26px);max-width:44ch;
  font-family:var(--ui);font-weight:400;font-size:clamp(16px,1.6vw,21px);line-height:1.45;
  color:var(--muted);
}
.rail__steps{
  list-style:none;margin:clamp(44px,6vw,72px) 0 0;padding:0;
  display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(14px,1.8vw,24px);
}
/* card anatomy: recessed visual stage on top, caption below (never text-only) */
.step{display:flex;flex-direction:column;padding:10px}
.step__stage{aspect-ratio:16/10;flex:none}
.step__stage svg{width:100%;height:100%;display:block}
.step__cap{padding:clamp(18px,2.2vw,26px) clamp(14px,1.6vw,20px) clamp(20px,2.4vw,30px)}
.step__title{
  display:flex;align-items:baseline;gap:.7em;
  font-family:var(--display);font-weight:500;font-size:clamp(20px,2vw,25px);
  letter-spacing:-.01em;color:var(--strong);
}
.step__num{
  font-family:var(--ui);font-weight:600;font-size:.62em;color:var(--faint);
  font-feature-settings:"tnum" 1;letter-spacing:.1em;
}
.step__body{
  margin-top:10px;
  font-family:var(--ui);font-weight:400;font-size:clamp(14px,1.2vw,16px);
  line-height:1.55;color:var(--muted);
}
.step__body b{font-weight:600;color:var(--strong)}


/* ============================================================
   7 · THE GLOBE — copy left, globe right
   ============================================================ */
.globe__grid{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.1fr);
  gap:clamp(32px,6vw,90px);align-items:center;
}
.globe__sub{
  margin-top:clamp(20px,2.6vw,30px);max-width:34ch;
  font-family:var(--ui);font-weight:500;font-size:clamp(16px,1.7vw,21px);
  line-height:1.45;color:var(--muted);
}
.globe__stage{display:flex;justify-content:center;min-width:0}
.globe__canvas{width:min(100%,640px);aspect-ratio:1;height:auto}

/* ============================================================
   8 · CLOSE — the end-of-page CTA
   ============================================================ */
/* the Contact anchor scrolls a bit PAST the section top so the
   statement + CTA cluster sits centred in the viewport, not low */
.close{scroll-margin-top:-24px}
.close__inner{text-align:center}
.tagline{
  margin:clamp(24px,3.2vw,34px) auto 0;max-width:30ch;
  font-family:var(--display);font-weight:500;font-size:clamp(20px,2.6vw,34px);
  line-height:1.3;letter-spacing:-.01em;color:var(--body);
}
.close__cta{
  margin-top:clamp(36px,5vw,56px);
  display:flex;flex-direction:column;align-items:center;gap:clamp(18px,2.4vw,26px);
}
.close__cta .arrowlink{color:var(--muted);font-weight:500}
.close__cta .arrowlink:hover{color:var(--text)}

/* ============================================================
   footer
   ============================================================ */
.foot{
  position:relative;z-index:2;border-top:1px solid var(--hair);
  padding-block:clamp(40px,6vw,64px) clamp(30px,4vw,46px);
}
.foot__inner{display:flex;justify-content:space-between;align-items:flex-start;gap:28px;flex-wrap:wrap}
.foot__brand{display:flex;flex-direction:column;gap:10px}
.foot__lock{display:flex;align-items:center;gap:.42em;font-family:var(--display);font-weight:500;font-size:clamp(17px,1.9vw,20px);letter-spacing:-.02em;color:var(--text)}
.foot__lock .prop{height:.95em;width:auto}
.foot__legal{font-family:var(--ui);font-size:clamp(13px,1.2vw,15px);color:var(--faint)}
.foot__links{display:flex;flex-direction:column;gap:10px;text-align:right}
.foot__links a{font-family:var(--ui);font-size:clamp(13px,1.2vw,15px);color:var(--muted);text-decoration:none;transition:color .3s ease}
.foot__links a:hover{color:var(--text)}
.foot__base{
  margin-top:clamp(28px,4vw,44px);display:flex;justify-content:space-between;align-items:center;
  gap:16px;flex-wrap:wrap;font-family:var(--ui);font-size:clamp(12px,1.1vw,13px);color:var(--faint)
}
.foot__base a{color:var(--faint);text-decoration:none;transition:color .3s ease}
.foot__base a:hover{color:var(--muted)}
@media (max-width:560px){.foot__links{text-align:left}}

/* ============================================================
   TEAM (team.html)
   ============================================================ */
.team{padding-block:clamp(140px,17vw,210px) clamp(84px,10vw,150px)}
.team__lede{
  margin-top:clamp(20px,2.6vw,30px);max-width:46ch;
  font-family:var(--ui);font-weight:500;font-size:clamp(16px,1.7vw,21px);
  line-height:1.45;color:var(--muted);
}
.team__grid{
  list-style:none;margin:clamp(48px,6.5vw,84px) 0 0;padding:0;
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(20px,2.6vw,34px);
}
.person{display:flex;flex-direction:column;min-width:0}
/* the portrait sits in a rounded frame on the canvas — no card dissolve here:
   a photograph is already a surface, and stacking the dissolve on top of one
   reads as a plate (hard rule 4 is about type-and-line cards, not images) */
.person__frame{
  position:relative;overflow:hidden;border-radius:clamp(14px,1.6vw,20px);
  aspect-ratio:4/5;background:var(--window);
  border:1px solid rgba(15,40,80,.08);
}
.person__img{
  width:100%;height:100%;object-fit:cover;object-position:50% 22%;
  display:block;
}
.person__name{
  margin-top:clamp(16px,1.8vw,22px);
  font-family:var(--display);font-weight:500;
  font-size:clamp(19px,1.9vw,25px);letter-spacing:-.015em;line-height:1.15;
  color:var(--strong);
}
.person__role{
  margin-top:6px;
  font-family:var(--ui);font-weight:600;font-size:clamp(11px,1vw,12.5px);
  letter-spacing:.16em;text-transform:uppercase;color:var(--faint);
}
/* LinkedIn mark: a quiet hairline disc that fills with ink on hover */
.person__in{
  display:inline-flex;align-items:center;justify-content:center;
  width:34px;height:34px;margin-top:clamp(12px,1.4vw,16px);
  border-radius:50%;border:1px solid var(--line-strong);
  color:var(--muted);
  transition:color .18s ease,background-color .18s ease,border-color .18s ease,transform .18s var(--e-enter);
}
.person__in svg{width:16px;height:16px;fill:currentColor}
.person__in:hover,.person__in:focus-visible{
  color:#fff;background:var(--strong);border-color:var(--strong);
  transform:translateY(-2px);
}
@media (max-width:820px){
  .team__grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:clamp(18px,3vw,26px)}
}
@media (max-width:560px){
  .team__grid{grid-template-columns:1fr;gap:clamp(26px,6vw,36px);max-width:380px}
}

/* ============================================================
   MODELS (models.html)
   ============================================================ */
/* A common headline, then one block per model: image left, copy right, both
   the same hand. The two models are siblings, so they deliberately share a
   reading order rather than alternating — this page is a spec sheet, not the
   front page's tour, and the alternation law (DESIGN.md §3.1) is about
   breaking the centred-slide rhythm of a single scrolling narrative. */
.models{padding-block:clamp(140px,17vw,210px) clamp(16px,2.5vw,34px)}
.models__lede{
  margin-top:clamp(20px,2.6vw,30px);max-width:52ch;
  font-family:var(--ui);font-weight:500;font-size:clamp(16px,1.7vw,21px);
  line-height:1.5;color:var(--muted);
}

.model{padding-block:clamp(52px,7vw,96px)}
/* a chapter seam between the two models — connective tissue, not a slide
   boundary (same device as .rail>.wrap on the front page) */
.model--seam>.wrap{border-top:1px solid var(--hair);padding-top:clamp(52px,7vw,96px)}
.model__grid{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:clamp(32px,5vw,80px);align-items:center;
}
/* the visual is a photograph's frame: rounded, hairline, recessed stage inside.
   No card dissolve — an image is already a surface (same call as .person__frame). */
.model__frame{
  margin:0;position:relative;overflow:hidden;
  border-radius:clamp(14px,1.6vw,20px);
  aspect-ratio:4/3;background:var(--window);
  border:1px solid rgba(15,40,80,.08);
}
.model__img{width:100%;height:100%;object-fit:cover;display:block}

/* Windpoint's slot is the live model instead of a photograph. The frame drops
   its 4:3 crop (a forecast widget is portrait and self-sizing) and grows a
   hairline header strip — product chrome, not a badge. The still illustration
   stays underneath as the fallback: liveWidget() in main.js hides the iframe
   wrap if the widget never reports a height, and the art shows through. */
.model__frame--live{
  aspect-ratio:auto;display:flex;flex-direction:column;
  min-height:clamp(320px,42vw,520px);
}
.model__livehead{
  /* above the absolutely-positioned fallback still, which fills the frame */
  position:relative;z-index:2;
  display:flex;align-items:baseline;gap:.7em;flex:none;
  padding:clamp(11px,1.2vw,15px) clamp(13px,1.4vw,18px);
  border-bottom:1px solid rgba(15,40,80,.08);
  background:rgba(248,250,252,.5);
}
.model__livelabel{
  font-family:var(--ui);font-weight:600;font-size:clamp(10.5px,1vw,12px);
  letter-spacing:.16em;text-transform:uppercase;color:var(--muted);
}
.model__livesite{
  font-family:var(--ui);font-weight:500;font-size:clamp(11.5px,1.1vw,13px);
  color:var(--faint);
}
/* the widget itself. It sits ABOVE the fallback still in the same box, so when
   it is hidden the art is already there — no reflow, no empty frame. */
.model__livebody{
  position:relative;z-index:1;flex:1 1 auto;
  display:flex;align-items:flex-start;justify-content:center;
  padding:clamp(12px,1.6vw,20px);min-height:0;
  /* the iframe can be scaled down on very narrow phones (see liveWidget) and a
     transform paints outside the box — clip it rather than let it spill */
  overflow:hidden;
}
.model__livebody[hidden]{display:none}
/* once the widget has reported its height, liveWidget sets this box to exactly
   that height and the frame must stop stretching past it. Before that both keep
   their placeholder sizing so the fallback art still fills the frame. */
/* height eases with the iframe rather than snapping: when the widget reports a
   SMALLER height the box would otherwise collapse instantly while the iframe is
   still easing down, and overflow:hidden clips the card for those 250ms */
.model__livebody.is-live{flex:0 0 auto;transition:height .25s ease}
.model__frame--live.is-live{min-height:0}
.model__livebody iframe{
  width:100%;max-width:460px;height:620px;border:0;display:block;
  /* the widget reports its own height; this is the pre-handshake size */
  transition:height .25s ease;
}
@media (prefers-reduced-motion:reduce){
  .model__livebody iframe,.model__livebody.is-live{transition:none}
}
/* the fallback still fills the frame BELOW the widget layer */
.model__frame--live .model__ph{z-index:0}
/* widget never answered: collapse back to the ordinary 4:3 frame so the
   fallback is indistinguishable from Windwall's, and take down everything that
   claimed there was live data — the header strip and the "See it live here"
   link both point at something that isn't there. */
.model.is-dead .model__frame--live{aspect-ratio:4/3;min-height:0}
.model.is-dead .model__livehead,
.model.is-dead .model__livelink{display:none}
/* The line-art stand-in. It carried a "Placeholder" chip in its first cut; the
   founder removed it on 2026-08-07 and chose to SHIP the illustration as-is for
   now, so this is production art until photographs replace it — it is no longer
   self-announcing, and the only record that a swap is pending is the HTML
   comment in models.html and DESIGN.md §3.11b. */
.model__ph{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}
.model__ph svg{width:100%;height:100%;display:block}

.model__copy{min-width:0}
/* Availability is stated in the prose, never in a status pill: the pills read
   as generic template furniture (founder call, 2026-08-07). It is also why
   there is no green "shipping" badge — green means MEASURED on this site and
   nothing else (hard rule 2). */
.model__name{margin-top:0}
.model__tag{
  margin-top:clamp(12px,1.5vw,18px);max-width:34ch;
  font-family:var(--display);font-weight:500;
  font-size:clamp(18px,1.9vw,24px);line-height:1.25;letter-spacing:-.012em;
  color:var(--body);
}
.model__body{margin-top:clamp(20px,2.4vw,30px);max-width:50ch}
.model__body p{
  margin-top:clamp(12px,1.4vw,17px);
  font-family:var(--ui);font-weight:400;font-size:clamp(15px,1.35vw,16.5px);
  line-height:1.62;color:var(--muted);
}
.model__body p:first-child{margin-top:0}
.model__list{
  list-style:none;margin-top:clamp(18px,2.2vw,26px);padding:0;display:grid;gap:11px;
  font-family:var(--ui);font-weight:400;font-size:clamp(14.5px,1.3vw,16px);
  line-height:1.55;color:var(--muted);
}
/* a hairline dash instead of a bullet — the same line-art vocabulary the
   illustrations use */
.model__list li{position:relative;padding-left:1.5em}
.model__list li::before{
  content:"";position:absolute;left:0;top:.72em;
  width:11px;height:1px;background:var(--hair-strong);
}
.model__note{color:var(--faint)!important}
.model__cta{
  margin-top:clamp(26px,3.2vw,38px);
  display:flex;align-items:center;gap:clamp(14px,2vw,24px);flex-wrap:wrap;
}
.model__cta .arrowlink{color:var(--muted);font-weight:500}
.model__cta .arrowlink:hover{color:var(--text)}
/* the mirrored block: copy left, image right. Done by grid PLACEMENT, not by
   reordering the markup — the figure stays first in the DOM so the one-column
   layout below still leads with the image on both blocks. */
.model--flip .model__frame{grid-column:2;grid-row:1}
.model--flip .model__copy{grid-column:1;grid-row:1}
@media (max-width:880px){
  /* one column: the image leads, the copy follows — on both blocks */
  .model__grid{grid-template-columns:1fr;gap:clamp(28px,5vw,40px)}
  .model--flip .model__frame,.model--flip .model__copy{grid-column:auto;grid-row:auto}
  .model__frame{max-width:560px}
}
/* NOT full-bleed on phones. Running the live frame edge to edge was tried on
   2026-08-07 to buy the widget ~70px and the founder rejected it on sight: the
   frame lost its margin and read as broken rather than generous. The widget
   fits by scaling instead (see liveWidget), which keeps the frame where every
   other frame on the site sits. */

/* ============================================================
   FORECASTS (forecasts.html)
   ============================================================ */
/* The one page on the site that is a TOOL rather than a narrative, so it
   carries a little more furniture than the rest: a search field, a coordinate
   map, a chart, a table. The grade holds it together — hairlines instead of
   boxes, one card (the result panel) carrying the dissolve, and the two data
   colours still rationed to their two meanings:

     GREEN  = the corrected forecast (what Windpoint says)
     CORAL  = the raw baseline model it corrects

   Nothing else on the page is allowed either colour. In particular the
   conditions line and the map pins are neutral ink on purpose: they are
   places and judgements, not measurements, and tinting them green would
   dissolve the one meaning green has here (hard rule 2). */
.fc{padding-block:clamp(140px,17vw,210px) clamp(60px,7vw,96px)}
/* The gallery has no display headline and no lede (founder, 2026-08-07: strip
   every text block, the map and dashboard ARE the page). This byline is the one
   line of copy left, and it is the <h1> — small on purpose, but it still has to
   claim the numbers below as ours, so the model name is the loud part. */
.fc__byline{
  max-width:56ch;
  font-family:var(--ui);font-weight:400;font-size:clamp(14px,1.3vw,16px);
  line-height:1.55;letter-spacing:0;color:var(--faint);
}
.fc__byline b{font-weight:600;color:var(--strong)}
/* the track-record row, if the founder supplies real figures. Tabular numerals
   so the numbers align; hairline separators rather than pills. */
.fc__record{
  list-style:none;margin:clamp(16px,2vw,24px) 0 0;padding:0;
  display:flex;flex-wrap:wrap;gap:clamp(14px,2.4vw,32px);
  font-family:var(--ui);font-weight:400;font-size:clamp(13px,1.2vw,14.5px);
  color:var(--faint);
}
.fc__record li{display:flex;align-items:baseline;gap:.5em}
.fc__record li+li{padding-left:clamp(14px,2.4vw,32px);border-left:1px solid var(--hair)}
.fc__record b{
  font-family:var(--display);font-weight:500;font-size:clamp(19px,2vw,26px);
  letter-spacing:-.015em;color:var(--strong);font-feature-settings:"tnum" 1;
}
.fc__app{margin-top:clamp(34px,4.4vw,56px)}

.fc__status{
  font-family:var(--ui);font-size:clamp(14px,1.3vw,15.5px);
  line-height:1.6;color:var(--faint);max-width:56ch;
}
.fc__status.is-fail{color:var(--drift-ink)}
.fc__status a{color:var(--strong);text-decoration:underline;
  text-decoration-color:var(--hair-strong);text-underline-offset:3px}

/* ---------- picker: list left, coordinate map right ---------- */
.fc__picker{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,.82fr);
  gap:clamp(32px,5vw,72px);align-items:start;
}
.fc__picker[hidden]{display:none}

.fc__searchlab{
  display:block;
  font-family:var(--ui);font-weight:600;font-size:clamp(11px,1vw,12.5px);
  letter-spacing:.16em;text-transform:uppercase;color:var(--muted);
}
/* A hairline UNDER the field, not a box around it — the same "line, not
   outline" instinct the cards use. The focus state thickens and inks that
   one line rather than adding a ring. */
.fc__search{
  width:100%;margin-top:12px;padding:11px 2px;
  background:transparent;border:0;border-bottom:1px solid var(--hair-strong);
  border-radius:0;-webkit-appearance:none;appearance:none;
  font-family:var(--display);font-weight:500;
  font-size:clamp(19px,2.2vw,27px);letter-spacing:-.015em;color:var(--strong);
  transition:border-color .2s ease;
}
.fc__search::placeholder{color:var(--faint);font-weight:400}
.fc__search:focus{outline:0;border-bottom-color:var(--strong)}
.fc__search::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}

.fc__count{
  margin-top:10px;
  font-family:var(--ui);font-size:12.5px;letter-spacing:.02em;color:var(--faint);
}

.fc__list{list-style:none;margin:clamp(14px,1.8vw,22px) 0 0;padding:0}
/* Each site is a full-width row with a hairline between rows. Selecting one
   inks the label and grows a 2px ink rule on the left — a marker in a
   margin, which is how the rest of the site indicates "this one". */
.fc__site{
  position:relative;width:100%;display:flex;align-items:baseline;
  gap:.7em;flex-wrap:wrap;
  padding:13px 0 13px 16px;
  background:transparent;border:0;border-bottom:1px solid var(--hair);
  text-align:left;cursor:pointer;
  transition:padding-left .2s var(--e-enter);
}
.fc__site::before{
  content:"";position:absolute;left:0;top:12px;bottom:12px;width:2px;
  background:var(--strong);
  transform:scaleY(0);transform-origin:50% 50%;
  transition:transform .25s var(--e-enter);
}
.fc__site[aria-current]::before{transform:scaleY(1)}
.fc__sitename{
  font-family:var(--display);font-weight:500;
  font-size:clamp(16px,1.6vw,19px);letter-spacing:-.012em;color:var(--body);
  transition:color .15s ease;
}
.fc__sitesport{
  font-family:var(--ui);font-weight:500;font-size:12px;
  letter-spacing:.14em;text-transform:uppercase;color:var(--faint);
}
.fc__site:hover .fc__sitename,
.fc__site:focus-visible .fc__sitename{color:var(--text)}
.fc__site[aria-current] .fc__sitename{color:var(--text)}
.fc__empty{
  padding:16px 0;font-family:var(--ui);font-size:15px;color:var(--faint);
}

/* ---------- the map of Finland ----------
   Was a dashed coordinate graticule with dots on it: geographically correct
   and completely unrecognisable as a country (founder, 2026-08-07). It is now
   the actual national outline, drawn once from a baked-in 38-point border, in
   the same hairline hand as the model illustrations. The pins cluster in the
   south because that is where our sites are — the map tells that truth rather
   than zooming until it disappears. */
.fc__mapwrap{margin:0;position:relative}
/* Cropped to the south the frame is landscape (~1.3:1), so it can take its
   natural height off the viewBox again — no letterboxing, no fixed height.
   (The full-country version was ~2.2× taller than wide and had to be capped,
   or it towered over the site list beside it.) overflow stays visible so a pin
   label near the coast is not clipped by the frame. */
.fc__map{width:100%;height:auto;display:block;overflow:visible}
/* the land: a pale blue wash off the same family as the ambient orbs, so the
   country reads as part of the page's atmosphere rather than a pasted asset */
.fc__land{
  fill:rgba(37,86,152,.07);
  stroke:var(--hair-strong);stroke-width:1;
  stroke-linejoin:round;stroke-linecap:round;
}
.fc__pin{cursor:pointer}
.fc__pinhit{fill:transparent}
.fc__pinring{fill:none;stroke:var(--hair-strong);stroke-width:1.5;
  transform-box:fill-box;transform-origin:50% 50%;
  transition:stroke .2s ease,opacity .2s ease,transform .25s var(--e-enter)}
.fc__pindot{fill:var(--faint);transition:fill .2s ease}
.fc__pinlab{
  font-family:var(--ui);font-size:12px;font-weight:600;letter-spacing:-.005em;
  fill:var(--faint);transition:fill .2s ease,opacity .2s ease;
  /* the canvas-coloured halo is what lets a label survive crossing a
     graticule line without a plate behind it */
  paint-order:stroke;stroke:var(--canvas);stroke-width:3.5px;stroke-linejoin:round;
}
/* a label renderMap() could not fit without overlapping one already placed —
   hidden at rest, returned on hover, focus or selection */
.fc__pinlab.is-crowded{opacity:0}
.fc__pin:hover .fc__pinlab.is-crowded,
.fc__pin:focus-visible .fc__pinlab.is-crowded,
.fc__pin.is-on .fc__pinlab.is-crowded{opacity:1}
.fc__pin:hover .fc__pindot,.fc__pin:focus-visible .fc__pindot{fill:var(--strong)}
.fc__pin:hover .fc__pinlab,.fc__pin:focus-visible .fc__pinlab{fill:var(--strong)}
.fc__pin:hover .fc__pinring,.fc__pin:focus-visible .fc__pinring{stroke:var(--line-strong)}
.fc__pin.is-on .fc__pindot{fill:var(--text)}
.fc__pin.is-on .fc__pinlab{fill:var(--text)}
.fc__pin.is-on .fc__pinring{stroke:var(--strong);stroke-width:2;transform:scale(1.12)}
.fc__pin:focus{outline:0}
.fc__pin:focus-visible .fc__pinring{stroke:var(--strong);stroke-width:2}
.fc__mapcap{
  margin-top:10px;text-align:center;
  font-family:var(--ui);font-size:12px;color:var(--faint);
}

/* ---------- the result panel ---------- */
/* The page's one card, so it carries the dissolve (hard rule 4) rather than
   an outline: the tint and the masked hairline ring both fade out before the
   bottom, and the panel melts into the canvas. */
.fc__panel{
  margin-top:clamp(44px,6vw,80px);
  padding:clamp(24px,3.2vw,42px);
}
.fc__panel[hidden]{display:none}
.fc__loading,.fc__fail p{
  font-family:var(--ui);font-size:clamp(14.5px,1.3vw,16px);
  line-height:1.6;color:var(--faint);max-width:52ch;
}
.fc__fail p{color:var(--drift-ink)}
.fc__fail .btn{margin-top:18px}

.fc__head{max-width:60ch}
.fc__title{margin:0}
.fc__meta{
  margin-top:10px;
  font-family:var(--ui);font-size:clamp(12.5px,1.15vw,13.5px);
  letter-spacing:.02em;color:var(--faint);
}

/* the "now" row: numbers big enough to read at a glance, hairline-separated
   rather than boxed, wrapping to two rows on a phone */
.fc__now{
  margin-top:clamp(24px,3vw,34px);
  display:grid;grid-template-columns:repeat(auto-fit,minmax(112px,1fr));
  gap:1px;background:var(--hair);
  border-block:1px solid var(--hair);
}
.fc__stat{
  background:var(--canvas);
  padding:clamp(14px,1.8vw,20px) clamp(12px,1.5vw,18px);
}
.fc__statlab{
  display:block;
  font-family:var(--ui);font-weight:600;font-size:10.5px;
  letter-spacing:.16em;text-transform:uppercase;color:var(--faint);
}
.fc__statval{
  display:block;margin-top:7px;
  font-family:var(--display);font-weight:500;
  font-size:clamp(25px,3vw,36px);line-height:1;letter-spacing:-.02em;
  color:var(--strong);
}
.fc__statunit{
  margin-left:.32em;
  font-family:var(--ui);font-weight:500;font-size:12.5px;letter-spacing:.01em;
  color:var(--faint);
}
/* a site whose Windpoint correction has not been fitted yet: stated in
   words, never in colour (see the section note) */
.fc__pending{
  margin-top:clamp(26px,3.4vw,40px);
  padding-left:16px;border-left:2px solid var(--hair-strong);
  max-width:60ch;
  font-family:var(--ui);font-size:clamp(13.5px,1.25vw,15px);
  line-height:1.6;color:var(--faint);
}
/* Neutral ink, deliberately — see the section note. */
.fc__state{
  margin-top:clamp(16px,2vw,22px);max-width:56ch;
  font-family:var(--ui);font-size:clamp(14px,1.3vw,15.5px);
  line-height:1.6;color:var(--muted);
}

/* ---------- the chart ---------- */
.fc__chart{margin:clamp(30px,4vw,46px) 0 0}
/* Wide content scrolls inside its own container instead of squeezing the
   curves into illegibility on a phone. */
.fc__chartscroll{overflow-x:auto;-webkit-overflow-scrolling:touch}
.fc__svg{width:100%;min-width:600px;height:auto;display:block}
.fc__cgrid line{stroke:var(--hair);stroke-width:1}
.fc__cday line{stroke:var(--hair-strong);stroke-width:1;stroke-dasharray:3 7}
.fc__ctick,.fc__chourlab{
  font-family:var(--ui);font-size:12px;font-weight:500;fill:var(--faint);
}
.fc__cdaylab{
  font-family:var(--ui);font-size:12.5px;font-weight:600;
  letter-spacing:.1em;text-transform:uppercase;fill:var(--muted);
}
/* the two meanings, and nothing else */
.fc__corr{fill:none;stroke:var(--measured);stroke-width:2.6;
  stroke-linejoin:round;stroke-linecap:round}
.fc__gust{fill:none;stroke:var(--measured);stroke-width:1.4;opacity:.42;
  stroke-dasharray:1 4;stroke-linecap:round}
.fc__raw{fill:none;stroke:var(--drift);stroke-width:2;opacity:.85;
  stroke-dasharray:6 6;stroke-linejoin:round;stroke-linecap:round}

.fc__legend{
  margin-top:16px;display:flex;flex-wrap:wrap;gap:10px 24px;align-items:center;
}
.fc__key{
  display:inline-flex;align-items:center;gap:.6em;
  font-family:var(--ui);font-weight:500;font-size:clamp(12.5px,1.15vw,13.5px);
  color:var(--muted);
}
.fc__keyline{width:26px;height:6px;flex:none}
.fc__key--corr line{stroke:var(--measured-ink);stroke-width:2.6;stroke-linecap:round}
.fc__key--gust line{stroke:var(--measured-ink);stroke-width:1.6;opacity:.5;
  stroke-dasharray:1 4;stroke-linecap:round}
.fc__key--raw line{stroke:var(--drift-ink);stroke-width:2;
  stroke-dasharray:6 6;stroke-linecap:round}
.fc__key--corr{color:var(--measured-ink)}
.fc__key--raw{color:var(--drift-ink)}
.fc__unit{
  margin-top:8px;font-family:var(--ui);font-size:12px;color:var(--faint);
}

/* ---------- the hours: a row read across ----------
   Replaced a six-column vertical table that was accurate and unreadable
   (founder, 2026-08-07). Same shape as the widget's own hourly band, so the
   two read alike — but with none of its condition colouring, because green
   here means "corrected" and cannot also mean "nice out" (hard rule 2). */
.fc__hours{margin-top:clamp(30px,4vw,46px)}
.fc__hourshead,.fc__charthead{
  padding-top:clamp(20px,2.4vw,28px);border-top:1px solid var(--hair);
}
.fc__hourstitle{
  font-family:var(--ui);font-weight:600;font-size:11px;
  letter-spacing:.16em;text-transform:uppercase;color:var(--muted);
}
.fc__hoursunit{
  margin-top:6px;
  font-family:var(--ui);font-size:12.5px;line-height:1.5;color:var(--faint);
}
/* the band scrolls sideways rather than squeezing 24 columns into a phone */
.fc__hoursscroll{
  margin-top:clamp(14px,1.8vw,20px);
  overflow-x:auto;-webkit-overflow-scrolling:touch;
  /* the fade tells you there is more to the right without adding a control */
  -webkit-mask:linear-gradient(90deg,#000 0,#000 calc(100% - 34px),transparent);
  mask:linear-gradient(90deg,#000 0,#000 calc(100% - 34px),transparent);
}
.fc__hourrow{list-style:none;margin:0;padding:0 34px 4px 0;display:flex;gap:0}
.fc__hour{
  flex:0 0 auto;width:clamp(52px,5.4vw,62px);
  display:grid;justify-items:center;gap:5px;
  padding:10px 0 12px;
  font-family:var(--ui);
}
/* a hairline opens each new day — the day name sits on it */
.fc__hour.is-daystart{border-left:1px solid var(--hair-strong)}
.fc__hour+.fc__hour:not(.is-daystart){border-left:1px solid var(--hair)}
.fc__hourday{
  font-weight:600;font-size:10px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--strong);min-height:1em;
}
.fc__hourtime{
  font-weight:500;font-size:12.5px;color:var(--faint);
  font-variant-numeric:tabular-nums;
}
.fc__hoursky{
  width:23px;height:23px;
  fill:none;stroke:var(--muted);stroke-width:1.5;
  stroke-linecap:round;stroke-linejoin:round;
}
.fc__hoursky .fc__ray{stroke-width:1.3}
.fc__hoursky .fc__rain{stroke:var(--faint)}
.fc__hourtemp{
  font-family:var(--display);font-weight:500;font-size:16.5px;
  letter-spacing:-.01em;color:var(--strong);
}
.fc__hourwind{
  font-weight:400;font-size:12.5px;color:var(--muted);
  font-variant-numeric:tabular-nums;
}
.fc__hourdir{
  width:13px;height:13px;
  fill:none;stroke:var(--faint);stroke-width:1.6;
  stroke-linecap:round;stroke-linejoin:round;
}
.fc__hourcard{
  font-weight:500;font-size:10px;letter-spacing:.1em;color:var(--faint);
}

/* ---------- provenance ---------- */
/* The receipt for the claim the page makes. Small, but never hidden: the
   model string names exactly which variables were corrected and against
   which baseline, straight from the API. */
.fc__prov{
  margin-top:clamp(28px,3.6vw,44px);padding-top:clamp(18px,2.2vw,26px);
  border-top:1px solid var(--hair);max-width:78ch;
}
.fc__provline,.fc__provsrc{
  font-family:var(--ui);font-size:12.5px;line-height:1.6;color:var(--faint);
}
.fc__provsrc{margin-top:8px}
.fc__prov a{
  color:var(--muted);text-decoration:underline;
  text-decoration-color:var(--hair-strong);text-underline-offset:3px;
}
.fc__prov a:hover{color:var(--strong)}

@media (max-width:980px){
  /* one column: search and list lead, the map follows as confirmation */
  .fc__picker{grid-template-columns:1fr;gap:clamp(34px,6vw,48px)}
  .fc__mapwrap{max-width:460px;margin-inline:auto}
}
@media (prefers-reduced-motion:reduce){
  .fc__site,.fc__site::before,.fc__pinring,.fc__search{transition:none!important}
  .fc__site::before{transform:scaleY(1);opacity:0}
  .fc__site[aria-current]::before{opacity:1}
  .fc__pin.is-on .fc__pinring{transform:none}
}

/* ---------- legal pages (privacy.html) ----------
   Quiet long-form prose: one narrow measure, chapter-seam hairlines on the
   h2s, no cards, no pure white (hard rule 8 — .rise only). */
.legal{padding-block:clamp(140px,17vw,208px) clamp(84px,10vw,150px)}
.legal__head,.legal__body{max-width:680px}
.legal h1{
  margin-top:16px;
  font-family:var(--display);font-weight:500;letter-spacing:-.025em;
  font-size:clamp(34px,5vw,56px);line-height:1.04;color:var(--strong);
}
.legal__meta{
  margin-top:16px;font-family:var(--ui);
  font-size:clamp(13px,1.2vw,14px);color:var(--faint);
}
.legal__lede{
  margin-top:clamp(26px,3vw,38px);
  font-size:clamp(16.5px,1.6vw,19px);line-height:1.6;color:var(--body);
}
.legal__body{margin-top:clamp(20px,2.5vw,32px)}
.legal__body h2{
  margin-top:clamp(40px,5vw,60px);padding-top:clamp(26px,3.2vw,36px);
  border-top:1px solid var(--hair);
  font-family:var(--display);font-weight:500;letter-spacing:-.015em;
  font-size:clamp(19px,2.1vw,24px);line-height:1.2;color:var(--strong);
}
.legal__body h3{
  margin-top:clamp(22px,2.6vw,30px);
  font-family:var(--ui);font-weight:600;
  font-size:clamp(14.5px,1.35vw,16px);color:var(--strong);
}
.legal__body p{
  margin-top:14px;
  font-size:clamp(15px,1.35vw,16.5px);line-height:1.68;color:var(--body);
}
.legal__body ul{
  margin-top:14px;padding-left:1.2em;display:grid;gap:9px;
  font-size:clamp(15px,1.35vw,16.5px);line-height:1.6;color:var(--body);
}
.legal__body li::marker{color:var(--faint)}
.legal__body a{
  color:var(--strong);text-decoration:underline;
  text-decoration-color:var(--hair-strong);text-underline-offset:3px;
  transition:text-decoration-color .15s ease;
}
.legal__body a:hover,.legal__body a:focus-visible{text-decoration-color:var(--muted)}

/* The analytics opt-out control. Deliberately NOT a card (see the section
   note above — legal prose carries no cards, so hard rule 4's dissolve does
   not apply here). A hairline above it marks it as an actionable aside in a
   page of prose, without boxing it. */
.optout{
  margin-top:clamp(18px,2.2vw,26px);padding-top:clamp(16px,2vw,22px);
  border-top:1px solid var(--hair);
  display:flex;flex-wrap:wrap;align-items:center;gap:14px 18px;
}
.optout__state{
  flex:1 1 260px;margin:0;
  font-family:var(--ui);font-size:clamp(13.5px,1.2vw,14.5px);
  line-height:1.55;color:var(--faint);
}
/* This is the only <button class="btn"> on the site — everywhere else .btn is
   an <a>. Anchors have no default background, buttons get the UA's `buttonface`
   grey, which renders .btn--ghost as a filled pill instead of an outline.
   Scoped here rather than on .btn to leave the shared component untouched. */
.optout__btn{flex:0 0 auto;background:transparent;-webkit-appearance:none;appearance:none}
@media (prefers-reduced-motion:reduce){
  .optout__btn{transition:none}
}

/* ============================================================
   responsive
   ============================================================ */
@media (max-width:820px){
  .rail__steps{grid-template-columns:1fr;gap:clamp(14px,3vw,20px)}
  .globe__grid{grid-template-columns:1fr;gap:clamp(36px,8vw,52px)}
  .globe__canvas{width:min(80vw,480px)}
}
@media (max-width:520px){
  .hero__inner{padding-block:18vh 8vh}
  /* PICTURE, THEN THE TEXT. Overlaid, the statement had to wrap to four lines
     and the label to three, stacked on top of the drone inside a 4/5 crop that
     existed only to make room for them. Below 521px the caption leaves the
     footage: the frame goes back to a landscape crop sized by the content, and
     the words become ordinary page copy under it — so neither the type nor the
     crop is dictated by how narrow the column is. */
  .flight__frame{min-height:0;aspect-ratio:4/3;border-radius:clamp(12px,3vw,16px)}
  .flight__scrim{display:none}          /* only ever existed for legibility */
  .flight__text{position:static;margin-top:clamp(16px,4vw,22px)}
  /* on the page now, not on the footage — so hard rule 8 puts them back to ink */
  .flight__stmt{color:var(--strong);text-shadow:none}
  .flight__label{color:var(--muted);text-shadow:none}
}

/* ============================================================
   reduced motion
   ============================================================ */
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  .hero__wind{opacity:1!important;transition:none!important;transform:none!important}
  .reveal-up,.word__i,.statement:not(.rise) .line{transition:none!important;opacity:1!important;transform:none!important}
  .card::before{display:none!important}
  .scrollcue::after{animation:none!important;opacity:.6}
  .nav{transition:none!important;opacity:1!important}
  .hero__inner{transform:none!important;opacity:1!important}
  .card,.card:hover{transform:none!important}
  .rotor{animation:none!important}
  .site__grid line{opacity:.7!important;transition:none!important}
  .site__cell{opacity:1!important;transition:none!important}
  .site__perim{opacity:.9!important;transition:none!important}
  /* the curtain never goes dark and never pins: it collapses to a plain white
     band with both statements stacked and the farm already drawn. curtain()
     bails out entirely under reduced motion, so nothing writes over these. */
  #veil{display:none!important}
  .curtain{height:auto!important;padding-block:clamp(84px,10vw,150px)}
  .curtain__pin{position:static;height:auto;z-index:auto;padding-inline:clamp(22px,5vw,64px)}
  .curtain__stage{gap:clamp(28px,4vw,48px)}
  .curtain__beat{opacity:1!important;transform:none!important;color:var(--strong)!important}
  .curtain__farm{opacity:1!important}
  /* the phone menu still opens and closes, it just does not travel: the panel
     appears in place and its links are already at rest. visibility keeps doing
     the showing/hiding, so nothing here can strand it half-open. */
  .nav__panel,.nav__menu a,.nav__burger::before,.nav__burger::after{transition:none!important}
  .nav--open .nav__menu a{opacity:1;transform:none;transition-delay:0s!important}
}
