/* ============================================================
   SANQ V4 — shared stylesheet
   Palette and voice inherited from V3; rebuilt for motion.
   ============================================================ */

:root{
  --cream:#F2F1EC;
  --cream-deep:#EAE8E0;
  --ink:#14201A;
  --ink-70:#4A544E;
  --ink-50:#5F6963;
  --line:rgba(20,32,26,.14);
  --line-soft:rgba(20,32,26,.08);
  --gold:#C08A2A;
  --gold-deep:#7D5F13;
  --forest:#0F2B22;
  --forest-deep:#0A1D17;
  --sage:#8BA97C;

  /* product UI tokens — kept separate so screens read as real screenshots */
  --ui-bg:#F4F5F7;
  --ui-surface:#FFFFFF;
  --ui-line:#EAECEF;
  --ui-ink:#111418;
  --ui-muted:#687076;
  --ui-danger:#C2453D;

  --serif:"Instrument Serif",Georgia,"Times New Roman",serif;
  --sans:"Inter",system-ui,-apple-system,sans-serif;
  --mono:"IBM Plex Mono",ui-monospace,monospace;
  --display:"Archivo","Inter",system-ui,sans-serif;

  --shell:1320px;
  --gut:clamp(20px,4vw,56px);
  --ease:cubic-bezier(.16,1,.3,1);
  /* Emil-style curves: the built-in CSS easings are too weak to read as intentional. */
  --ease-out:cubic-bezier(.23,1,.32,1);
  --ease-in-out:cubic-bezier(.77,0,.175,1);
  --t-press:160ms;   /* press feedback  */
  --t-hover:220ms;   /* hover/colour    */
  --t-state:260ms;   /* small state     */
}

*,*::before,*::after{box-sizing:border-box}

html{ -webkit-text-size-adjust:100%; overflow-x:clip; background:#F2F1EC; }
body{
  margin:0; background:var(--cream); color:var(--ink);
  font-family:var(--sans); font-size:16px; line-height:1.6;
  -webkit-font-smoothing:antialiased; -moz-osx-font-smoothing:grayscale;
  /* NB: no overflow-x here. `overflow-x: hidden` on body makes it a scroll
     container, which silently breaks every `position: sticky` descendant —
     it killed the sticky phone and the stacking cards. `overflow-x: clip`
     on <html> contains stray horizontal overflow without that side effect. */
}
body.is-locked{ overflow:hidden; }

img{max-width:100%;display:block}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}

::selection{background:rgba(192,138,42,.25);color:var(--ink)}





/* Skip link — first stop in the tab order, invisible until focused. */
.skip{
  position:fixed;top:0;left:50%;transform:translate(-50%,-120%);z-index:300;
  background:var(--forest);color:var(--cream);
  font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  padding:14px 22px;border-radius:0 0 8px 8px;
  transition:transform .25s var(--ease);
}
.skip:focus{transform:translate(-50%,0);outline:2px solid var(--gold);outline-offset:2px}
main:focus{outline:none}

/* ---------- primitives ---------- */

.shell{max-width:var(--shell);margin:0 auto;padding:0 var(--gut)}
.shell-wide{max-width:1560px;margin:0 auto;padding:0 clamp(12px,3vw,40px)}

.eyebrow{
  font-family:var(--mono); font-size:11px; line-height:1.4;
  letter-spacing:.18em; text-transform:uppercase; color:var(--ink-50);
}
.eyebrow--gold{color:var(--gold-deep)}
.eyebrow--light{color:rgba(242,241,236,.55)}

.h1{font-family:var(--serif);font-weight:400;font-size:clamp(52px,9.2vw,132px);line-height:.92;letter-spacing:-.024em;margin:0}
.h2{font-family:var(--serif);font-weight:400;font-size:clamp(42px,6.4vw,96px);line-height:.94;letter-spacing:-.024em;margin:0}
.h3{font-family:var(--serif);font-weight:400;font-size:clamp(30px,3.6vw,52px);line-height:1.05;letter-spacing:-.02em;margin:0}
.lede{font-size:clamp(16px,1.25vw,19px);line-height:1.62;color:var(--ink-70);text-wrap:pretty;margin:0}
.ital{font-style:italic}

.rule{border:0;border-top:1px solid var(--line);margin:0}

/* buttons */
.btn{
  display:inline-flex;align-items:center;gap:9px;
  font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  padding:15px 22px;border-radius:2px;border:1px solid var(--line);
  min-height:44px; position:relative; overflow:hidden;
  /* `scale` is its own property, so press feedback composes with the magnetic
     cursor's inline `transform` instead of being overwritten by it. */
  transition:color var(--t-hover) var(--ease-out),
             border-color var(--t-hover) var(--ease-out),
             border-radius var(--t-state) var(--ease-out),
             scale var(--t-press) var(--ease-out);
}
.btn > span{position:relative;z-index:1}
.btn::before{
  content:"";position:absolute;inset:0;background:currentColor;opacity:0;
  transform:translateY(101%);
  /* Exit is faster than entrance — a slow retreat reads as lag. */
  transition:transform 190ms var(--ease-out),opacity 190ms var(--ease-out);
}
/* One filled primary per screen. Everything else is quieter than it. */
.btn--solid{background:var(--forest);border-color:var(--forest);color:var(--cream)}
.btn--solid::before{background:var(--gold-deep);opacity:1;transform:translateY(101%)}
.btn--quiet{border-color:transparent;color:var(--ink-70);padding-left:6px;padding-right:6px}
.btn--quiet::before{display:none}
.btn--quiet span{box-shadow:inset 0 -1px 0 rgba(20,32,26,.22)}

.btn--gold{border-color:var(--gold);color:var(--gold-deep)}
.btn--gold::before{background:var(--gold-deep);opacity:1;transform:translateY(101%)}
/* The fill is a hover reveal (see .btn--gold:hover::before further down) —
   on a device with no hover it never appears, so every gold CTA site-wide
   (nav, pricing's "Get a quote", the demo form's submit button) was
   permanently stuck as a thin gold outline with gold-on-cream text: low
   contrast, doesn't read as the thing you're supposed to press. hover:none
   is the correct test for this (not a viewport width — a touch laptop or
   a narrow desktop window isn't the same problem), and shows the fill by
   default with text swapped to read against it, everywhere this class is
   used, in one place. */
@media (hover:none){
  .btn--gold{color:var(--forest)}
  .btn--gold::before{transform:translateY(0)}
}
.btn--ink{border-color:rgba(20,32,26,.22);color:var(--ink)}
.btn--ink::before{background:var(--ink);opacity:1;transform:translateY(101%)}
.btn--light{border-color:rgba(242,241,236,.3);color:var(--cream)}
.btn--light::before{background:var(--cream);opacity:1;transform:translateY(101%)}

/* ---------- reveal primitives (JS adds .is-in) ---------- */

/* No clip mask, no transform on the line wrapper itself — V2 already found
   that exact combination (overflow:hidden mask + an animating translate/
   yPercent on the same overlapping boxes) renders as visibly corrupted,
   torn glyphs on real hardware, not just a theoretical risk. Carrying the
   clip+transform version back into V3 reintroduced the same bug. */
.r-line{display:block}
.r-line > span{display:block}

/* The word-level reveal itself — V2's gold-then-ink colour settle, plus a
   side swipe: clip-path alone, no transform paired with it, so it can't
   reproduce the clip+transform corruption above. clip-path doesn't affect
   layout/offsetTop either, so it's safe to rest words in the fully-clipped
   state before splitLines even measures them. JS staggers each word's
   animation-delay (42ms apart, see initReveals) then flips on .is-revealed
   to start them together. */
[data-split] .w{clip-path:inset(0 100% 0 0)}
[data-split].is-revealed .w{animation:wordGlow .6s var(--ease-out) forwards}
@keyframes wordGlow{
  0%{clip-path:inset(0 100% 0 0);color:var(--gold)}
  55%{clip-path:inset(0 0 0 0);color:var(--gold)}
  100%{clip-path:inset(0 0 0 0);color:inherit}
}
.r-up{opacity:0;transform:translateY(22px)}
.r-fade{opacity:0}

/* Headings stay hidden only until JS has split them into masked lines — the
   loader covers this. `reveals-ready` is set by JS and also by a failsafe in the
   inline head script, so this can never strand text. */
html:not(.reveals-ready) [data-split]{visibility:hidden}
.no-js [data-split]{visibility:visible!important}

/* If JS never runs, nothing should stay invisible. */
.no-js .r-up,.no-js .r-fade{opacity:1;transform:none}
.no-js .scr{opacity:1;visibility:visible;position:relative}
.no-js .stn{opacity:1}
.no-js .stn-ico path,.no-js .stn-ico circle{stroke-dashoffset:0!important}
.no-js .mark::before{transform:scaleX(1);opacity:1}

/* ============================================================
   Loader
   ============================================================ */
#loader{
  position:fixed;inset:0;z-index:200;background:var(--cream);
  display:flex;align-items:center;justify-content:center;
}
/* If sanq.js never loads, the intro must not hold the page hostage. */
.no-js #loader{display:none}
/* The intro is a first-impression device — never replay it on in-site navigation. */
.has-visited #loader{display:none!important}
#loader.is-done{pointer-events:none}
.load-mark{display:flex;align-items:center;gap:14px}
.load-bar{width:26px;height:64px;border-radius:7px;background:var(--forest);transform-origin:50% 100%}
.load-tiles{display:flex;flex-direction:column;gap:3px}
.load-tile{
  width:18px;height:18px;border-radius:4px;display:grid;place-items:center;
  font-family:var(--display);font-weight:700;font-size:10px;color:var(--forest);
}
.load-word{
  font-family:var(--display);font-weight:700;font-size:44px;letter-spacing:-.01em;
  color:var(--forest);overflow:hidden;
}
.load-word span{display:block}

