/* Astral — landing surface, v3: warm field, elevated surfaces, blur arrival.

   v2's structure, copy and wheel are untouched. What changed is the material
   the page is made of, and it is the direction approved on 7 August 2026 after
   the founder compared the page against wire.mn and named two faults: it read
   cold, and it read cheap.

     · The field is warm — §7.1's canvas on §7.1's plum-black ink, not the cool
       sky tokens the page had drifted into. Nothing was warm except the signal,
       which therefore read as an alarm rather than an accent.
     · There is a second surface tier. §7.1 always provisioned two — `canvas`
       and an "elevated or cleaner" `surface` — and only one was ever built. The
       things a reader handles now sit on the second one, under a shadow.
     · Display is Spectral 600 at the §8.3 display size. 400 at 69px is the
       whole of what read as under-set.
     · Arrivals are wire.mn's recipe: opacity, an 18px rise and a 10px blur, all
       three on one duration and one curve. This deliberately reverses the
       earlier rule against preset curves and coupled fade-plus-travel — see
       docs/surfaces/landing.md. The curve was never the problem.

   The reference implementation this is ported from is web/landing-lab/ (hero
   and section 01 only). Everything below section 01 is adaptation, and the
   rules it was adapted under are in the comments where they apply.

   Tokens are redefined here rather than in shared.css. Onboarding and the
   reading surface have not had their warm pass yet, and shared.css is all
   three. Redefining shared.css's own names on :root means every component it
   owns — the skip link, the language control, the buttons, the focus ring —
   follows this page without being restyled one at a time. All three surfaces
   are now warm, so this block is finally collapsible into shared.css; see
   docs/surfaces/onboarding.md for why that was not done as a side effect of a
   colour pass. */

html { scroll-behavior: smooth; }

/* ── the field ──────────────────────────────────────────────────────────── */

:root {
  /* §7.1, verbatim. */
  --canvas:      #F3EFE7;
  --surface:     #FAF7F1;
  --surface-lit: #FFFDF8;   /* the same surface under a pointer */
  --mauve-deep:  #6E5F6C;   /* §7.1's mineral-mauve, carried to 4.9:1 so it
                               can hold type — the atmosphere value is 1.96:1 */

  --mineral:     58, 76, 92;      /* the cool the warm field is lit against */
  --mineral-lit: 72, 116, 146;

  --radius: 8px;
  --radius-sm: 5px;

  /* Three layers, and the widest is the coloured one — wire's structure, tinted
     with the page's own mineral cool rather than a brand hue. A cool shadow
     under a warm surface is what daylight does. A plum-tinted version was built
     and rejected as too heavy. */
  --lift:
    0 1px  2px        rgba(var(--mineral), .040),
    0 10px 24px -14px rgba(var(--mineral), .090),
    0 36px 80px -28px rgba(var(--mineral-lit), .18),
    inset 0 1px 0     rgba(255, 255, 255, .62);

  /* Secondary plates. The same light from further away, so section 01's card
     stays the one object on the page that is being offered to the reader. */
  --lift-sm:
    0 1px  2px        rgba(var(--mineral), .034),
    0 6px  16px -10px rgba(var(--mineral), .075),
    0 22px 48px -26px rgba(var(--mineral-lit), .13),
    inset 0 1px 0     rgba(255, 255, 255, .55);

  /* One curve for the whole page. wire.mn's, and the point of adopting it is
     that everything moves with the same hand — a page with three arrival
     curves reads as three pages. */
  --ease-arrive: cubic-bezier(.22, 1, .36, 1);

  /* ── shared.css's vocabulary, remapped onto the warm field ────────────── */
  --sky-1: #DCD5CA;
  --sky-2: #EAE4D9;
  --sky-3: var(--canvas);

  --ink:   #1C191D;
  --ink-2: #4A434B;   /* 8.3:1 on canvas — secondary running text */
  --ink-3: #706970;   /* 4.9:1 on surface — the quietest text allowed */
  --ink-4: #A79FA2;   /* decoration only, never running text */
  --rule:      #CBC4BA;
  --rule-soft: rgba(28, 25, 29, .07);

  /* Night, warm. The band under section 03 used to be #0A0E12, which is the
     dark end of a cool page; on ivory a blue-black reads as a hole cut in the
     paper rather than as the same page turned down. This is §7.1's plum-black
     taken darker, so the field changes value without changing family. */
  --night:      #171319;
  --night-2:    #241E28;
  --on-night:   #F0EBE3;
  --on-night-2: #A79DA4;
  --rule-night: rgba(240, 235, 227, .15);

  /* The reference's own orange *(founder, 14 August 2026)*, replacing §7.1's
     coral #F05B47. It marks; it does not fill.

     The two companions are re-derived from its hue rather than kept, because
     they exist to satisfy contrast and the old ones were measured against a
     different colour. A filled button is the exception to "marks, does not
     fill": white on #F04E23 is 3.6:1, under §19.1 for a 16px label, so filled
     surfaces and focus rings take the deepened value at 4.7:1 — the same figure
     the coral's deep value carried, so nothing downstream shifts.

     One thing improved on the way. The coral was 2.9:1 against the ivory, just
     under the 3:1 a non-text mark owes its surroundings, which is the whole
     reason `--signal-deep` had to be reached for on focus rings. This is 3.2:1
     and clears it on its own. */
  --signal:        #F04E23;
  --signal-bright: #FF8A66;   /* on night — 7.9:1 there */
  --signal-deep:   #D24019;   /* filled, and focus rings — 4.7:1 with white */
}

body { background: var(--canvas); }

/* Atmosphere at the page level, never behind a section — §21 forbids a gradient
   standing in for hierarchy. Warm at the centre where the reading happens,
   mineral at the edges, so the daylight-sky idea survives a warm field. Fixed,
   so it belongs to the window rather than to any one band. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(138% 108% at 50% 10%, rgba(255, 253, 246, .84) 0%, rgba(255, 253, 246, 0) 56%),
    radial-gradient(124% 100% at 50% 30%, rgba(108, 138, 156, 0) 32%, rgba(108, 138, 156, .23) 100%);
}

/* U+2648–2653 default to emoji presentation, so a browser reaches past --sym
   for a colour font and the twelve arrive as purple tiles. The markup carries
   U+FE0E; this is the second belt. The live page had neither and rendered
   correctly only because macOS ships Apple Symbols. */
.glyph, .chip__glyph, .w-sign, .w-body, .cr-glyph, .chartrefs .g {
  font-variant-emoji: text;
}

/* Non-text contrast: coral on ivory is 2.9:1, just under the 3:1 a focus
   indicator owes its surroundings. The deepened value clears it at 4.1:1. */
:focus-visible { outline-color: var(--signal-deep); border-radius: 2px; }

/* ── typography: sans running text, serif display ───────────────────────────
   Founder request, 7 August 2026. Scoped to this file, so onboarding and the
   reading surface keep the serif body and the two can be compared side by side.

   Two things stay serif deliberately. The specimen reproduces the reading
   surface, and the portrait's prose is the product's own voice — switching it
   here would show a page Astral does not produce. The pull quote is a quotation
   and reads as one.

   IBM Plex Sans carries more x-height than Spectral at the same nominal size —
   measured at 100px, 51 units against 45 — so the size comes down slightly and
   the leading tightens to match. These values are the ones the face was chosen
   at; retune them only against a real page, not against the ratio. */
body { font-family: var(--sans); font-size: 1rem; line-height: 1.66; }
@media (min-width: 720px) { body { font-size: 1.055rem; } }
.sheet__prose { font-family: var(--serif); }

/* ── buttons ────────────────────────────────────────────────────────────────
   shared.css's button on the warm field: the page's radius rather than the
   2px it carried when nothing on the page had a corner, and a shadow that
   belongs to the same light as the surfaces. The unequal vertical padding is
   shared.css's and is load-bearing — Plex's font box is lopsided and every
   Mongolian label here has a descender. Do not even it up. */
.btn {
  background: var(--signal-deep);
  border-radius: var(--radius);
  box-shadow:
    0 1px  2px        rgba(28, 25, 29, .10),
    0 12px 30px -16px rgba(150, 48, 32, .40);
  transition: background .28s var(--ease-arrive),
              transform  .18s var(--ease-arrive),
              box-shadow .18s var(--ease-arrive);
}
.btn::after { display: none; }
.btn:hover {
  background: #C33518;
  transform: translateY(-1px);
  box-shadow:
    0 1px  2px        rgba(28, 25, 29, .10),
    0 16px 34px -16px rgba(150, 48, 32, .46);
}
.btn--small { border-radius: var(--radius-sm); }
.btn--quiet { box-shadow: none; }
.btn--quiet:hover { background: var(--ink); color: var(--canvas); }

/* ── masthead + rail bar ────────────────────────────────────────────────── */
/* Slimmer *(founder, 8 August 2026)*: 34px of padding above and below put the
   bar at 91px on a desktop, which is a navigation header's height on a page
   that has no navigation in it — two items, one of them a language switch.
   24px brings it to about 70px and lets the hero start higher.

   `align-items: center`, not baseline. The lockup is a mark and a word in one
   inline-flex box, so its baseline is the word's; aligning the language
   control to that used to work when both were plain text and now leaves the
   moon hanging above the line. Centring the two boxes is what the eye reads
   as level here, because neither is running text. */
.masthead {
  position: absolute; z-index: 20; top: 0; left: 0; right: 0;
  padding: clamp(14px, 2.4vw, 24px) var(--gutter);
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem;
}
/* Both sit over the wheel, which is line art with glyphs in it, so both carry
   a bloom to stay legible against it — now the field's own ivory rather than
   the cool it was mixed from. */
.masthead .wordmark,
.masthead .lang {
  text-shadow:
    0 0 6px  rgba(250, 247, 241, .95),
    0 0 18px rgba(250, 247, 241, .9),
    0 0 34px rgba(250, 247, 241, .8);
}
/* The moon needs the same bloom and cannot take a text-shadow: it is drawn
   geometry, not glyphs. `drop-shadow` follows the alpha channel, so it haloes
   the ring and the craters rather than a box. Two passes, matching the outer
   two of the three above — a third at this size only muddies the terminator. */
.masthead .wordmark__mark {
  filter:
    drop-shadow(0 0 5px rgba(250, 247, 241, .95))
    drop-shadow(0 0 12px rgba(250, 247, 241, .85));
}
/* Fake mode only. The masthead is positioned against the page, so the server's
   provider notice — which is written into the flow just after it — landed
   underneath, putting --ink type on an --ink banner: the wordmark disappeared
   from every review screenshot. Taking the masthead out of position for this
   one case lets the notice push it down instead. Carried across from
   `reading.css`, which found it and asked for it. The onboarding surface does
   not need it: its `.mast` is already in flow. */
body:has(.astral-mode-banner) .masthead { position: static; }

