/* ==========================================================================
   E0 Labs — single page, one route.
   Ground is near-black with a real trace of blue, so the field has somewhere
   to live. One accent, spent three times: the level jump, the occupied E0,
   the electron. Everything else is cream, dimmed.
   ========================================================================== */

@font-face {
  font-family: "Azeret Mono";
  src: url("../fonts/azeret-mono-var.woff2") format("woff2-variations");
  font-weight: 100 900; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Copperplate CC";
  src: url("../fonts/copperplate-cc-bold.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "EB Garamond";
  src: url("../fonts/eb-garamond-var.woff2") format("woff2-variations");
  font-weight: 400 800; font-style: normal; font-display: swap;
}

:root {
  --ground:    #070B16;
  --cream:     #F2EFE9;   /* 17.6:1 */
  --cream-2:   #B4B3AF;   /*  9.3:1 */
  --cream-3:   #8B8A87;   /*  5.6:1 — still AA for body */
  --cream-4:   #6C6E75;   /*  3.6:1 — large text and rules only */
  --rule:      rgba(242, 239, 233, 0.10);
  --rule-soft: rgba(242, 239, 233, 0.055);

  --violet:    #6D4DFF;   /* the transition colour */
  --violet-hi: #A78BFA;   /* legible tint, 7.4:1 */

  /* The sheet's glow. Blue-purple, sitting between the blue-black ground and
     the violet accent rather than beside them — so the proof reads as lit
     from within the same system, not as a second brand colour. It never
     touches text; it is only ever a glow at low alpha. */
  --biolum: 150, 128, 255;

  /* Read by field.js when it picks its two drawing colours. */
  --ascii-dim: #2A3554;
  --ascii-lit: #C9C6BF;
  --ascii-hot: #8A6BFF;
  /* The crest of a wave passing through the mark. Violet-white: the accent
     with the light turned up, not a new hue. */
  --ascii-flash: #EFEAFF;

  --display: "Copperplate CC", "Copperplate Gothic Light", "Copperplate", ui-serif, serif;
  --mono: "Azeret Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --serif: "EB Garamond", Georgia, "Times New Roman", serif;

  --measure: 62rem;
  --pad: clamp(1.25rem, 4vw, 3rem);
  --gap: clamp(5rem, 14vh, 9rem);

  --t-micro: 0.6875rem;
  --t-body: clamp(0.875rem, 0.84rem + 0.18vw, 1rem);

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

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

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

body {
  margin: 0;
  background: var(--ground);
  color: var(--cream);
  font-family: var(--mono);
  font-size: var(--t-body);
  font-weight: 350;
  line-height: 1.8;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h1, h2, h3, p, ol, ul, dl, dd, figure { margin: 0; }
ol, ul { padding: 0; list-style: none; }
sup { font-size: 0.6em; vertical-align: super; line-height: 0; letter-spacing: 0; }
sub { font-size: 0.6em; vertical-align: sub; line-height: 0; letter-spacing: 0; }
a { color: inherit; text-decoration: none; }

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

.skip {
  position: fixed; top: 0; left: 0; z-index: 100;
  padding: 0.75rem 1.25rem;
  background: var(--cream); color: var(--ground);
  font-size: var(--t-micro); letter-spacing: 0.14em; text-transform: uppercase;
  transform: translateY(-120%);
}
.skip:focus { transform: none; }

.sep { color: var(--cream-4); padding-left: 0.45em; padding-right: 0.15em; }

/* The page's annotation voice: letterspaced mono caps, always quiet. */
.eyebrow {
  font-family: var(--mono);
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--cream-4);
}

/* ==========================================================================
   0 · the stage
   ========================================================================== */

/* The mark is the reason anyone stays on this screen, so it is sized first
   and the copy is fitted around it — the inverse of what it used to be. Its
   track is no longer the remainder of a fixed-height grid; it is a definite
   height of its own, taken from the viewport, and the stage grows to hold
   whatever that leaves. Definite is the operative word: a canvas reports its
   backing-store size as its intrinsic size, and resize() sets that from the
   box it is given, so an auto-height mark inside an auto-height stage is a
   feedback loop that grows every pass. Both rows are auto here and the loop
   is broken at the canvas instead. */
.stage {
  position: relative;
  min-height: 100svh;
  display: grid;
  grid-template-rows: auto auto;
  justify-items: center;
  align-items: center;
  gap: clamp(0.5rem, 1.6vh, 1.25rem);
  /* Kept tight on purpose: every rem here comes straight out of the mark.
     The chrome overlaps the mark's row vertically but sits in the margins
     horizontally, so the top can be tighter than it looks. */
  padding: clamp(2.75rem, 5.5vh, 4rem) var(--pad) clamp(3.25rem, 6.5vh, 4.5rem);
  overflow: hidden;
}

.stage__field {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  z-index: 0;
  pointer-events: none;
  --ascii-font: "Azeret Mono", ui-monospace, monospace;
  /* The strata run the full width, but the copy column is cut out of them:
     the ground-state band is the densest one and it sits exactly where the
     CTA does. Flanking segments keep the diagram legible as a diagram and
     leave the text on clean ground. Same idiom as the frontier fields. */
  mask-image: linear-gradient(90deg,
    #000 0 11%, transparent 26%, transparent 74%, #000 89%, #000 100%);
}

/* Row one, sized from the viewport rather than from the leftovers.

   height: the stage is meant to land on one screen, so the mark is sized
   from what one screen has left. Everything else in the stage — the two
   paddings, the gap, and the copy — costs roughly 18rem plus a fifth of the
   viewport height, since the paddings are themselves vh-based. Taking the
   complement of that, 80svh - 18rem, is what makes the hero close on the
   fold instead of running past it, and it is why the two terms have to move
   together: raise the constant and the mark shrinks, raise the padding or
   the copy and this constant has to follow. It fell by 4.5rem when the two
   readings of E0 left the hero for their own section — that block was the
   mark's height, spent on prose. The floor keeps the mark present on a short
   screen; the cap stops it outgrowing a tall one.

   max-height: the figure is wider than it is tall — a quarter turn sweeps
   1.34 units of width for every 1 of height — so a frame that is not at
   least ~1.3:1 clips its sides. This caps the height against the real
   available width (minus a rem for the scrollbar 100vw includes) so the
   silhouette is whole at every viewport. Raising it clips the mark. */
.stage__mark {
  grid-row: 1;
  position: relative;
  width: 100%;
  max-width: 74rem;
  height: clamp(10rem, calc(80svh - 18rem), 36rem);
  max-height: calc((100vw - 2 * var(--pad) - 1rem) / 1.30);
  z-index: 1;
  /* The only interactive surface on the stage, and only as large as the
     mark's own band — so taking touch gestures here never costs a scroll. */
  pointer-events: auto;
  cursor: grab;
  touch-action: none;
  --ascii-font: "Azeret Mono", ui-monospace, monospace;
}
.stage__mark.is-dragging { cursor: grabbing; }


/* ---- instrument chrome ---- */

.chrome {
  position: absolute; inset: var(--pad) var(--pad) auto var(--pad);
  z-index: 3;
  display: flex; justify-content: space-between; gap: 1.5rem;
  pointer-events: none;
}
.chrome__col { display: grid; gap: 0.15rem; }
.chrome__col--end { text-align: right; }

.chrome__line {
  font-size: var(--t-micro);
  line-height: 1.5;
  letter-spacing: 0.16em;
  color: var(--cream-4);
  white-space: nowrap;
}
.chrome__line--id { color: var(--cream-2); letter-spacing: 0.22em; }
.chrome__key { color: rgba(242, 239, 233, 0.22); padding-left: 0.7em; }
/* A leading key is a label, not a separator, so it does not get the gap. */
.chrome__key:first-child { padding-left: 0; }
/* The two live quantities read brighter than their units and labels: the
   number is the instrument, the rest is the legend. */
.chrome__col--end [data-cell],
.chrome__col--end [data-gibbs] { color: var(--cream-2); }

/* ---- the copy, centred over the field ---- */

.stage__copy {
  grid-row: 2;
  position: relative; z-index: 2;
  display: grid;
  justify-items: center;
  text-align: center;
  gap: clamp(0.6rem, 1.4vh, 1rem);
  max-width: 44rem;  /* the mark occupies the clear space above */
}

.thesis {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(1.375rem, 3.9vw, 2.75rem);
  line-height: 1.2;
  letter-spacing: 0.03em;
  max-width: 20ch;
  text-wrap: balance;
}

/* Completes the headline. Emphasis is carried by brightness, not by oblique —
   the mono has no true italic and the synthetic slant is ugly at this size. */
.creed {
  max-width: 46ch;
  font-size: clamp(0.875rem, 1.5vw, 1.0625rem);
  line-height: 1.75;
  letter-spacing: 0.01em;
  color: var(--cream-2);
  text-wrap: pretty;
}
.creed em {
  font-style: normal;
  font-weight: 500;
  color: var(--cream);
}

.position {
  max-width: 58ch;
  font-size: 0.9375rem;
  line-height: 1.75;
  color: var(--cream-2);
  text-wrap: pretty;
}

.act { display: grid; justify-items: center; gap: 0.9rem; }

/* The secondary route. Present, findable, and clearly not the primary. */
.alt {
  margin-top: 0.35rem;
  font-size: var(--t-micro);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--cream-3);
  border-bottom: 1px solid rgba(242, 239, 233, 0.16);
  padding-bottom: 0.2rem;
  transition: color 0.25s var(--ease), border-color 0.25s var(--ease);
}
.alt:hover, .alt:focus-visible { color: var(--cream); border-bottom-color: rgba(242, 239, 233, 0.45); }

.cue {
  position: absolute; z-index: 3;
  left: 50%; transform: translateX(-50%);
  bottom: clamp(0.85rem, 2.2vh, 1.4rem);
  font-size: var(--t-micro);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--cream-3);
  padding: 0.4rem 0.6rem;
  transition: color 0.25s var(--ease);
}
.cue:hover { color: var(--cream); }

/* The sentence and the button own the first beat; the rest arrives after. */
[data-stage] { opacity: 0; }
body.is-ready [data-stage="1"] { opacity: 1; transition: opacity 0.9s var(--ease) 0.6s; }

/* ---- arrival ------------------------------------------------------------
   The generic version of what the frontiers do: stepped, not eased, because
   every transition on this page is a state change. Only ever hidden once JS
   is running and the observer exists to un-hide it, so the content is
   complete without scripting.
   ------------------------------------------------------------------------ */
body.is-ready [data-reveal]:not(.is-in) { opacity: 0; transform: translateY(0.7rem); }
[data-reveal] {
  transition: opacity 340ms steps(3, end), transform 340ms steps(3, end);
}
[data-reveal]:nth-of-type(2) { transition-delay: 70ms; }
[data-reveal]:nth-of-type(3) { transition-delay: 140ms; }

@media (prefers-reduced-motion: reduce) {
  body.is-ready [data-reveal]:not(.is-in) { opacity: 1; transform: none; }
}

/* ---- the cell ----------------------------------------------------------
   The button is the electrochemical cell. Its two vertical edges are the
   electrodes; the electron crosses the gap between them along the base.
   ------------------------------------------------------------------------ */

.cta {
  position: relative;
  display: inline-flex; align-items: center;
  padding: 0.9rem 1.6rem;
  border: 1px solid var(--rule);
  border-left: 2px solid var(--cream-3);
  border-right: 2px solid var(--cream-3);
  background: rgba(7, 11, 22, 0.55);
  backdrop-filter: blur(3px);
  font-family: var(--display);
  font-size: 0.8125rem;
  letter-spacing: 0.16em;
  color: var(--cream);
  transition: border-color 0.3s var(--ease), background-color 0.3s var(--ease);
}
.cta:hover, .cta:focus-visible {
  border-top-color: rgba(242, 239, 233, 0.26);
  border-bottom-color: rgba(242, 239, 233, 0.26);
  background: rgba(242, 239, 233, 0.05);
}
.cta--lg { padding: 1.2rem 2.1rem; font-size: clamp(0.875rem, 1.6vw, 1.0625rem); }

.cta__electron {
  position: absolute; bottom: 0; left: 0;
  width: 6px; height: 6px; margin-left: -3px; margin-bottom: -3px;
  border-radius: 50%;
  background: var(--violet);
  opacity: 0;
}
.cta:hover .cta__electron,
.cta:focus-visible .cta__electron { animation: transfer 600ms var(--ease) forwards; }

.cta.is-spontaneous { border-left-color: var(--violet-hi); border-right-color: var(--violet-hi); }
.cta.is-spontaneous .cta__electron {
  opacity: 1; left: 100%;
  box-shadow: 0 0 16px 4px rgba(109, 77, 255, 0.8);
}

@keyframes transfer {
  0%   { opacity: 0; left: 0; }
  12%  { opacity: 1; }
  88%  { opacity: 1; box-shadow: 0 0 16px 4px rgba(109, 77, 255, 0.85); }
  100% { opacity: 1; left: 100%; box-shadow: 0 0 16px 4px rgba(109, 77, 255, 0.5); }
}

/* ==========================================================================
   shared section frame
   ========================================================================== */

section:not(.stage) {
  position: relative;
  max-width: var(--measure);
  margin-inline: auto;
  padding: var(--gap) var(--pad);
}

/* ==========================================================================
   1 · the name
   The two readings, out of the hero and given a beat of their own. It is a
   definition, so it is set like one: mark, label, term, gloss, on one line each,
   with a rule between the pair. Quiet — it is the last thing before the
   proof and it must not compete with it.
   ========================================================================== */

.reading { border-top: 1px solid var(--rule-soft); text-align: center; }

.reading__lead {
  margin-top: clamp(1.5rem, 4vh, 2.25rem);
  font-size: clamp(0.9375rem, 1.7vw, 1.125rem);
  letter-spacing: 0.02em;
  color: var(--cream-2);
}

.reading__list {
  margin-top: clamp(1.5rem, 4vh, 2.25rem);
  display: grid;
  border-block: 1px solid var(--rule-soft);
}
.reading__row + .reading__row { border-top: 1px solid var(--rule-soft); }

/* One reading, one line. The label sits beside its definition, and from
   50rem up the definition is held to a single line as well. Below that there
   is genuinely not enough width for the longer gloss at a readable size, so it
   wraps rather than shrinking to something nobody can read.

   50rem is measured, not chosen: the chemistry row — the longer of the two —
   needs ~800px of viewport to set on one line at this size. It was 44rem when
   the glosses were shorter. **Lengthen either gloss and this has to be
   re-measured, or the row overruns the screen**, because a nowrap row cannot
   shrink to fit and the overflow lands on the document. */
.reading__row {
  display: flex; flex-wrap: nowrap; justify-content: center;
  align-items: baseline; gap: 0.15rem 0.9rem;
  padding: clamp(0.9rem, 2.2vh, 1.35rem) 0;
  max-width: 100%;
}
@media (min-width: 50rem) {
  .reading__row { white-space: nowrap; }
}

.reading dt {
  flex: none;
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--cream-4);
}
/* The mark in front of each label. Serif and full size against the mono
   micro-caps beside it, because it is the thing being defined rather than
   another label — and because the raised and dropped ring only read as a
   pair when they are large enough to see. Sits on the row's baseline, so
   the two circles land visibly above and below the same line. */