/* ============================================================
   Page transition (curved wipe)
   ============================================================ */
/* Page transitions are a content crossfade, not a cover. Every page shares the
   same cream ground, so nothing needs to be painted over — the content dissolves
   and the next page's content resolves in its place. Primed before first paint
   so the incoming page never flashes in at full opacity first. */
.is-transitioning main,
.is-transitioning .nav,
.is-transitioning footer{opacity:0}

/* ============================================================
   Navigation
   ============================================================ */
/* The nav is a floating pill, always — same shape on every page and at every
   scroll position. Scrolling only firms up its background; it never changes form,
   so you can always jump somewhere else from anywhere on the site. */
.nav{position:fixed;top:0;left:0;right:0;z-index:120;pointer-events:none}
.nav-inner{
  max-width:min(940px,calc(100% - 32px));margin:12px auto 0;
  height:56px;padding:0 8px 0 22px;
  display:flex;align-items:center;justify-content:space-between;gap:18px;
  position:relative;pointer-events:auto;
}
.nav-bg{
  position:absolute;inset:0;z-index:0;pointer-events:none;border-radius:999px;
  background:rgba(242,241,236,.8);
  backdrop-filter:saturate(150%) blur(14px);-webkit-backdrop-filter:saturate(150%) blur(14px);
  border:1px solid rgba(20,32,26,.07);
  box-shadow:0 1px 2px rgba(20,32,26,.03),0 12px 30px -24px rgba(20,32,26,.4);
  transition:background .45s var(--ease),border-color .45s var(--ease),box-shadow .45s var(--ease);
}
.nav.is-compact .nav-bg{
  background:rgba(242,241,236,.9);border-color:rgba(20,32,26,.11);
  box-shadow:0 1px 2px rgba(20,32,26,.04),0 18px 40px -28px rgba(20,32,26,.55);
}

/* The pill floats above the page and is legible either way, so this is a
   belonging cue, not a contrast fix — it matches the room it's passing over
   rather than sitting cream on top of forest. Same dark-section detection the
   progress rail already used. */
.nav.is-dark .nav-bg{
  background:rgba(15,43,34,.72);border-color:rgba(242,241,236,.14);
  box-shadow:0 1px 2px rgba(0,0,0,.1),0 18px 40px -24px rgba(0,0,0,.5);
}
.nav.is-dark .nav-logo img{filter:brightness(0) invert(1)}
.nav.is-dark .nav-link{color:rgba(242,241,236,.62)}
.nav.is-dark .nav-burger i{background:var(--cream)}
.nav.is-dark .btn--quiet,
.nav.is-dark .nav-link.is-active{color:var(--cream)}
.nav-links{gap:clamp(12px,1.8vw,24px)}
.nav-link{font-size:11.5px}
/* Concentric: 56px pill -> r28; 40px button -> r20; the 8px gap is the difference,
   so the two curves stay parallel. */
.nav .btn{padding:11px 20px;min-height:40px;font-size:10px;border-radius:999px}
.nav-cta{background:var(--gold-deep);border-color:var(--gold-deep);color:var(--cream)}
.nav-cta::before{background:var(--ink)}
.nav-inner > *:not(.nav-bg){position:relative;z-index:2}

.nav-logo{display:flex;align-items:center;gap:10px;position:relative;z-index:2;padding:12px 0}
.nav-inner > *:not(.nav-bg){position:relative;z-index:2}
.nav-logo img{height:20px;width:auto}

.nav-links{
  display:flex;align-items:center;gap:clamp(14px,2.4vw,32px);
  position:relative;z-index:2;
}
.nav-link{
  font-family:var(--sans);font-size:12px;font-weight:450;
  letter-spacing:.11em;text-transform:uppercase;color:var(--ink-50);
  position:relative;padding:13px 0;
  transition:color var(--t-hover) var(--ease-out),font-variation-settings var(--t-state) var(--ease-out);
}
.nav-link::after{
  content:"";position:absolute;left:0;right:0;bottom:11px;height:1px;background:var(--gold);
  transform:scaleX(0);transform-origin:right;transition:transform var(--t-state) var(--ease-out);
}
.nav-link.is-active{color:var(--ink)}
.nav-cta{position:relative;z-index:2}

.nav-burger{
  display:none;position:relative;z-index:2;width:42px;height:42px;
  align-items:center;justify-content:center;border-radius:999px;
  transition:background .35s var(--ease);
}
.nav-burger{background:rgba(20,32,26,.05)}
.nav-burger i{display:block;position:absolute;width:22px;height:1.5px;background:var(--ink);transition:transform .4s var(--ease),opacity .3s}
.nav-burger i:nth-child(1){transform:translateY(-4px)}
.nav-burger i:nth-child(2){transform:translateY(4px)}
body.menu-open .nav-burger i{background:var(--cream)}
body.menu-open .nav-burger i:nth-child(1){transform:rotate(45deg)}
body.menu-open .nav-burger i:nth-child(2){transform:rotate(-45deg)}
body.menu-open .nav-logo img{filter:brightness(0) invert(1)}

/* full-screen mobile menu */
.menu{
  position:fixed;inset:0;z-index:110;background:var(--forest);
  display:flex;flex-direction:column;justify-content:center;
  padding:96px var(--gut) 40px;
  clip-path:inset(0 0 100% 0);visibility:hidden;
}
.menu-list{list-style:none;margin:0;padding:0}
.menu-list li{overflow:hidden}
.menu-list a{
  display:block;padding:8px 0;font-family:var(--serif);font-weight:400;
  font-size:clamp(38px,11vw,64px);line-height:1.12;color:var(--cream);
  transition:color .3s var(--ease),transform .5s var(--ease);
}
.menu-foot{margin-top:auto;padding-top:36px;border-top:1px solid rgba(242,241,236,.16);
  display:flex;justify-content:space-between;align-items:center;gap:16px;flex-wrap:wrap}

/* Thin gold scroll-progress line at the very top of every page. Cheap and
   unfussy on mobile, where the side rail below is hidden outright. */
.scroll-progress{
  position:fixed;top:0;left:0;right:0;height:2px;z-index:130;
  background:rgba(20,32,26,.08);pointer-events:none;
}
.scroll-progress i{
  display:block;height:100%;width:100%;background:var(--gold);
  transform-origin:left;transform:scaleX(0);
}
@media (prefers-reduced-motion:reduce){ .scroll-progress i{transition:none} }

/* onepage progress rail */
.rail{
  position:fixed;right:22px;top:50%;transform:translateY(-50%);z-index:100;
  display:flex;flex-direction:column;gap:14px;align-items:flex-end;
}
.rail a{display:flex;align-items:center;gap:10px;height:14px}
.rail i{
  display:block;width:34px;height:1px;background:rgba(20,32,26,.28);
  transform:scaleX(.647);transform-origin:right;  /* 22/34 — grows to full on active */
  transition:transform var(--t-state) var(--ease-out),background var(--t-state) var(--ease-out);
}
.rail span{
  font-family:var(--mono);font-size:9px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-50);opacity:0;transform:translateX(6px);white-space:nowrap;
  transition:opacity var(--t-state) var(--ease-out),transform var(--t-state) var(--ease-out),color var(--t-state) var(--ease-out);
}
.rail a.is-active i{transform:scaleX(1);background:var(--gold)}
.rail a.is-active span{opacity:1;transform:none;color:var(--gold-deep)}
.rail.is-away{opacity:0;pointer-events:none;transform:translateY(-50%) translateX(14px)}
.rail{transition:opacity .45s var(--ease),transform .45s var(--ease)}
.rail.on-dark i{background:rgba(242,241,236,.32)}
.rail.on-dark span{color:rgba(242,241,236,.6)}
.rail.on-dark a.is-active i{background:var(--gold)}
.rail.on-dark a.is-active span{color:var(--gold)}

/* ---------- magnetic cursor ---------- */
.cursor{
  position:fixed;top:0;left:0;z-index:180;pointer-events:none;
  width:9px;height:9px;border-radius:50%;background:var(--gold);
  transform:translate(-50%,-50%);mix-blend-mode:normal;opacity:0;
}
.cursor-ring{
  position:fixed;top:0;left:0;z-index:179;pointer-events:none;
  width:38px;height:38px;border-radius:50%;border:1px solid rgba(192,138,42,.5);
  transform:translate(-50%,-50%);opacity:0;
}
@media (pointer:coarse),(max-width:900px){ .cursor,.cursor-ring{display:none} }

/* ============================================================
   Hero
   ============================================================ */
.hero-head{margin-top:clamp(28px,3.5vw,48px);max-width:56ch}
.hero-actions{display:flex;gap:14px;flex-wrap:wrap;align-items:center;margin-top:clamp(32px,3.6vw,46px)}
.hero-note{margin:clamp(22px,2.4vw,30px) 0 0;font-size:13.5px;color:var(--ink-50)}
.hero-actions .btn{justify-content:center}

.scroll-cue{
  display:flex;align-items:center;gap:12px;margin-top:clamp(30px,4vw,52px);
  font-family:var(--mono);font-size:10px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink-50);
}
.scroll-cue i{display:block;width:46px;height:1px;background:var(--line);position:relative;overflow:hidden}
.scroll-cue i::after{
  content:"";position:absolute;inset:0;background:var(--gold);
  transform:translateX(-100%);animation:cue 2.6s var(--ease) infinite;
}
@keyframes cue{0%{transform:translateX(-100%)}55%{transform:translateX(0)}100%{transform:translateX(100%)}}
@media (prefers-reduced-motion:reduce){ .scroll-cue i::after{animation:none;transform:none;opacity:.5} }