.railbar {
  position: fixed; z-index: 50; top: 0; left: 0; right: 0;
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem;
  padding: .62rem var(--gutter);
  background: color-mix(in srgb, var(--surface) 84%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  backdrop-filter: blur(14px) saturate(1.2);
  border-bottom: 1px solid var(--rule-soft);
  box-shadow: 0 10px 30px -22px rgba(var(--mineral), .45);
  transform: translateY(-101%);
  transition: transform .5s var(--ease-arrive),
              background .5s var(--ease-arrive),
              border-color .5s var(--ease-arrive);
}
.railbar.is-on { transform: none; }
/* At the close the page makes its own offer; the bar stands down. */
.railbar.is-off { transform: translateY(-101%); }
/* The lockup, at the one size the bar has room for. Everything else about it —
   face, tracking, the mark's proportion — comes from `.wordmark`. */
.railbar__mark { font-size: .92rem; }
/* Where you are, in the bar that follows you.
   The leading is not decoration. This element needs `overflow: hidden` for its
   ellipsis and it clips at the content box — so at line-height 1 the box is 1em
   tall while Plex's font box is 1.29em, and the bottom .145em is cut off. In
   Latin that costs a few descenders; in Mongolian it takes the tails off у, р,
   ц, д and щ, which is most section titles on this page. 1.5 clears it, and the
   bar does not grow: the button beside it is taller either way. */
.railbar__here {
  flex: 1;
  text-align: center;
  font: 400 .78rem/1.5 var(--sans);
  font-variant-numeric: tabular-nums;
  letter-spacing: .02em;
  color: var(--ink-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  transition: color .5s var(--ease-arrive), opacity .3s var(--ease-arrive);
}
@media (max-width: 679px) { .railbar__here { display: none; } }

/* Over the night band the glass goes dark rather than sitting on it like a
   strip of daylight. */
.railbar--dark {
  background: color-mix(in srgb, var(--night) 82%, transparent);
  border-bottom-color: rgba(240, 235, 227, .1);
  box-shadow: none;
}
.railbar--dark .railbar__mark { color: var(--on-night); }
.railbar--dark .railbar__here { color: var(--on-night-2); }

/* ── the dial ───────────────────────────────────────────────────────────────
   The section index, set the way a plate is numbered: a tick that grows and a
   numeral that colours. No pill, no dot, no travelling indicator.

   Three things distinguish this from the version it replaces, and all three
   are why it reads as an index rather than as a control.

     · The numeral is the fixed element and the tick grows away from it, into
       the page. Right-aligning the whole row instead pinned the *tick's* right
       edge, so the numeral shunted sideways every time the reader crossed a
       section — five small movements down one scroll.
     · One colour per row. The tick takes `currentColor`, so numeral and rule
       change together and the current section is one mark, not two marks in
       two states.
     · The tick lengthens without thickening. A hairline that doubles in weight
       reads as a control being switched on; a hairline that extends reads as a
       measurement being taken.

   It also sits further in from the edge — roughly the gutter less a numeral's
   width, rather than hard against the glass — so it belongs to the page's
   margin instead of the browser's. */
.dial {
  position: fixed; z-index: 40;
  right: max(1.25rem, calc(var(--gutter) - 1.5rem)); top: 50%;
  transform: translateY(-50%);
  display: none;
  flex-direction: column; gap: 14px;
  opacity: 0; pointer-events: none;
  transition: opacity .5s var(--ease-arrive);
}
.dial.is-live { opacity: 1; pointer-events: auto; }
@media (min-width: 1200px) { .dial { display: flex; } }
.dial a {
  display: flex; align-items: center; justify-content: flex-end; gap: 9px;
  text-decoration: none;
  padding: 2px 0;
  color: var(--ink-3);
  transition: color .35s var(--ease-arrive);
}
.dial i {
  display: block; width: 14px; height: 1px;
  background: currentColor;
  transition: width .42s var(--ease-arrive);
}
/* Main's numerals, unchanged: the face, the size and the tabular figures are
   the ones the rest of the page numbers its sections in. */
.dial span {
  font: var(--label-sm);
  letter-spacing: .08em;
  font-variant-numeric: tabular-nums;
  color: inherit;
}
.dial a:hover { color: var(--ink-2); }
.dial a.is-here { color: var(--signal); }
.dial a.is-here i { width: 30px; }
/* Over the night band the index inverts with it. */
.dial--dark a { color: var(--on-night-2); }
.dial--dark a:hover { color: var(--on-night); }
.dial--dark a.is-here { color: var(--signal-bright); }

/* ── shared type ────────────────────────────────────────────────────────────
   `.lede` and `.aside` were here and are gone. `.aside` had no user in the
   markup at all, and `.lede`'s only one was section 01's opening paragraph,
   which is now `.card__lede` inside the card. Re-add them the day a section
   needs them rather than keeping rules nothing selects. */

/* The pull quote. Section 01 spends its length widening one sign into a whole
   chart, and this line is the turn it lands on, so it is set as a quotation
   rather than as another paragraph: serif against the sans body, a short rule
   standing in for the quotation marks, and enough size to stop the eye.

   No quotation glyph on purpose. Mongolian sets quotations in guillemets and
   English in inverted commas, and a mark hard-coded here would be wrong in one
   language or the other. A rule is right in both.

   It sits on the field rather than on the card, and that is the whole reason
   the card ends where it does: the card is the exhibit and this is the page's
   reply to it. */
.pull {
  position: relative;
  font-family: var(--serif);
  font-size: clamp(1.5rem, 4.4vw, 2.15rem);
  line-height: 1.28;
  letter-spacing: -.008em;
  font-style: italic;
  color: var(--ink);
  max-width: 20ch;
  padding-top: clamp(1.1rem, 2.4vw, 1.5rem);
}
.pull::before {
  content: ""; position: absolute; top: 0; left: 0;
  width: 3.2rem; height: 2px;
  background: var(--ink);
}

/* Kinetic setting. Each word rides up out of a mask on its own delay, so the
   line assembles left to right instead of arriving whole. The mask is what
   makes it read as typography rather than as a card animating in — there is no
   box moving, only letterforms clearing an edge.

   The curve is the page's one arrival curve. Travel and opacity now share it,
   as everything else here does. */
.pull .w { display: inline-block; overflow: hidden; vertical-align: bottom; }
.pull .w > i {
  display: inline-block; font-style: inherit;
  transform: translateY(105%);
  opacity: 0;
}
.js .pull.is-kin .w > i {
  transform: none;
  opacity: 1;
  transition: transform .82s var(--ease-arrive, cubic-bezier(.22, 1, .36, 1)),
              opacity   .62s var(--ease-arrive, cubic-bezier(.22, 1, .36, 1));
  transition-delay: calc(var(--wi, 0) * var(--wstep, 52ms));
}
/* Nothing to reveal if the words were never hidden. */
.pull.is-flat .w > i { transform: none; opacity: 1; }

/* ═══ arrival ═══════════════════════════════════════════════════════════════
   wire.mn's recipe, adopted in full: opacity, an 18px rise and a 10px blur,
   all three on one duration and one curve, staggered by --i from the script.

   Transitions rather than keyframes, and this is load-bearing. A filling
   animation outranks every ordinary declaration in the cascade, so an element
   that had arrived could not afterwards be told to leave — which is exactly
   what the twelve have to do when a sign is picked. Do not convert these back.

   Nothing is hidden unless the script is running. Without it the page is
   simply finished.
   ═══════════════════════════════════════════════════════════════════════════ */
.js .reveal {
  opacity: 0;
  filter: blur(10px);
  transform: translateY(18px);
  transition:
    opacity   .8s var(--ease-arrive, cubic-bezier(.22, 1, .36, 1)),
    filter    .8s var(--ease-arrive, cubic-bezier(.22, 1, .36, 1)),
    transform .8s var(--ease-arrive, cubic-bezier(.22, 1, .36, 1));
  transition-delay: calc(var(--i, 0) * 90ms);
}
.js .reveal.is-in { opacity: 1; filter: blur(0); transform: none; }

/* ═══════════════════════════════════════════════════  HERO  ═══════════ */
/* No local sky. The page's own vignette is the atmosphere now, and a gradient
   owned by the hero would put a seam where the hero ends. */
.hero {
  position: relative; isolation: isolate;
  min-height: 100svh;
  display: flex; flex-direction: column; justify-content: center;
  padding: clamp(84px, 12vh, 140px) var(--gutter) clamp(90px, 14vh, 130px);
  overflow: hidden;
}

/* ── the wheel ──────────────────────────────────────────────────────────── */
/* Mobile: the wheel rises past the top-right corner — cropped through its
   rings, never through its glyphs — and the words get the field below to
   themselves. */
.wheel {
  position: relative; z-index: 0;
  margin: 0;
  pointer-events: none;
  width: 136vw;
  align-self: flex-start;
  margin-left: -50vw;
  margin-right: -6vw;
  margin-top: -24vw;
  margin-bottom: clamp(-6vw, -2vw, 0px);
}
.wheel svg { width: 100%; height: auto; overflow: visible; }

/* On a phone the hero is taller than the screen whatever it does — the wheel is
   half of it — so `min-height` never binds and the block padding stops centring
   anything: it only pushes the offer further down. Tightened, with the wheel
   brought in from 136vw to 118vw, the headline, the deck and the button all land
   inside 812px instead of the button starting 40px below the fold.

   The composition is the same one: the wheel still rises past the top-right
   corner and is still cropped through its rings. The left offset moved with the
   width on purpose — every glyph position here is width-relative, so it holds at
   every screen size, and -44vw is the value at which no sign or planet straddles
   an edge. At -41vw Capricorn is cut in half. Re-measure if this is ever
   retuned; the contract is that the crop never goes through a glyph. */
@media (max-width: 899px) {
  .hero { padding-block: clamp(56px, 8vh, 84px) clamp(56px, 8vh, 96px); }
  .wheel {
    width: 118vw;
    margin-left: -44vw;
    margin-top: -21vw;
    margin-bottom: -3vw;
  }
}

.hero__type { position: relative; z-index: 2; max-width: 40rem; }

@media (min-width: 900px) {
  .hero { padding-block: clamp(150px, 20vh, 210px) clamp(110px, 15vh, 160px); }
  /* Desktop: the whole artifact, held inside the field. The type column may
     overlap its rim; a local pool of daylight keeps the words in front. */
  .wheel {
    position: absolute; z-index: 0;
    width: min(50vw, 720px);
    align-self: auto;
    margin: 0;
    right: clamp(8px, 3vw, 60px); top: 50%;
    transform: translateY(-50%);
  }
  .hero__type { max-width: min(44rem, 50vw); }
  .hero__type::before {
    content: ""; position: absolute; z-index: -1;
    inset: -4rem -8rem -4rem -6rem;
    border-radius: 50%;
    background: radial-gradient(closest-side,
      rgba(252, 250, 245, .95) 0%,
      rgba(252, 250, 245, .6) 58%,
      rgba(252, 250, 245, 0) 100%);
    filter: blur(26px);
  }
}
@media (min-width: 1500px) {
  .wheel { width: min(46vw, 780px); right: clamp(24px, 4vw, 90px); }
}

/* §8.3 puts desktop display at 88/84. The page ran 69px at weight 400, which is
   the whole of what read as under-set. */
h1 {
  font-family: var(--serif);
  font-weight: 600;
  font-size: clamp(2rem, 9.4vw, 3.4rem);
  line-height: .985;
  letter-spacing: -.02em;
}
@media (min-width: 900px) { h1 { font-size: clamp(2.9rem, 6.4vw, 5.5rem); line-height: .955; } }
h1 .l1, h1 .l2, h1 .l3 { display: block; }
/* Two-tone rather than the roman/italic pair the page had. §8.4 asks for a
   semantic reason for editorial italics and there is not one here; a change of
   value says the same thing without borrowing a fashion device. */
h1 .l3 { font-style: normal; color: var(--mauve-deep); }

.deck {
  margin-top: clamp(1.4rem, 3vw, 2.1rem);
  max-width: 32rem;
  font-size: 1.08em;
  line-height: 1.54;
  color: var(--ink-3);
}
.cta { margin-top: clamp(1.8rem, 4vw, 2.6rem); }
.cta-note {
  margin-top: 1.2rem;
  font: 400 .92rem/1.5 var(--sans);
  color: var(--ink-3);
}
.cta-note b { font-weight: 600; color: var(--ink); }

/* Unnumbered: 01 belongs to the section itself, a screen below. All this has
   to say is that the page continues. */
.scrollcue {
  position: absolute; z-index: 2;
  right: var(--gutter); bottom: clamp(26px, 5vh, 46px);
  display: none; align-items: center;
  text-decoration: none;
}
.scrollcue i { display: block; width: 1px; height: 46px; background: var(--rule); }
@media (min-width: 900px) { .scrollcue { display: flex; } }

/* ── wheel ink ──────────────────────────────────────────────────────────── */
/* The artifact is graded. Structure whispers, positions speak, and the one
   determined thing — the exact Jupiter–Pluto opposition — is the only
   saturated line on the page's largest object. */
.w-ring { fill: none; stroke: var(--ink); stroke-width: 1.1; opacity: .32; }
.w-ring--faint { opacity: .15; }
.w-signdiv { stroke: var(--ink); stroke-width: 1.1; opacity: .24; }
.w-grad line { stroke: var(--ink); stroke-width: 1; opacity: .16; }
.w-cusp { stroke: var(--ink); stroke-width: 1; opacity: .18; }
.w-cusp--angle { stroke-width: 1; opacity: .12; stroke-dasharray: 4 7; }
.w-housenum {
  font: 400 15px/1 var(--sans);
  fill: var(--ink); opacity: .34;
  text-anchor: middle; dominant-baseline: central;
}
.w-tick { stroke: var(--ink); stroke-width: 1.4; opacity: .5; }
.w-leader { stroke: var(--ink); stroke-width: 1; opacity: .22; }

.w-sign {
  font-family: var(--sym); font-size: 30px;
  fill: var(--ink); opacity: .48;
  text-anchor: middle; dominant-baseline: central;
}
.w-body {
  font-family: var(--sym); font-size: 38px;
  fill: var(--ink);
  text-anchor: middle; dominant-baseline: central;
}
.w-body--text { font-family: var(--sans); font-size: 21px; font-weight: 500; fill: var(--ink-3); }
.w-body--sun { font-size: 44px; }

.w-asp { fill: none; }
.w-asp--hard { stroke: var(--ink); stroke-width: 1.25; opacity: .26; }
.w-asp--soft { stroke: var(--ink); stroke-width: 1; opacity: .11; }
.w-asp--signal { stroke: var(--signal); opacity: .95; stroke-width: 1.8; }

/* staged assembly — sun → structure → positions → relationships */
.wheel .w-signs, .wheel .w-grad, .wheel .w-ring, .wheel .w-houses,
.wheel .w-bodies > *, .wheel .w-aspects line {
  transition: opacity .9s var(--ease-arrive);
}
.wheel.is-staged .w-signs,
.wheel.is-staged .w-grad,
.wheel.is-staged .w-ring,
.wheel.is-staged .w-houses,
.wheel.is-staged .w-bodies > *,
.wheel.is-staged .w-aspects line { opacity: 0; }

.wheel.is-staged .w-bodies > .w-body--sun { opacity: 1; }
.wheel.is-staged .w-aspects line {
  stroke-dasharray: var(--len, 1000);
  stroke-dashoffset: var(--len, 1000);
}

.wheel.is-2 .w-ring { opacity: .32; }
.wheel.is-2 .w-ring--faint { opacity: .15; }
.wheel.is-2 .w-signs { opacity: 1; }
.wheel.is-2 .w-grad { opacity: 1; }
.wheel.is-2 .w-houses { opacity: 1; }

.wheel.is-3 .w-bodies > * { opacity: 1; transition-delay: calc(var(--i, 0) * 34ms); }
.wheel.is-3 .w-tick { opacity: .5; }
.wheel.is-3 .w-leader { opacity: .22; }

.wheel.is-4 .w-aspects line {
  opacity: 1;
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 1.05s var(--ease-arrive), opacity .5s var(--ease-arrive);
  transition-delay: calc(var(--i, 0) * 65ms);
}
.wheel.is-4 .w-asp--hard { opacity: .26; }
.wheel.is-4 .w-asp--soft { opacity: .11; }
.wheel.is-4 .w-asp--signal { opacity: .95; }

/* ═══════════════════════════════════════════════════  BANDS  ══════════ */
/* The rail narrowed from 6.5rem to 4.5rem when the band measure went to 68rem
   *(founder, 14 August 2026)*. The wider measure only ever grew rightward — the
   card's left edge never moved — so the page came out leaning right, with 180px
   of margin on one side and the content starting 172px in on the other.

   The rail is the only thing that can give: the section number already sits at
   the gutter, so pulling the number closer to the screen edge is not available,
   and closing the gap between the number and the content is. Two rem back moves
   every band 32px left and leaves the number about 50px of air, which is still
   enough for it to read as a margin note rather than a label stuck to the
   title. 3rem was tried and is too tight — at that width the number and the
   title start to look like one column. */
:root { --rail: 0px; }
@media (min-width: 900px) { :root { --rail: 4.5rem; } }

/* The full-bleed hairline between sections is gone. It was half of the
   wireframe diagnosis — a page held together by 15%-black rules — and it is
   redundant now that the sections carry surfaces. What separates them is what
   the design contract always said carried the most weight: whitespace rhythm
   and the numbered rail. The footer keeps its rule, because a colophon is the
   one place a line is still the right word. */
/* Trimmed about a fifth at every width *(founder, 8 August 2026)*. It was
   clamp(4.5rem, 12vw, 8.5rem), which put 272px between one section's last line
   and the next section's first on a 1440 screen — enough that a reader
   scrolling at speed crossed a screenful of nothing between arguments.

   The proportion is what is preserved: the floor, the slope and the ceiling all
   come down by the same fraction, so the rhythm is the same rhythm at a
   phone width as at a desktop one. Separation on this page is whitespace and
   the numbered rail — there is no hairline to fall back on — so this is as far
   as it goes without the sections starting to run together. */
/* The band measure *(founder, 14 August 2026)*. One number for every section,
   raised from 56rem, and section 01's separate 64rem folded into it.

   It comes from the redesign branch, which set its content column at 68rem and
   was right about it: the page is read at 1440 far more often than the old
   measure assumed, and 56rem left 372px of nothing down the right-hand side of
   every section while the argument crowded itself on the left. The sections
   were not balanced, they were merely left-aligned.

   68rem is the width at which the card, the two editions and the stages each
   get the room the redesign gave them, and it lands the block within a few
   pixels of that page's own edges — the card starts at 172 against its 176, and
   ends at 1260 against its 1264. The numbered rail is the difference in how
   they get there: the redesign set its number above the content, this page
   keeps it out in the left margin, and that margin is now the reason the card
   sits symmetrically on the screen (172 to its left, 180 to its right) rather
   than needing to be centred.

   This is a measure, not a layout: the things inside a band that are set to a
   reading measure — the titles at 19ch, the pull quote at 20ch, the specimen at
   40rem — keep their own limits and do not grow with it. */
:root { --band: 68rem; }

.band {
  position: relative;
  padding: clamp(3.6rem, 9.2vw, 6.6rem) var(--gutter);
}

/* The dial's lane *(founder, 14 August 2026)*. From 1200px up the dial is
   fixed furniture standing in the right margin, and until now nothing told the
   bands it was there — so every element that reaches a band's right padding
   edge ran underneath it. Section 03 was the worst of them: its chart-fact
   aside is sticky and full-height, so the index sat on the orb figures for the
   whole length of the specimen. The card, the stages, the editions and the hint
   all reached the same edge.

   Reserving the lane in the padding, rather than nudging each offender, is what
   makes it stay fixed — a band cannot put anything under the dial by accident
   afterwards. The width is the dial's own footprint: it sits `--gutter` less
   1.5rem from the edge and is about 3.5rem wide at its widest, which is the
   current section, whose tick is the long one. 4rem past the gutter clears that
   with about 2rem of air.

   This binds only where the page is space-limited. Past about 1340px the bands
   are already held by the 68rem measure and sit well inside the lane, so the
   layout at a full desktop width is unchanged by it. */
@media (min-width: 1200px) {
  .band { padding-right: calc(var(--gutter) + 4rem); }
}
@media (min-width: 900px) {
  .band { display: grid; grid-template-columns: var(--rail) minmax(0, 1fr); }
}
.band__rail { display: none; }
@media (min-width: 900px) { .band__rail { display: block; } }
.band__num {
  display: block;
  font: 400 .95rem/1 var(--sans);
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  letter-spacing: .1em;
  padding-top: .5rem;
}
.band__body { max-width: var(--band); }

.band__title {
  font-family: var(--serif);
  font-weight: 600;
  font-size: clamp(1.62rem, 4.2vw, 2.75rem);
  line-height: 1.09;
  letter-spacing: -.014em;
  max-width: 19ch;
}
.band__title ~ * { margin-top: clamp(1.5rem, 3.2vw, 2.4rem); }
.band__title + .stages,
.band__title + .editions,
.band__title + .specimen,
.band__title + .facts { margin-top: clamp(2.2rem, 4.6vw, 3.2rem); }

@media (max-width: 899px) {
  .band__body { position: relative; }
  .band__title::before,
  .card__title::before {
    content: attr(data-num);
    display: block;
    font-size: .82rem; letter-spacing: .1em;
    color: var(--ink-3);
    margin-bottom: 1.1rem;
    font-variant-numeric: tabular-nums;
  }
}

/* Set in sentence case, against the uppercase treatment these labels used to
   share. Letterspaced caps are a Latin device: they work because Latin capitals
   are simple, narrow and familiar in a row. Mongolian Cyrillic caps at .7rem
   with .14em between them lose the features that tell Ү from У and Ө from О,
   and the tracking pulls the words apart faster than it opens them up.

   Overridden here rather than in shared.css: onboarding and the reading surface
   use .tag too, and they are not this session's to change. If this holds up, it
   should move down into shared.css and the override should go. */
.tag {
  font: 500 .78rem/1.4 var(--sans);
  letter-spacing: .01em;
  text-transform: none;
}

/* ── 01 · the card ──────────────────────────────────────────────────────────
   The section is one elevated object: the argument on the left, the twelve on
   the right. Lighter than the field, small radius, and the shadow does the
   separating — no border is needed while the surface sits above the ground.

   Picking a sign collapses the grid, flies the chosen glyph to the card's top
   right as the way back, and the answer arrives in the grid's place. */

/* The card used to be the one section allowed to run wider than the others —
   64rem against their 56rem — because two columns will not fit in a reading
   measure. Every band is 68rem now, which is wider than the exception was, so
   the exception is gone and the card simply fills its band. */

.card {
  position: relative;
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--lift);
  padding: clamp(30px, 4.4vw, 60px);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(28px, 4.6vw, 64px);
  align-items: start;
}

.card__title {
  font-family: var(--serif);
  font-weight: 600;
  font-size: clamp(1.62rem, 3.2vw, 2.5rem);
  line-height: 1.09;
  letter-spacing: -.014em;
  margin-bottom: .72em;
}
.card__lede { color: var(--ink-3); max-width: 42ch; }

/* The twelve and the answer share one cell, so the card is already as tall as
   whichever is taller and nothing below it moves when the swap happens. */
.card__right {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
}
.card__right > .picker__grid,
.card__right > .picker__out { grid-row: 2; grid-column: 1; align-self: start; }

.picker__prompt {
  font: 500 .92rem/1.4 var(--sans);
  letter-spacing: .005em;
  color: var(--ink-3);
  margin-bottom: 1.05rem;
}
/* The question stops being asked once it is answered, but it keeps its space so
   the answer does not jump up into it. */
.card[data-state="answered"] .picker__prompt { visibility: hidden; }

/* ── the twelve ─────────────────────────────────────────────────────────── */
.picker__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.picker__btn {
  appearance: none; border: 0; margin: 0;
  background: var(--surface);
  color: var(--ink);
  padding: 13px 14px 15px;
  text-align: left; cursor: pointer;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 9px;
  align-items: center;
  font: 500 .94rem/1.3 var(--sans);
  transition: background .16s linear;
}
.picker__btn:hover { background: var(--surface-lit); }
.picker__btn .glyph {
  font-family: var(--sym); font-size: 1.19rem; line-height: 1;
  color: var(--ink-3);
  transition: color .16s linear;
}
.picker__btn:hover .glyph { color: var(--signal); }
.picker__btn small {
  display: block;
  font-size: .78rem; font-weight: 400; color: var(--ink-3);
  line-height: 1.3;
  margin-top: .12em;
}
/* The ring goes *inside* the tile. At the sheet's +3px offset it is drawn
   outside the button's own box, where two different things eat it and the
   reader gets three-quarters of a rectangle:

   - the grid clips at its padding box — `overflow: hidden`, which is what gives
     the twelve their shared corner radius — so a tile on any outer edge loses
     the whole of the side facing out;
   - a tile in the middle is overpainted by every sibling that comes after it in
     the grid, none of which is raised, so it loses its bottom and its right.

   Which quarter goes missing therefore depends on which of the twelve is
   focused, which is why it read as intermittent. It is seen most on the way
   back from an answer, because `unpick()` returns focus to the sign you came
   from and that is the one moment the script moves focus itself.

   Inset, the ring lies inside the tile that owns it: nothing clips it and no
   sibling can reach it. Same device, for the same reason, as `.fact > summary`
   further down. -2px keeps it clear of the grid's own 4px corner arc. */
.picker__btn:focus-visible { outline-offset: -2px; }

/* The same ring, stated rather than inferred. `:focus-visible` is the browser's
   guess at how the page is being driven and it goes false as soon as a mouse is
   used, so the ring that came back with you on a tabbed first choice was gone by
   the second — see the note in `landing.js`'s `unpick()`. Set by the script on
   the sign it returns focus to, and cleared when focus leaves.

   It restates the whole ring rather than only the offset because it is not
   `:focus-visible` and inherits nothing from it: `shared.css` sets the width and
   `--signal`, this page's `:focus-visible` deepens it to `--signal-deep` and
   rounds it. Those three have to stay in step with this one. */
.picker__btn.is-returned {
  outline: 2px solid var(--signal-deep);
  outline-offset: -2px;
  border-radius: 2px;
}
@media (max-width: 1099px) and (min-width: 900px) {
  .picker__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .picker__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .picker__btn { padding: 11px 11px 13px; font-size: .9rem; column-gap: 7px; }
  .picker__btn small { font-size: .72rem; }
}

/* Out on the same recipe it came in on, only quicker.

   `.reveal` is carried in the selector to win a specificity tie: the arrival
   rule above is `.js .reveal.is-in`, also three classes, and it is later in the
   file. Without the fourth class the twelve arrive and then refuse to leave —
   the cascade, not the script, keeps them on screen. */
.card[data-state="answered"] .picker__grid.reveal {
  opacity: 0;
  filter: blur(8px);
  transform: translateY(-10px);
  transition:
    opacity   .34s var(--ease-arrive),
    filter    .34s var(--ease-arrive),
    transform .34s var(--ease-arrive);
}
.card[data-state="answered"] .picker__grid { pointer-events: none; }

/* ── the answer ─────────────────────────────────────────────────────────── */
/* The grid takes a transform on the way out, which puts it in the positioned
   paint phase and above an ordinary sibling. The answer needs its own stacking
   context or it arrives underneath the thing it replaces. */
.picker__out {
  max-width: 44ch;
  visibility: hidden;
  position: relative; z-index: 1;
}
.card[data-state="answered"] .picker__out { visibility: visible; }

.picker__head {
  font-family: var(--serif);
}
.picker__head b {
  display: block; font-weight: 600;
  font-size: clamp(1.5rem, 2.7vw, 2rem);
  line-height: 1.12;
  letter-spacing: -.012em;
}
.picker__head i {
  display: block; font-style: normal;
  font: 400 .84rem/1.4 var(--sans); color: var(--ink-3);
  margin-top: .5rem;
}
.picker__note {
  margin-top: 1.35rem;
  color: var(--ink-2);
}
/* The line that sets the quote up. It is the last thing the answer says and the
   quote below is its reply, so it sits in the quote's voice already — serif
   italic, at a size that reads as the run-up rather than the arrival. Mongolian
   puts a question here and English a statement; both work, because what the
   style has to carry is the hanging beat, not the grammar. */
.picker__turn {
  margin-top: 1.15rem;
  font-family: var(--serif);
  font-style: italic;
  font-size: clamp(1.06rem, 2.6vw, 1.2rem);
  line-height: 1.4;
  color: var(--ink-3);
}

/* The chosen glyph, parked top right. Its travel from the grid is measured in
   the script; everything here is only its resting state. */
.card__chip {
  position: absolute;
  top: clamp(22px, 3vw, 36px);
  right: clamp(22px, 3vw, 36px);
  display: inline-flex;
  align-items: center;
  gap: 9px;
  background: none;
  border: 0;
  padding: 6px 8px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  color: var(--ink-3);
  font: 400 .78rem/1 var(--sans);
  transition: color .16s linear, background .16s linear;
}
.card__chip:hover { color: var(--ink); background: rgba(28, 25, 29, .04); }
.chip__glyph {
  font-family: var(--sym);
  font-size: 1.7rem;
  line-height: 1;
  color: var(--signal);
  display: inline-block;
}
.chip__back { opacity: 0; transition: opacity .16s linear; }
.card__chip:hover .chip__back,
.card__chip:focus-visible .chip__back { opacity: 1; }

/* And once the answer is there, the quote closes up to meet it — the two are
   one sentence and should not sit a section's worth of air apart. Only once it
   is there: with no sign chosen the quote is the section's own closing line and
   keeps the full gap from the card. */
.card + .pull { margin-top: clamp(2.2rem, 4.4vw, 3.2rem); }
.card[data-state="answered"] + .pull { margin-top: clamp(1.4rem, 2.6vw, 2rem); }

/* ── 02 · stages ────────────────────────────────────────────────────────────
   Not cards in the sense section 01 is — this is the page explaining, not
   something the reader handles. But three diagrams drawn in hairlines directly
   on the field is where the wireframe read was worst, and the three *are*
   objects: one process in three states. So each takes a plate at the quieter
   lift, which gives the line art a ground to sit on and makes the sequence
   read as three things rather than three columns of text. */
.stages { list-style: none; margin: 0; padding: 0; display: grid; gap: clamp(1.4rem, 3vw, 1.8rem); }
@media (min-width: 820px) {
  .stages { grid-template-columns: repeat(3, 1fr); gap: clamp(1.2rem, 2.2vw, 1.8rem); }
}
.stage {
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--lift-sm);
  padding: clamp(22px, 3vw, 32px);
  display: flex; flex-direction: column;
}
.stage h3 {
  display: flex; align-items: baseline; gap: .7rem;
  font: 500 1.06rem/1.3 var(--sans);
  margin: 1.3rem 0 .65rem;
}
.stage h3 span {
  font-family: var(--serif); font-style: italic;
  color: var(--ink-3); font-size: .95rem;
  min-width: 1.5ch;
}
.stage p { color: var(--ink-2); font-size: .96em; line-height: 1.6; }
.stage__fig { margin: 0; width: min(150px, 40%); }
@media (min-width: 820px) { .stage__fig { width: 100%; max-width: 168px; } }