.reading__sym {
  font-family: var(--serif);
  font-size: 0.9375rem;
  letter-spacing: 0;
  text-transform: none;
  color: var(--cream);
  margin-right: 0.15em;
}

.reading dd {
  margin: 0;
  font-size: clamp(0.8125rem, 1.4vw, 0.9375rem);
  line-height: 1.7;
  color: var(--cream-3);
}
.reading dd i { font-style: normal; color: var(--cream); }

/* Just under the one-line breakpoint the two rows would otherwise disagree:
   physics is the shorter gloss and still fits on one line where chemistry has
   already broken, and a 1-against-2 pair reads as a mistake rather than as a
   measure. Holding the gloss to a measure narrower than either row's one-line
   width makes both break together. Inert below ~44rem, where the column is
   already narrower than this. */
@media (max-width: 49.99rem) {
  .reading dd { max-width: 33rem; }
}

/* Under the one-line breakpoint, buy the definition back the width the label
   is taking and set it a step smaller, so the pair lands on two clean lines
   rather than five ragged ones. Stated after the rules it overrides, which
   at equal specificity is the only thing that makes it win. */
@media (max-width: 43.99rem) {
  /* The label takes its own line and the gloss gets the full column. Beside
     the label there is only room for ~34 characters, which breaks the longer
     gloss across three ragged lines; with the width back it sets in two. */
  .reading__row { flex-wrap: wrap; gap: 0.1rem 0.55rem; }
  .reading dt { flex: 1 0 100%; letter-spacing: 0.1em; }
  .reading dd { font-size: 0.75rem; line-height: 1.62; }
  .reading__sym { font-size: 0.8125rem; }
}
@media (max-width: 24rem) {
  .reading dd { font-size: 0.6875rem; }
  .reading dt { font-size: 0.625rem; letter-spacing: 0.08em; }
  .reading__sym { font-size: 0.75rem; }
}