/* ---------- hero ----------
   One layer, not two columns with an edge between them: the film is the
   background for the whole first screen, the argument sits on top of it. A
   hard column split always reads as a seam no matter how well it's clipped —
   removing the boundary removes the seam. An earlier version put the proof
   below the fold entirely, which meant the landing screen was a wall of type
   with nothing to look at; this keeps the motion without the cut line. */
/* `clip` not `hidden`: it contains the glow's bleed without creating a scroll
   container, so it can never break a sticky descendant. .hero is shared by
   about.html and pricing.html too, which use it plainly (content straight in
   a .shell, no .hero-copy) and still need this top padding to clear the
   fixed nav — only index.html's full-bleed-film hero drops it, via
   .hero--film below. Losing that scoping once already sent Pricing's
   heading right up under the nav. */
.hero{padding:calc(72px + clamp(18px,2.4vw,38px)) 0 clamp(48px,6vw,80px);overflow:clip;position:relative}
.hero--film{padding-top:0}

/* About's hero used to hand off to a full-bleed photo section of its own — a
   whole extra stop for one image. It reads better as ambience behind the
   headline: barely-there, cream still doing the work, gone before "Meet the
   people" so it never competes with the founders' actual photographs. */
.hero--photobg{position:relative;padding-bottom:clamp(64px,8vw,96px)}
/* Confirmed live on mobile: this hero has no height logic at all — it's
   just as tall as its text, which on a phone is ~500px against an ~800px
   screen. The next section (Meet the people) was showing in the same
   first screen a visitor lands on, the same "bleeding in" bug as the film
   hero, just more of it because nothing here was even trying to fill the
   viewport. Centered rather than top-padded to fill it: the content here
   is short by design (this hero is meant to be quick), so centering it in
   a full screen reads as intentional, where top-aligned-plus-a-big-gap
   would not. */
@media (max-width:760px){
  .hero--photobg{
    min-height:100svh;min-height:100dvh;
    display:flex;flex-direction:column;justify-content:center;
    padding-bottom:0;
  }
}
/* A soft halo behind headline text that sits over a photo — lifts it off a
   busy background without boxing it in a scrim panel. The cream blur close
   in does the actual contrast work against whatever's behind it, light or
   dark; the faint gold ring further out is the brand signature, kept
   barely-there so it reads as warmth, not as a badge. */
.glow-text{
  text-shadow:
    0 0 1px rgba(242,241,236,.9),
    0 0 22px rgba(242,241,236,.85),
    0 0 56px rgba(192,138,42,.3);
}
.hero-photobg{
  position:absolute;inset:0;z-index:0;
  opacity:.16;filter:saturate(85%);
  -webkit-mask-image:linear-gradient(to bottom,#000,#000 55%,transparent 96%);
          mask-image:linear-gradient(to bottom,#000,#000 55%,transparent 96%);
}
.hero-photobg img{width:100%;height:100%;object-fit:cover;display:block}
.hero--photobg .shell{position:relative;z-index:1}
.hero .h1{font-size:clamp(48px,6.6vw,100px)}
.hero-copy .lede{margin-top:clamp(28px,3vw,40px);max-width:44ch}
/* Sits on top of the film, not beside it. max-width keeps it from ever
   reading as a column even at very wide viewports — it's a block of text
   floating over the scene, not a panel with an edge. min-height (not on
   .hero itself) gives .hero-stage's absolute inset:0 a full first screen to
   fill, while leaving .hero-card free to flow normally right after it. */
.hero-copy{
  position:relative;z-index:2;min-width:0;max-width:640px;min-height:100svh;
  display:flex;flex-direction:column;justify-content:center;
  padding:calc(72px + clamp(18px,2.4vw,38px)) clamp(28px,4vw,56px) 0
          max(var(--gut), calc((100vw - var(--shell)) / 2));
}

/* ---------- the living stage ----------
   The film is the hero's background — but only behind the headline's own
   first screen (height:100svh, not inset:0). inset:0 sized this to match
   .hero's FULL height, headline plus the dashboard card below it combined,
   which did two things wrong at once: bled the film in behind the card
   (which sits lower on the page, past the "hero" a visitor actually sees
   first) and, since it's positioned while the card is plain in-flow content,
   painted on top of the card instead of behind it — a positioned element at
   z-index:0 still paints after ordinary in-flow siblings by spec. Capping
   the height keeps the stage inside the headline's own screen, where both
   problems stop applying. Modelled on how wisprflow.ai and granola.ai carry
   a hero: not a one-shot reveal of static cards, but a loop that never
   stops. */
/* clip-path, not just overflow:hidden: an SVG filter region inside a
   transformed iframe can otherwise paint past overflow:hidden in Chromium. */
.hero-stage{position:absolute;top:0;left:0;right:0;height:100svh;z-index:0;overflow:hidden;clip-path:inset(0)}
/* When the motion graphic is present it takes the stage outright and the coded
   loop is removed. Until then this element is empty and costs nothing. */
.stage-film{display:none;position:absolute;inset:0}
.stage-film svg,.stage-film canvas,.stage-film img{width:100%;height:100%;display:block}
/* Cover-fit in pure CSS, no JS sizing: height locked to the container, width
   left to aspect-ratio at the film's own native canvas (2100:900). On any
   box narrower than that (basically anything except an ultra-wide monitor —
   a full-height mobile screen most of all) that makes the iframe wider than
   its container, centred and clipped by .hero-stage's clip-path. A JS
   version of this (measure the box, set width/height in px, redo it on
   resize) existed first and was dropped: it depends on the box already
   having a stable, correct size at the moment the script runs, which mobile
   Safari doesn't reliably give you (the visual viewport is still settling,
   the address bar is still collapsing) — this recalculates on the browser's
   own layout pass instead, with nothing to race. */
.stage-film iframe{
  position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
  height:100%;width:auto;aspect-ratio:2100/900;
  display:block;border:0;
}
.hero-stage.has-film .stage-film{display:block}
/* The coded fallback keeps its old compact, centred card shape — but stays
   hidden by default now. It used to be visible from the first paint and get
   hidden once has-film landed, which flashed the old ticket-marquee/
   odometer version on every load (the HEAD check in initFilm is async, so
   there's always a gap before has-film can apply). Only initFilm's no-film
   class — set when the film is genuinely missing or fails to load — reveals
   it now, so the default assumption is silence, not a flash of the old
   hero, while the film loads in. */
.stage-fallback{
  display:none;position:relative;height:100%;flex-direction:column;
  justify-content:center;align-items:flex-end;gap:clamp(16px,1.8vw,22px);
  padding:clamp(28px,4vw,56px);
}
.stage-fallback > *{width:100%;max-width:480px}
.hero-stage.no-film .stage-fallback{display:flex}
/* Desktop never sees this — the film covers the stage there. Only the mobile
   media query below turns it on, in place of the film. */
.hero-mobile-photo{display:none}

/* The read-zone: a soft wash from the page's own cream, fading to nothing
   past where the text sits. Not a panel — no edge, no line, just enough
   contrast that the drawing underneath never fights the type. The film's
   own composition already keeps this side clear of world labels (see the
   SAFE zone in sanq-hero-film.jsx), so this is a second, gentle guarantee
   rather than the only thing doing the work. */
.hero-scrim{
  position:absolute;top:0;left:0;right:0;height:100svh;z-index:1;pointer-events:none;
  background:linear-gradient(90deg,var(--cream) 0%,var(--cream) 22%,rgba(242,241,236,.75) 42%,rgba(242,241,236,0) 62%);
}
.stage-glow{
  position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  width:70%;aspect-ratio:1;pointer-events:none;z-index:0;
  background:radial-gradient(circle at 50% 50%,rgba(192,138,42,.14),rgba(192,138,42,0) 62%);
  opacity:0;
}

/* the rail of arriving orders — a seamless marquee, always moving */
.feed{position:relative;z-index:1}
.feed-k{
  display:flex;align-items:center;gap:8px;margin-bottom:10px;
  font-family:var(--mono);font-size:9.5px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink-50);
}
.feed-live{width:6px;height:6px;border-radius:50%;background:var(--gold);animation:pip 2s ease-in-out infinite}
.feed-rail{
  position:relative;overflow:hidden;
  border:1px solid var(--line);border-radius:12px;background:#FBFAF5;
  padding:12px 0;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent);
          mask-image:linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent);
}
.feed-row{display:flex;gap:10px;width:max-content;will-change:transform;animation:feed 26s linear infinite}
@keyframes feed{from{transform:translateX(0)}to{transform:translateX(-50%)}}
.feed-rail:hover .feed-row{animation-play-state:paused}
.tkt{
  display:inline-flex;align-items:center;gap:8px;white-space:nowrap;flex:none;
  font-family:var(--mono);font-size:10px;letter-spacing:.06em;
  color:var(--ink);background:var(--cream);border:1px solid var(--line);
  padding:8px 13px;border-radius:999px;
}
.tkt b{font-weight:400;color:var(--ink-50)}

/* the thing in the middle that nobody has to operate */
.engine{position:relative;z-index:1;display:flex;align-items:center;gap:14px}
.engine-wire{flex:1;height:1px;background:var(--line);position:relative;overflow:hidden}
.engine-wire i{
  position:absolute;top:0;left:0;height:100%;width:38%;display:block;background:var(--gold);
  animation:pulseWire 2.6s cubic-bezier(.45,0,.25,1) infinite;
}
.engine-wire:last-child i{animation-delay:.5s}
@keyframes pulseWire{
  0%{transform:translateX(-110%);opacity:0}
  25%{opacity:1}
  100%{transform:translateX(310%);opacity:0}
}
.engine-mark{
  font-family:var(--serif);font-size:clamp(19px,1.8vw,24px);letter-spacing:.02em;color:var(--ink);
  padding:0 4px;
}