.m-ring { fill: none; stroke: var(--ink); stroke-width: 1; opacity: .3; }
.m-div { stroke: var(--ink); stroke-width: 1; opacity: .22; }
.m-dot { fill: var(--ink); opacity: .78; }
.m-asp { stroke: var(--ink); stroke-width: .9; opacity: .16; }
.m-asp--hard { opacity: .32; }
.m-asp--signal { stroke: var(--signal); opacity: 1; stroke-width: 1.5; }
.m-line { fill: var(--ink); opacity: .26; }
.m-line--signal { fill: var(--signal); opacity: .95; }

/* The grounding declaration. Everything above it is the mechanism, and a reader
   who has just been told about ephemerides and arc-minutes is carrying more
   than they were asked to. This line takes it back off them, so it has to stop
   the run rather than continue it.

   What changes is the field, which is the page's own way of changing subject:
   section 03 drops to night, this lightens by one step and runs to the gutters.
   Bleeding out of the measure is the point — it is the width that says the
   explaining has stopped. Serif, and roman rather than italic: Spectral is what
   the page uses when it means something, and the three stages above are sans,
   so the face changing is half the register change.

   Colour is not carrying this alone. Width, type, size and the silence above it
   all say the same thing, so it still lands in a high-contrast or
   forced-colours rendering where the wash does not survive. */