/* ==========================================================================
   2 · the proof
   ========================================================================== */

.proof { border-top: 1px solid var(--rule-soft); }
.proof__doc {
  max-width: 38rem;
  margin-inline: auto;
  font-family: var(--serif);
  font-size: clamp(1.0625rem, 1rem + 0.35vw, 1.25rem);
  line-height: 1.82;
  color: var(--cream-2);
  font-variant-numeric: tabular-nums oldstyle-nums;
}

/* ---- the sheet ----------------------------------------------------------
   A charter, not a card. The proof is set on a document that materialises as
   it is written: a ruled edge, an inner keyline held off it the way an
   engraved border is, corner marks, and a woven ground you should have to
   look for. --unfurl is the same 0..1 the writer computes for the character
   reveal, so the sheet cannot lead or lag the words it sits beneath.

   Faint on purpose. If you notice the parchment before the proof, it is too
   bright.
   ------------------------------------------------------------------------ */

.proof__doc { position: relative; --unfurl: 0; }

.sheet {
  position: absolute;
  inset: -3rem -2.25rem;
  z-index: -1;
  pointer-events: none;
  border: 1px solid rgba(var(--biolum), 0.2);
  border-radius: 3px;
  background:
    /* the weave — two crossed hairline grids, almost subliminal */
    repeating-linear-gradient(90deg,
      rgba(var(--biolum), 0.028) 0 1px, transparent 1px 7px),
    repeating-linear-gradient(0deg,
      rgba(var(--biolum), 0.022) 0 1px, transparent 1px 7px),
    /* the sheen, brightest at the head where the writing starts */
    linear-gradient(180deg,
      rgba(var(--biolum), 0.075),
      rgba(var(--biolum), 0.02) 34%,
      rgba(var(--biolum), 0.008) 68%,
      transparent);
  box-shadow:
    0 0 70px -18px rgba(var(--biolum), 0.55),
    inset 0 0 52px -20px rgba(var(--biolum), 0.75);
  clip-path: inset(0 0 calc((1 - var(--unfurl)) * 100%) 0);
}