/* what the loop keeps changing */
.reads{position:relative;z-index:1;display:grid;grid-template-columns:1fr 1fr;gap:clamp(10px,1.2vw,16px)}
.read{
  background:#FBFAF5;border:1px solid var(--line);border-radius:14px;
  padding:clamp(15px,1.7vw,20px) clamp(16px,1.8vw,22px);
}
.read:last-child{border-color:rgba(192,138,42,.36)}
.read-k{
  display:block;font-family:var(--mono);font-size:9px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink-50);
}
.read-v{
  display:flex;align-items:baseline;gap:.12em;margin-top:9px;
  font-family:var(--serif);font-weight:400;line-height:1;
  font-size:clamp(32px,3.6vw,50px);letter-spacing:-.02em;color:var(--ink);
}
.read-v small{font-size:.34em;color:var(--ink-50)}

.stage-note{
  position:relative;z-index:1;text-align:center;
  font-family:var(--mono);font-size:9.5px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--ink-50);min-height:1.2em;
}

@media (max-width:960px){
  /* The full-bleed-background treatment (film behind everything, a scrim to
     keep the text legible over it) makes sense when the copy only ever
     covers part of a wide screen. On a tall narrow phone the text column
     runs almost the full width anyway, so "behind everything" meant the
     film was landing directly behind the reading area, not in the empty
     space above it — which is exactly backwards from what a background
     video is for here. Stacked instead: the film gets its own fixed zone
     at the top, edge to edge and cleared of the nav; the copy sits below
     it in normal flow, compact, with neither fighting the other for room
     or needing a scrim, since they no longer overlap at all. */
  /* Second attempt at "fill the screen, no bleed, no dead gap." The first
     used a magic-constant calc() against 100svh — wrong on two counts.
     svh is deliberately the SMALLEST the viewport ever gets (address bar
     fully expanded); the instant that bar collapses on scroll, the real
     viewport grows past it, and whatever was sized to svh falls short —
     the next section peeking in underneath, confirmed on a real phone.
     And a hardcoded "everything else is ~458px" constant can only ever be
     right for one exact font/copy configuration.
     Flexbox instead of arithmetic: the section is a column with a real
     min-height against dvh (the LIVE viewport, tracks the address bar as
     it collapses/expands rather than assuming its most-collapsed state),
     the image is the one flexible item, and it absorbs however much
     space the copy doesn't need — recalculated by the browser's own
     layout, not guessed by us. If dvh isn't supported, svh under-fills
     slightly (a hair short of full-screen) rather than bleeding. */
  .hero--film{
    padding-top:calc(64px + clamp(18px,2.4vw,32px));
    display:flex;flex-direction:column;
    min-height:100svh;min-height:100dvh;
  }
  .hero-stage{
    position:relative;top:auto;left:auto;right:auto;
    height:auto;flex:1 1 auto;min-height:220px;
  }
  .hero-scrim{display:none}
  .hero-copy{
    flex:none;
    min-height:auto;justify-content:flex-start;
    /* Bottom padding here, not left as 0: the actions row and the note
       need real breathing room off the bottom edge, not to sit flush
       against it just because there was room to spare. */
    padding:clamp(14px,3vw,20px) var(--gut) clamp(28px,6vw,48px);max-width:none;
  }
  /* Tightened so the actions row — the one thing on this screen that has to
     be visible without scrolling — actually lands inside the first
     viewport on a normal phone. Verified against 375×812 and 390×844. */
  .hero-copy .h1{margin-top:clamp(10px,2.2vw,16px)!important;font-size:clamp(36px,9vw,56px)}
  .hero-copy .lede{margin-top:clamp(12px,2.2vw,16px)}
  .hero-actions{margin-top:clamp(18px,3vw,26px)}
  .hero-note{margin-top:clamp(12px,2.2vw,16px);font-size:12px}
  .stage-fallback{align-items:center}
  .stage-fallback > *{max-width:420px}
  /* A moving film reads as a background; at this width the copy runs nearly
     edge to edge below it, so a still fits the zone better than a loop
     fighting for the same attention as the text right under it. initFilm
     skips the fetch/iframe below this width (see sanq.js) so nothing loads
     twice. */
  .stage-glow,.stage-film,.stage-fallback{display:none!important}
  .hero-mobile-photo{display:block;width:100%;height:100%}
  .hero-mobile-photo img{width:100%;height:100%;object-fit:cover;display:block}
}
@media (prefers-reduced-motion:reduce){
  /* Movement goes; the information stays. */
  .feed-row{animation:none}
  .engine-wire i{animation:none;opacity:.5;transform:none;width:100%}
  .feed-live{animation:none}
}

/* ---------- photography ---------- */
.shot{
  position:relative;margin:0;border-radius:16px;overflow:hidden;
  background:var(--cream-deep);
}
.shot img{width:100%;height:100%;object-fit:cover;display:block}
.shot--hero{aspect-ratio:16/9}
.hero-shot{margin-top:clamp(40px,6vw,88px)}
/* The card used to ride up over the hero photograph. The hero now leads with the
   proof panel instead, so it sits in normal flow — the old negative margin was
   dragging it straight over the panel. */
.hero-card{margin-top:clamp(56px,8vw,110px)}
@media (min-width:901px){
  .hero-card .app{max-width:1300px;margin:0 auto}
}

/* The dining-room photo used to be its own full-bleed section between Premise
   and the chain — a standalone stop that didn't earn its scroll. It reads
   better as the floor "SANQ makes sense of it" is standing on: barely-there,
   behind the giant serif line rather than a card, same ambient treatment as
   About's hero photo. */
.premise-photobg{position:relative}
.premise-bg{
  position:absolute;inset:0;z-index:0;
  opacity:.14;filter:saturate(85%);
  -webkit-mask-image:linear-gradient(to bottom,transparent,#000 14%,#000 86%,transparent);
          mask-image:linear-gradient(to bottom,transparent,#000 14%,#000 86%,transparent);
}
.premise-bg img{width:100%;height:100%;object-fit:cover;display:block}
.premise-photobg .arc-in{position:relative;z-index:1}

/* a full-bleed photographic band between chapters */
.band{margin:clamp(56px,8vw,110px) auto}
.band .shot{border-radius:16px}
.band figcaption{text-align:right}
@media (max-width:700px){ .band .shot{aspect-ratio:3/2!important} }

/* progressive blur — layers injected by initProgressiveBlur(), each masked
   lower and blurred more than the last so the photo feathers into the page
   rather than stopping on a hard edge or a flat gradient scrim. */
.pblur{position:absolute;inset:0;pointer-events:none;z-index:1}

/* a photograph inside a product-tour step — full width of the text column, so the
   photography carries as much weight as the copy beside it */
.tour-shot{margin-top:clamp(28px,3.4vw,46px);max-width:none}
.tour-shot .shot{aspect-ratio:3/2}
.tour-shot--square .shot{aspect-ratio:4/3}
.shot figcaption,.tour-shot figcaption,.band figcaption{
  margin-top:10px;font-family:var(--mono);font-size:10px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-50);
}

/* Before/after split slider — one number, dragged apart by hand. Both sides use
   the same card language on purpose: four items, same rows, either side —
   only the numbers (and now the status) resolve from a guess to a fact.
   "Before" is a CSS-drawn tally sheet; "after" matches its proportions
   exactly, just resolved. */
.compare{
  position:relative;min-height:340px;border-radius:16px;overflow:hidden;
  background:var(--cream-deep);cursor:ew-resize;touch-action:pan-y;user-select:none;
}
.compare:focus-visible{outline:2px solid var(--gold);outline-offset:3px}
.compare-after,.compare-before{position:absolute;inset:0}
.compare-before{
  clip-path:inset(0 0% 0 0);will-change:clip-path;
  background:#F1EFE6;
}

/* the "after" side — the same tally-sheet shape, resolved rather than
   guessed: real status, real numbers, no scrawl. */
.ctruth-list{
  height:100%;padding:26px 22px 46px;background:var(--ui-bg);
  display:flex;flex-direction:column;gap:13px;
}
.ctruth-row{
  display:flex;align-items:baseline;gap:12px;
  padding-bottom:9px;border-bottom:1px dashed rgba(20,32,26,.12);
  font-family:var(--sans);font-size:13.5px;
}
.ctruth-name{font-weight:600;color:var(--ui-ink)}
.ctruth-status{
  font-family:var(--mono);font-size:10px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--ui-muted);
}
.ctruth-status.is-crit{color:var(--gold-deep)}
.ctruth-status.is-out{color:var(--ui-muted);font-style:italic}
.ctruth-qty{margin-left:auto;color:var(--ui-ink);font-variant-numeric:tabular-nums}
.ctruth-qty small{color:var(--ui-muted);font-size:.85em;margin-left:.15em}

.compare-tag{
  position:absolute;bottom:14px;z-index:2;
  font-family:var(--mono);font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;
  padding:7px 11px;border-radius:999px;white-space:nowrap;
}
/* .compare-after fills the whole container (the "before" panel sits on top of
   it, clipped), so its tag has to live on the right — at left:14px it was
   sitting directly under the before-panel's tag and the two texts overlapped
   into an unreadable mash whenever the handle was anywhere near the middle. */