/* `.band__body >` only to outrank `.band__title ~ *`, which sets the standard
   gap between everything in a band and would otherwise win on specificity. */
.band__body > .ground {
  position: relative; isolation: isolate;
  margin-top: clamp(3.2rem, 7vw, 5rem);
  margin-inline: calc(var(--gutter) * -1);
  padding: clamp(2.4rem, 5.6vw, 3.6rem) var(--gutter);
}
/* Edge to edge, and not by half measures. Held to the body's own box this reads
   as a card dropped on the page. The field has to reach the window the way
   section 03's does, so the wash runs a viewport past each side and
   `overflow-x` on the band takes care of the rest. */
.ground::before {
  content: ""; position: absolute; z-index: -1;
  top: 0; bottom: 0; left: -100vw; right: -100vw;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--sky-1) 40%, var(--canvas)) 0%,
    color-mix(in srgb, var(--sky-1) 62%, var(--canvas)) 100%);
}
/* And clipped at the band, so the overflow is contained where it is made rather
   than left for the viewport to swallow. `clip`, not `hidden`: hidden would
   make this band a scroll container, and section 03's aside is sticky. */
.band:has(.ground) { overflow-x: clip; }
.ground p {
  font-family: var(--serif);
  font-size: clamp(1.2rem, 3.3vw, 1.6rem);
  line-height: 1.44;
  letter-spacing: -.006em;
  color: var(--ink);
  /* Wider than it looks. Spectral's `ch` is about four fifths of Plex's, so the
     40ch this held while it was set in sans came back as a narrow column
     stranded in a full-width band. 48ch is the same three lines the sans set. */
  max-width: 48ch;
}
/* Arrival, separately from the section reveals. The field is what settles — it
   washes up from nothing while the words travel a few pixels under it, and the
   wash outlasts the travel, so the page reads as changing temperature around
   the sentence rather than as a panel sliding in. Nothing here moves as a box:
   `.ground` itself never transforms, and it never blurs — a full-bleed blur
   would smear the wash across the gutters. */
.js .ground.reveal { opacity: 1; filter: none; transform: none; transition: none; }
.js .ground::before {
  opacity: 0;
  transform: scaleY(.82);
  transform-origin: 50% 100%;
  transition: opacity 1.15s var(--ease-arrive, cubic-bezier(.22, 1, .36, 1)),
              transform 1.35s var(--ease-arrive, cubic-bezier(.22, 1, .36, 1));
}
.js .ground.is-in::before { opacity: 1; transform: none; }
.js .ground p {
  opacity: 0;
  transform: translateY(7px);
  transition: opacity .8s var(--ease-arrive, cubic-bezier(.22, 1, .36, 1)) .18s,
              transform 1.1s var(--ease-arrive, cubic-bezier(.22, 1, .36, 1)) .12s;
}
.js .ground.is-in p { opacity: 1; transform: none; }

/* ── 03 · the night band ────────────────────────────────────────────────────
   The one hard field change on the page, and the one place in this port that
   had to be re-thought rather than translated: it was designed to sit inside a
   cool page and now has a warm one around it. The band keeps its job — the
   specimen is the only object on the landing the customer can actually look at
   — and changes family, from blue-black to §7.1's plum-black. The lift in the
   gradient carries a little of the mauve, so the band reads as this page with
   the lights down rather than as a different site. */
.band--dark {
  background:
    radial-gradient(120% 70% at 50% 0%, rgba(52, 43, 56, .78) 0%, rgba(23, 19, 25, 0) 64%),
    var(--night);
  color: var(--on-night);
}
.band--dark .band__num { color: var(--on-night-2); }
.band--dark .band__title::before { color: var(--on-night-2); }

/* Scoped to `#s03`, not to `.band--dark`. This is the aside's layout — a third
   column for what the voice is reading from — and it has nothing to do with the
   field being dark. It rode on `.band--dark` only because section 03 was the
   one dark band on the page; the moment another band takes that class it would
   force that band's body into a 40rem column too. */
@media (min-width: 1200px) {
  #s03 {
    grid-template-columns: var(--rail) minmax(0, 40rem) minmax(16rem, 1fr);
    column-gap: clamp(2.5rem, 5vw, 6rem);
  }
  #s03 .band__aside {
    grid-column: 3;
    align-self: start;
    position: sticky; top: 7rem;
    max-width: 21rem;
    padding-top: 8.2rem; /* the first hairline meets the sheet's top edge */
  }
}

