@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap');

/*
  Visual reference: activetheory.net. Deep navy-to-black, XXL grotesk over a
  strict twelve-column grid, and a console-mono voice for everything a machine
  produced, laid over data-forward furniture: a comparison grid, tooltip dots
  and hairline rules rather than cards and shadows.

  Two typefaces, and the split between them is the idea. Prose is set in a
  grotesk because a person wrote it. Every figure, address, identifier, degree
  and dash is set in mono because a machine will read it off a chain. Nothing
  crosses over.

  Three accents, all semantic, none decorative:
    gold  — value arriving. A fee, before anyone has decided anything.
    live  — retained, owned, earned. The accent of the thing that was bought.
    iris  — the compounding loop, and only the loop.
  What leaves as rent is given no colour at all. That absence is the argument,
  and it is why beat 00 is the one screen with no accent anywhere on it.
*/
:root{
  --void:#04050a;
  --deep:#070a12;
  --navy:#0b1120;
  --panel:#0a0e18cc;
  --ink:#eef1f8;
  --ink-2:#98a4bd;
  --ink-3:#5a6680;
  --ink-4:#38415a;
  --rule:#ffffff16;
  --rule-2:#ffffff0a;
  --rule-3:#ffffff05;

  --gold:#ffb45c;
  --live:#5ff0d4;
  --iris:#8b7cff;
  --live-dim:#5ff0d41f;
  --gold-dim:#ffb45c1c;

  --display:'Space Grotesk',system-ui,-apple-system,'Segoe UI',sans-serif;
  --mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace;

  /* Twelve columns, one gutter, and every block in the page snaps to them. */
  --gutter:clamp(20px,4.4vw,80px);
  --shell:min(1440px,100vw - var(--gutter) * 2);
  --col:calc((var(--shell) - 11 * var(--gap)) / 12);
  --gap:clamp(12px,1.4vw,24px);

  --ease:cubic-bezier(.2,.6,.2,1);
  --ease-io:cubic-bezier(.66,0,.34,1);
  color-scheme:dark;
}
*{box-sizing:border-box}
html{background:var(--void);overflow-x:clip;scroll-behavior:smooth}
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
body{
  margin:0;background:var(--void);color:var(--ink);
  font-family:var(--display);font-size:15px;line-height:1.62;font-weight:400;
  overflow-x:clip;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
}
h1,h2,h3,p,dl,dd,dt{margin:0}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;background:none;border:0;padding:0;cursor:pointer}
::selection{background:var(--live);color:var(--void)}
:focus-visible{outline:1px solid var(--live);outline-offset:3px}
.skip{
  position:fixed;top:-64px;left:var(--gutter);z-index:80;padding:11px 18px;
  background:var(--live);color:var(--void);font-family:var(--mono);font-size:12px;
  font-weight:500;transition:top .2s var(--ease);
}
.skip:focus{top:14px}

/* ---------------------------------------------------------------- typography
   The display sizes are deliberately extreme at the top end. An XXL grotesk
   is the whole reason a frame reads as art-directed rather than as a template,
   and it only works if the tracking tightens as the size grows. */
.display{
  font-family:var(--display);font-weight:500;
  line-height:.94;letter-spacing:-.045em;
  /* No text-wrap:balance here. The lines are authored with <br>, each one is
     masked separately, and a line that wraps inside its own mask animates as a
     two-line block — which is what happened at the old sizes. The type is now
     scaled against the column it sits in so the authored breaks are the only
     breaks, and `nowrap` makes any future overflow visible instead of silently
     re-wrapping. */
  text-wrap:nowrap;
}
h1.display{font-size:clamp(40px,7.6vw,118px)}
h2.display{font-size:clamp(31px,4.6vw,74px)}
.display em{
  font-style:normal;color:var(--live);
  /* The accent word is the only place the display face goes lighter, so the
     emphasis is a change of weight and colour rather than colour alone. */
  font-weight:400;
}
/* Beat 00 is the one screen with no accent on it, and that has to include the
   emphasised word. "Rent is the only cost that never buys anything" set with
   "buys" in the colour that means owned is an argument against itself.
   (Attribute selector, not `.beat-0` — the sections carry `data-beat`.) */
[data-beat="0"] .display em{color:var(--ink-2)}

/* The pi is drawn, not typed. Every one on the page is the same geometry at a
   different size, so no typeface has to have Greek in it and the mark cannot
   drift between the tab icon, the header, the social card and the render. */
/* Colours go in as custom properties, not as selectors. `<use>` clones the
   symbol into a shadow tree that `.mark-glyph circle` cannot reach, but
   inherited custom properties cross that boundary. */
.mark-glyph,.boot-glyph{
  display:block;width:auto;overflow:visible;
  --mark-dot:var(--live);
  --mark-pi:var(--ink);
}