.compare-tag--after{right:14px;background:var(--gold-deep);color:var(--cream);box-shadow:0 4px 12px -4px rgba(20,32,26,.4)}
.compare-tag--before{left:14px;background:rgba(20,32,26,.08);color:var(--ink-50)}

/* the tally sheet */
.clipboard{
  position:absolute;inset:0;padding:26px 22px 22px;
  display:flex;flex-direction:column;gap:13px;
}
.clipboard-clip{
  position:absolute;top:0;left:50%;transform:translateX(-50%);
  width:52px;height:16px;border-radius:0 0 6px 6px;
  background:rgba(20,32,26,.14);
}
.clipboard-line{
  display:flex;justify-content:space-between;align-items:baseline;gap:12px;
  padding-bottom:9px;border-bottom:1px dashed rgba(20,32,26,.18);
  font-family:var(--sans);font-size:13.5px;color:var(--ink-70);
}
.clipboard-line span{font-weight:600;color:var(--ink)}
.clipboard-line i{font-style:italic;color:var(--ink-50)}
.clipboard-line--x span,.clipboard-line--x i{
  text-decoration:line-through;text-decoration-color:rgba(20,32,26,.35);color:var(--ink-50)
}
.clipboard-scrawl{
  margin-top:auto;height:26px;
  background-image:linear-gradient(90deg, rgba(20,32,26,.16) 0 60%, transparent 60%);
  background-size:14px 2px;background-repeat:repeat-x;background-position:0 8px, 0 18px;
  opacity:.6;
}

/* the handle */
.compare-handle{
  position:absolute;top:0;bottom:0;left:50%;transform:translateX(-50%);
  z-index:3;display:flex;align-items:center;justify-content:center;
  pointer-events:none;will-change:left;
}
.compare-handle i{
  position:absolute;top:0;bottom:0;left:50%;width:2px;transform:translateX(-50%);
  background:var(--cream);box-shadow:0 0 0 1px rgba(20,32,26,.12);
}
.compare-handle svg{
  position:relative;width:34px;height:34px;padding:7px;border-radius:50%;
  background:var(--cream);color:var(--ink);box-shadow:0 6px 16px -6px rgba(20,32,26,.4);
}

/* gradient wave text */
.wave{
  background-image:linear-gradient(100deg,var(--ink) 0%,var(--ink) 34%,var(--gold) 50%,var(--ink) 66%,var(--ink) 100%);
  background-size:280% 100%;background-position:130% 0;
  -webkit-background-clip:text;background-clip:text;color:transparent;
  -webkit-text-fill-color:transparent;
}

/* ============================================================
   Product card chrome (marketing-side "app window")
   ============================================================ */
.app{
  background:var(--ui-surface);border:1px solid var(--ui-line);border-radius:18px;
  box-shadow:0 1px 1px rgba(20,32,26,.03),0 48px 90px -48px rgba(20,32,26,.28);
  font-family:var(--sans);overflow:hidden;color:var(--ui-ink);
}
.app-bar{display:flex;align-items:center;justify-content:space-between;gap:16px;
  padding:16px clamp(18px,3vw,36px);border-bottom:1px solid var(--ui-line)}
.app-brand{display:flex;align-items:center;gap:10px;font-weight:700;font-size:13px;letter-spacing:.06em}
.app-brand i{width:9px;height:20px;border-radius:3px;background:var(--forest);display:block}
.app-meta{font-family:var(--mono);font-size:11px;letter-spacing:.08em;color:var(--ui-muted)}
.app-body{padding:clamp(24px,3.2vw,44px) clamp(18px,3vw,36px)}
.ui-cap{font-size:11px;font-weight:600;letter-spacing:.15em;text-transform:uppercase;color:var(--ui-muted)}
.stat-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(128px,1fr));gap:clamp(14px,2vw,26px);margin-top:clamp(24px,3vw,40px)}
.stat-v{font-family:var(--mono);font-variant-numeric:tabular-nums;font-size:clamp(22px,2.4vw,34px);line-height:1.1}
.stat-l{margin-top:7px;font-size:11px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--ui-muted)}