/* The margin of the night band: what the voice is reading from. */
.band__aside { margin-top: clamp(2.5rem, 6vw, 3.5rem); max-width: 26rem; }
.chartrefs { list-style: none; margin: .4rem 0 0; padding: 0; }
.chartrefs li {
  display: flex; align-items: baseline; gap: .7rem;
  padding: .62rem 2px .62rem 0;
  border-top: 1px solid var(--rule-night);
  font: 400 .85rem/1.4 var(--sans);
  color: var(--on-night-2);
}
.chartrefs li:last-child { border-bottom: 1px solid var(--rule-night); }
.chartrefs .g {
  font-family: var(--sym);
  font-size: 1rem; line-height: 1;
  color: var(--on-night);
  min-width: 2.4ch;
}
.chartrefs b {
  font-weight: 400;
  flex: none;
  margin-left: auto;
  padding-left: .5rem;
  font-variant-numeric: tabular-nums;
  color: var(--on-night-2);
}
.chartrefs .is-signal { box-shadow: inset 2px 0 0 var(--signal-bright); padding-left: .8rem; }
.chartrefs .is-signal, .chartrefs .is-signal .g { color: var(--on-night); }
.chartrefs__note {
  margin-top: .9rem;
  font: 400 .8rem/1.62 var(--sans);
  color: var(--on-night-2);
  max-width: 24rem;
}

/* ── 03 · the specimen, as a sheet ──────────────────────────────────────────
   The evidence is the artifact, not a quotation about it: the opening of the
   reading surface itself, lit, floating clear of the field, and cropped by
   nothing but its own ending. It is real text, not a screenshot — same markup
   vocabulary as the reading surface — so it reflows at 320px, stays selectable
   and localizes with everything else.

   The sheet is warm now, because the page is. It is the reading surface's own
   paper, and when that surface has its warm pass the two have to agree — a
   landing showing a cooler page than the product prints is showing a page
   Astral does not produce. Recorded as a dependency in the surface note. */

.specimen { margin: 0; max-width: 40rem; }

.specimen__stage {
  position: relative;
  perspective: 1300px;
  perspective-origin: 42% 36%;
}

/* A lit page bounces light onto whatever it is held above, and a shadow needs
   something to fall on: on a field this close to black, none of the depth below
   reads without it. It also does most of the work of seating the sheet in the
   band — the field is not asked to jump from paper to near-black in one pixel.
   Widest and brightest toward the near, lit edge. */
.specimen__stage::before {
  content: "";
  position: absolute;
  /* Fixed rather than percentage bleed: a percentage of the sheet is wider than
     the gutter on a phone and pushes the document sideways. */
  inset: -110px -70px -80px;
  background:
    radial-gradient(54% 48% at 36% 42%,
      rgba(214, 199, 178, .24) 0%,
      rgba(214, 199, 178, .09) 46%,
      rgba(214, 199, 178, 0) 74%),
    radial-gradient(82% 68% at 46% 48%,
      rgba(146, 184, 209, .13) 0%,
      rgba(146, 184, 209, 0) 78%);
  pointer-events: none;
}

/* Turned to face up and to the right, so we are reading it from its left.
   Resting is the default, so no-JS and reduced motion land on the finished
   composition rather than on a pose waiting to be corrected. */
.specimen__sheet {
  position: relative; z-index: 1;
  transform: rotateX(3.2deg) rotateY(14deg) rotateZ(-1.2deg);
  transform-origin: 46% 44%;
  transition: transform 1.3s var(--ease-arrive, cubic-bezier(.22, 1, .36, 1));
  transition-delay: 80ms;
  /* drop-shadow, not box-shadow: it reads the sheet's alpha, so the shadow
     dissolves with the page instead of drawing a hard edge under the fade.
     Thrown down and to the right, away from the light; the last two are the
     page spilling light back onto the field, which is what stops the edge
     reading as a cut. */
  filter:
    drop-shadow(1px 1px 1px rgba(0, 0, 0, .6))
    drop-shadow(14px 20px 26px rgba(0, 0, 0, .5))
    drop-shadow(38px 54px 64px rgba(0, 0, 0, .45))
    drop-shadow(0 0 34px rgba(226, 210, 186, .14))
    drop-shadow(0 0 96px rgba(198, 186, 170, .16));
}
/* Arrives further off and further turned. */
.js .specimen:not(.is-in) .specimen__sheet {
  transform: rotateX(8deg) rotateY(27deg) rotateZ(-3.2deg) scale(.94);
}

/* The page. Lit from the left — the near edge under this rotation — so the
   sheet carries a real raking gradient rather than being a flat rectangle.
   Deliberately not #FFF: this is the ivory the reading surface is printed on,
   which is also a shorter fall to the night around it. */
.sheet__page {
  --sheet-ink: #1C191D;
  --pad-b: clamp(1.1rem, 3vw, 1.8rem);
  position: relative;
  border-radius: var(--radius-sm);
  padding: clamp(1.7rem, 4.4vw, 2.9rem) clamp(1.5rem, 4.2vw, 3.1rem) var(--pad-b);
  background: linear-gradient(102deg, #FDFBF6 0%, #F3EFE7 40%, #D3CABB 100%);
  color: var(--sheet-ink);
  /* An excerpt, so the page does not end — it leaves the field. Measured from
     the bottom rather than as a percentage of the whole: the paragraph wraps to
     twice as many lines on a phone, and a percentage would cut somewhere
     different at every width. This lands on the second paragraph everywhere. */
  --fade: 165px;
  -webkit-mask-image: linear-gradient(180deg,
    #000 calc(100% - var(--fade)),
    rgba(0, 0, 0, .26) calc(100% - var(--fade) * .45),
    rgba(0, 0, 0, 0) 100%);
  mask-image: linear-gradient(180deg,
    #000 calc(100% - var(--fade)),
    rgba(0, 0, 0, .26) calc(100% - var(--fade) * .45),
    rgba(0, 0, 0, 0) 100%);
}

/* The edges that turn away from us fall into shadow, so the sheet meets the
   band in a middle tone instead of a cut. This is the difference between an
   object lit in the same room and a bright rectangle pasted onto black. */
.sheet__page::after {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background:
    linear-gradient(102deg,
      rgba(23, 19, 25, 0) 14%,
      rgba(23, 19, 25, .07) 42%,
      rgba(23, 19, 25, .17) 68%,
      rgba(23, 19, 25, .36) 100%),
    linear-gradient(180deg,
      rgba(23, 19, 25, .17) 0%,
      rgba(23, 19, 25, 0) 24%);
}

/* The reading page's masthead, reproduced: the same lockup at the same rank,
   because that is what sits above a portrait. */
.sheet__mark {
  font-size: .96rem;
  color: var(--ink);
  margin-bottom: clamp(2rem, 5.4vw, 3.2rem);
}

.sheet__doc { margin: 0; }

/* Sentence case and the sans, following `reading.css`'s `.folio__edition`
   rather than this page's own label grammar: this block is a reproduction of
   another page, and the letterspaced caps it used to take are the treatment
   that file argues against for ҮНДСЭН ХӨРӨГ specifically. */
.sheet__edition {
  display: flex; align-items: center; gap: .8rem;
  font: 500 .78rem/1.4 var(--sans);
  letter-spacing: .005em;
  color: var(--ink-3);
}

/* The reader's own name, which is the only thing of this rank on the reading
   page. `.folio__title`'s proportions, scaled to the sheet. */
.sheet__title {
  margin-top: clamp(1.1rem, 2.8vw, 1.55rem);
  font-family: var(--serif);
  font-weight: 600;
  font-size: clamp(1.6rem, 5.6vw, 2.3rem);
  line-height: 1.06;
  letter-spacing: -.023em;
  color: var(--ink);
  text-wrap: balance;
}
/* `reading.css`'s `.folio::after`: the full measure, giving out to the right.
   The 3.2rem stub that used to sit here belonged to the older folio and was
   never what the reading page draws. */
.sheet__folio::after {
  content: ""; display: block;
  height: 1px;
  margin-top: clamp(1.6rem, 4.2vw, 2.4rem);
  background: linear-gradient(90deg,
    rgba(23, 19, 25, .32) 0%,
    rgba(23, 19, 25, .30) 44%,
    rgba(23, 19, 25, 0) 100%);
}

/* The first section's own title. Centred caps in the serif at weight 600 —
   `reading.css`'s `.prose h2`, at the sheet's scale. Without it the specimen
   showed prose starting straight after a name, which is not how a portrait
   arrives: every reading is in named sections, and this is the only place on
   the landing page that shows one. No chapter number, because Essential does
   not carry them. */
.sheet__section {
  text-align: center;
  font-family: var(--serif);
  font-weight: 600;
  font-size: clamp(.98rem, 3vw, 1.1rem);
  line-height: 1.34;
  letter-spacing: .085em;
  text-transform: uppercase;
  text-wrap: balance;
  color: var(--ink);
  margin-bottom: 1.3em;
}

.sheet__prose {
  margin-top: clamp(1.6rem, 4.2vw, 2.3rem);
  font-size: clamp(1rem, 2.9vw, 1.065rem);
  line-height: 1.76;
}
/* The second paragraph exists to be dissolved, so it is clipped to exactly the
   height the fade covers. Held in step with --fade: paragraph one always ends
   just as the page starts leaving, at every width. */
.sheet__prose p + p {
  margin-top: 1.16em;
  max-height: calc(var(--fade) - var(--pad-b));
  overflow: hidden;
}
.sheet__prose .lead {
  font-size: 1.1em;
  line-height: 1.6;
  letter-spacing: -.006em;
  color: var(--ink);
}
.sheet__prose .lead + p { margin-top: 1.28em; }

/* The phone keeps the turn — it is the whole idea of the section — but takes it
   gentler. A narrow column has no width to spare to foreshortening, and the
   measure has to survive down to 320px. */
@media (max-width: 899px) {
  .specimen__stage { perspective: 900px; perspective-origin: 40% 34%; }
  .specimen__stage::before { inset: -70px -18px -60px; }
  .specimen__sheet {
    transform: rotateX(2.2deg) rotateY(8.5deg) rotateZ(-1deg);
    filter:
      drop-shadow(1px 1px 1px rgba(0, 0, 0, .6))
      drop-shadow(9px 14px 20px rgba(0, 0, 0, .5))
      drop-shadow(22px 34px 44px rgba(0, 0, 0, .42))
      drop-shadow(0 0 50px rgba(212, 198, 178, .1));
  }
  .js .specimen:not(.is-in) .specimen__sheet {
    transform: rotateX(5.5deg) rotateY(17deg) rotateZ(-2.4deg) scale(.95);
  }
  /* Foreshortening eats into the right margin; give the text somewhere to go. */
  .sheet__page { padding-right: clamp(2.1rem, 8vw, 3.1rem); }
}

.specimen figcaption {
  margin-top: clamp(1.6rem, 3.4vw, 2.1rem);
  font: 400 .8rem/1.62 var(--sans);
  color: var(--on-night-2);
  max-width: 30rem;
}

/* ── 04 · editions ──────────────────────────────────────────────────────────
   The two products, and the one place on the page where the reader is choosing
   rather than reading — so they take the surface tier. Both take exactly the
   same one: §"The two launch products" is explicit that In-Depth must not look
   like the real product against a downgraded Quick. What distinguishes them is
   the measure below the scope, which is the writing's own shape.

   The hairline over each card is gone with the rest of them; the plate is what
   says "specification" now. The hint below keeps its rule, because a rule
   spanning both cards is the device that lets one sentence answer them both. */
.editions { display: grid; gap: clamp(1.4rem, 3vw, 1.8rem); }
@media (min-width: 760px) {
  .editions {
    grid-template-columns: 1fr 1fr;
    /* Column gap only. `gap` would apply to the rows as well, and once the
       cards became subgrids those rows are the cards' *insides* — a 32px gutter
       landed between the price and the scope, between the scope and the list,
       and between the list and the figures, adding 96px to both cards and
       replacing spacing the children already carry themselves. The scope's
       `.9rem`, the list's `1.5rem` and the hairline above it are the rhythm;
       the grid should not be inventing a second one on top. */
    column-gap: clamp(1.4rem, 2.6vw, 2rem);
    row-gap: 0;
    /* Four rows the pair shares: the name and price, the scope, the section
       list, the figures. The list row takes the slack so the figures stay at
       the foot — the same job the `auto` margin did under flex, and it keeps
       doing it below 760px where these rules do not apply. */
    grid-template-rows: auto auto 1fr auto;
  }
}
/* The two cards carry scopes and section lists of different lengths, so without
   help the fact rows sit at different heights and the pair reads as misaligned.
   Column flex with the slack absorbed *above the facts* — see
   `.edition__facts` — puts both sets of figures at the foot of their card,
   where the grid has already made the cards equal height, so Length and Ready
   in line up across the pair at every width, whatever the copy does. */
.edition {
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--lift-sm);
  padding: clamp(24px, 3.2vw, 36px);
  display: flex; flex-direction: column;
}