/* The inner keyline, held off the border. */
.sheet::before {
  content: "";
  position: absolute;
  inset: 7px;
  border: 1px solid rgba(var(--biolum), 0.1);
  border-radius: 1px;
}

/* Corner marks. Only the two strokes that meet, so they read as engraved
   register marks rather than as a second box. */
.sheet__edge {
  position: absolute;
  width: 13px; height: 13px;
  border: 0 solid rgba(var(--biolum), 0.42);
}
.sheet__edge--tl { top: 13px; left: 13px; border-top-width: 1px; border-left-width: 1px; }
.sheet__edge--tr { top: 13px; right: 13px; border-top-width: 1px; border-right-width: 1px; }
.sheet__edge--bl { bottom: 13px; left: 13px; border-bottom-width: 1px; border-left-width: 1px; }
.sheet__edge--br { bottom: 13px; right: 13px; border-bottom-width: 1px; border-right-width: 1px; }

/* The write head: a lit edge on the boundary the clip is cutting. Fades in as
   the sheet starts and out as it completes — a finished document is just a
   document. */
.proof__doc::after {
  content: "";
  position: absolute;
  left: -2.25rem;
  right: -2.25rem;
  top: calc(var(--unfurl) * 100%);
  height: 1px;
  pointer-events: none;
  background: linear-gradient(90deg,
    transparent,
    rgba(var(--biolum), 0.16) 8%,
    rgba(var(--biolum), 0.8) 30%,
    rgba(var(--biolum), 0.8) 70%,
    rgba(var(--biolum), 0.16) 92%,
    transparent);
  box-shadow: 0 0 18px 1px rgba(var(--biolum), 0.45);
  opacity: min(calc(var(--unfurl) * 9), calc((1 - var(--unfurl)) * 6), 1);
}

@media (max-width: 46rem) {
  .sheet { inset: -1.6rem -1.15rem; }
  .sheet::before { inset: 5px; }
  .sheet__edge { width: 9px; height: 9px; }
  .sheet__edge--tl { top: 9px; left: 9px; }
  .sheet__edge--tr { top: 9px; right: 9px; }
  .sheet__edge--bl { bottom: 9px; left: 9px; }
  .sheet__edge--br { bottom: 9px; right: 9px; }
  .proof__doc::after { left: -1.15rem; right: -1.15rem; }
}

.proof__title {
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(1.5rem, 3.2vw, 2rem);
  line-height: 1.3;
  color: var(--cream);
  text-align: center;
  max-width: 22ch;
  margin: 0 auto clamp(2.5rem, 6vh, 4rem);
  text-wrap: balance;
}

.stmt { margin-block: 1.6em; text-align: justify; hyphens: auto; }
.stmt b {
  font-weight: 600;
  color: var(--cream);
  font-variant-caps: small-caps;
  font-feature-settings: "smcp" 1;
  letter-spacing: 0.05em;
}
.stmt--conjecture { margin-top: 0; }
.stmt--theorem { margin-bottom: 0; }

/* The tombstone. It closes the proof — without it the theorem just stops. */
.qed {
  display: inline-block;
  width: 0.52em; height: 0.52em;
  margin-left: 0.5em;
  background: var(--cream-2);
}

/* The document writes itself as you scroll. Characters are in the DOM from
   the start; only their paint changes, so nothing here affects reading order,
   selection, or search. */
.is-writing .ink { opacity: 0.06; }
.is-writing .ink.is-in { opacity: 1; }
.is-writing .ink.is-head {
  color: var(--cream);
  text-shadow: 0 0 14px rgba(242, 239, 233, 0.55);
}

/* Lemma 2's evidence. Static. The numbers are the drama. */
.figures {
  display: grid; gap: 0.55rem;
  margin: 2em 0;
  padding: 1.4rem 0;
  border-block: 1px solid var(--rule-soft);
  font-family: var(--mono);
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--cream-3);
}
.fig { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.1rem 0.6rem; }
.fig__name { flex: 0 0 8.5rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--cream-4); }
.fig__was { color: var(--cream-3); }
.fig__now { color: var(--cream); }
.fig__arrow { color: var(--cream-4); padding-inline: 0.25rem; }
.fig__yr { color: var(--cream-4); font-size: 0.6875rem; }
.fig i { font-style: normal; color: var(--cream-4); }

.corollaries {
  margin-top: clamp(3rem, 7vh, 4.5rem);
  padding-top: clamp(2rem, 5vh, 3rem);
  border-top: 1px solid var(--rule);
  display: grid;
  gap: clamp(1.5rem, 3.5vh, 2.25rem);
}
.cor {
  font-size: clamp(1.1875rem, 1rem + 1vw, 1.625rem);
  line-height: 1.42;
  color: var(--cream);
  text-wrap: pretty;
}
.cor b {
  display: block;
  font-family: var(--mono);
  font-size: var(--t-micro);
  font-weight: 500;
  font-variant: normal;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--cream-4);
  margin-bottom: 0.6rem;
}

/* ==========================================================================
   2 · four frontiers
   ========================================================================== */

