  :root{
    /* ---- neutrals: BUI core.json tokens, unchanged ---- */
    --ink:#1D2D35;        /* neutral.900 */
    --muted:#505050;      /* neutral.500 */
    --border:#DADADA;     /* neutral.200 */
    --border-strong:#C4CDD5; /* neutral.300 */

    /* ---- driven by the control rail ---- */
    --page:#FCFBF9; --surface:#FFFFFF; --faint:#787265; /* 4.62:1 on this paper — WCAG AA for 11px text */
    --l1:#8493A0; --l2:#A6B2BD; --l3:#C7D0D8; --l4:#E3E8ED;
    --pstroke:#1D2D35; --ghost:#D5DAE0; --shadowc:#B4BCC5; --dot:#DDD9D1;
    --accent:#7C3AED;     /* color.brand.primary */
    --accent-hover:#6D28D9;
    --accent-subtle:#F1EBFD;
    --btn-radius:6px;
    --font:"Sen","Avenir Next",system-ui,sans-serif;
    --display:var(--font);
    --display-var:normal;      /* font-variation-settings for the display face (Archivo's wdth) */
    --display-track:-0.01em;
    --chip-font:var(--font);
    --chip-track:0;
    --chip-size:var(--fs-xs);
    /* ---- MODULAR TYPE SCALE ----------------------------------------------
       The page had 15 unrelated font-sizes. Every text role now maps onto ONE
       geometric scale, so "proportional" is enforced by construction instead of
       judged per rule — and the whole page resizes from a single base.
       Note the anchor: 2xl x 2 = 75.4 ~ the 76px wordmark, so the largest body
       step and the wordmark are related rather than coincidental. */
    --fs-ratio:1.22;
    --fs-base:17px;
    --fs-2xs:calc(var(--fs-base) / var(--fs-ratio) / var(--fs-ratio));
    --fs-xs: calc(var(--fs-base) / var(--fs-ratio));
    --fs-sm: var(--fs-base);
    --fs-md: calc(var(--fs-base) * var(--fs-ratio));
    --fs-lg: calc(var(--fs-md) * var(--fs-ratio));
    --fs-xl: calc(var(--fs-lg) * var(--fs-ratio));
    --fs-2xl:calc(var(--fs-xl) * var(--fs-ratio));
    /* Line-height and measure belong WITH the scale, not scattered: type size alone
       does not make a paragraph readable — the leading and the line length decide it,
       and all three move together. Measure is in `ch` so it tracks the font, not px. */
    --lh-body:1.55; --lh-head:1.12; --measure:52ch;
    --s3-height:150vh;   /* S3 pin budget: 100vh of it is the sticky hold */
    --part-stroke:var(--pstroke); --part-strokew:0px; --part-join:miter; --ghost-w:1.5px;
    /* Wordmark hatch. Same geometry as P10 (offset .09em ruled by Guilherme, 5px tile)
       but coloured from THIS lab's accent — violet, not blueprint blue — so the
       treatment can be judged without dragging the palette question along with it. */
    --hatch:rgba(124,58,237,.55); --hatch-x:.09em; --hatch-y:.1035em; --hatch-tile:5px;
    --bp-line:rgba(29,45,53,.28); --bp-ink:rgba(29,45,53,.62); --bp-quiet:rgba(29,45,53,.44);
    --bp-sweep-a:rgba(124,58,237,.10); --bp-sweep-b:rgba(124,58,237,.06); --bp-sweep-c:rgba(124,58,237,.022);
    --ghost-hero:0.7;
    --hero-bg:#241E19; --hero-ink:#F2EFEA; --hero-muted:#A79E93;
  }
  *{margin:0;padding:0;box-sizing:border-box}
  html{scroll-behavior:smooth}
  body{background:var(--page);color:var(--ink);font-family:var(--font);-webkit-font-smoothing:antialiased}

  /* Driven by a CLASS, not by a scroll-linked timeline. The timeline was bound to the
     hero's exit range only, so once the reader was past the hero the observer stopped
     updating and the dock never reached opacity 1 — it was never visible on the page. */
  body.docked .dock, body.docked .dock-cta{opacity:1;pointer-events:auto}
  .dock{position:fixed;top:26px;left:40px;z-index:20;transition:opacity .35s ease;font-family:var(--display);font-variation-settings:var(--display-var);letter-spacing:var(--display-track);font-size:var(--fs-md);font-weight:800;opacity:0;pointer-events:none}
  .dock-cta{position:fixed;top:20px;right:40px;z-index:20;opacity:0;transition:opacity .35s ease;font:inherit;font-size:var(--fs-xs);padding:9px 18px;
    border:1px solid var(--ink);border-radius:var(--btn-radius);background:transparent;color:var(--ink);cursor:pointer}

  .hero{min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:30px;padding:40px;position:relative;overflow:hidden}
  /* Frame zero: the mountain's DESTINATIONS, drawn from the same geom() the scene
     uses, sitting behind the hero content. Not decoration — the empty structure the
     scroll is about to fill in. Opacity is the whole calibration: too strong spoils
     the reveal, too faint reads as a rendering artefact. */
  .hero-bg{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none;z-index:0}
  .hero-bg svg{width:min(1150px,124vw);height:auto;opacity:var(--ghost-hero)}
  .hero-bg canvas{position:absolute;inset:0;width:100%;height:100%;display:block}
  /* Blueprint element layer, in THIS lab's palette rather than blueprint blue — the
     point is to judge the language without the colour question riding along. Marks
     take their tone from the ground, because a white hairline is invisible on paper
     and an ink hairline vanishes on coffee. */
  #bpLayer{position:absolute;inset:0;width:100%;height:100%;z-index:0;pointer-events:none}
  #bpLayer line,#bpLayer circle{stroke:var(--bp-line);stroke-width:1;fill:none}
  #bpLayer text{font-family:"IBM Plex Mono",ui-monospace,monospace;font-size:10px;
    letter-spacing:1.2px;fill:var(--bp-ink)}
  #bpLayer text.q{font-size:9px;fill:var(--bp-quiet)}
  /* Exposure pass. Radial, never a linear band: a linear gradient on a rectangle
     only reaches transparency along its own axis, so the element's edge shows as a
     hard cut sliding across the page. An ellipse has no edges. */
  .bp-sweep{position:absolute;top:-45%;left:0;width:130%;height:190%;z-index:0;
    pointer-events:none;opacity:0;will-change:transform;
    background:radial-gradient(ellipse 38% 70% at 50% 50%,
      var(--bp-sweep-a) 0%, var(--bp-sweep-b) 26%, var(--bp-sweep-c) 52%, transparent 100%);}
  @media (prefers-reduced-motion:reduce){ .bp-sweep{display:none} }

  /* Dark hero. Scoped to S1 ONLY: the mountain palette, the paper and every contrast
     value on this page assume light ground, so a whole-page inversion is a different
     decision, not a bigger version of this one. */
  .hero[data-dark="1"]{background:var(--hero-bg)}
  .hero[data-dark="1"] h1{color:var(--hero-ink)}
  .hero[data-dark="1"] .typed{color:var(--hero-muted)}
  .hero[data-dark="1"] .caret{background:var(--hero-ink)}
  .hero[data-dark="1"] .email input{background:#ffffff14;border-color:#ffffff2e;color:var(--hero-ink)}
  .hero[data-dark="1"] .email input::placeholder{color:var(--hero-muted)}
  .hero[data-dark="1"] .email small{color:var(--hero-muted)}
  .hero[data-dark="1"] .email button{background:var(--accent);border-color:var(--accent);color:#fff}
  /* The overlay layers must be EXCLUDED explicitly. This rule is more specific than
     `.bp-sweep`, so without the exclusions it turned the sweep into a zero-height flex
     item — animated, but with no size to show. #bpLayer only escaped because an ID
     selector outranks this one. */
  .hero > *:not(.hero-bg):not(#bpLayer):not(.bp-sweep){position:relative;z-index:1}
  .hero h1{font-family:var(--display);font-variation-settings:var(--display-var);font-size:clamp(44px,8vw,76px);font-weight:800;letter-spacing:var(--display-track);line-height:1;position:relative;isolation:isolate}
  /* Real aria-hidden span, not ::after + attr(): pseudo-element content is announced
     by some screen readers, which would make the hero read "Braveinnov Braveinnov". */
  .hero h1 .hatch{
    position:absolute; left:var(--hatch-x); top:var(--hatch-y); z-index:-1;
    white-space:nowrap; pointer-events:none; user-select:none;
    background-image:linear-gradient(45deg,
      transparent 42%, var(--hatch) 42%, var(--hatch) 58%, transparent 58%);
    background-size:var(--hatch-tile) var(--hatch-tile);
    -webkit-background-clip:text; background-clip:text;
    -webkit-text-fill-color:transparent; color:transparent;
    animation:hatch-drift 22s linear infinite;
  }
  /* The drift STOPS when the page settles. A hero that keeps moving after the
     construction has come down contradicts the whole point of the settle — the
     scaffolding finished, so the hatch finishes with it. */
  body.is-settled .hero h1 .hatch{animation-play-state:paused}
  body.no-hatch .hero h1 .hatch{display:none}
  @keyframes hatch-drift{ from{background-position:0 0} to{background-position:100% -100%} }
  @media (prefers-reduced-motion:reduce){ .hero h1 .hatch{animation:none} }
  .typed{font-size:clamp(var(--fs-md),2.4vw,var(--fs-xl));color:var(--muted);min-height:1.4em}
  .caret{display:inline-block;width:2px;height:1em;background:var(--ink);vertical-align:-2px;animation:blink 1s steps(1) infinite}
  @keyframes blink{50%{opacity:0}}
  .email{margin-top:46px;display:flex;flex-direction:column;align-items:center;gap:14px}
  .email-row{display:flex;gap:8px;flex-wrap:wrap;justify-content:center}
  .email input{font:inherit;font-size:var(--fs-sm);padding:0 16px;height:48px;width:min(360px,72vw);
    border:1px solid var(--border-strong);border-radius:var(--btn-radius);background:var(--surface);color:var(--ink)}
  .email input:focus{outline:2px solid var(--accent);outline-offset:1px;border-color:var(--accent)}
  .email small{color:var(--faint);font-size:var(--fs-xs)}

  /* ---- button treatments: body class drives all CTAs at once ---- */
  .email button,.dock-cta{transition:background .15s,color .15s,border-color .15s,box-shadow .15s}
  .email button{font:inherit;font-size:var(--fs-sm);padding:0 22px;height:48px;border:1px solid transparent;
    border-radius:var(--btn-radius);background:var(--ink);color:#fff;cursor:pointer}
  body.btn-ink    .email button{background:var(--ink);color:#fff;border-color:var(--ink)}
  body.btn-brand  .email button{background:var(--accent);color:#fff;border-color:var(--accent)}
  body.btn-brand  .email button:hover{background:var(--accent-hover);border-color:var(--accent-hover)}
  body.btn-out    .email button{background:transparent;color:var(--ink);border-color:var(--ink)}
  body.btn-out    .email button:hover{background:var(--ink);color:var(--page)}
  body.btn-tint   .email button{background:var(--accent-subtle);color:var(--accent-hover);border-color:transparent;font-weight:700}
  body.btn-tint   .email button:hover{background:var(--accent);color:#fff}
  body.btn-brand  .dock-cta{border-color:var(--accent);color:var(--accent)}
  body.btn-tint   .dock-cta{border-color:var(--accent);color:var(--accent)}
  .email button:focus-visible,.dock-cta:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
  /* The dock inherits the PAGE ink, which is correct over S2's paper and invisible
     over S3 — where the ground is `#1D2D35` and so was the wordmark, exactly the same
     colour. Over a dark section it takes the hero's own ink, so the docked mark is the
     same colour as the mark it docked from. Placed after the button-treatment rules so
     it wins for every treatment, not just the current one. */
  body.over-dark .dock{color:var(--hero-ink)}
  body.over-dark .dock-cta{color:var(--hero-ink);border-color:var(--hero-ink)}
  .dock,.dock-cta{transition:opacity .35s ease,color .3s ease,border-color .3s ease}

  .scrolly{height:780vh;position:relative}
  .stage{position:sticky;top:0;height:100vh;display:flex;align-items:center;overflow:hidden}
  /* The S2 grid sits on the PINNED stage, so it stays put while the scene assembles
     over it — the ground the layers land on, rather than something that scrolls past. */
  .stage-grid{position:absolute;inset:0;width:100%;height:100%;z-index:0;pointer-events:none}
  .stage-layer{position:absolute;inset:0;width:100%;height:100%;z-index:0;pointer-events:none}
  /* S2's ground is ALWAYS paper, so its marks are always ink. They deliberately do
     NOT read `--bp-line`/`--bp-ink`: those track the HERO's ground and flip to white
     when the hero is dark, which would render S2's marks invisible on its own light
     paper. Same values as the pale branch, stated once here rather than inherited. */
  #s2Bp line{stroke:var(--bp-line-lt,rgba(29,45,53,.28));stroke-width:1;fill:none}
  #s2Bp text{font-family:"IBM Plex Mono",ui-monospace,monospace;font-size:10px;
    letter-spacing:1.2px;fill:var(--bp-ink-lt,rgba(29,45,53,.62))}
  .stage-inner{position:relative;z-index:1}
  .stage-inner{width:min(1400px,96vw);margin:0 auto;display:grid;grid-template-columns:minmax(300px,500px) 1fr;gap:40px;align-items:center}
  .head{position:absolute;top:9vh;left:0;right:0;text-align:center;z-index:1}
  .eyebrow{font-size:var(--fs-xs);letter-spacing:.18em;color:var(--faint)}
  .head h2{font-family:var(--display);font-variation-settings:var(--display-var);letter-spacing:var(--display-track);font-size:clamp(var(--fs-lg),3.2vw,var(--fs-2xl));font-weight:800;margin-top:12px;line-height:var(--lh-head)}
  .head p{font-size:var(--fs-sm);color:var(--muted);margin-top:12px;max-width:640px;margin-inline:auto;line-height:var(--lh-body)}

  /* Floor only — the real height is measured at runtime by fitPhases(), because
     five text controls can each change how tall the tallest phase is. */
  .phases{position:relative;min-height:460px}
  /* The six phases are stacked at inset:0 and only one is visible — but an element at
     opacity 0 STILL RECEIVES POINTER EVENTS, so every click landed in the last phase in
     the DOM rather than the one on screen. Interactivity is therefore granted per beat
     from the timeline (see the onUpdate below), never left to the paint order. */
  .phase{position:absolute;inset:0;opacity:0;pointer-events:none}
  /* The dots are CONTROLS, so they are <button>s, not <i>s: they need a tab stop, an
     accessible name and Enter/Space for free. A styled <i> with a click handler is a
     control only for people using a mouse. */
  .dots{display:flex;gap:8px;align-items:center;margin-bottom:24px}
  .dots button{width:8px;height:8px;border-radius:50%;background:var(--dot);display:block;
    padding:0;border:0;cursor:pointer;transition:background .2s,width .2s,height .2s,transform .15s}
  .dots button:hover{transform:scale(1.35)}
  .dots button:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
  .dots button.done{background:var(--faint)}
  .dots button.on{background:var(--accent);width:12px;height:12px}
  .phase h3{font-family:var(--display);font-variation-settings:var(--display-var);letter-spacing:var(--display-track);font-size:var(--fs-2xl);font-weight:800;margin-bottom:16px;line-height:var(--lh-head)}
  .phase p{font-size:var(--fs-sm);color:var(--muted);max-width:var(--measure);line-height:var(--lh-body)}
  .phase p.lead{font-size:var(--fs-md);font-weight:700;color:var(--ink)}
  .phase p.lead + p{margin-top:18px}
  .chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:20px}
  /* Chips are enumerable capability names, not prose — a mono makes them read as
     entries in a system rather than as marketing adjectives. */
  .chips span{font-family:var(--chip-font);font-size:var(--chip-size);letter-spacing:var(--chip-track);
    color:var(--muted);border:1px solid var(--border-strong);border-radius:14px;padding:5px 12px}
  .phase p.post{margin-top:20px;font-size:var(--fs-xs);color:var(--muted);line-height:var(--lh-body)}
  .first{margin-top:20px;font-weight:700}

  svg{width:100%;height:auto;display:block}
  /* Stroke properties come from CSS VARIABLES, not from SVG presentation attributes:
     a CSS rule always beats a presentation attribute, so setting stroke-width="0" on
     the element would be silently ignored while this rule exists. */
  .ghost{fill:none;stroke:var(--ghost);stroke-width:var(--ghost-w)}
  /* S2 no longer ASSEMBLES — it arrives built and centred, then slides right while
     the detail appears. So the scene's resting state is FINISHED, not empty: opacity
     starts at 1 here rather than being animated up from 0. The previous values were
     the converge model's, where the section legitimately began with nothing. */
  /* fill-box + centre origin so a scale grows the band about ITSELF. Without these
     an SVG transform pivots on the viewBox origin and the band flies off. */
  .part{stroke:var(--part-stroke);stroke-width:var(--part-strokew);stroke-linejoin:var(--part-join);opacity:1;
    transform-box:fill-box;transform-origin:50% 50%;cursor:pointer}
  .part:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
  #mtn .lbl{cursor:pointer}
  .lbl{font-size:var(--fs-sm);font-weight:700;fill:var(--ink);opacity:1}
  .lead-line{stroke:var(--border-strong);stroke-width:1;opacity:1}
  #shadow{opacity:.55}
  .ghost{display:none}
  /* Ported from P11. SCREEN, not normal: a translucent light painted flat over a
     coloured band averages the two and drifts to white, which is why a blue wave
     reads as a white smear. Screen adds light, so it brightens the layer along its
     own hue. The wave runs on the layer the current beat is ABOUT. */
  .s2wave{mix-blend-mode:screen;opacity:0}
  @keyframes s2waveRun{from{transform:translateX(0)}to{transform:translateX(1308px)}}
  @media (prefers-reduced-motion:reduce){ .s2wave{display:none} }
  #mtn{will-change:transform}

  /* S3 is PINNED like the scene above it: the section is taller than the viewport and
     the content sticks centred while it passes. Height is a token so the hold can be
     tuned — a pin with nothing happening is scroll jail, so this stays deliberately
     short (the default holds for roughly half a viewport, not several). */
  /* The gutter belongs to the CONTENT, not to the section. It was on `.close`, so
     `.close-inner` — which carries the grid, the drafting marks and the ground — was
     40px narrower on each side than the hero and the stage. Measured: hero grid at
     left 0 / width 1995, S3 grid at left 40 / width 1915. The 70px module therefore
     did not continue across sections and S3's crop marks sat 88px from the page edge
     against 48px everywhere else. Moving the padding inward makes the layers
     full-bleed while the copy keeps exactly the same inset. */
  .close{min-height:var(--s3-height);position:relative;padding:0}
  .close-inner{position:sticky;top:0;height:100vh;display:flex;flex-direction:column;
    align-items:center;justify-content:center;gap:28px;text-align:center;overflow:hidden;
    padding:0 40px}
  /* S3 mirrors S1: same ground, same grid, same drafting marks. Layers excluded from
     the stacking rule EXPLICITLY — the hero taught me that a class-based overlay loses
     to `.parent > *` and silently collapses to zero height. */
  .s3-layer{position:absolute;inset:0;width:100%;height:100%;z-index:0;pointer-events:none}
  /* ---- S3's ground drifts ---------------------------------------------------
     Guilherme's animated-gradient reference, brought inside our own palette. The demo
     spanned ~250 degrees of hue; this spans SIX (199-205), so it is a drift in depth
     and temperature rather than a colour show — it should be noticeable only as
     "that has moved", never as "that is moving".
     16s, NOT the 72s I first chose. 72s was wrong and the reason is worth keeping:
     perception of gradual change is governed by RATE, not by total amplitude. The
     end-to-end change here is dE 5.55 — "obvious" on a side-by-side scale — but spread
     over 72s that is 0.077 dE per second, which nobody can see. Same colours at 16s
     deliver 0.35 dE/s and read as a ground that is alive. The amplitude was never the
     problem; the clock was. Still 4x slower than the reference's 4s.
     Moved by TRANSFORM on an oversized layer, not by background-position: the latter
     repaints the full viewport every frame, and this page already carries three grid
     canvases that are unprofiled on a phone.
     Contrast checked at BOTH ends, because a moving ground has to pass at its
     brightest too — ink 13.84 -> 11.22, muted 5.98 -> 4.85, both clear of AA. */
  .s3-grad{position:absolute;top:0;left:0;width:200%;height:100%;z-index:0;pointer-events:none;
    /* Every stop is a colour the system already owns, except black, which Guilherme
       asked for: #1D2D35 is `neutral.900`, #14395E is the Kernel layer / drafting-mark
       ink. The two mid-tones I had invented before (#16252C, #1B3547) are gone — they
       were hand-mixed neutrals with almost no blue in them, which is precisely why the
       effect could not be seen.
       BLACK IS THE FREE LEVER. The contrast ceiling only ever applied to the BRIGHT end
       (widening upward drops the body copy below AA); going darker costs nothing,
       because light text on a darker ground only gains contrast. Adding it takes the
       end-to-end change from dE 5.55 to dE 19.05 — more than 3x — while the brightest
       stop stays exactly where it was allowed to be.
       Checked under the COPY, not at the screen edge: across the centred column the
       worst muted-text contrast is 4.81 at the end of the drift and 6.96 at the start.
       The 4.50 figure that #14395E produces occurs only at the far right edge, where
       no text sits.
       THREE stops, asymmetric: a symmetric ramp would put the middle stop under the
       centred copy at BOTH ends of an `alternate` cycle, so the one place the reader is
       actually looking would never change. */
    background:linear-gradient(90deg,#000000,#1D2D35,#14395E);
    animation:s3Drift 16s ease-in-out infinite alternate;will-change:transform}
  @keyframes s3Drift{from{transform:translateX(0)}to{transform:translateX(-50%)}}
  @media (prefers-reduced-motion:reduce){ .s3-grad{animation:none} }
  /* ---- DAWN --------------------------------------------------------------
     Not a cycle. Day/night says time passes and returns; "built slowly, on purpose"
     is about being EARLY and still going, so this is first light arriving and never
     quite reaching day. It never sets.
     Driven by SCROLL, not by a clock: S3 pins for 150vh and that travel was doing
     nothing. Mapped onto the light, the dawn becomes the reader's own progress
     through the section, it uses the same scroll-linked mechanic as the rest of the
     page rather than adding a second timer, and it has no loop seam.
     A GLOW, not a disc. A drawn sun would be the only figurative object on a page
     that is otherwise a drafting sheet; a directional light source says the same
     thing without anyone drawing it.
     Sits LOW and off-centre because that is where the contrast budget is: the copy
     column is dark sky, and the glow can be far brighter than the reading area is
     ever allowed to be precisely because no text sits in it. */
  .s3-dawn{position:absolute;left:0;bottom:0;width:100%;height:78%;z-index:0;pointer-events:none;
    opacity:0;mix-blend-mode:screen;will-change:transform,opacity;
    background:radial-gradient(ellipse 66% 52% at 34% 104%,
      rgba(61,139,255,.85) 0%, rgba(61,139,255,.47) 26%,
      rgba(46,104,190,.22) 52%, rgba(30,64,120,.07) 74%, transparent 100%)}
  /* The vertical radius is 52%, NOT the 96% I first used, and that number is a
     contrast constraint rather than a taste one. At 96% the glow reached the
     microcopy — computed at ellipse d=0.58, dropping `--hero-muted` to 4.28 against
     a 4.5 floor, a failure that would have shipped because it only appears at FULL
     dawn on the BRIGHT end of the drift, a state no casual look lands on.
     Pulled down to the horizon, every line of copy sits at d >= 0.99 — outside the
     glow entirely — so contrast returns to the sky's own 4.96 and the peak could rise
     from .62 to .85 at the same time. Brighter AND safer, because the light moved
     rather than dimmed. */
  @media (prefers-reduced-motion:reduce){ .s3-dawn{opacity:.55!important;transform:none!important} }
  .s3-sweep{position:absolute;top:-45%;left:0;width:130%;height:190%;z-index:0;
    pointer-events:none;opacity:0;will-change:transform;
    background:radial-gradient(ellipse 38% 70% at 50% 50%,
      var(--bp-sweep-a) 0%, var(--bp-sweep-b) 26%, var(--bp-sweep-c) 52%, transparent 100%);}
  .close-inner > *:not(.s3-layer):not(.s3-sweep):not(.s3-grad):not(.s3-dawn){position:relative;z-index:1}
  #s3Bp line,#s3Bp circle{stroke:var(--bp-line);stroke-width:1;fill:none}
  #s3Bp text{font-family:"IBM Plex Mono",ui-monospace,monospace;font-size:10px;
    letter-spacing:1.2px;fill:var(--bp-ink)}
  #s3Bp text.q{font-size:9px;fill:var(--bp-quiet)}
  .close[data-dark="1"]{background:var(--hero-bg)}
  .close[data-dark="1"] h2{color:var(--hero-ink)}
  .close[data-dark="1"] p.say{color:var(--hero-muted)}
  .close[data-dark="1"] .cadence{color:var(--hero-muted)}
  .close[data-dark="1"] .email input{background:#ffffff14;border-color:#ffffff2e;color:var(--hero-ink)}
  .close[data-dark="1"] .email input::placeholder{color:var(--hero-muted)}
  .close[data-dark="1"] .email button{background:var(--accent);border-color:var(--accent);color:#fff}
  @media (prefers-reduced-motion:reduce){ .s3-sweep{display:none} }
  .close h2{font-family:var(--display);font-variation-settings:var(--display-var);letter-spacing:var(--display-track);font-size:clamp(var(--fs-lg),3.6vw,var(--fs-2xl));font-weight:800;max-width:16ch;line-height:var(--lh-head)}
  .close p.say{font-size:clamp(var(--fs-sm),1.6vw,var(--fs-md));color:var(--muted);max-width:var(--measure);line-height:var(--lh-body)}
  .close .email{margin-top:20px}
  .close .cadence{color:var(--faint);font-size:var(--fs-xs)}
  @media (max-width:820px){ .close{min-height:var(--s3-height);padding:0} .close-inner{height:100svh;padding:0 24px} }


  /* ---- MOBILE. Restored from P8 after this block was dropped in the fork, which
     left the desktop two-column grid in force on phones: the second column
     collapsed to 0px and the mountain rendered at 0x0 while the text still read
     fine. Parity with P8 is the point — the lab must not diverge in layout.
     Note: `.scrolly` height here is overridden by the editor's scroll-budget
     slider, which writes an inline height; that is intended. ---- */
  @media (max-width:820px){
    .dock{left:20px;top:20px;font-size:16px} .dock-cta{right:20px;top:16px;padding:7px 12px;font-size:12px}
    .stage-inner{grid-template-columns:1fr;gap:16px}
    .head{top:5vh} .phases{min-height:380px;order:2} .phase h3{font-size:var(--fs-xl)}
    /* Mobile only: the dots, the phase title and the chips centre, matching the
       section heading above them. They are ANCHORS — same position in every beat —
       so centring them is stable. The body and post stay left on purpose: their
       length varies per phase, and centring a varying-length block moves its left
       edge on every cross-fade, so the eye's return point would jump between beats.
       Desktop keeps everything left because there the text is a side rail beside
       the scene, not a column under it. */
    .dots{justify-content:center}
    .phase h3,.phase p.lead{text-align:center}
    .chips{justify-content:center}
    .scrolly{height:700vh}
  }


  @media (prefers-reduced-motion:reduce){
    /* `relative`, NOT `static`. The stage must stay a containing block: #s2Bp is
       inset:0, so a static stage lets it escape to .scrolly — measured at 1994x9376
       against a 1557 viewBox, smearing the crop marks across the whole section.
       Only reduced-motion users would ever have seen it. */
    .scrolly{height:auto} .stage{position:relative;height:auto;padding:60px 0}
    .part,.lbl,.lead-line,#shadow,.dock{opacity:1!important} .phase{position:relative;opacity:1!important;margin-bottom:26px}
    .phases{min-height:0} .hud{display:none}
  }

  /* ===================== V0 VARIANT 1 — FROZEN STATE =====================
     Every value below was a switch in P9. Read off the live lab at
     `#E-warm-ink-6-sen` with the graphite ground, not transcribed by hand.
     Palette E "Blueprint" · warm paper · solid-ink button · radius 6 · Sen.
     Violet is the BRAND ANCHOR and appears in exactly two places: the wordmark
     hatch and the call to action. Everything structural is blue. ============ */
  :root{
    --l1:#14395E; --l2:#245089; --l3:#3F6FA8; --l4:#5C8CBF;
    --pstroke:#93AECB; --ghost:#C3D2E2; --shadowc:#A9BACE; --dot:#D3DBE4;
    --page:#FCFBF9; --surface:#FFFFFF; --faint:#787265; --accent:#7C3AED;
    --btn-radius:6px;
    --font:"Sen","Avenir Next",system-ui,sans-serif;
    --display:"Sen","Avenir Next",system-ui,sans-serif;
    --display-var:normal; --display-track:-0.01em;
    --chip-font:"Sen","Avenir Next",system-ui,sans-serif; --chip-track:0;
    --chip-size:calc(17px / 1.22);
    /* The drafting apparatus. applyMarks() composes --bp-* / --grid-* from these. */
    --pal-ink:20,57,94; --pal-lit:188,214,242; --pal-trail:#2C5AA0; --pal-sweep:44,90,160;
    --hero-bg:#1D2D35; --hero-ink:#EDF1F3; --hero-muted:#93A2AA; --ghost-hero:0;
    --fs-base:17px; --fs-ratio:1.22; --lh-body:1.55; --lh-head:1.12;
    --measure:52ch; --s3-height:150vh;
    --hatch:rgba(124,58,237,.55); --hatch-x:0.09em; --hatch-y:0.1035em; --hatch-tile:5px;
    --part-stroke:#93AECB; --part-strokew:0px; --part-join:miter; --ghost-w:1.5px;
  }

  /* ---- form: visible, honest states ---- */
  .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
    clip:rect(0 0 0 0);white-space:nowrap;border:0}
  .form-status{margin-top:10px;font-size:var(--fs-xs);line-height:var(--lh-body)}
  .form-status[data-tone="err"]{color:#A8342B}
  .form-status[data-tone="ok"]{color:#2F6B45}
  .form-status[data-tone="note"]{color:var(--faint)}
  .hero .form-status[data-tone="err"]{color:#F0A79F}
  .hero .form-status[data-tone="ok"]{color:#9FD9B6}
  .hero .form-status[data-tone="note"]{color:var(--hero-muted)}
  .close[data-dark="1"] .form-status[data-tone="note"]{color:var(--hero-muted)}
  .email input:invalid:not(:placeholder-shown){border-color:#A8342B}