.alerts{border-top:1px solid var(--ui-line);padding:clamp(18px,2.4vw,28px) clamp(18px,3vw,36px) clamp(24px,3vw,36px)}
.alert-box{margin-top:16px;border:1px solid var(--ui-line);border-radius:12px;overflow:hidden}
.alert-lead{padding:18px 20px;background:#FBFBFA}
.alert-row{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:15px 20px;border-top:1px solid var(--ui-line)}
.alert-name{font-weight:600;font-size:15px}
.alert-sub{font-size:13px;color:var(--ui-muted)}
.dot-pulse{width:6px;height:6px;border-radius:50%;background:var(--gold);flex:none}
.disc{overflow:hidden}
.disc-in{padding-top:16px;margin-top:16px;border-top:1px solid var(--ui-line)}
.disc-k{font-size:11px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--ui-muted)}
.disc-v{margin-top:7px;font-size:15px;line-height:1.6;color:#3C4540}

/* ============================================================
   Premise — arc scroll transition
   ============================================================ */
.arc{position:relative;background:var(--cream-deep);margin-top:-1px;
  border-radius:50% 50% 0 0/ 90px 90px 0 0;overflow:hidden;will-change:border-radius}
.arc-in{padding:clamp(96px,14vw,220px) 0 clamp(96px,14vw,200px)}
.strike-list{margin-top:clamp(40px,6vw,88px);border-top:1px solid var(--line)}
.strike{padding:20px 0;border-bottom:1px solid var(--line);font-size:clamp(15px,1.4vw,17px);color:var(--ink-50)}
.strike-t{position:relative;display:inline-block}
.strike-line{position:absolute;left:-.06em;right:-.06em;top:52%;height:1px;background:var(--ink-50);transform:scaleX(0);transform-origin:left}
.strike-key{padding:26px 0;border-bottom:1px solid var(--line);font-family:var(--serif);font-size:clamp(24px,2.6vw,36px);line-height:1.2;color:var(--ink)}

/* ============================================================
   The chain — one pinned stage

   One unbroken thread, not a network. Seven stations on a single line, labels
   alternating above and below it, so nothing crosses and nothing collides.
   ============================================================ */
.chain{
  background:var(--forest);color:var(--cream);position:relative;
  /* The section is tall; the stage sticks inside it. Native sticky rather than a
     scripted pin — a pin has to measure the page, and that measurement goes stale
     the moment fonts or images shift the layout, which showed up as a ~280px jump
     at both ends of the section. Sticky is laid out by the browser and cannot
     drift. Height sets how long the eight beats take to play. */
  height:460svh;
}
.chain-stage{
  position:sticky;top:0;
  /* svh, then dvh: scrolling to/through this section is exactly what
     collapses Safari's address bar, so the real viewport grows past svh
     mid-scroll — a sticky box still pinned at the smaller svh height then
     shows plain forest-green empty space below it for the rest of the
     real (dvh) screen, not because there's too little content but because
     the box stopped an actual viewport-height short of the real one. */
  height:100svh;height:100dvh;min-height:600px;display:flex;flex-direction:column;
  padding:clamp(84px,11vh,120px) var(--gut) clamp(26px,4vh,44px);
  background:var(--forest);
  overflow:hidden;
}
@media (max-width:860px){ .chain{height:380svh} }
@media (prefers-reduced-motion:reduce){ .chain{height:auto} .chain-stage{position:static;height:auto;min-height:0;padding-bottom:clamp(48px,8vh,80px)} }
.chain-top{display:flex;justify-content:space-between;align-items:baseline;gap:20px;flex-wrap:wrap;flex:none}
.chain-title{font-family:var(--serif);font-weight:400;font-size:clamp(28px,3.6vw,52px);line-height:1.02;color:var(--cream);margin:0;letter-spacing:-.015em}
.chain-count{font-family:var(--mono);font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:rgba(242,241,236,.4)}

/* ---- the line ---- */
.thread{
  position:relative;flex:0 0 auto;display:flex;align-items:center;
  height:min(400px,46vh);
  margin:clamp(34px,7vh,88px) 0 auto;   /* auto pins the caption to the floor */
}
.thread-rail{position:absolute;left:0;right:0;top:50%;height:1px;background:rgba(242,241,236,.16)}
.thread-fill{
  position:absolute;left:0;top:50%;height:1px;background:var(--gold);
  width:0;transform-origin:left center;opacity:0;
  transform:scaleX(0);
  transition:transform 1.05s cubic-bezier(.16,1,.3,1),opacity .4s var(--ease);
  box-shadow:0 0 12px 1px rgba(192,138,42,.45);
}
.thread-fill.has-length{opacity:1}
.thread-stations{position:relative;width:100%;display:flex;justify-content:space-between;align-items:center}

.stn{position:relative;flex:1;display:flex;flex-direction:column;align-items:center;min-width:0}
.stn-dot{
  width:9px;height:9px;border-radius:50%;flex:none;
  border:1px solid rgba(242,241,236,.35);background:var(--forest);
  transition:background .45s var(--ease-out),border-color .45s var(--ease-out),box-shadow .45s var(--ease-out);
  position:relative;z-index:2;
}
.stn.is-lit .stn-dot{background:var(--gold);border-color:var(--gold);box-shadow:0 0 0 4px rgba(192,138,42,.18)}
.stn.is-now .stn-dot{box-shadow:0 0 0 6px rgba(192,138,42,.22),0 0 20px 4px rgba(192,138,42,.45)}

/* label + value hang off the dot, alternating sides */
.stn-card{
  position:absolute;left:50%;transform:translateX(-50%);
  width:clamp(112px,13vw,178px);text-align:center;
  display:flex;flex-direction:column;align-items:center;gap:12px;
  opacity:.32;transition:opacity .5s var(--ease),transform .5s var(--ease);
}
.stn-txt{display:flex;flex-direction:column;gap:7px}
/* Cards below the line mirror the ones above it, so the value always sits
   closest to the thread and the icon always sits furthest from it. */
.stn:nth-child(even) .stn-card{flex-direction:column-reverse}
.stn:nth-child(even) .stn-txt{flex-direction:column-reverse}
.stn.is-lit .stn-card{opacity:1}
.stn:nth-child(odd) .stn-card{bottom:calc(50% + 30px)}
.stn:nth-child(even) .stn-card{top:calc(50% + 30px)}
.stn-ico{
  display:block;margin:0;width:clamp(58px,6.2vw,84px);height:auto;aspect-ratio:64/58;flex:none;
  color:rgba(242,241,236,.42);
  transition:color .45s var(--ease),transform .55s var(--ease);
}
.stn-ico path,.stn-ico circle{
  transition:stroke-dashoffset .9s cubic-bezier(.16,1,.3,1) .05s;
}
.stn.is-lit .stn-ico{color:var(--gold)}
.stn.is-lit .stn-ico path,.stn.is-lit .stn-ico circle{stroke-dashoffset:0!important}
.stn.is-now .stn-ico{transform:scale(1.08)}

.stn-k{
  font-family:var(--mono);font-size:9.5px;letter-spacing:.18em;text-transform:uppercase;
  color:rgba(242,241,236,.5);display:block;transition:color .45s var(--ease);
}
.stn.is-lit .stn-k{color:var(--gold)}
.stn-v{
  display:block;font-family:var(--serif);font-weight:400;
  font-size:clamp(15px,1.5vw,22px);line-height:1.16;letter-spacing:-.01em;color:var(--cream);
  opacity:0;transform:translateY(6px);
  transition:opacity .55s var(--ease) .1s,transform .55s var(--ease) .1s;
}
.stn.is-lit .stn-v{opacity:1;transform:none}
/* a hairline tying each card to its dot */
.stn-card::after{
  content:"";position:absolute;left:50%;width:1px;height:16px;
  background:rgba(242,241,236,.2);transition:background .45s var(--ease);
}
.stn.is-lit .stn-card::after{background:rgba(192,138,42,.55)}
.stn:nth-child(odd) .stn-card::after{top:100%}
.stn:nth-child(even) .stn-card::after{bottom:100%}

/* ---- caption + beat bars ---- */
.chain-cap{flex:none;min-height:5.6em;position:relative;max-width:60ch;padding-bottom:4px}
.cap{position:absolute;inset:0;opacity:0}
.cap b{display:block;font-family:var(--serif);font-weight:400;font-size:clamp(22px,2.7vw,36px);line-height:1.14;color:var(--cream)}
.cap i{display:block;margin-top:10px;font-style:normal;font-size:clamp(13px,1.1vw,15px);line-height:1.55;color:rgba(242,241,236,.58)}


/* ---- narrow: the same thread, stood on end ---- */
@media (max-width:860px){
  .chain-stage{padding-top:92px}
  /* One spine: the rail, the dots and the fill all share --spine so they can't drift. */
  /* flex:1 1 auto used to force .thread to fill 100% of whatever room was
     left in the sticky column, and .thread-stations matched it with
     height:100% + justify-content:space-between — so with only 7 stations,
     every extra pixel of leftover height went into stretching each row
     taller (each .stn was itself flex:1), leaving the icon+text floating
     in the middle of a very tall, mostly empty row. Centering the whole
     stack instead, with a fixed comfortable gap, means the 7 rows read as
     one cluster with intentional spacing — however much room is actually
     available, not divided seven ways into dead air. */
  /* Bigger top/bottom margin than before: the station cluster was sitting
     right up against "One service · eight beats" above it and the caption
     ("One thing needs a human", etc.) below it. This margin, plus the
     smaller icons/gap further down, buys a clear gap on both ends so the
     timeline reads as its own band, not crowding the text it sits between. */
  .thread{--spine:26px;display:flex;align-items:center;flex:1 1 auto;height:auto;min-height:0;margin:clamp(26px,4.5vh,44px) 0}
  .thread-rail{left:var(--spine);right:auto;top:0;bottom:0;width:1px;height:auto}
  .thread-fill{left:var(--spine);top:0;width:1px;height:0;
    transform:scaleY(0);transform-origin:center top;
    transition:transform 1.05s cubic-bezier(.16,1,.3,1),opacity .4s var(--ease)}
  .thread-stations{flex-direction:column;align-items:stretch;height:auto;justify-content:center;gap:clamp(10px,2vh,18px)}
  .stn{flex:none;flex-direction:row;align-items:center;justify-content:flex-start;padding-left:64px;position:relative}
  .stn-dot{position:absolute;left:calc(var(--spine) - 4px);transform:none}
  .stn-card,
  .stn:nth-child(odd) .stn-card,
  .stn:nth-child(even) .stn-card{
    position:static;transform:none;width:auto;text-align:left;top:auto;bottom:auto;
    display:flex;flex-direction:row;align-items:center;gap:18px;
  }
  .stn-txt,
  .stn:nth-child(even) .stn-txt{flex-direction:column;gap:4px}
  .stn-card::after{display:none}
  /* Was 44px: eight rows spread across the full sticky screen (justify-
     content:space-between on .thread-stations) left real gaps of plain
     forest-green between them, and a 44px icon read as lost in that much
     room. Bigger icon, bigger value — same layout, less of it reading as
     empty. */
  .stn-ico{margin:0;width:50px;height:auto;aspect-ratio:64/58;flex:none;color:rgba(242,241,236,.5)}
  .stn-k{font-size:12px}
  .stn-v{font-size:18px}
}

/* ============================================================
   Sticky phone product tour
   ============================================================ */
.tour{padding:clamp(80px,10vw,140px) 0}
.tour-grid{display:grid;grid-template-columns:1fr minmax(300px,380px);gap:clamp(32px,6vw,96px);align-items:start}
.tour-stick{position:sticky;top:calc(50svh - 330px);justify-self:center}
.tour-steps{display:flex;flex-direction:column}
.tour-step{min-height:82svh;display:flex;flex-direction:column;justify-content:center;padding:40px 0}
.tour-step h2{margin:16px 0 0;font-family:var(--serif);font-weight:400;font-size:clamp(34px,4.4vw,68px);line-height:.98;letter-spacing:-.02em}
.tour-step p{margin:22px 0 0;max-width:46ch;font-size:clamp(15px,1.2vw,18px);line-height:1.65;color:var(--ink-70)}
.tour-facts{margin:28px 0 0;padding:0;list-style:none;border-top:1px solid var(--line)}
.tour-facts li{display:flex;justify-content:space-between;gap:16px;padding:13px 0;border-bottom:1px solid var(--line);font-size:14px;color:var(--ink-70)}
.tour-facts b{font-family:var(--mono);font-weight:400;font-variant-numeric:tabular-nums;color:var(--ink)}

/* per-step phone, mobile only */
.tour-phone-m{display:none;margin-top:clamp(24px,5vw,34px)}
.phone-fit{width:330px;margin:0 auto;transform-origin:top center}
@media (max-width:374px){ .phone-fit{transform:scale(.84);margin-bottom:-106px} }

/* phone */
.phone{
  width:330px;height:660px;border-radius:44px;padding:10px;
  background:#141719;box-shadow:0 2px 6px rgba(20,32,26,.18),0 60px 90px -50px rgba(20,32,26,.5);
  position:relative;flex:none;
}
.phone-screen{
  width:100%;height:100%;border-radius:35px;overflow:hidden;position:relative;
  background:var(--ui-bg);color:var(--ui-ink);font-family:var(--sans);
}
.phone-notch{position:absolute;top:16px;left:50%;transform:translateX(-50%);width:96px;height:26px;border-radius:14px;background:#141719;z-index:9}
.scr{
  position:absolute;inset:0;display:flex;flex-direction:column;
  opacity:0;visibility:hidden;filter:blur(5px);
  transition:opacity 460ms var(--ease-out),filter 460ms var(--ease-out),visibility 460ms;
}
.scr.is-on{opacity:1;visibility:visible;filter:blur(0)}
.scr-status{flex:none;display:flex;justify-content:space-between;align-items:center;padding:14px 22px 4px;font-size:12px;font-weight:600}
.scr-body{flex:1;overflow:hidden;padding:10px 16px 0}
.scr-h{font-family:var(--display);font-weight:700;font-size:23px;letter-spacing:-.01em;margin:6px 0 2px}
.scr-sub{font-size:12px;color:var(--ui-muted)}
.scr-tabs{flex:none;display:flex;background:var(--ui-surface);border-top:1px solid var(--ui-line);padding:8px 6px 18px}
.scr-tab{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;font-size:10px;color:var(--ui-muted);padding:6px 0}
.scr-tab.on{color:var(--ui-ink);font-weight:600}
.scr-tab svg{width:19px;height:19px;stroke:currentColor;fill:none;stroke-width:1.7}

.card{background:var(--ui-surface);border-radius:14px;padding:13px 14px;margin-top:9px}
.card-flat{background:rgba(255,255,255,.6)}
.kv{display:flex;justify-content:space-between;align-items:center;gap:10px}
.pill{font-size:9px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;padding:4px 8px;border-radius:99px}
.pill--ok{background:rgba(139,169,124,.18);color:#436138}
.pill--warn{background:rgba(217,182,104,.22);color:#7D5F13}
.pill--bad{background:rgba(194,69,61,.12);color:var(--ui-danger)}
.mini{font-size:10px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--ui-muted)}
.num{font-family:var(--display);font-weight:700;font-variant-numeric:tabular-nums;font-size:18px}
.emoji{width:34px;height:34px;border-radius:10px;background:var(--ui-bg);display:grid;place-items:center;font-size:17px;flex:none}
.attn{display:flex;align-items:center;gap:11px;background:var(--ui-surface);border-radius:14px;padding:12px;margin-top:8px;border-left:3px solid transparent}
.attn--warn{border-left-color:var(--gold)}
.attn--bad{border-left-color:var(--ui-danger)}
.attn--info{border-left-color:#5B7FD4}
.attn b{display:block;font-size:13px;font-weight:600;line-height:1.3}
.attn s{display:block;text-decoration:none;font-size:11.5px;margin-top:2px;color:var(--ui-muted)}
.attn--warn s{color:var(--gold-deep)}
.attn--bad s{color:var(--ui-danger)}

/* odometer */
.od{display:inline-flex;align-items:baseline;font-variant-numeric:tabular-nums}
.od-d{display:inline-block;height:1em;overflow:hidden;vertical-align:baseline}
.od-col{display:block;will-change:transform}
.od-col span{display:block;height:1em;line-height:1}

/* ============================================================
   System — the constellation

   Was hard to read: hairline spokes, dim labels, everything the same weight.
   Now it has structure — orbit rings behind, a weighted core, spokes that carry
   a travelling pulse inward, and labels that sit on their own ground.
   ============================================================ */
.system{background:var(--forest);color:var(--cream);padding:clamp(90px,12vw,170px) 0;position:relative;overflow:hidden}
.constel{position:relative;margin-top:clamp(48px,6vw,88px);aspect-ratio:16/9;max-height:660px}
/* Mobile: a compact square orbit around SANQ (data-nx/data-ny), rings and
   pulses drawn circular here instead of on the 16:9 ellipse. The section
   fills a full screen (dvh, so the next section can't peek in under it as
   the address bar collapses) with the content centred, and the orbit is
   allowed to grow into the space that leaves rather than staying small in
   the middle of a lot of empty forest green. */
@media (max-width:900px){
  .system{
    min-height:100svh;min-height:100dvh;
    display:flex;flex-direction:column;justify-content:center;
    padding:clamp(72px,14vh,120px) 0;
  }
  .constel{
    aspect-ratio:1/1;max-height:none;max-width:none;width:auto;
    /* Break out of .shell's ~20px side padding — boxed inside it the orbit
       only got ~317px on a 390 screen with a lot of dead space around it.
       Equal negative margins so it grows symmetrically past its parent to
       min(440px, 94vw); the pair being equal is what keeps it centred no
       matter where the parent sits. */
    margin-left:calc((100% - min(440px,94vw)) / 2);
    margin-right:calc((100% - min(440px,94vw)) / 2);
    margin-top:clamp(32px,6vw,52px);
  }
}

.constel svg{position:absolute;inset:0;width:100%;height:100%;overflow:visible}
.constel svg .ring{fill:none;stroke:rgba(242,241,236,.09);stroke-width:1}
.constel svg .spoke{fill:none;stroke:rgba(242,241,236,.28);stroke-width:1}
.constel svg .pulse{fill:none;stroke:var(--gold);stroke-width:1.6;stroke-linecap:round}

/* No transform here — see the GSAP note in the README. GSAP animates these for
   the reveal; when it finds a CSS `translate(-50%,-50%)` it cannot fully
   decompose, it keeps that string as a literal prefix and appends its own
   translate on top, double-centring the node. Centring is applied by JS instead
   (via xPercent/yPercent when GSAP is present), with the rule below as the
   only-if-no-JS fallback. */
.cn{position:absolute;text-align:center;width:clamp(112px,13.5vw,168px)}
.no-js .cn{transform:translate(-50%,-50%)}
.cn-k{
  font-family:var(--mono);font-size:10px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--gold);display:block;
}
.cn-v{margin-top:6px;font-size:clamp(11.5px,1vw,13.5px);line-height:1.4;color:rgba(242,241,236,.62)}
/* a node sits on its own patch of ground so a spoke never runs through its type */
.cn::before{
  content:"";position:absolute;inset:-12px -10px;border-radius:12px;
  background:var(--forest);z-index:-1;
}
.cn-dot{
  display:block;width:5px;height:5px;border-radius:50%;background:rgba(242,241,236,.5);
  margin:0 auto 9px;
}

.cn--core{width:clamp(150px,19vw,220px)}
.cn--core::before{
  inset:-26px -20px;border-radius:999px;
  background:radial-gradient(ellipse at center,rgba(192,138,42,.18),rgba(15,43,34,.96) 62%,var(--forest) 100%);
}
.cn--core .cn-dot{display:none}
.cn--core .cn-k{
  font-family:var(--serif);font-size:clamp(30px,3.6vw,50px);letter-spacing:-.01em;
  text-transform:none;color:var(--cream);line-height:1;
}
.cn--core .cn-v{margin-top:8px;color:var(--gold);font-family:var(--mono);font-size:10px;
  letter-spacing:.16em;text-transform:uppercase}

/* Compact-circle reframe of the SAME radial diagram for phones — not a
   different layout. The eight sources keep data-nx/data-ny positions, now
   evenly on one orbit around SANQ; the JS still draws spokes and the gold
   inward pulses converging on the centre (circular here — see build()).
   Each node's description line drops so only the name is left: that's what
   keeps the labels off each other at this size. The gold pulse is thicker
   and glows so it actually reads on a small dark diagram. Placed after the
   base .cn / .cn--core rules so it wins on source order at equal
   specificity — the ordering trap that's bitten twice this session. */
@media (max-width:900px){
  .cn{width:auto;max-width:38vw}
  .cn-v{display:none}
  .cn-k{font-size:9px;letter-spacing:.1em;white-space:nowrap}
  .cn::before{inset:-6px -8px;border-radius:8px}
  .cn-dot{margin:0 auto 5px}
  .cn--core{width:auto;max-width:44vw}
  .cn--core .cn-k{font-size:clamp(22px,6.5vw,30px)}
  .cn--core .cn-v{display:block;font-size:8.5px;margin-top:5px}
  .cn--core::before{inset:-16px -14px}
  .constel svg .spoke{stroke:rgba(242,241,236,.22)}
  .constel svg .pulse{stroke-width:3;filter:drop-shadow(0 0 4px rgba(192,138,42,.75))}
}

/* ============================================================
   Intelligence — the dashboard card, moved here from the hero
   ============================================================ */
/* Used to be three separate abstract .stack-card summaries, sticky-stacked
   on scroll. The real dashboard card said the same thing (three alerts,
   "what needs your attention") right after the hero already — one card
   making the point once, here, replaces both. */
.stack{padding:clamp(80px,10vw,130px) 0 0}
#intelligence .hero-card{margin-top:clamp(40px,6vw,72px)}

/* highlight marker
   Used to be a ::before sized with position:absolute + inset() to the
   .mark element's own box. That only ever measured right on a phrase short
   enough to stay on one line: display:inline elements that wrap don't have
   one box, they have one fragment per line, and an absolutely-positioned
   child sizes itself to the OVERALL bounding rect of every fragment
   combined — full width, top of the first line to bottom of the last. On a
   phone, where these phrases wrap two or three lines, that painted one big
   rectangle over blank space and unrelated lines instead of following the
   actual text — confirmed live: a 3-line phrase produced a highlight box
   spanning all three lines at full measure width, not the text's own
   shape. Fixed with the standard multi-line-safe technique instead: the
   highlight is the element's own background, and box-decoration-break:
   clone makes each wrapped line generate and size its own copy of that
   background independently, matching its own text width. */
.mark{
  position:relative;padding:0 .08em;
  background-image:linear-gradient(rgba(192,138,42,.32),rgba(192,138,42,.32));
  background-repeat:no-repeat;background-position:0 0;background-size:0 100%;
  -webkit-box-decoration-break:clone;box-decoration-break:clone;
  transition:background-size .75s var(--ease);
}
.mark.is-marked{background-size:100% 100%}

/* ============================================================
   Audience / close / footer
   ============================================================ */
.who{padding:clamp(80px,10vw,140px) 0;border-top:1px solid var(--line)}
.who-grid{
  margin-top:clamp(40px,5vw,72px);display:grid;
  grid-template-columns:repeat(auto-fit,minmax(260px,1fr));
  gap:clamp(14px,1.6vw,22px);
}
.who-cell{
  position:relative;display:flex;flex-direction:column;
  padding:clamp(26px,3vw,40px) clamp(22px,2.4vw,34px) clamp(30px,3.4vw,44px);
  background:#FBFAF5;border:1px solid var(--line);border-radius:14px;
  box-shadow:0 1px 2px rgba(20,32,26,.03);
  transition:transform var(--t-state) var(--ease-out),
             box-shadow var(--t-state) var(--ease-out),
             border-color var(--t-state) var(--ease-out);
}
.who-cell::before{
  content:"";position:absolute;left:clamp(22px,2.4vw,34px);top:0;width:26px;height:2px;
  background:var(--gold);transform:scaleX(0);transform-origin:left;
  transition:transform 420ms var(--ease-out);
}
.who-cell.is-in::before{transform:scaleX(1)}
.who-cell h3{
  margin:14px 0 0;font-family:var(--serif);font-weight:400;
  font-size:clamp(22px,2.2vw,30px);line-height:1.14;
}
.who-cell p{margin:12px 0 0;font-size:14.5px;line-height:1.6;color:var(--ink-70)}
.who-num{
  margin-top:auto;padding-top:clamp(22px,2.6vw,34px);
  font-family:var(--mono);font-size:10px;letter-spacing:.2em;color:rgba(20,32,26,.28);
}

.close{padding:clamp(100px,14vw,200px) 0;background:var(--forest);color:var(--cream);text-align:left}
.close .h2{color:var(--cream)}
/* On a narrow phone, 14vw of padding is a sliver — the section this most
   needs to feel like a deliberate finale (site's last word before a plain
   copyright footer) was actually the most compressed version of itself,
   proportionally, of any section on the page. Sized off viewport HEIGHT
   instead so it holds its ceremony regardless of how narrow the phone is,
   and the heading gets its own, larger floor — the closing line earns
   being the biggest text on the page, not tied to the same 42px floor as
   every mid-page heading. */
@media (max-width:760px){
  /* Slightly less padding than the first pass, redirected into the one
     thing that should actually own this much real estate: the CTA. A
     closing section that's mostly padding around normal-sized text still
     reads as empty even when it technically fills the screen — "Book a
     demo" is the whole point of the site, so it gets to be the biggest,
     most obviously-pressable thing on it, not a button the same size as
     everywhere else. */
  .close{padding:clamp(72px,15vh,120px) 0}
  .close .h2{font-size:clamp(50px,12vw,72px)}
  /* stretch, not center: the two buttons then share a height whatever
     their label length, so the pair reads as one uniform unit. */
  .close .hero-actions{margin-top:clamp(36px,6vw,56px);gap:14px;align-items:stretch}
  /* Both buttons share the sizing so the pair reads as uniform; only the
     gold one gets the glow that marks it as the primary action. */
  .close .hero-actions .btn{
    min-height:60px;padding:18px 24px;font-size:13px;letter-spacing:.14em;
  }
  .close .hero-actions .btn--gold{
    box-shadow:0 12px 32px -8px rgba(192,138,42,.55);
  }
}

.foot{background:var(--forest-deep);color:rgba(242,241,236,.65);padding:44px 0 40px;font-size:13px}
.foot-in{display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap;align-items:center}
.foot a{color:rgba(242,241,236,.65);transition:color .3s}

/* directional list hover */
.dlist{list-style:none;margin:0;padding:0;border-top:1px solid var(--line)}
.dlist li{border-bottom:1px solid var(--line);position:relative;overflow:hidden}
.dlist a{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:clamp(18px,2.4vw,30px) 0;position:relative;z-index:1;
  transition:color var(--t-hover) var(--ease-out),transform var(--t-state) var(--ease-out)}
.dlist a > *{transition:transform var(--t-state) var(--ease-out)}
/* No transform in this rule, ever — GSAP's yPercent treats a CSS-authored
   percentage transform as a fixed baseline and adds its own offset on top of it
   rather than replacing it, so the fill got permanently stuck ~100px down no
   matter what yPercent was requested. Hidden by `visibility` instead, which
   doesn't touch the transform GSAP owns. This is also the safe fallback for
   touch, reduced-motion and no-JS/no-GSAP, none of which run the script below
   that reveals it — without this, the fill would sit visible at rest and the
   text would read light-on-dark or worse, dark-on-dark. */
.dlist .dfill{position:absolute;inset:0;background:var(--ink);z-index:0;visibility:hidden}
/* The text must turn light BEFORE the dark fill reaches it, or there is a beat of
   dark-on-dark. Colour leads at 110ms; the fill takes 450ms to arrive. */
.dlist a{transition:color 110ms linear,transform var(--t-state) var(--ease-out)}
.dlist li.is-hovered a{color:var(--cream)}
.dlist li.is-hovered a > :first-child{transform:translateX(8px)}
.dlist li.is-hovered a > :last-child{transform:translateX(-8px)}
.dlist .dl-t{font-family:var(--serif);font-size:clamp(22px,2.6vw,36px);line-height:1.1}
.dlist .dl-m{font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;opacity:.6}

/* ============================================================
   Forms
   ============================================================ */
.form-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:clamp(18px,2.4vw,30px);margin-top:clamp(32px,4vw,52px)}
.field label{display:block;font-family:var(--mono);font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-50);margin-bottom:9px}
.field input,.field select{
  width:100%;min-height:48px;padding:12px 14px;background:transparent;
  border:0;border-bottom:1px solid var(--line);border-radius:0;
  font-family:var(--sans);font-size:16px;color:var(--ink);
  transition:border-color .3s var(--ease);
}
.field input:focus,.field select:focus{outline:none;border-bottom-color:var(--gold)}
.field input::placeholder{color:rgba(20,32,26,.32)}
/* On mobile this form was four plain underlined fields floating directly
   on the page background — visually identical to reading prose, nothing
   marking it as the one place a visitor actually becomes a lead. This is
   the entire commercial point of the page: it gets to look like it, not
   blend in. A defined card, bigger touch targets, and a full-width,
   impossible-to-miss submit button. */
@media (max-width:760px){
  .form-grid{
    background:var(--cream-deep);border-radius:16px;
    padding:26px 20px 30px;gap:22px;
  }
  .field input,.field select{min-height:52px;font-size:16.5px}
  #demoForm .btn--gold{width:100%;justify-content:center;min-height:58px;font-size:13.5px}
}

:where(a,button,input,select,[tabindex]):focus-visible{outline:2px solid var(--gold);outline-offset:3px;border-radius:2px}

/* ============================================================
   Responsive
   ============================================================ */
@media (max-width:1080px){
  .rail{display:none}
  .tour-grid{grid-template-columns:1fr;gap:0}
  .tour-stick{display:none}
  .tour-step{min-height:0;padding:clamp(30px,6vw,48px) 0}
  .tour-phone-m{display:block}
}
@media (max-width:900px){
  .nav-links{display:none}
  .nav-cta{display:none}
  .nav-burger{display:flex}
  .nav-inner{height:54px;max-width:calc(100% - 24px);margin:10px auto 0;padding:0 6px 0 18px}
  /* 54px pill -> r27; 42px burger -> r21; 6px gap = the difference. */
  body.menu-open .nav-bg{opacity:0}
}
@media (max-width:860px){
  .node{width:clamp(116px,34vw,168px);padding:9px 11px;border-radius:8px}
  .node-v{font-size:12.5px;line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .node-k{font-size:8.5px}
  .chain-cap{min-height:6.4em}
}
@media (max-width:760px){
  .hero{padding-top:calc(64px + 44px)}
  .hero-head{align-items:flex-start}
  .hero-actions{width:100%}
  .hero-actions .btn{flex:1;justify-content:center}
  .chain-stage{padding-top:84px}
  .who-cell{border-right:0}
}
@media (max-width:520px){
  .app-body{padding:22px 16px}
  .app-bar{padding:14px 16px}
  .alerts{padding:16px 16px 22px}
  .app-meta{font-size:10px;letter-spacing:.06em}
  .hide-sm{display:none}
}
@media (max-width:440px){
  .btn{font-size:10px;letter-spacing:.1em;padding:14px 16px}
  .nav-inner{padding:0 20px}
}

/* ============================================================
   Reduced motion — remove movement, keep meaning
   ============================================================ */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}
  html{scroll-behavior:auto}
  .r-up,.r-fade{opacity:1!important;transform:none!important}
  [data-split] .w{clip-path:none!important;animation:none!important;color:inherit!important}
  .node{opacity:1}
  .constel svg path{stroke-dashoffset:0!important}
  .stn-ico path,.stn-ico circle{stroke-dashoffset:0!important}
  .tour-stick{position:static}
}