.frontiers { text-align: center; }
.frontier-list { margin-top: clamp(2rem, 5vh, 3rem); }

.frontier {
  position: relative;
  padding: clamp(1.75rem, 4vh, 2.5rem) 0;
  border-top: 1px solid var(--rule-soft);
  isolation: isolate;
  /* Stepped, not eased: arrival is a state change like every other
     transition on the page. */
  transition: opacity 360ms steps(3, end), transform 360ms steps(3, end),
              border-color 0.4s var(--ease);
}
.frontier:last-child { border-bottom: 1px solid var(--rule-soft); }

/* Only hidden once JS is running, so the list is complete without it. */
body.is-ready .frontier:not(.is-in) { opacity: 0; transform: translateY(0.9rem); }
.frontier:nth-child(2) { transition-delay: 60ms; }
.frontier:nth-child(3) { transition-delay: 120ms; }
.frontier:nth-child(4) { transition-delay: 180ms; }
.frontier:nth-child(5) { transition-delay: 240ms; }

.frontier:hover { border-top-color: rgba(242, 239, 233, 0.16); }

.frontier__field {
  position: absolute; inset: 0; z-index: -1;
  width: 100%; height: 100%;
  --ascii-font: "Azeret Mono", ui-monospace, monospace;
  --ascii-dim: #1B2338;
  --ascii-lit: #59637F;
  /* Centred copy means the clear space is now on both flanks, so the field
     is revealed symmetrically. It also means a travelling pulse enters on one
     side and leaves on the other instead of dying under the text. */
  mask-image: linear-gradient(90deg,
    #000 0 6%, transparent 27%, transparent 73%, #000 94%, #000 100%);
}

/* ---- the occupied frontier ----------------------------------------------
   Same logic as E0 in the ladder: the accent marks what is actually running,
   never what merely looks good. Held well below the CTA violet so the
   primary conversion moment still owns the brightest instance of the hue.
   -------------------------------------------------------------------------- */

.frontier--primary { border-top-color: rgba(139, 116, 255, 0.28); }
.frontier--primary + .frontier { border-top-color: rgba(139, 116, 255, 0.14); }
/* Same mask as the others: the copy overruns the mask edge by design, so a
   wider reveal here would put a bright field under the text rather than
   behind it. This one is distinguished by hue and density, not by area. */
.frontier--primary .frontier__field { --ascii-hot: #2E2660; }
.frontier--primary .frontier__n { color: var(--violet-hi); }

.frontier__flag {
  margin-top: 0.7rem;
  font-size: var(--t-micro);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--cream-3);
}
.frontier__now { color: var(--violet-hi); letter-spacing: 0.16em; }

.frontier__text { display: grid; justify-items: center; gap: 0.3rem; }
.frontier__n {
  font-size: var(--t-micro);
  letter-spacing: 0.28em;
  color: var(--cream-4);
  margin-bottom: 0.15rem;
  transition: color 0.3s var(--ease);
}
.frontier__name {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(1rem, 2vw, 1.25rem);
  letter-spacing: 0.12em;
  color: var(--cream);
}
.frontier__line {
  max-width: 46ch;
  margin-inline: auto;
  font-size: 0.875rem;
  color: var(--cream-3);
  text-wrap: pretty;
}

.exclusion {
  margin-top: clamp(1.75rem, 4vh, 2.5rem);
  font-size: var(--t-micro);
  letter-spacing: 0.22em;
  color: var(--cream-3);
}

/* ==========================================================================
   3 · the ladder
   The three bars of the mark, separated, drawn in characters.
   ========================================================================== */

.ladder { text-align: center; }

.levels {
  position: relative;
  margin-top: clamp(2.5rem, 6vh, 4rem);
  display: grid;
  justify-items: center;
  gap: clamp(2.25rem, 5.5vh, 3.5rem);
}

/* ---- the quantum --------------------------------------------------------
   One particle, climbing. It only ever exists on a level: it is placed on
   the centre line of the bar it occupies, and a jump is what a jump actually
   is — it stops being here and starts being there, with no position in
   between. Sliding it up the diagram would have been prettier and would have
   said the wrong thing, and it would have dragged a lit dot straight through
   the copy on the way, which is how the mistake announced itself.
   ------------------------------------------------------------------------ */

.quantum {
  position: absolute;
  top: 0; left: 0;
  width: 7px; height: 7px;
  margin: -3.5px 0 0 -3.5px;
  border-radius: 50%;
  background: var(--violet-hi);
  box-shadow: 0 0 14px 3px rgba(139, 116, 255, 0.55);
  pointer-events: none;
  transform: translate3d(var(--qx, 0px), var(--qy, 0px), 0);
  transition: opacity 130ms linear;
}
/* The instant of the transition, and the only time it is nowhere. */
.quantum.is-jumping { opacity: 0; }

/* The photon. A ring at the moment of arrival — wider and brighter coming
   down, because that is the half of the cycle that gives energy back. */
.quantum::after {
  content: "";
  position: absolute; inset: -3px;
  border-radius: 50%;
  border: 1px solid var(--violet-hi);
  opacity: 0;
}
.quantum.is-landing::after { animation: photon 700ms var(--ease); }
.quantum.is-emitting.is-landing::after { animation: photon-big 900ms var(--ease); }

@keyframes photon {
  from { opacity: 0.7;  transform: scale(0.6); }
  to   { opacity: 0;    transform: scale(4); }
}
@keyframes photon-big {
  from { opacity: 0.95; transform: scale(0.6); }
  to   { opacity: 0;    transform: scale(8); }
}

/* Centred single column. */
.level {
  display: grid;
  justify-items: center;
  gap: 0.3rem;
  max-width: 100%;
}

/* No stagger. The staircase reads as a staircase when the levels share a left
   edge; centred, the same offsets read as misalignment. The ascent is carried
   by the bar characters, the opacity ramp and the occupied state. */

/* The label and the name ride the axis together. Kept out of the column
   grid so the full-width bar cannot stretch them apart. */
.level__head {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  gap: 0.15rem 0.9rem;
}

/* The bar is a readout of that level's occupancy. --e is written by the
   ladder driver every frame, colour along with it; everything here just
   states how energy is allowed to look. Without JS the authored characters
   and the static opacities below stand in unchanged. */
.level__bar {
  width: 100%;
  margin-bottom: 1rem;
  text-align: center;
  font-size: clamp(0.75rem, 1.4vw, 1rem);
  line-height: 1;
  letter-spacing: 0.08em;
  white-space: nowrap;
  overflow: hidden;
  color: var(--cream-2);
}
.level:nth-child(1) .level__bar { opacity: 0.34; }
.level:nth-child(2) .level__bar { opacity: 0.6; }
.level--occupied .level__bar {
  color: var(--violet);
  opacity: 1;
  text-shadow: 0 0 22px rgba(109, 77, 255, 0.55);
}

/* Once the driver is running, occupancy is the only thing setting these. */
.levels.is-live .level__bar {
  opacity: calc(0.22 + 0.78 * var(--e, 0.3));
  text-shadow: 0 0 calc(4px + var(--e, 0) * 24px)
               rgba(109, 77, 255, calc(var(--e, 0) * 0.6));
}

/* Pointing at a level is another way to put energy into it — the same
   affordance the frontiers have, and the driver reads it as a surge. */
.levels.is-live .level { cursor: default; }

.level__e {
  font-family: var(--display);
  font-size: 0.875rem;
  letter-spacing: 0.1em;
  color: var(--cream-3);
}
.level--occupied .level__e { color: var(--cream); }

.level__name {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(1rem, 2vw, 1.25rem);
  letter-spacing: 0.12em;
  color: var(--cream-2);
}
.level--occupied .level__name { color: var(--cream); }

.level__items {
  max-width: 52ch;
  margin-top: 0.5rem;
  font-size: 0.8125rem;
  color: var(--cream-3);
  text-wrap: pretty;
}
.level--empty .level__items { color: var(--cream-4); }

/* ==========================================================================
   4 · the bar
   ========================================================================== */

.bar { border-top: 1px solid var(--rule-soft); text-align: center; }
.bar__line {
  margin-inline: auto;
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(1.25rem, 3.4vw, 2.125rem);
  line-height: 1.24;
  letter-spacing: 0.03em;
  max-width: 22ch;
  color: var(--cream);
  text-wrap: balance;
}
.bar__grid {
  margin-top: clamp(2.5rem, 6vh, 4rem);
  display: grid;
  gap: clamp(2rem, 5vh, 3rem) clamp(2rem, 6vw, 5rem);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
}
.plain { margin-top: 1.1rem; display: grid; justify-items: center; gap: 0.55rem; }
.plain li { color: var(--cream-2); font-size: 0.875rem; }

/* ==========================================================================
   5 · join
   ========================================================================== */

.join {
  border-top: 1px solid var(--rule-soft);
  display: grid;
  justify-items: center;
  text-align: center;
  gap: 1.1rem;
}
/* The heading is the button. Nothing to style here but the box that holds
   it — the type comes from .cta. */
.join__title { font-size: inherit; font-weight: inherit; line-height: 1; }

.join .cta--lg {
  padding: 1.5rem 2.75rem;
  font-size: clamp(1rem, 2.2vw, 1.375rem);
  border-left-width: 3px;
  border-right-width: 3px;
  border-left-color: var(--cream);
  border-right-color: var(--cream);
}

/* ---- the scroll unrolls -------------------------------------------------
   Clipped to its own centre line until the section arrives, then opened from
   the middle outward. The clip is dropped once it finishes: the redox
   electron overflows the border box on its way across, and a clip that
   stayed would shear the one moment the page saves the accent for.
   ------------------------------------------------------------------------ */
/* Rolled up only once JS is running — the same guard the frontiers use — so
   with scripting off the button is simply there. */
body.is-ready .join .cta--lg {
  clip-path: inset(50% 0 50% 0);
  transition: clip-path 780ms var(--ease),
              border-color 0.3s var(--ease), background-color 0.3s var(--ease);
}
body.is-ready .join.is-open .cta--lg { clip-path: inset(0 0 0 0); }
body.is-ready .join.is-open-done .cta--lg { clip-path: none; }

@media (prefers-reduced-motion: reduce) {
  body.is-ready .join .cta--lg { clip-path: none; transition: none; }
}
.join__note { max-width: 52ch; margin-inline: auto; font-size: 0.8125rem; color: var(--cream-3); text-wrap: pretty; }

/* Noticeable by size and position, not by colour. */
/* One line now, so it centres as a row and the arrow rides beside the label
   rather than being pinned to a corner of an empty box. */
.census {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.7rem;
  width: 100%;
  margin-top: clamp(2.5rem, 6vh, 4rem);
  padding: clamp(1.15rem, 2.6vh, 1.6rem) clamp(1.5rem, 4vw, 2.5rem);
  border: 1px solid var(--rule);
  transition: border-color 0.3s var(--ease), background-color 0.3s var(--ease);
}
.census:hover, .census:focus-visible { border-color: rgba(242, 239, 233, 0.3); background: rgba(242, 239, 233, 0.03); }
.census__label {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(0.9375rem, 1.8vw, 1.125rem);
  letter-spacing: 0.1em;
  color: var(--cream-2);
}
.census:hover .census__label, .census:focus-visible .census__label { color: var(--cream); }
.census__arrow {
  font-size: 1rem;
  line-height: 1;
  color: var(--cream-4);
  transition: transform 0.3s var(--ease), color 0.3s var(--ease);
}
.census:hover .census__arrow { color: var(--cream); transform: translate(2px, -2px); }

/* ---- reaching a person --------------------------------------------------
   Above the fold of the footer rather than inside it, so contact is part of
   the ask instead of a legal afterthought. Sized as body text and coloured
   as an aside: findable in a glance, never competing with the CTA.
   ------------------------------------------------------------------------ */
.reach {
  margin-top: clamp(1.25rem, 3vh, 1.75rem);
  font-size: 0.8125rem;
  letter-spacing: 0.02em;
  color: var(--cream-4);
  text-wrap: pretty;
}
.reach a {
  color: var(--cream-2);
  border-bottom: 1px solid rgba(242, 239, 233, 0.18);
  padding-bottom: 0.1rem;
  transition: color 0.25s var(--ease), border-color 0.25s var(--ease);
}
.reach a:hover, .reach a:focus-visible {
  color: var(--cream);
  border-bottom-color: rgba(242, 239, 233, 0.5);
}

/* ==========================================================================
   footer
   ========================================================================== */

.foot {
  max-width: var(--measure);
  margin-inline: auto;
  padding: clamp(3rem, 7vh, 4.5rem) var(--pad) clamp(6rem, 12vh, 8rem);
  border-top: 1px solid var(--rule-soft);
  display: grid;
  gap: 1rem;
  align-items: baseline;
  grid-template-columns: 1fr auto;
}
.foot__motto { font-family: var(--display); font-size: 0.8125rem; letter-spacing: 0.2em; color: var(--cream-2); }

/* The links wrap. An `auto` grid track cannot shrink below its min-content,
   and three tracked micro-caps links — the longest being the address — come
   to ~317px, which is wider than the content box on a 320px handset; the
   footer then ran past the screen and `body { overflow-x: hidden }` shaved
   the ends off rather than scrolling to them. Wrapping drops min-content to
   the widest single link, and under 30rem the motto and the links stop
   sharing a row at all. */
.foot__links { display: flex; flex-wrap: wrap; gap: 0.75rem 1.5rem; }
@media (max-width: 30rem) {
  .foot { grid-template-columns: 1fr; }
}
.foot__links a {
  font-size: var(--t-micro);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--cream-3);
  transition: color 0.25s var(--ease);
}
.foot__links a:hover { color: var(--cream); }
.foot__copy { grid-column: 1 / -1; font-size: var(--t-micro); letter-spacing: 0.14em; color: var(--cream-4); }