/* The section kickers are gone. A short rule followed by a tracked all-caps
   label is one of the most heavily templated things on the web, and it was
   doing no work here that the headline and the header label were not already
   doing. Micro-labels elsewhere on the page dropped the caps and the wide
   tracking for the same reason: they read as data now, not as decoration. */
.label{
  font-family:var(--mono);font-size:11px;font-weight:500;
  letter-spacing:.01em;color:var(--ink-4);
}

.lede{font-size:clamp(16px,1.28vw,19px);line-height:1.56;color:var(--ink-2);max-width:46ch}
.lede em,.body em{font-style:normal;color:var(--ink)}
.body{font-size:15px;line-height:1.62;color:var(--ink-3);max-width:44ch}
/* Dimmed by colour rather than by opacity. `.body.warn` is two classes, so an
   `opacity` here outranks the reveal rule's `opacity:var(--p)` and pins the
   paragraph visible at all times — it appeared ahead of its own heading. */
.body.warn{color:#dc9c50}
.note{
  font-family:var(--mono);font-size:11px;line-height:1.62;letter-spacing:.01em;
  color:var(--ink-4);max-width:52ch;
}

/* -------------------------------------------------------------------- loader
   Pi in XXL mono gaining a decimal place as the ring compiles, its real
   decimals drifting underneath, and a hairline that fills. Nothing spins.

   The panel wipes upward off the page rather than dissolving, and the content
   lifts out ahead of it, so handing over to the site is a move instead of a
   fade. Visibility is switched at the end of the wipe, not the start. */
.boot{
  position:fixed;inset:0;z-index:70;background:var(--void);
  display:grid;place-content:center;
  clip-path:inset(0 0 0 0);
  transition:clip-path .9s cubic-bezier(.76,0,.24,1) .16s,visibility 0s linear 1.1s;
}
.scene-ready .boot{clip-path:inset(0 0 100% 0);visibility:hidden}
.boot-inner{
  display:grid;justify-items:center;gap:clamp(15px,2.1vw,28px);text-align:center;
  transition:transform .85s var(--ease) .08s,opacity .5s ease .08s;
}
.scene-ready .boot-inner{transform:translateY(-26px);opacity:0}

/* Everything arrives on its own beat on the very first paint. */
.boot-inner>*{animation:boot-in .75s var(--ease) both}
.boot-inner>*:nth-child(1){animation-delay:.03s}
.boot-inner>*:nth-child(2){animation-delay:.09s}
.boot-inner>*:nth-child(3){animation-delay:.16s}
.boot-inner>*:nth-child(4){animation-delay:.21s}
.boot-inner>*:nth-child(5){animation-delay:.26s}
@keyframes boot-in{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:none}}

.boot-glyph{height:26px;--mark-pi:var(--ink-2)}

/* The figure is pi, gaining a decimal place as the ring compiles. Each
   character has its own slot at a fixed width, so a new digit pops into place
   instead of pushing the figure sideways every time one arrives. */
.boot-num{
  display:flex;align-items:baseline;justify-content:center;
  font-family:var(--mono);font-size:clamp(56px,11.5vw,168px);font-weight:500;
  letter-spacing:-.05em;line-height:1;color:var(--ink);
}
.boot-num i{
  font-style:normal;display:block;width:.60em;text-align:center;
  opacity:0;transform:translateY(.24em) scale(.8);
  transition:opacity .4s var(--ease),transform .55s cubic-bezier(.18,1.5,.32,1);
}
.boot-num i:nth-child(2){width:.34em}
.boot-num i.on{opacity:1;transform:none}

/* Pi's real decimals, sliding past. Two identical strips each travelling their
   own full width, which loops with no seam and no timer. */
.boot-strip{
  display:flex;width:min(60ch,84vw);overflow:hidden;
  font-family:var(--mono);font-size:10px;letter-spacing:.08em;color:var(--ink-4);
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 14%,#000 86%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 14%,#000 86%,transparent);
}
.boot-strip span{flex:none;white-space:nowrap;animation:boot-drift 38s linear infinite}
@keyframes boot-drift{to{transform:translateX(-100%)}}