/* ── the glow ───────────────────────────────────────────────────────────────
   The reference's pricing-card treatment *(founder, 14 August 2026)*, taken off
   its live page. It is **two layers**, which is what the first pass here got
   wrong and why ours read fainter than theirs:

     1. a backlight — a rectangle the shape of the card, 8px larger on every
        side, filled with the accent, blurred 16px, at 20% opacity, sitting
        behind the card;
     2. the card itself, with a 1px accent border and two accent shadows at zero
        offset breathing on a 3.5s loop — 14→20px at 22→30%, 24→26px at 12→15%.

   The box-shadows alone are a rim. The backlight is the part that puts light on
   the field around the card, and it is most of what the eye reads as "glow".

   The accent is ours, `--signal`, not the reference's own orange: the glow is a
   device, not a colour import, and this page kept its palette when the redesign
   was set aside.

   On the pointer, both layers are off at rest and come up on hover — which is
   how the reference actually drives it too. Its featured card holds the
   backlight at opacity 1 and the rest sit at 0, raised by script on hover. On
   touch there is no hover to give, so both cards are lit from the start.

   That split also settles a question the always-on version left open. Lighting
   one card of two would make it the recommended one, and Quick and In-Depth are
   equally polished siblings — the foundation is explicit that In-Depth must not
   look like the real product beside a downgraded Quick. Hover marks whichever
   card the reader is considering, which is the reader's choice and not ours;
   touch lights both, which favours neither.

   All three layers are box-shadows, including the backlight. The obvious build
   is the reference's own — a blurred element behind the card — and it was tried
   here and abandoned: a running animation makes `.edition` a stacking context,
   and a `z-index: -1` pseudo-element inside a stacking context paints *above*
   its own element's background, so the orange washed across the cream instead
   of sitting behind it. A box-shadow has no such problem. It is always drawn
   outside the border box and always behind the element, whatever the stacking.

   `0 0 16px 8px` is that backlight expressed as a shadow: spread 8px is the
   -8px inset, blur 16px is the blur. It holds steady while the two rim shadows
   breathe, which is how the reference has it — their keyframes only touch the
   shadows, and the backlight's own movement is a separate spring in script. */
@keyframes edition-glow {
  0%, 100% {
    box-shadow:
      0 0 0 1px var(--signal),
      0 0 16px 8px color-mix(in srgb, var(--signal) 20%, transparent),
      0 0 14px color-mix(in srgb, var(--signal) 22%, transparent),
      0 0 24px color-mix(in srgb, var(--signal) 12%, transparent);
  }
  50% {
    box-shadow:
      0 0 0 1px var(--signal),
      0 0 16px 8px color-mix(in srgb, var(--signal) 20%, transparent),
      0 0 20px color-mix(in srgb, var(--signal) 30%, transparent),
      0 0 26px color-mix(in srgb, var(--signal) 15%, transparent);
  }
}

/* The glow lives on a layer of its own, and that is what makes it come up *and*
   go back down *(founder, 14 August 2026)*.

   Two earlier builds put it on the card and both were lopsided. `box-shadow`
   cannot be transitioned and animated at once — whichever runs second wins — so
   the first snapped on arrival, and the second, which faded in and then handed
   over to the animation on a delay, snapped on the way *out*: removing an
   animation returns the property to its base value without a transition, so
   there was nothing for the shadow to ease from.

   Moving the glow to `::after` removes the conflict rather than timing around
   it. The layer owns the box-shadow outright and breathes continuously; what
   hover changes is its `opacity`, which is an ordinary transitioned property
   and therefore symmetric by construction — the same curve, the same duration,
   in both directions, with no keyframe handover to get right.

   Two things fall out of it. The card keeps `--lift-sm`, so the glow now sits
   *with* the lift instead of replacing it. And because the animation runs even
   while the layer is invisible, the breathing is already mid-cycle when it
   fades up — an opacity-0 element is not painted, so this costs nothing.

   `z-index: -1` puts the layer behind the card's own background. That works
   only while `.edition` opens no stacking context of its own, which is why the
   animation must stay on the pseudo-element and not move back up here.

   **The card itself declares no `transition`, and must not.** `.js .reveal`
   carries the page's arrival — opacity, filter and transform over .8s — as a
   `transition` on this same element, at the same specificity, earlier in the
   sheet. Anything setting `transition` here replaces that list outright, and
   the two cards then skip the fade and unblur that every other element on the
   page arrives with. That is exactly what an earlier version of this block did
   by transitioning `border-color`.

   So the accent ring is not a border any more. It is the `0 0 0 1px` layer of
   the glow's own shadow — a spread-only shadow is a 1px ring, it rides the same
   opacity as the rest of the glow, and it needs nothing from the card. It also
   costs no layout, where a real border would have.

   `inset: 0`, so the layer's box is the card's box exactly. It was `-1px` on
   the reasoning that the ring had to clear the card's edge to avoid being
   covered — which was simply wrong: a box-shadow is drawn *outside* the border
   box and can never be painted over by its own element's background. All the
   inset did was start the ring a pixel out, leaving a hairline of field showing
   between the card and its own outline. Flush against the edge is both correct
   and what it looks like it should do. */
:root { --glow-in: .45s; }

.band--dark .edition { position: relative; }

.band--dark .edition::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  opacity: 0;
  transition: opacity var(--glow-in) var(--ease-arrive);
  animation: edition-glow 3.5s cubic-bezier(.37, 0, .63, 1) infinite;
  pointer-events: none;
}

/* A pointer can ask. Nothing is lit until it does. */
@media (hover: hover) and (pointer: fine) {
  .band--dark .edition:hover::after { opacity: 1; }
}

/* A finger cannot. Both cards carry the treatment from the start. */
@media (hover: none) {
  .band--dark .edition::after { opacity: 1; }
}

/* Both cards read off the same four rows *(founder, 14 August 2026)*, so the
   section lists begin on one line across the pair.

   Under flex each card sized its own rows, and the two scopes are not the same
   length — Premium's runs a line longer — so Essential's list started high and
   Premium's started low, and the two outlines never agreed. Every row now takes
   the height of the taller card's content, which is the only way to align them
   without hard-coding a height that Mongolian and English would need different
   values for.

   Placed *after* the flex declaration above, not folded into the earlier media
   query with `.editions`. A media query adds no specificity, so `.edition`
   inside one still loses to a plain `.edition` further down the sheet — which
   is exactly what happened on the first attempt: the rows appeared on the
   parent and the cards stayed flex.

   Padding is identical on both cards, so it offsets both subgrids equally and
   the pair still agrees with itself. Below 760px the cards stack, each is alone
   in its row, and none of this applies. */