/* ==========================================================================
   persistent corner affordance
   ========================================================================== */

.dock {
  position: fixed; z-index: 60;
  right: clamp(1rem, 3vw, 2rem);
  bottom: clamp(1rem, 3vw, 2rem);
  display: inline-flex; align-items: center; gap: 0.6rem;
  padding: 0.7rem 1.1rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: rgba(9, 14, 28, 0.88);
  backdrop-filter: blur(14px);
  font-size: var(--t-micro);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--cream-2);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.4s var(--ease), transform 0.4s var(--ease), border-color 0.3s var(--ease), color 0.3s var(--ease);
}
.dock[hidden] { display: none; }
.dock.is-in { opacity: 1; transform: none; }
.dock:hover, .dock:focus-visible { color: var(--cream); border-color: rgba(242, 239, 233, 0.28); }
.dock__dot { width: 5px; height: 5px; border-radius: 50%; background: var(--cream-4); flex: none; }

/* ==========================================================================
   4 · rabbit — the research branch

   The link is the diagram. Nodes and edges sit dim; a route threads them on
   arrival and again on hover, drawn by stroke-dashoffset so it reads as a
   trail being followed rather than a shape appearing.
   ========================================================================== */

.rabbit { border-top: 1px solid var(--rule-soft); text-align: center; }