.boot-bar{
  display:block;width:min(340px,62vw);height:1px;background:var(--rule);
  position:relative;overflow:hidden;
}
/* Driven per frame off the follower, so it has no transition of its own. */
.boot-bar u{
  display:block;height:100%;background:var(--live);
  transform-origin:left;transform:scaleX(0);
}
/* A highlight travelling the track, so the bar is alive while it waits. */
.boot-bar::after{
  content:'';position:absolute;inset:0;width:32%;
  background:linear-gradient(90deg,#5ff0d400,#5ff0d4,#5ff0d400);
  animation:boot-sweep 2s cubic-bezier(.45,0,.55,1) infinite;
}
@keyframes boot-sweep{from{transform:translateX(-110%)}to{transform:translateX(420%)}}

.boot-pct{
  font-family:var(--mono);font-size:11px;letter-spacing:.01em;color:var(--ink-4);
  font-variant-numeric:tabular-nums;
}
.boot-pct b{font-weight:500;color:var(--ink-3)}

@media(prefers-reduced-motion:reduce){
  .boot{transition:opacity .3s linear,visibility 0s linear .3s}
  .scene-ready .boot{clip-path:none;opacity:0}
  .boot-inner,.boot-inner>*{animation:none;transition:none}
  .boot-strip span,.boot-bar::after{animation:none}
  .boot-num i{transition:opacity .2s linear;transform:none}
}

/* --------------------------------------------------------------------- stage
   The canvas is the page's floor. Everything else sits above it, and the
   twelve column rules are drawn over it so the grid is visibly the grid. */
.stage{position:fixed;inset:0;z-index:0}
.stage canvas{display:block;width:100%;height:100%}
.scene-failed .stage canvas{opacity:0}
.stage-grid{
  position:absolute;inset:0;pointer-events:none;
  width:var(--shell);left:50%;transform:translateX(-50%);
  background:repeating-linear-gradient(90deg,
    var(--rule-3) 0 1px,transparent 1px calc(var(--col) + var(--gap)));
  opacity:.9;
}
@media(max-width:900px){.stage-grid{display:none}}

/* The copy has to win, always. A left-weighted shade keeps the render off the
   type without dimming the whole frame — the ring lives in the right half, so
   the gradient has cleared before it reaches the subject.

   `--veil` is a second, flat dim that the scroller raises only for the two
   beats that are tables rather than statements. A comparison grid and a ledger
   have to be read, and a lit ring behind a table is just noise on it. */
.stage-shade{
  position:absolute;inset:0;pointer-events:none;
  background:
    linear-gradient(90deg,#04050af2 0%,#04050ae6 24%,#04050a9e 40%,#04050a2b 55%,#04050a00 66%),
    linear-gradient(180deg,#04050a00 62%,#04050a8c 100%);
}
.stage-shade::after{
  content:'';position:absolute;inset:0;background:var(--void);
  opacity:var(--veil,0);transition:opacity .45s var(--ease);
}
/* On a phone the copy sits directly over the render, so a one-sided shade is
   no use. Covering the whole frame instead went much too far: at 85-95% black
   the ring was gone and the phone got a dark page with no subject on it, which
   is the opposite of the point.

   The global layer is now light enough to see the scene through, and the copy
   is protected locally instead, by a shade that travels with the text block.
   Legibility comes from the copy having its own ground, not from putting out
   the lights. */
@media(max-width:860px){
  /* Kept deliberately light. The copy block fills most of a phone screen, so a
     strong shade behind the copy IS a strong shade over everything, and the two
     layers were compounding to about 95% black. The global layer now only takes
     the edge off, and the local one gives the text just enough ground. */
  .stage-shade{
    background:linear-gradient(180deg,
      #04050a33 0%,#04050a4d 38%,#04050a5c 70%,#04050a91 100%);
  }
  .beat-body,.hero-body,.close-body{position:relative}
  /* Strong under the text, gone by its edges. The copy block does not fill the
     screen any more, so this can be dark enough to kill whatever is behind the
     headline without also putting out the ring in the band above it. */
  .beat-body::before,.hero-body::before,.close-body::before{
    content:'';position:absolute;z-index:-1;inset:-54px -30px -34px -30px;
    background:radial-gradient(124% 80% at 40% 52%,
      #04050aeb 0%,#04050ad9 44%,#04050a87 72%,#04050a2b 88%,#04050a00 100%);
  }
}

/* ---------------------------------------------------------------------- head
   No backdrop-filter anywhere on this bar. A full-width fixed element with
   one over the canvas breaks compositing in Chromium, and it must not come
   back. The scrim is a pseudo-element gradient instead, and it falls away
   over three times the header height so it never draws a hard line across
   the render. */
.head{
  position:fixed;inset:0 0 auto 0;z-index:40;pointer-events:none;
  display:grid;grid-template-columns:auto 1fr auto;align-items:center;
  gap:var(--gap);padding:22px var(--gutter);
}
.head::before{
  content:'';position:absolute;inset:0 0 -200% 0;z-index:-1;pointer-events:none;
  background:linear-gradient(180deg,#04050ae0 0%,#04050a99 34%,#04050a00 100%);
}
.head>*{pointer-events:auto}
.mark{display:inline-flex;align-items:center;gap:10px}
.mark .mark-glyph{height:16px;transition:opacity .3s var(--ease)}
.mark:hover .mark-glyph{opacity:.72}
.mark-word{font-size:18px;font-weight:600;letter-spacing:-.04em}
.head-claim{
  justify-self:center;font-family:var(--mono);font-size:11px;letter-spacing:.01em;
  color:var(--ink-4);
}
.head-state{
  /* Pinned right explicitly. The claim between them is hidden below 1080px,
     which leaves this sitting in the empty 1fr column next to the wordmark
     unless it is told where to go. */
  justify-self:end;
  display:flex;align-items:center;gap:9px;
  font-family:var(--mono);font-size:11px;letter-spacing:.01em;
  color:var(--ink-2);
}
.head-state i{
  width:5px;height:5px;border-radius:50%;background:var(--ink-4);flex:none;
  transition:background .4s var(--ease),box-shadow .4s var(--ease);
}
.hasKept .head-state i{background:var(--live);box-shadow:0 0 0 3px var(--live-dim)}
@media(max-width:1080px){.head-claim{display:none}}
@media(max-width:640px){
  .head{padding:16px var(--gutter)}
  .head-state span{display:none}
}

/* ---------------------------------------------------------------------- rail
   Seven entries, one marker that slides rather than jumps. It reads as an
   instrument scale, which is the point: you are somewhere on a revolution. */
/* An instrument scale: one tick per section, the current one longer and in the
   accent. There are no numbers on it, and the sections are not numbered
   either — a counted-off list of steps is one of the surer signs that nobody
   sat and wrote the thing. The names stay in the markup for the accessibility
   tree, where they are read rather than drawn, which also keeps them from
   opening leftward across the right-hand column. */
.rail{
  position:fixed;z-index:40;right:20px;top:50%;transform:translateY(-50%);
  display:grid;gap:9px;
  transition:opacity .5s var(--ease);
}
.rail.idle{opacity:.32}
.rail a{display:block;padding:3px 0}
.rail a::before{
  content:'';display:block;width:11px;height:1px;background:var(--ink-4);
  margin-left:auto;
  transition:width .34s var(--ease),background-color .34s var(--ease);
}
.rail a:hover::before{width:15px;background:var(--ink-2)}
.rail a[aria-current]::before{width:22px;background:var(--live)}
/* Visually hidden, still announced. */
.rail a span{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip-path:inset(50%);white-space:nowrap;
}
@media(max-width:1024px){.rail{display:none}}

/* ----------------------------------------------------------------------- hud
   Console output, in the corners, where activetheory.net keeps it. The orbit
   readout is the only number on the page that is real: it is the camera's
   actual bearing around the ring. */
.hud{
  position:fixed;inset:auto 0 0 0;z-index:40;pointer-events:none;
  display:grid;grid-template-columns:auto 1fr auto;align-items:end;
  gap:var(--gap);padding:20px var(--gutter);
}
.hud::before{
  content:'';position:absolute;inset:-140% 0 0 0;z-index:-1;pointer-events:none;
  background:linear-gradient(0deg,#04050ad9 0%,#04050a8c 38%,#04050a00 100%);
}
.hud>*{pointer-events:auto}
.hud p{display:flex;align-items:baseline;gap:8px;font-family:var(--mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase}
.hud i{font-style:normal;color:var(--ink-4)}
.hud b{font-weight:500;color:var(--ink-2);font-variant-numeric:tabular-nums}
/* Pinned right explicitly. The ring-status readout used to sit between these
   two and hold the middle column open; with it gone the button would otherwise
   fall into the empty 1fr next to the orbit readout. */
.hud-motion{
  justify-self:end;
  font-family:var(--mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-3);border:1px solid var(--rule-2);padding:7px 12px;
  transition:color .3s var(--ease),border-color .3s var(--ease);
}
.hud-motion b{color:var(--live)}
.hud-motion:hover{color:var(--ink);border-color:var(--rule)}
.hud-motion[aria-pressed=true] b{color:var(--ink-4)}
@media(max-width:640px){.hud{padding:14px var(--gutter)}}

/* -------------------------------------------------------------------- layout
   Every section is a twelve-column grid. Copy takes five, the aside takes
   three and sits against the far rule, and the middle four are left empty for
   the render to live in. That empty channel is the composition. */
main{position:relative;z-index:10}
.hero,.beat,.close{
  width:var(--shell);margin-inline:auto;
  display:grid;grid-template-columns:repeat(12,1fr);gap:var(--gap);
}
.hero{min-height:100svh;align-content:center;padding:16vh 0 12vh;position:relative}
.hero-body{grid-column:1/span 8;display:grid;gap:clamp(18px,2vw,30px);justify-items:start}
.hero-lede{font-size:clamp(16px,1.35vw,20px);line-height:1.54;color:var(--ink-2);max-width:46ch}
.hero-sub{
  font-family:var(--mono);font-size:12px;letter-spacing:.01em;color:#4dc1ac;
}
.hero-actions{display:flex;flex-wrap:wrap;gap:12px;margin-top:6px}

/* One caption, sitting in the empty middle columns beside the ring rather
   than in a viewport corner, because the HUD already owns all four corners. */
.hero-cap{
  grid-column:9/span 4;align-self:end;justify-self:start;
  font-family:var(--mono);font-size:11px;letter-spacing:.01em;line-height:1.6;
  color:var(--ink-4);pointer-events:none;margin-bottom:6vh;max-width:30ch;
}
@media(max-width:1080px){.hero-cap{display:none}}

.beat{min-height:100svh;align-content:center;padding:14vh 0}
.beat-body{grid-column:1/span 6;display:grid;gap:clamp(16px,1.8vw,26px);justify-items:start}
.beat-aside{
  grid-column:10/span 3;align-self:center;display:grid;gap:18px;
  padding-left:20px;border-left:1px solid var(--rule-2);position:relative;
}
/* The asides sit in the far right column, which is exactly where the render is
   brightest — the left-weighted stage shade has cleared by then, deliberately,
   so the ring is not dimmed. These get their own local shade instead, and it
   fades out well past its own edges: a scrim that stops at its bounds draws a
   visible rectangle across the render, which is worse than the problem. */
.beat-aside::before,.close-facts::before{
  content:'';position:absolute;z-index:-1;inset:-48px -56px -48px -44px;
  /* The ellipse is smaller than the box it sits in, so the gradient has
     reached fully transparent before it gets to any edge. Sized the other way
     round it clips mid-falloff and draws a rectangle on the render. */
  background:radial-gradient(94% 80% at 52% 50%,
    #04050afa 0%,#04050af0 32%,#04050ab3 56%,#04050a40 78%,#04050a00 100%);
}
.beat--wide .beat-body{grid-column:1/span 12;max-width:none}
.beat--wide .lede{max-width:64ch}
/* A statement can afford 104px of display type. A section that is a statement
   plus a seven-row table cannot: the block grows past the viewport, and
   because these sections are centred when they are read, the overflow goes
   out of BOTH ends — the eyebrow ends up behind the fixed header and the
   closing note behind the HUD. The headline gives up the difference. */
.beat--wide h2.display{font-size:clamp(34px,4.6vw,72px)}
.beat--wide .beat-body{gap:clamp(13px,1.3vw,19px)}
.beat--wide .dash-note{margin-top:14px}
.close{min-height:96svh;align-content:center;padding:14vh 0 10vh;row-gap:clamp(40px,7vw,96px)}
.close-body{grid-column:1/span 6;display:grid;gap:clamp(18px,2vw,28px);justify-items:start}
.close-facts{grid-column:8/span 5;align-self:center;display:grid;gap:0;position:relative}
.close-facts>div{
  display:grid;gap:5px;padding:16px 0;border-top:1px solid var(--rule-2);
}
.close-facts>div:last-child{border-bottom:1px solid var(--rule-2)}
.close-facts dt{font-family:var(--mono);font-size:11px;letter-spacing:.01em;color:var(--ink-4)}
.close-facts dd{font-size:13.5px;line-height:1.6;color:var(--ink-2)}
.close-actions{display:flex;align-items:center;flex-wrap:wrap;gap:20px;margin-top:6px}

@media(max-width:1080px){
  .hero-body{grid-column:1/span 9}
  .beat-body{grid-column:1/span 7}
  .beat-aside{grid-column:9/span 4}
  .close-body{grid-column:1/span 7}
  .close-facts{grid-column:1/span 8}
}
@media(max-width:860px){
  .hero-body,.beat-body,.beat-aside,.close-body,.close-facts{grid-column:1/span 12}
  .beat-aside{padding-left:0;border-left:0;border-top:1px solid var(--rule-2);padding-top:20px;margin-top:8px}
  /* The render is lifted into the top of the frame on a phone, so the copy is
     pushed down to sit under it rather than on top of it. Both get a band of
     their own instead of fighting over the middle. */
  .beat{min-height:0;padding:24vh 0 12vh;align-content:end}
  .hero{padding:34vh 0 11vh;align-content:end}
  .beat--wide{padding:16vh 0 12vh}
  h1.display{font-size:clamp(44px,12vw,64px)}
  .beat--wide h2.display,h2.display{font-size:clamp(32px,9vw,52px)}
}

/* --------------------------------------------------------------- the reveal
   `--in` is written by the scroller as a 0..1 arrival for each section, so
   dragging the scrollbar backwards un-reveals a section exactly the way it
   revealed it.

   Everything is scrubbed off that one value and nothing uses a transition
   delay. An earlier pass mixed the two: the headline had `transition-delay`
   while the paragraphs read `--in` directly, so the body copy arrived before
   the heading it belonged to and the heading lagged behind the scroll. Order
   now comes from `--seq`, an offset into the same arrival, which means the
   sequence is fixed and every element still tracks the wheel exactly.

   `--in` is registered so it can be interpolated, which is what lets the hero
   run the identical cascade once on load instead of needing its own. */
@property --in{syntax:'<number>';inherits:true;initial-value:1}

.beat-body>*,.beat-aside>*,.close-body>*,.close-facts>div,.hero-body>*,.hero-cap{
  --span:.60;
  --p:clamp(0,calc((var(--in,1) - var(--seq,0)) / var(--span)),1);
  opacity:var(--p);
  transform:translateY(calc((1 - var(--p)) * 20px));
}
/* The heading is the first child of every block now that the kickers are gone,
   so it leads on its own and the copy under it follows a clear beat later. */
.beat-body>:nth-child(1),.close-body>:nth-child(1),.hero-body>:nth-child(1){--seq:0}
.beat-body>:nth-child(2),.close-body>:nth-child(2),.hero-body>:nth-child(2){--seq:.21}
.beat-body>:nth-child(3),.close-body>:nth-child(3),.hero-body>:nth-child(3){--seq:.28}
.beat-body>:nth-child(4),.close-body>:nth-child(4),.hero-body>:nth-child(4){--seq:.34}
.beat-body>:nth-child(5),.close-body>:nth-child(5),.hero-body>:nth-child(5){--seq:.39}
.beat-body>:nth-child(6){--seq:.43}
.beat-aside>:nth-child(1){--seq:.26}
.beat-aside>:nth-child(2){--seq:.32}
.close-facts>:nth-child(1){--seq:.24}
.close-facts>:nth-child(2){--seq:.28}
.close-facts>:nth-child(3){--seq:.32}
.close-facts>:nth-child(4){--seq:.36}
.hero-cap{--seq:.42}

/* Each display line is masked and rises out of its own box, one after the
   next, driven by the heading's own progress rather than a timer. */
.display .line{
  display:block;overflow:hidden;
  /* Room for descenders inside the clip box. At a display line-height of .94
     the line box is shorter than the glyphs, so the tails of y, g and p were
     being sliced off where the mask ended. The negative margins give that room
     back to the mask without moving anything in the layout. */
  padding:.06em 0 .20em;margin:-.06em 0 -.20em;
}
.display .line-in{
  display:block;
  --lp:clamp(0,calc((var(--p,1) - var(--lseq,0)) / .56),1);
  transform:translateY(calc((1 - var(--lp)) * 118%));
}
.display .line:nth-child(2) .line-in{--lseq:.11}
.display .line:nth-child(3) .line-in{--lseq:.22}
.display .line:nth-child(4) .line-in{--lseq:.31}

@media(prefers-reduced-motion:reduce){
  .beat-body>*,.beat-aside>*,.close-body>*,.close-facts>div,.hero-body>*,.hero-cap{
    opacity:1;transform:none;
  }
  .display .line-in{transform:none}
}

/* ------------------------------------------------------------------- buttons
   Single-pixel borders, no radius, and a fill that wipes in from the left.
   Tactile brutalism: the affordance is the geometry, not a gradient. */
.button{
  position:relative;display:inline-flex;align-items:center;gap:12px;
  padding:14px 20px;border:1px solid var(--live);color:var(--void);
  font-family:var(--mono);font-size:11px;font-weight:500;letter-spacing:.14em;
  text-transform:uppercase;overflow:hidden;isolation:isolate;
  transition:color .34s var(--ease);
}
.button::before{
  content:'';position:absolute;inset:0;z-index:-1;background:var(--live);
  transform-origin:left;transition:transform .42s var(--ease);
}
/* No glyph and no drawn rule after the label. It was an arrow first, which is
   a tell, and then a short line, which just read as an em dash sitting inside
   the button. The wipe on hover is the affordance. */
.button:hover{color:var(--live)}
.button:hover::before{transform:scaleX(0)}
.button.ghost{border-color:var(--rule);color:var(--ink-2)}
.button.ghost::before{background:var(--ink);transform:scaleX(0)}
.button.ghost:hover{color:var(--void);border-color:var(--ink)}
.button.ghost:hover::before{transform:scaleX(1)}
.button.ghost:disabled{opacity:.4;cursor:not-allowed}
.button.ghost[aria-pressed=true]{border-color:var(--gold);color:var(--gold)}
.button.ghost[aria-pressed=true]::before{background:var(--gold)}

/* ------------------------------------------------------------------ readouts
   Machine values, so mono throughout, and the label is smaller than the
   value it labels. A dash is a legitimate value here and is styled as one. */
.readout{display:grid;gap:0}
.readout>div{
  display:grid;gap:4px;padding:13px 0;border-top:1px solid var(--rule-2);
}
.readout>div:last-child{border-bottom:1px solid var(--rule-2)}
.readout dt{font-family:var(--mono);font-size:11px;letter-spacing:.01em;color:var(--ink-4)}
.readout dd{
  font-family:var(--mono);font-size:14px;font-weight:500;color:var(--ink);
  letter-spacing:-.01em;font-variant-numeric:tabular-nums;
}
.readout dd.pending{color:var(--ink-4)}
.readout dd.zero{color:var(--ink-3)}
.readout dd.live{color:var(--live)}
.readout.deed dd{font-size:13px}

/* ---------------------------------------------------------------------- legs */
.legs{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--rule-2);width:100%;margin-top:4px}
.legs .leg{display:grid;gap:6px;padding:18px;background:var(--void)}
.leg b{font-family:var(--mono);font-size:11px;font-weight:500;letter-spacing:.01em;color:var(--ink-4)}
.leg span{font-size:15px;color:var(--ink)}
.leg i{font-style:normal;font-family:var(--mono);font-size:11px;line-height:1.6;color:var(--ink-3)}
.leg--own span{color:var(--live)}
.leg--own i{color:var(--iris);opacity:.82}
@media(max-width:560px){.legs{grid-template-columns:1fr}}

/* ------------------------------------------------------------------ tooltips
   A bordered dot beside the jargon, and a dark card that explains it without
   making the reader leave the sentence. */
.qt{display:inline;position:relative;white-space:nowrap}
.qt-dot{
  display:inline-grid;place-items:center;width:15px;height:15px;margin-left:5px;
  border:1px solid var(--ink-4);border-radius:50%;color:var(--ink-3);
  font-family:var(--mono);font-size:9px;font-weight:700;line-height:1;
  vertical-align:1px;cursor:help;transition:color .2s var(--ease),border-color .2s var(--ease);
}
.qt-dot:hover,.qt:focus-within .qt-dot{color:var(--live);border-color:var(--live)}
.qt-tip{
  position:absolute;top:calc(100% + 10px);left:0;z-index:30;width:min(280px,72vw);
  padding:13px 15px;background:var(--navy);border:1px solid var(--rule);
  font-family:var(--display);font-size:12.5px;line-height:1.56;color:var(--ink-2);
  white-space:normal;letter-spacing:0;text-transform:none;
  opacity:0;visibility:hidden;transform:translateY(-4px);
  transition:opacity .2s var(--ease),transform .2s var(--ease),visibility .2s;
  box-shadow:0 18px 44px -18px #000c;
}
.qt:hover .qt-tip,.qt:focus-within .qt-tip{opacity:1;visibility:visible;transform:none}

/* ------------------------------------------------------- comparison + ledger
   Both are hairline grids on the void with no fill, so they read as printed
   tables rather than as cards. The dotpi column is the only tinted one. */
.table-scroll{width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;margin-top:6px}
.cmp{display:grid;grid-template-columns:minmax(220px,1.1fr) minmax(200px,1fr) minmax(200px,1.15fr);min-width:660px}
.cmp-row{display:contents}
.cmp-crit,.cmp-cell{padding:16px 20px;border-top:1px solid var(--rule-2);font-size:13.5px;line-height:1.56}
.cmp-row--head .cmp-crit,.cmp-row--head .cmp-cell{border-top:0}
.cmp-crit{color:var(--ink-2);padding-left:0}
.cmp-cell{color:var(--ink-3)}
.cmp-cell--own{
  color:var(--ink);background:linear-gradient(180deg,#5ff0d40a,#5ff0d403);
  border-left:1px solid var(--live-dim);
}
.cmp-cell--head{
  font-family:var(--mono);font-size:10px;font-weight:500;letter-spacing:.01em;color:var(--ink-4);padding-bottom:12px;
}
.cmp-row--head .cmp-crit{font-family:var(--mono);font-size:11px;font-weight:500;letter-spacing:.01em;color:var(--ink-4)}
.cmp-cell--own.cmp-cell--head{
  display:flex;align-items:center;gap:8px;color:var(--live);
  font-family:var(--display);font-size:16px;font-weight:600;letter-spacing:-.03em;
  text-transform:none;border-top:1px solid var(--live);
}
.cmp-cell--own.cmp-cell--head b{font-weight:400;font-size:14px;opacity:.7}
.cmp-row:last-child .cmp-crit,.cmp-row:last-child .cmp-cell{border-bottom:1px solid var(--rule-2)}

.ledger{display:grid;grid-template-columns:minmax(190px,.8fr) minmax(320px,1.7fr);min-width:560px}
.ledger-row{display:contents}
.ledger-row>span{
  padding:15px 20px;border-top:1px solid var(--rule-2);
  font-size:13.5px;line-height:1.56;color:var(--ink-3);
}
.ledger-row>span:first-child{padding-left:0}
.ledger-row>span:last-child{padding-right:0}
.ledger-row>span b{color:var(--ink);font-weight:500}
.ledger-row--head>span{
  border-top:0;padding-bottom:11px;font-family:var(--mono);font-size:10px;
  font-weight:500;letter-spacing:.15em;text-transform:uppercase;color:var(--ink-4);
}
.ledger-row--flag>span:first-child b{color:var(--gold)}
.ledger-row:last-child>span{border-bottom:1px solid var(--rule-2)}
.dash-note{
  margin-top:22px;padding-left:18px;border-left:1px solid var(--gold-dim);
  font-size:13.5px;line-height:1.62;color:var(--ink-3);max-width:64ch;
}
.dash-note b{color:var(--gold);font-weight:500}

/* Both tables collapse to blocks on a phone rather than scrolling sideways.
   Left to scroll, the two columns that carry the entire argument are the ones
   off the right edge: the dotpi column in the comparison, and the all-dashes
   Value column in the ledger. A reader would have swiped to find out that
   every value is a dash, which is the one thing the section is for.
   The column names come back from each cell's data-col. */
@media(max-width:860px){
  .table-scroll{overflow-x:visible}

  .cmp{display:block;min-width:0}
  .cmp-row{display:block;border-top:1px solid var(--rule-2);padding:15px 0 17px}
  .cmp-row--head{display:none}
  .cmp-row:last-child{border-bottom:1px solid var(--rule-2)}
  .cmp-crit{
    display:block;padding:0 0 11px;border-top:0;color:var(--ink);
    font-family:var(--mono);font-size:11px;font-weight:500;letter-spacing:.01em;
  }
  .cmp-cell{
    display:grid;grid-template-columns:70px 1fr;gap:12px;align-items:baseline;
    padding:5px 0;border-top:0;border-left:0;background:none;font-size:13.5px;
  }
  .cmp-cell::before{
    content:attr(data-col);
    font-family:var(--mono);font-size:10px;font-weight:500;letter-spacing:.01em;color:var(--ink-4);
  }
  .cmp-cell--own{
    color:var(--ink);margin-top:7px;padding:11px 13px;
    background:linear-gradient(180deg,#5ff0d40f,#5ff0d405);
    border-left:1px solid var(--live-dim);
  }
  .cmp-cell--own::before{color:var(--live);opacity:.85}
  .cmp-row:last-child .cmp-crit,.cmp-row:last-child .cmp-cell{border-bottom:0}

  .ledger{display:block;min-width:0}
  .ledger-row{display:block;padding:14px 0;border-top:1px solid var(--rule-2)}
  .ledger-row--head{display:none}
  .ledger-row:last-child{border-bottom:1px solid var(--rule-2)}
  /* The field names the row; how it is worked out sits underneath it. */
  .ledger-row>span{display:block;padding:0;border-top:0}
  .ledger-row>span:first-child{font-size:14.5px;margin-bottom:4px}
  .ledger-row>span:nth-child(2){font-size:12.5px;color:var(--ink-4)}
  .ledger-row:last-child>span{border-bottom:0}
}

/* ---------------------------------------------------------------------- gate
   Overscrolling at the foot fills a dial rather than doing nothing. The last
   thing a reader does on the page is the thing the mechanism is named after. */
.gate{display:flex;align-items:center;gap:12px}
.gate-dial{
  position:relative;width:22px;height:22px;border-radius:50%;
  border:1px solid var(--rule);display:grid;place-items:center;flex:none;
}
.gate-dial i{
  display:block;width:10px;height:10px;border-radius:50%;background:var(--live);
  transform:scale(0);transition:transform .16s linear;
}
.gate-copy{display:flex;align-items:baseline;gap:8px;font-family:var(--mono);font-size:11px;letter-spacing:.01em;color:var(--ink-4)}
.gate-copy b{color:var(--live);font-weight:500;font-variant-numeric:tabular-nums}

.veil{
  position:fixed;inset:0;z-index:60;background:var(--void);pointer-events:none;
  opacity:0;transition:opacity .24s var(--ease);
}
.wrapping .veil{opacity:1}

/* --------------------------------------------------------------------- foot */
.foot{
  position:relative;z-index:10;width:var(--shell);margin-inline:auto;
  display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:var(--gap);
  padding:26px 0 74px;border-top:1px solid var(--rule-2);
}
.foot-mark{display:inline-flex;align-items:baseline;gap:8px;font-size:15px;font-weight:600;letter-spacing:-.03em}
.foot-mark b{font-weight:400;color:var(--live);opacity:.8}
.foot p{font-family:var(--mono);font-size:10.5px;line-height:1.6;letter-spacing:.02em;color:var(--ink-4);max-width:70ch;justify-self:center}
.foot-year{font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;color:var(--ink-4)}
@media(max-width:900px){
  .foot{grid-template-columns:1fr;row-gap:14px;padding-bottom:86px}
  .foot p{justify-self:start}
}

/* ------------------------------------------------------------ hero entrance
   The opening runs the same cascade as every other section. The only
   difference is what drives it: there is no scroll position to read at the top
   of the page, so `--in` is interpolated once when the ring reports in, and
   the type and the render therefore arrive together. */
.hero{--in:0;transition:--in 1.25s var(--ease) .06s}
.scene-ready .hero{--in:1}
@media(prefers-reduced-motion:reduce){
  .hero{--in:1;transition:none}
}

/* When the canvas cannot start, the page has to stay a readable document. */
.scene-failed .stage{background:
  radial-gradient(900px 600px at 62% 34%,#0d1730,#04050a 72%)}