/* ============================================================
   Hover — pointer devices only.

   A touch device fires :hover on tap and then leaves it stuck until the next
   tap elsewhere, so every hover state below is gated. Moved here as one block
   rather than 13 scattered media queries.
   ============================================================ */
@media (hover: hover) and (pointer: fine) {
  .btn--gold:hover{color:var(--cream)}
  .btn--gold:hover::before{transform:translateY(0);transition-duration:300ms}
  .btn--ink:hover{color:var(--cream)}
  .btn--ink:hover::before{transform:translateY(0);transition-duration:300ms}
  .btn--light:hover{color:var(--forest)}
  .btn--light:hover::before{transform:translateY(0);transition-duration:300ms}
  .nav-link:hover{color:var(--ink);font-variation-settings:"wght" 700}
  .nav-link:hover::after{transform:scaleX(1);transform-origin:left}
  .nav-burger:hover{background:rgba(20,32,26,.08)}
  .menu-list a:hover{color:var(--gold)}
  .rail a:hover span{opacity:1;transform:none}
  .foot a:hover{color:var(--gold)}
  .who-cell:hover{transform:translateY(-3px);border-color:rgba(20,32,26,.2);
    box-shadow:0 1px 2px rgba(20,32,26,.04),0 26px 44px -32px rgba(20,32,26,.45)}
  /* CSS fallback for when the JS hover never binds. */
  .dlist a:hover{color:var(--cream)}
}

/* ============================================================
   Press feedback

   Every pressable confirms the press. `scale` is used rather than `transform`
   so it composes with the magnetic cursor's inline transform.
   ============================================================ */
.btn:active,
.nav-burger:active,
.menu-list a:active,
.dlist a:active,
.hero-index a:active{ scale:.97 }
.nav-burger,.menu-list a,.dlist a{ transition:scale var(--t-press) var(--ease-out) }

@media (prefers-reduced-motion: reduce){
  /* Reduced motion removes movement, never feedback — the press still registers. */
  .btn:active,.nav-burger:active,.menu-list a:active,.dlist a:active{ scale:.97 }
}