.trail {
  position: relative;
  display: grid;
  justify-items: center;
  gap: 0.55rem;
  width: 100%;
  max-width: 44rem;
  margin: clamp(2rem, 5vh, 3rem) auto 0;
  padding: clamp(1.5rem, 3.5vh, 2.25rem) clamp(1.25rem, 4vw, 2.5rem) clamp(1.25rem, 3vh, 2rem);
  border: 1px solid var(--rule);
  transition: border-color 0.35s var(--ease), background-color 0.35s var(--ease);
}
.trail:hover, .trail:focus-visible {
  border-color: rgba(167, 139, 250, 0.34);
  background: rgba(109, 77, 255, 0.045);
}

.trail__net { width: min(100%, 22rem); height: auto; overflow: visible; }

/* Limbs are drawn, not revealed: each is dashed to its own length and the
   dash is retracted, so growth runs root -> branch -> source. Depth 2 waits
   for depth 1 to finish, which is what makes it read as propagation rather
   than as eleven lines fading in together. */
.limb {
  stroke: rgba(167, 139, 250, 0.5);
  stroke-width: 1.2;
  stroke-linecap: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}
.node { fill: var(--cream-4); opacity: 0; }
.node--root { fill: var(--violet-hi); }
.node--branch { fill: var(--cream-3); }

/* Without JS or motion the tree is simply drawn. */
.trail__net .limb { stroke-dashoffset: 0; }
.trail__net .node { opacity: 1; }

body.is-ready .trail:not(.is-tracing) .limb { stroke-dashoffset: 1; }
body.is-ready .trail:not(.is-tracing) .node { opacity: 0; }

.trail.is-tracing .limb { animation: grow 0.55s var(--ease) forwards; }
.trail.is-tracing .limb--1:nth-of-type(1) { animation-delay: 0.10s; }
.trail.is-tracing .limb--1:nth-of-type(2) { animation-delay: 0.16s; }
.trail.is-tracing .limb--1:nth-of-type(3) { animation-delay: 0.22s; }
.trail.is-tracing .limb--2 { animation-duration: 0.5s; }
.trail.is-tracing .limb--2:nth-of-type(4) { animation-delay: 0.66s; }
.trail.is-tracing .limb--2:nth-of-type(5) { animation-delay: 0.72s; }
.trail.is-tracing .limb--2:nth-of-type(6) { animation-delay: 0.78s; }
.trail.is-tracing .limb--2:nth-of-type(7) { animation-delay: 0.72s; }
.trail.is-tracing .limb--2:nth-of-type(8) { animation-delay: 0.78s; }
.trail.is-tracing .limb--2:nth-of-type(9) { animation-delay: 0.72s; }
.trail.is-tracing .limb--2:nth-of-type(10) { animation-delay: 0.78s; }
.trail.is-tracing .limb--2:nth-of-type(11) { animation-delay: 0.84s; }

/* Hover re-runs the whole growth from the root. */
.trail:hover .limb, .trail:focus-visible .limb { animation: grow 0.45s var(--ease) forwards; }
.trail:hover .node, .trail:focus-visible .node { animation: land 0.35s var(--ease) forwards; }