@media (min-width: 760px) {
  .edition {
    display: grid;
    grid-template-rows: subgrid;
    grid-row: 1 / -1;
  }
}
.edition header { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.edition h3 {
  font-family: var(--serif); font-weight: 600;
  font-size: clamp(1.4rem, 3.4vw, 1.75rem); letter-spacing: -.012em;
}
.edition__price { font: 500 1rem/1 var(--sans); color: var(--ink); font-variant-numeric: tabular-nums; }
/* The `auto` that used to close this margin is gone. It existed to push the
   figures to the foot of the card so Length and Ready in lined up across the
   pair — right when the cards held a sentence and two rows, and wrong now that
   they hold a list of five against a list of eight. The slack it absorbed had
   to go *somewhere*, and with the section list underneath it landed between
   Essential's prose and the hairline above its list: a rule floating in 150px
   of nothing, which reads as a bug rather than as breathing room.
   The slack is taken at the foot instead — see `.edition__facts` — where it is
   plain whitespace with no rule sitting in it. Do not put the `auto` back
   here. */
.edition__scope { margin: .9rem 0 0; color: var(--ink-2); font-size: .97em; line-height: 1.6; max-width: 26rem; }

/* The headings the portrait arrives in *(founder, 8 August 2026)*, in place of
   the measure bar that stood here through two attempts. The bar's problem was
   never its drawing: it could only say *more*, and more is the least
   interesting difference between these two. Premium is not a longer Essential —
   it goes into Inner Life, Mind and Expression, Work and Under Pressure, which
   Essential does not touch — and a named list is the only form that says so.

   Ordered rather than unordered, because the order is the reading's order and
   the numbers are real ordinals from `sections.py`. They are `aria-hidden` and
   the `<ol>` supplies the counting a screen reader announces, so the numbers
   are not read out twice.

   The rule above is the same hairline the fact list sits under elsewhere on the
   page; it separates two kinds of fact rather than decorating. */
.secs {
  margin: 1.5rem 0 0; padding: 1.1rem 0 0;
  list-style: none;
  border-top: 1px solid var(--rule-soft);
}
.secs li {
  display: flex; gap: .7rem;
  font: 400 .855rem/1.75 var(--sans);
  color: var(--ink-2);
}
.secs b {
  flex: none; min-width: 1.15rem;
  font: 400 .72rem/1.75 var(--sans);
  color: var(--ink-4);
  font-variant-numeric: tabular-nums;
}

/* `margin-top: auto` is what makes Length and Ready in line up across the pair:
   the grid stretches both cards to the taller one's height, and the auto hands
   the shorter card's slack to the gap above its fact rows. It belongs here and
   not on `.edition__scope`, which had it once and lost it — the slack has to
   land somewhere, and the only place it can land without a hairline sitting in
   the middle of it is the foot of the card, below `.secs`'s rule rather than
   above it. Aligned figures are most of what makes the two cards read as
   siblings; the cost is 70–150px of air here, because Essential lists five
   sections against Premium's eight.

   The 1.15rem that used to be this element's top margin is now its padding: an
   `auto` margin resolves to 0 when there is no free space to absorb, so on the
   taller card — the one setting the height, with no slack by definition — the
   gap above the figures would otherwise disappear entirely and the rows would
   sit flush against the last section. Padding is the floor; the auto is the
   slack. */
.edition__facts { margin: auto 0 0; padding-top: 1.15rem; display: grid; gap: .35rem; }
.edition__facts > div { display: flex; gap: .6rem; font: 400 .86rem/1.5 var(--sans); }
.edition__facts dt { color: var(--ink-3); min-width: 5.4rem; }
.edition__facts dd { margin: 0; color: var(--ink-2); }

/* The upgrade note is an offer, not a disclaimer — and more than that, it is
   the one place on the page where Astral leans over and tells the reader what
   it would actually do. A left rule would make it a blockquote, which is the
   wrong grammar: nobody is being quoted. It takes a rule spanning both cards
   instead, and what sits under it is a spoken aside rather than a
   specification: serif italic, larger than the cards' prose, at full ink. */
.hint {
  margin-top: clamp(2.4rem, 4.8vw, 3.3rem);
  padding-top: clamp(1.35rem, 2.8vw, 1.85rem);
  border-top: 1px solid var(--rule);
}
.hint p {
  font-family: var(--serif);
  font-style: italic;
  font-size: clamp(1.08rem, 2.7vw, 1.3rem);
  line-height: 1.5;
  letter-spacing: -.006em;
  color: var(--ink);
  max-width: 46ch;
}

/* ── 05 · facts, folded ─────────────────────────────────────────────────────
   Six questions on one plate. The rows keep their hairlines, and here that is
   the right word: inside a surface a divided list reads as a list, where the
   same rules on the open field read as a wireframe. */
/* Rebuilt to the reference *(founder, 14 August 2026)*: melius.com's own FAQ,
   measured off the live page, with our faces kept.

   The plate is gone and the two columns with it. Theirs is one column of ruled
   rows sitting directly on the field — no surface, no radius, no shadow, no
   padding — and the rules run the full width of the column, closed top and
   bottom. That is the whole device: a list held by lines rather than a card
   holding a list.

   Note this reverses the plate staying light, decided two hours earlier in this
   same session. It is not a contradiction — that call was about a plate, and
   the reference has no plate — but it is the reason the FAQ no longer matches
   the edition cards above it, which do keep theirs. */
.facts {
  /* Their rule, matched by what it composites to rather than by its alpha.
     Theirs is rgba(185,185,185,.4) on a #0E0E0E field, which lands at about
     #525252. The same alpha over our #171319 would come out darker, so it takes
     .27 here to arrive at the same line. `--rule-night` stays at .15 for
     section 03's chart list, which the founder has already signed off. */
  --rule-faq: rgba(240, 235, 227, .27);
  margin: 0;
  display: grid; gap: 0; align-content: start;
  /* Closes the list. Every row draws its own rule above it, so without this the
     last answer would end on nothing. */
  border-bottom: 1px solid var(--rule-faq);
}
.fact { border-top: 1px solid var(--rule-faq); align-self: start; }

/* The row. 16px above and below the question, which is the reference's own
   padding and tighter than the 18–24px this had — their rows are 62px tall and
   the list reads as a list rather than as eight separate things.

   The question is set larger and lighter than it was: their 19px at 400 against
   our 15.2px at 500. The face is ours, per the founder; the size and the weight
   are theirs, because a small bold question and a large light one are not the
   same design. `padding-right` reserves the mark's lane — the label is a bare
   text node with nothing to flex against, so the mark is positioned rather than
   laid out. */
.fact > summary {
  position: relative;
  display: block;
  padding: 1rem 2.5rem 1rem 0;
  cursor: pointer;
  list-style: none;
  font: 400 1.19rem/1.4 var(--sans);
  color: var(--on-night);
  transition: color .22s var(--ease-arrive);
}
.fact > summary::-webkit-details-marker { display: none; }
.fact > summary::marker { content: ""; }
.fact > summary:hover { color: var(--signal-bright); }
.fact > summary:focus-visible { outline-offset: -2px; }

/* The mark, built the way theirs is: two 1px bars of the same length, the
   vertical one turning to lie on the horizontal one. Closed it is a plus; open
   the turning arm has become the minus.

   This is not the same as turning the whole mark a quarter and dropping an arm,
   which is what stood here. Both end on a minus, but theirs reads as one stroke
   swinging down and ours read as the mark rotating — at 11px and a fifth of a
   second the difference is small, and it is still the difference between
   matching the reference and approximating it.

   Their curve, too: a symmetric ease-in-out rather than this page's arrival
   curve, which is shaped to land things and would make a 1px bar look like it
   overshot. */
.fact > summary::before,
.fact > summary::after {
  content: "";
  position: absolute;
  right: 0; top: 50%;
  width: 11.4px; height: 1px;
  background: var(--on-night);
  transition: background-color .22s var(--ease-arrive);
}
.fact > summary::before {
  transform: translateY(-50%) rotate(90deg);
  transition: transform .2s cubic-bezier(.37, 0, .63, 1),
              background-color .22s var(--ease-arrive);
}
.fact > summary::after { transform: translateY(-50%); }
.fact[open] > summary::before { transform: translateY(-50%) rotate(180deg); }
.fact > summary:hover::before,
.fact > summary:hover::after { background: var(--signal-bright); }

/* The panel's height is animated open and shut in landing.js, so it has to clip
   its own contents on the way — without this the paragraph is visible in full
   from the first frame and only the box moves. */
.fact__a { padding-bottom: 2rem; overflow: hidden; }
/* Their answer is set at the question's own size and only a shade below it in
   value — 250 against 255 — where this had a smaller, much greyer paragraph at
   a 30rem measure. The value is theirs; the size comes back down a step
   *(founder, 14 August 2026)*, from 19px to 17.3px against the question's 19px.

   That is a deliberate departure from the reference and the one place the FAQ
   does not follow it. At our column the answer runs about 114 characters a line
   where theirs runs 94 — our band is 68rem against their 56 — and a long line
   is the wrong place to also be at the question's full size. The step down buys
   back a little of the hierarchy the reference gives up, which a wider measure
   needs more than a narrow one does. */
.fact__a p {
  color: var(--on-night);
  font-size: 1.08rem; line-height: 1.45;
  opacity: .92;
}
.fact a {
  color: var(--on-night);
  text-decoration-thickness: 1px; text-underline-offset: .18em;
}
.fact a:hover { color: var(--signal-bright); }

/* The fallback opening, for browsers without the Web Animations API. Closing is
   instant here, because a <details> removes its own content before a closing
   transition could run — which is the limitation landing.js exists to get past.

   `.js-fold` is set on each answer by that script as it takes the movement
   over. Without the guard both run at once and fight over the same frames. */
.fact[open]:not(.js-fold) > .fact__a { animation: fact-open .38s var(--ease-arrive) both; }
@keyframes fact-open {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}

/* ── close ──────────────────────────────────────────────────────────────── */
/* The field deepens toward the bottom of the page rather than lightening, which
   is where the vignette is already going: the page ends in the mineral it is
   lit against. No hairline above it — the silence is the separation. */
.close {
  position: relative; isolation: isolate; overflow: hidden;
  /* Down by the same fifth as `.band`, so the close still reads as the widest
     breath on the page without being measured against the old rhythm. */
  padding: clamp(4.4rem, 12.6vw, 8.6rem) var(--gutter) clamp(5.6rem, 16vw, 10.2rem);
  background: linear-gradient(to bottom,
    rgba(243, 239, 231, 0) 0%,
    color-mix(in srgb, var(--sky-1) 34%, var(--canvas)) 62%,
    color-mix(in srgb, var(--sky-1) 62%, var(--canvas)) 100%);
  text-align: left;
}
.close > * { position: relative; z-index: 1; }

/* The zodiac band, turning. Wider than the section so its ends leave through
   the sides rather than stopping inside the frame, and dissolved as it descends
   so it leaves through the bottom instead of being cut by it.

   **That dissolve is no longer a mask on this element** *(founder, 14 August
   2026)*. It was `mask-image: linear-gradient(…)` here, which fades by taking
   alpha away — so the further down the band went, the more of the night's dot
   lattice came through it, and dots showing through an object read as dots
   lying on it. The ramp moved inside the SVG and into the field's own colour;
   see `#cr-descend` in the markup. Nothing about the descent's shape changed,
   only what the band fades *to*. Do not put a mask back here.
   Declared after `.close > *` on purpose — it has to win the position. */
.close > .close__ring {
  position: absolute; z-index: 0;
  left: 50%; bottom: -2rem;
  width: max(112%, 470px); max-width: none; height: auto;
  transform: translateX(-50%);
  pointer-events: none;
}

/* A phone has far less room under the last line, so the band comes in smaller
   there — which also drops its crown clear of the price note and widens the arc
   that fits on screen. */
@media (min-width: 900px) {
  .close > .close__ring { width: max(112%, 700px); }
}

.close-ring__spin {
  transform-box: view-box;
  transform-origin: 600px 850px;
  animation: close-ring-turn 360s linear infinite;
}
@keyframes close-ring-turn { to { transform: rotate(360deg); } }

/* Quieter than the wheel in the hero: this one is the field, not the subject.
   Only the inner rule is still drawn as a stroke — the band itself became a
   solid object on 14 August, and everything about it now lives in `#cr-band`. */
.cr-ring { fill: none; stroke: var(--ink); stroke-width: 1.4; opacity: .16; }
.cr-ring--faint { opacity: .07; }

/* ── the band as an object ──────────────────────────────────────────────────
   What was two stroked circles with glyphs floating between them is one filled
   band with the signs and the spokes cut out of it *(founder, 14 August 2026)*.
   The geometry did not have to move: the glyphs already sat at radius 744, dead
   centre between the old rings at 706 and 782, so the band is a 76-wide stroke
   on that same centreline and the marks fall exactly where they always did.

   Everything in the mask is a knockout — black takes light away — so these are
   the shapes that stay dark, and the ring is what surrounds them. */
/* The spokes stayed at their hairline weight while the band grew — they are
   divisions, and a division that thickens with its band stops reading as a
   ruled line and starts reading as a gap. The glyphs went up with it: at 34px
   they were sized for a 76-wide band and looked marooned in a 112-wide one. */
.cr-cut line { stroke: #000; stroke-width: 1.4; }
.cr-cut text {
  font-family: var(--sym); font-size: 46px;
  fill: #000;
  text-anchor: middle; dominant-baseline: central;
}

/* The band at rest. **Opaque, not a tint.** It was `--ink` at 8.5% and that is
   what put the dot lattice apparently on top of the ring: at that opacity the
   band hides nothing, so the dots behind it came through unchanged and read as
   lying over it. Nothing was in the wrong order — the canvas is a sibling of
   `.close` at `z-index: -1` and paints below it correctly — the band simply was
   not solid enough to be an object.

   So it is a flat fill at full opacity, mixed to exactly the value the tint
   used to composite to. It reads the same and occludes what is behind it, which
   is what a solid object does. Flat rather than following the close's own
   gradient on purpose: an object that tracks the field it sits on is a veil. */
.cr-plate { fill: color-mix(in srgb, var(--ink) 9%, var(--canvas)); opacity: 1; }

/* What the band dissolves into, and the whole reason it can stay opaque while
   it does. This is the field's own colour at the foot of the section — the last
   stop of whatever gradient is painting behind the ring — so the arms run out
   into the field rather than into a hole in it.

   It only has to be exact where the ramp reaches full strength, which is the
   section's bottom edge; higher up the ramp is partial and the field's own
   drift over that span is about four values a channel, so a flat colour is
   under a value out anywhere it can be seen. Measured, not assumed. A stop with
   no `stop-color` is black, so this rule is the floor and every field that
   moves the ground under the ring owes itself an override. */
.cr-descend__stop { stop-color: color-mix(in srgb, var(--sky-1) 62%, var(--canvas)); }

/* The light carries its own colour now, so there is nothing to set here. */
.cr-light { opacity: 1; }

/* Grain over the whole band rather than only the lit part. `overlay` keeps the
   hues and works the value, which is what makes the colour read as a material
   rather than as a gradient — and over the unlit band it gives the plate a
   tooth it did not have. */
.cr-grain { mix-blend-mode: overlay; opacity: .32; }
/* This one is a whole sentence, ending in a full stop, and it was set in
   letterspaced caps — which is why it read worst of the four. Caps make a
   sentence into a sign. Spoken register, sentence case, and enough size to be
   the line that sets up the headline under it. */
.close__kicker {
  font: 400 clamp(1rem, 2.4vw, 1.14rem)/1.55 var(--sans);
  letter-spacing: 0;
  color: var(--ink-3);
  max-width: 30ch;
  margin: 0 0 clamp(1.4rem, 2.8vw, 2rem);
}
.close h2 {
  font-family: var(--serif);
  font-weight: 600;
  font-size: clamp(1.85rem, 5.6vw, 3.6rem);
  line-height: 1.05;
  letter-spacing: -.019em;
  max-width: 20ch;
}
.close .cta { margin-top: clamp(2rem, 5vw, 3rem); }

/* ── footer ─────────────────────────────────────────────────────────────── */
/* Colophon, not a section — and the one hairline left on the page, because a
   colophon is where a line is still the right word. */
.site-footer {
  padding: clamp(1.7rem, 3.4vw, 2.4rem) var(--gutter) clamp(1.9rem, 3.6vw, 2.6rem);
  border-top: 1px solid var(--rule);
  background: color-mix(in srgb, var(--sky-1) 62%, var(--canvas));
  display: grid; gap: .7rem;
  font: 400 .8rem/1.52 var(--sans);
  color: var(--ink-3);
}
.site-footer__mark {
  /* The brand name in the brand's face, at the running size. The moon belongs
     to the masthead lockup; a mark set inside a sentence would be an ornament.
     What this replaces was IBM Plex Sans at .17em — the interface face doing
     duty as a wordmark, which is what the whole 8 August pass took out. */
  /* 1.14em, not 1em: Cormorant's cap height is .632em against IBM Plex
     Sans's ~.698em, so setting the two at the same nominal size leaves the
     mark visibly smaller than the sentence it introduces. */
  font: 700 1.14em/1 var(--display-sc); letter-spacing: .025em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.site-footer nav { display: flex; gap: 1.4rem; }
.site-footer a { text-decoration: none; }
.site-footer a:hover { color: var(--ink); text-decoration: underline; }
.site-footer__fine { max-width: 40rem; }
@media (min-width: 820px) {
  .site-footer { grid-template-columns: auto auto 1fr; align-items: baseline; gap: 2.2rem; }
  .site-footer__fine { justify-self: end; text-align: right; max-width: 34rem; }
}

/* ── narrow ─────────────────────────────────────────────────────────────── */
/* §9.2: the hierarchy survives, the geometry does not have to. The card stops
   being two columns; the twelve go full width under the argument; the chosen
   glyph leaves the corner, because a 350px card has no corner to spare. */
@media (max-width: 899px) {
  .card { grid-template-columns: minmax(0, 1fr); }
  .card__lede { max-width: none; }
  .picker__out { max-width: none; }

  /* One column has no top right corner to park in. The glyph keeps its meaning
     — the way back — by sitting directly above what it replaced, and it carries
     its label, because a touch screen never hovers to find one. */
  .card { row-gap: 20px; }
  .card__left  { order: 0; }
  .card__chip  { order: 1; position: static; align-self: start; padding-left: 0; margin-bottom: 0; }
  .card__right { order: 2; }
  .chip__back  { opacity: 1; }

  /* And in one column the reserved question is 37px of nothing. Its job in two
     columns is to stop the answer sliding up into the space the question held;
     here the chip is already sitting in that space and doing it, so the row
     closes instead. The card settles by that much on the tap, which is a settle
     rather than the jump the reservation exists to prevent. */
  .card[data-state="answered"] .picker__prompt { display: none; }
}

/* ── reduced motion ─────────────────────────────────────────────────────── */
/* The still page is the finished composition, not a disabled one. */
@media (prefers-reduced-motion: reduce) {
  .js .reveal { opacity: 1; filter: none; transform: none; }
  .card[data-state="answered"] .picker__grid.reveal {
    transition: none; filter: none; transform: none;
  }
  /* The grounding band arrives in two parts and neither is a .reveal, so both
     have to be stood down here by name — otherwise the rule above lands the
     wrapper while the wash and the sentence still animate underneath it. */
  .js .ground::before,
  .js .ground p { opacity: 1; transform: none; }
  .railbar { transition: none; }

  /* The glow stops breathing. It is an infinite animation, which is the kind
     this setting exists for — and unlike an arrival there is no "finished"
     state to land on, so the resting frame has to be named.

     What does not change is *when* the glow appears — the hover gating is an
     answer to the pointer, not decoration, and dropping it here would light both
     cards for exactly the readers who asked for less. Nor does the fade: a
     change of light is not the kind of movement this setting is about, and it
     is the same opacity transition in both directions either way.

     Only the breathing stops. The layer then needs its shadow stated outright,
     because with no animation there are no keyframes to supply one and the
     `::after` would have nothing to draw. */
  .band--dark .edition::after {
    animation: none;
    box-shadow:
      0 0 0 1px var(--signal),
      0 0 16px 8px color-mix(in srgb, var(--signal) 20%, transparent),
      0 0 14px color-mix(in srgb, var(--signal) 22%, transparent),
      0 0 24px color-mix(in srgb, var(--signal) 12%, transparent);
  }
}

/* ═══════════════════════════════════  THE NIGHT HALF  ═══════ EXPERIMENT ══ */
/* Founder proposal, 14 August 2026: everything below the specimen goes dark,
   the last section included. Uncommitted and self-contained — delete this block
   and the three class names in `index.html` to put the page back.

   The rule it follows: the field goes dark, the objects on it do not. The
   edition cards and the FAQ plate keep `--surface` and their own ink — see
   "what stays light" below.

   Everything that sits on the open field takes the same treatment: what was
   `--ink` becomes `--on-night`, what was `--ink-2`/`--ink-3` becomes
   `--on-night-2`, hairlines invert from black-on-light to light-on-black at the
   same weight, and the signal goes to its bright variant because `--signal` is
   mixed for a warm field and sits too close to the plum on this one. */

/* ── the lattice ────────────────────────────────────────────────────────────
   Ported from the redesign branch, which took it from the reference and pinned
   it to exact values: a 15px tile carrying one 2px rounded dot, pure white at
   8%. Untinted on purpose — a warm dot at this density greys the whole field —
   and this density is half what an earlier version used, which read as a
   visible pattern rather than as tooth in the paper.

   It scrolls with the page. `background-attachment: fixed` would hold the grid
   still while the sections move through it, which makes it the window's texture
   rather than the page's; the founder settled that on 13 August and it is worth
   not relitigating by accident here.

   `z-index: -1` puts it above the band's own background and below every piece
   of content, which is why the band has to isolate: without a stacking context
   the layer would sink behind the section's dark fill and disappear. */
:root {
  --grid-dot-plate: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='15'%3E%3Crect x='6.5' y='6.5' width='2' height='2' rx='1' ry='1' fill='rgb(255,255,255)' fill-opacity='0.08'/%3E%3C/svg%3E");
}

/* One layer for the whole run, which is why `.night` exists at all.

   Per-section layers were tried first and are wrong: a tile starts at its own
   element's top edge, so each section began the grid at whatever sub-tile
   offset its own position happened to land on — measured at 10.2px, 1.5px,
   14.4px and 12.3px down the page, a jump of up to half a tile at every
   boundary. Worse, those offsets move whenever anything above changes height,
   so opening one FAQ row re-broke the grid below it.

   Putting the field on the wrapper is what makes one layer possible. A layer on
   the wrapper cannot sit between a *child's* background and that child's
   content — so the children give up their backgrounds and the wrapper paints
   the night, the lift at the top of the run and the deepening at the bottom as
   three stops of one background. The two gradients are sized in pixels rather
   than percentages so they stay put as the sections grow. */
.night {
  position: relative;
  isolation: isolate;
  background:
    radial-gradient(120% 800px at 50% 0%,
      rgba(52, 43, 56, .78) 0%, rgba(23, 19, 25, 0) 100%) top / 100% 800px no-repeat,
    linear-gradient(to bottom,
      rgba(23, 19, 25, 0) 0%,
      color-mix(in srgb, var(--night) 88%, #2A2230) 45%,
      color-mix(in srgb, var(--night) 74%, #332A3A) 100%) bottom / 100% 780px no-repeat,
    var(--night);
}

/* The lattice stops at the hairline *(founder, 14 August 2026)*. It ran into
   the colophon as well, on its own layer, and that was wrong about what the
   colophon is: below the rule the page is no longer the night field but a
   record of it — the disclaimer, the terms, the privacy link. Ground for
   reading, not a surface with a texture. The rule now separates two materials
   rather than dividing one.

   It also retires the phase-break problem that layer had to be argued around.
   There is one lattice, on one wrapper, and nothing else carries it. */
.night::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--grid-dot-plate);
  background-size: 15px 15px;
  pointer-events: none;
  z-index: -1;
}

/* The sections are now transparent — the field belongs to the run, not to any
   one of them. They keep only their ink. */
.band--dark,
.close--dark { background: none; }

/* ── the ink on the open field ──────────────────────────────────────────── */
/* The field itself is on `.night` above. What is left here is what the sections
   still own: their type.

   An earlier pass had each band painting its own field, which is what made the
   seams — three radial lifts stacked, each brightest at its own top edge, so
   every boundary drew a hard line across the page. That is gone now rather than
   patched: there is one field and there are no boundaries in it. */
.close--dark { color: var(--on-night); }
.close--dark .close__kicker { color: var(--on-night-2); }
/* The close's price line is gone *(founder, 14 August 2026)* — the page states
   both prices in the hero and again in full in section 04, and repeating them
   a third time under the closing button was the only thing forcing the ring's
   light to work around a piece of copy. `.cta-note` itself stays: the hero
   still uses it. */

/* The turning zodiac band. Drawn in `--ink` at low opacity, which on the night
   field disappears entirely — the close lost its one piece of astrological
   material and became a headline on a dark rectangle. Inverted to `--on-night`
   at values that hold the original relationship: the glyphs still carry more
   than the rules, so it reads as the zodiac rather than as two arcs.

   The opacities are lower than the light version's, not equal to them. Light
   ink on a dark field reads stronger than dark ink on a light one at the same
   alpha, and at .22 the glyphs stopped being the field and started competing
   with the headline. */
.close--dark .cr-ring { stroke: var(--on-night); opacity: .10; }
.close--dark .cr-ring--faint { opacity: .05; }
/* The band is a filled object on this field, not a stroke, so it takes a fill
   rather than the two stroke values that used to stand in for it — and an
   opaque one, for the reason in `.cr-plate`.

   **20%, not the 8.5% it started at** *(founder, 14 August 2026)*. Making the
   band opaque at the value the old tint composited to was arithmetically
   faithful and read as a loss: the translucent version had been picking up the
   lattice and the close's own deepening gradient through itself, and a flat
   fill has neither. Measured against the reference's wordmark at rest — a flat
   `rgb(62,62,62)` on its near-black field, 1.77:1 — this page's band was
   sitting at 1.23:1. 20% puts it at 1.77:1 exactly, so the ring is as
   conspicuous unlit as the thing it is modelled on.

   It lifts the sign cutouts with it: they show the field through the band, so
   the same ratio now separates glyph from ring. */
.close--dark .cr-plate {
  fill: color-mix(in srgb, var(--on-night) 20%, var(--night));
  opacity: 1;
}
/* The night run's own last stop, the same expression `.site-footer--dark` sets
   its field to — the two meet at that edge, so the band's arms and the colophon
   below them run out into the same value. Change the bottom of `.night` and
   this comes with it. */
.close--dark .cr-descend__stop {
  stop-color: color-mix(in srgb, var(--night) 74%, #332A3A);
}

/* ── what stays light ───────────────────────────────────────────────────────
   The edition cards and the FAQ plate keep `--surface` and their own dark ink
   *(founder, 14 August 2026)*. They are objects on the field, not part of it,
   so the night goes around them rather than through them — and nothing inside
   them is touched: a white card carries the same type on a dark band as on a
   light one.

   That leaves the specimen sheet no longer the only lit thing in the dark. It
   stays the brightest and the largest, and it is the only one set in the
   reading's own serif at the reading's own measure, so it still reads as the
   exhibit. Worth watching if more panels arrive down here later.

   Only the edition cards, now: section 05's FAQ gave its plate up when it was
   rebuilt to the reference, and its rows sit on the field like everything else.

   The cards have to take `--ink` back explicitly. `.band--dark` sets `color` on
   the section, and the pieces inside a card that carry no colour of their own —
   the edition headings among them — inherit it straight through the card's
   background. Leaving the background light and the inherited colour inverted is
   a white-on-white fault one layer in. Reset it at the card's root and
   everything under it follows. */
.band--dark .edition { color: var(--ink); }

.band--dark .hint p { color: var(--on-night-2); }
.band--dark .hint { border-top-color: rgba(240, 235, 227, .12); }

/* ── the colophon ───────────────────────────────────────────────────────── */
/* Taken dark too. Not because it is a section — it is not — but because the
   page would otherwise end on a light strip below a night close, which reads
   as the footer belonging to a different page. Say the word and it goes back. */
.site-footer--dark {
  background: color-mix(in srgb, var(--night) 74%, #332A3A);
  border-top-color: rgba(240, 235, 227, .1);
  color: var(--on-night-2);
}
.site-footer--dark .site-footer__mark { color: var(--on-night); }
.site-footer--dark a { color: var(--on-night-2); }
.site-footer--dark a:hover { color: var(--on-night); }