@keyframes grow { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
@keyframes land { from { opacity: 0; } to { opacity: 1; } }

/* ---- and then it keeps running ------------------------------------------
   The tree is the map; this is the traffic on it. Each limb carries one
   short dash — 15% of its own length, whatever that length is, because
   pathLength normalises it — travelling root to tip. Depth 2 is delayed by
   exactly the time depth 1 takes, so a packet appears to leave a branch and
   enter the limbs below it rather than eleven dashes moving at once. Both
   depths share the cycle, so the loop never drifts.

   The nodes answer on the same clock: root at the top of the cycle, branch
   when the first packet lands, leaf when the second does.
   ------------------------------------------------------------------------ */

.flow {
  stroke: #E6DEFF;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-dasharray: 0.15 0.85;
  stroke-dashoffset: 1;
  opacity: 0;
  /* The packet carries its own light, so it reads as something moving along
     the limb rather than as a lighter section of it. */
  filter: drop-shadow(0 0 3px rgba(167, 139, 250, 0.9));
}

.trail.is-tracing .flow { animation: run 3.4s linear infinite; }
/* Held until the tree it runs along has finished being drawn. */
.trail.is-tracing .flow--1 { animation-delay: 1.5s; }
.trail.is-tracing .flow--2 { animation-delay: 2.72s; }

@keyframes run {
  0%   { stroke-dashoffset: 1;    opacity: 0; }
  8%   { opacity: 0.95; }
  30%  { opacity: 0.95; }
  36%  { stroke-dashoffset: 0.02; opacity: 0; }
  100% { stroke-dashoffset: 0.02; opacity: 0; }
}

/* Arrival, not a heartbeat: a node brightens the moment a packet reaches it
   and settles straight back. transform-box keeps the scale about the
   circle's own centre rather than the viewBox origin. */
.trail.is-tracing .node {
  transform-box: fill-box;
  transform-origin: center;
  animation: land 0.4s var(--ease) forwards, arrive 3.4s var(--ease) infinite;
}
.trail.is-tracing .node--root   { animation-delay: 0s,    1.5s; }
.trail.is-tracing .node--branch { animation-delay: 0.6s,  2.72s; }
.trail.is-tracing .node--leaf   { animation-delay: 1.14s, 3.94s; }
.trail.is-tracing .node--leaf:nth-of-type(even) { animation-delay: 1.2s, 3.99s; }

@keyframes arrive {
  0%, 100% { transform: scale(1); filter: none; }
  4%       { transform: scale(1.85); filter: brightness(2.4); }
  16%      { transform: scale(1); filter: none; }
}

/* Hover restarts the growth, so the flow layer stands down for its duration
   rather than running along limbs that are being redrawn under it. */
.trail:hover .flow, .trail:focus-visible .flow { animation: none; opacity: 0; }
.trail:hover .node, .trail:focus-visible .node { animation: land 0.35s var(--ease) forwards; }

@media (prefers-reduced-motion: reduce) {
  .limb { stroke-dashoffset: 0 !important; animation: none !important; }
  .node { opacity: 1 !important; animation: none !important; transform: none !important; }
  .flow { animation: none !important; opacity: 0 !important; }
}

.trail__name {
  margin-top: 0.35rem;
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(1.125rem, 2.6vw, 1.5rem);
  letter-spacing: 0.18em;
  color: var(--cream);
}
.trail__expand {
  font-size: var(--t-micro);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--cream-4);
}
.trail__line {
  max-width: 46ch;
  margin-top: 0.35rem;
  font-size: 0.875rem;
  color: var(--cream-3);
  text-wrap: pretty;
}
.trail__go {
  margin-top: 0.35rem;
  font-size: var(--t-micro);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--cream-2);
  transition: color 0.3s var(--ease);
}
.trail:hover .trail__go, .trail:focus-visible .trail__go { color: var(--violet-hi); }

/* ==========================================================================
   responsive
   ========================================================================== */

/* Small screens get the same page, scaled — not a different one. The two
   overrides that used to live here (a 2-column grid for .frontier__text and
   .level, and margin-left stagger on the levels) predated those sections
   being centred, and were quietly rendering the old layout on mobile only.
   They are gone; the centred rules now apply at every width.

   The instrument readout stays visible too. It does not fit beside the clock
   at 11px, so the whole chrome steps down a size rather than half of it
   disappearing. */
@media (max-width: 46rem) {
  .chrome__line { font-size: 0.5625rem; letter-spacing: 0.1em; }
  .chrome__line--id { letter-spacing: 0.14em; }
  .chrome__key { padding-left: 0.45em; }
  .chrome { gap: 0.75rem; }

  /* On a wide screen the mark is centred in a frame far wider than itself,
     so its silhouette never reaches the corners the chrome sits in. On a
     handset the frame is barely wider than the figure, and a half turn
     swings the ring out over the clock. The stage's top padding is what
     holds the two apart: it has to clear the chrome's four lines, so it is
     set from them rather than from the desktop rhythm. */
  .stage { padding-top: clamp(5.25rem, 10vh, 6rem); }
  /* Which the mark then has to pay for, along with the taller copy a narrow
     column wraps to — hence a larger constant than the desktop budget. */
  .stage__mark { height: clamp(7.5rem, calc(80svh - 22rem), 36rem); }

  .stmt { text-align: left; hyphens: manual; }
  .fig__name { flex: 0 0 100%; }
  .foot { grid-template-columns: 1fr; }
}

/* 360px-class handsets: one more step down, so the readout still fits beside
   the clock rather than running off the edge and being clipped. */
@media (max-width: 24rem) {
  .chrome__line { font-size: 0.5rem; letter-spacing: 0.06em; }
  .chrome { gap: 0.5rem; }
}

/* ==========================================================================
   reduced motion
   Full content parity. The field renders one static frame, level jumps
   become instant state changes, and the redox is drawn already completed.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  [data-stage] { opacity: 1; }
  .dock { opacity: 1; transform: none; }
  .cta__electron { opacity: 1; left: 100%; }
  .cta { border-left-color: var(--violet-hi); border-right-color: var(--violet-hi); }
}
