/* Astral — the portrait onboarding surface, v3: the warm field.

   v2's flow, copy, truth contract and figure are untouched. What changed on
   7 August 2026 is the material, brought over from `landing.css` and
   `reading.css` so that one customer crossing three pages crosses one site.

   The tier rule is the landing's own, and it is a rule rather than a taste:
   **the field carries what the page is saying; the surface tier carries what
   the reader acts on.** On a page that is almost entirely interface that
   sounds like it would put everything on a plate. It does not, because the
   two halves are genuinely different here:

     · The question, its lede, the asides, the review of your own answers and
       the figure are Astral *speaking*. They stay on the canvas.
     · The two places where you choose between offered objects — the three
       answers about time, and the two editions — take plates. So does the
       birthplace list, which floats above the page.
     · Text stays a rule you write on, not a box you type into. An input that
       became a filled well is the one move that would turn this page into a
       form kit; what the second tier does here is *light* the field you are
       working in, which is what v2's improvised white wash was already
       reaching for without having a tier to reach for.

   What v2 established and this pass keeps: one language for "chosen"
   everywhere (lit surface, ink edge, the signal standing at the left); one
   voice for asides, behind the same left rule; the figure showing its
   seventeen seats as ghosts from the start; and the generation field keeping
   a low line of light on the horizon — the same one the reading arrives on,
   and now in the same warm dark.

   Tokens are redefined here rather than in shared.css, exactly as
   `landing.css` and `reading.css` redefine them. All three surfaces are now
   warm, so the block is finally collapsible — but that is a three-file
   refactor and not a side effect of a colour pass. See
   docs/surfaces/onboarding.md. */

/* ── the room ──────────────────────────────────────────────────────────── */
:root {
  --lit: 0;

  /* ── the warm field ──────────────────────────────────────────────────────
     §7.1, verbatim, by way of `landing.css`. Redefining shared.css's own names
     on :root means every component it owns — the skip link, the wordmark, the
     buttons, the focus ring — follows this page without being restyled one at
     a time. */
  --canvas:      #F3EFE7;
  --surface:     #FAF7F1;
  --surface-lit: #FFFDF8;   /* the same surface under a pointer */

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

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

  /* Three layers, the widest of them the coloured one. A cool shadow under a
     warm surface is what daylight does. */
  --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);
  --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);

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

  --ink:   #1C191D;
  --ink-2: #4A434B;
  --ink-3: #706970;
  --ink-4: #A79FA2;
  --rule:      #CBC4BA;
  --rule-soft: rgba(28, 25, 29, .07);

  /* Night, warm — and this closes a seam rather than restyling one. The
     reading surface's arrival picks up the field this page leaves dark,
     unbroken, and it has been #171319 there since its own warm pass while
     this page was still turning the lights out at the cool #0A0E12. The two
     rooms agreed about everything except their colour, across the one
     navigation that matters most in the product. They agree again. */
  --night:      #171319;
  --night-2:    #241E28;
  --on-night:   #F0EBE3;
  --on-night-2: #A79DA4;
  --rule-night: rgba(240, 235, 227, .15);

  /* §7.1's coral, and this surface uses **only the deepened value**. On the
     landing the coral is a mark drawn *over* things — a chord in the wheel, a
     glyph — and it earns its brightness there. Here every use of the signal is
     a small filled shape sitting on a light ground: a 2px tick in the rail, a
     3px bar down the edge of a chosen row, a 15px checkmark, a 1.5px rule
     under an errored field. Filled shapes owe their background 3:1, and the
     coral measures 2.89:1 on the canvas and 2.62:1 on a hovered list row. The
     deepened value clears it everywhere (4.04:1 and 3.67:1), and at three
     pixels wide the two are indistinguishable — the landing made the same
     measurement for its own filled button.

     So: one rule for the whole page rather than a per-element judgement.
     `--signal` and `--signal-bright` stay defined because shared.css's
     vocabulary and the night room speak them. */
  /* **The reference's orange, brought across from the landing — 14 August
     2026.** The landing took melius.com's own `#F04E23` on the founder's call
     and this surface was left on §7.1's coral `#F05B47`, so the site was
     carrying two accents a click apart in the same journey. It carries one now.

     The whole triple moved together, not just the top value: the companions
     exist to satisfy contrast and were measured against the coral. These are
     the landing's own re-derived values, and they transfer exactly because all
     three surfaces sit on the *same* fields — canvas `#F3EFE7`, surface
     `#FAF7F1`, night `#171319` — so every figure below was re-measured here
     rather than inherited on trust. Nothing regressed and one thing improved:
     the mark is 3.15:1 on the canvas where the coral was 2.91:1, under the 3:1
     a non-text mark owes its surroundings. */
  --signal:        #F04E23;
  --signal-bright: #FF8A66;   /* on night — 7.9:1 there */
  --signal-deep:   #D24019;   /* everything this surface fills, and focus rings */
  /* A third value, and this is the surface that needs it. Errors are the only
     running text on the site set in the signal, and neither value above can
     hold text on ivory: the orange measures 3.4:1 and even the deepened value
     reaches only 4.38:1, against the 4.5:1 a 13.9px sentence owes its
     background. This is the same hue carried to 5.35:1 on the canvas and
     5.74:1 on the surface — the identical pair the coral's `#B33418` held, so
     the message reads exactly as legibly as it did. Text only — nothing is
     filled with it — and colour is not the sole carrier either way: an errored
     field takes the signal rule and the message keeps its em dash. */
  --signal-text:   #AE3A1B;

  /* One curve for the whole site. The founder was offered three ways to
     resolve the two motion recipes and chose the landing's throughout,
     explicitly including converting interactions that had already been built
     decoupled; the reading surface converted its after-zone the same evening.
     This was the last surface holding the old pair.

     It governs the *daylight* page — the stage arrivals and the asides. It
     does not govern the generation room, and that is not an exemption left
     unconverted: the room is deliberately identical to the reading surface's
     upgrade wait, which kept its own hand-tuned ambient motion through its
     own warm pass. Two pages, one room, one set of curves. */
  --ease-arrive: cubic-bezier(.22, 1, .36, 1);

  /* shared.css owns the two curves the generation room still runs on.
     --ease-settle is the newer of them, so it
     is the one a cached shared.css can be missing — and an undefined token
     invalidates the whole declaration it appears in, not just the one value,
     which silently turns an animation off rather than degrading it. Resolving
     it once here, with the literal as the fallback, keeps that failure from
     having to be written out at every use. */
  --settle: var(--ease-settle, cubic-bezier(.32, .06, .18, .96));
  /* The third curve, resolved here for the same reason. It is for opacity
     alone: --ease reaches 68% of its output in the first fifth of its duration,
     which is right for a control answering a press and wrong for something
     appearing — it arrives almost fully lit before it has finished moving, and
     reads as a snap rather than a fade. */
  --fade: var(--ease-fade, cubic-bezier(.44, .04, .3, 1));
}

body { min-height: 100svh; background: var(--canvas); }

/* 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; }

/* shared.css's button on the warm field — the page's own radius rather than
   the 2px it carried when nothing here had a corner, and a shadow belonging to
   the same light as the surfaces. The unequal vertical padding is shared.css's
   and is load-bearing; do not even it up. Same block as `landing.css` and
   `reading.css`. */
.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); }
/* Restated, and not as belt-and-braces. shared.css sets
   `.btn--quiet { background: transparent }` and the `.btn` rule above sets a
   filled coral; both are one class deep, so source order decides and this file
   is second. Without this the quiet variant renders as a filled coral button
   keeping the dark border and dark label that only make sense on a transparent
   one — plum-black on coral, 3.78:1, under §19.1. It cost the reading surface a
   visible bug. This page has no `.btn--quiet` yet; the first one it grows would
   otherwise be born broken. */
.btn--quiet { background: transparent; box-shadow: none; }
.btn--quiet:hover { background: var(--ink); color: var(--canvas); }

/* ── sans running text, serif display ───────────────────────────────────────
   Carried over from the landing page, founder's decision on 7 August 2026 after
   it was tried there and kept. Same reasoning, and it matters more here: this
   surface is almost entirely interface — labels, hints, errors, review rows —
   and most of it was already sans. What was still inheriting Spectral from
   shared.css was the running text around it, so the page was setting its
   questions and its answers in two faces that were not doing two jobs.

   What stays serif is display and display only: the question, the value you
   type, the stage numeral, the package name, the generation headline, and the
   two italic echoes that hand an answer back to you. Nothing at body scale.

   IBM Plex Sans carries more x-height than Spectral at the same nominal size —
   51 units against 45, measured at 100px — so the size comes down and the
   leading tightens to match. These are the landing page's numbers; the two
   surfaces are meant to read as one site.

   Scoped to this file rather than moved into shared.css because the reading
   surface has not had its pass yet, and there the serif body *is* the product.
   When it does, these blocks collapse into one there and both copies go. */
body { font-family: var(--sans); font-size: 1rem; line-height: 1.66; }
@media (min-width: 720px) { body { font-size: 1.055rem; } }

/* The same argument as the landing page's, and it is this page that proves it:
   letterspaced caps are a Latin device. At .7rem with .13em between them,
   Cyrillic loses what separates Ү from У and Ө from О — and the labels here are
   not decoration, they are what tells a customer which of four things they are
   being asked for. `.tag` is overridden rather than fixed in shared.css for the
   reading surface's sake; see the note above. */
.tag {
  font: 500 .78rem/1.4 var(--sans);
  letter-spacing: .01em;
  text-transform: none;
}

/* Atmosphere at the page level, never behind a section — §21 forbids a
   gradient standing in for hierarchy. This is the landing page's own vignette,
   with one term added: its two alphas are tied to `--lit`, so the room opens
   as the questions are answered and lands, at the fourth stage, on exactly the
   field the landing page was on. That is not a progress bar in disguise —
   `--lit` is set from genuinely completed stages, it is carried by light
   rather than by a measure, and it reports nothing about work being done.

   The linear top-to-bottom wash the cool version carried is gone with the
   hairlines it belonged to. One vignette over the canvas is the whole
   atmosphere on all three surfaces now. */
body::before {
  content: ""; position: fixed; inset: 0; z-index: -2;
  background:
    radial-gradient(138% 108% at 50% 10%,
      rgba(255, 253, 246, calc(.52 + .34 * var(--lit))) 0%,
      rgba(255, 253, 246, 0) 58%),
    radial-gradient(124% 100% at 50% 30%,
      rgba(108, 138, 156, 0) 32%,
      rgba(108, 138, 156, calc(.27 - .09 * var(--lit))) 100%);
  transition: opacity 1.2s var(--ease);
}

/* ── masthead ──────────────────────────────────────────────────────────── */
.mast {
  position: relative; z-index: 20;
  display: flex; align-items: baseline; justify-content: space-between; gap: 1.2rem;
  padding: clamp(18px, 3.4vw, 30px) var(--gutter) 0;
}
/* The right end carries two things now — the language control above the note
   about how long this takes. They stack rather than sitting side by side because
   the note is a sentence that wraps and the control is a fixed pair of words;
   in a row the wrapping sentence would drag the control off the wordmark's
   baseline every time the viewport changed. */
.mast__end {
  display: flex; flex-direction: column; align-items: flex-end; gap: .34rem;
}
.mast__here {
  font: 400 .76rem/1.4 var(--sans);
  color: var(--ink-3);
  text-align: right;
  max-width: 13rem;
  text-wrap: balance;
  margin: 0;
}
@media (max-width: 400px) {
  .mast { flex-direction: column; align-items: flex-start; gap: .45rem; }
  .mast__end { align-items: flex-start; }
  .mast__here { text-align: left; max-width: none; }
}

/* ── the stage track ───────────────────────────────────────────────────── */
.track ol {
  list-style: none; margin: 0; padding: clamp(1.1rem, 3vw, 1.6rem) var(--gutter) 0;
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px;
}
.track button {
  appearance: none; background: none; border: 0; padding: 0; margin: 0;
  display: block; width: 100%; text-align: left; cursor: pointer;
}
.track button[disabled] { cursor: default; }
.track i {
  display: block; height: 2px; background: var(--rule);
  transform-origin: left;
  transition: background .5s var(--ease);
}
/* Leading 1.35 rather than 1: these are the only four words that name the whole
   flow, and a 1em line box cannot hold Plex's 1.29em font box — Төрсөн and
   Хөрөг both hang below the baseline. */
.track span {
  display: block; margin-top: .5rem;
  font: 500 .76rem/1.35 var(--sans);
  letter-spacing: .005em; text-transform: none;
  color: var(--ink-4);
  transition: color .4s var(--ease);
}
/* Signal discipline, and the rail was the one place on this page that broke
   it. The landing's signal marks what the *chart* determined; the reading
   surface's marks what the *reader* chose; this surface's marks what the
   *customer* determined — and the customer has determined the stages behind
   them, not the one they are standing in. The coral was on `is-now`, which
   made it a location pin, so the page's one saturated colour was saying
   "here" while everything else on the site says "settled".
   It now accumulates as the rail is answered, the same way the figure fills
   and the field lights, and the current stage is the darkest, longest tick
   instead — which is what actually reads as position. */
.track li.is-done i { background: var(--signal-deep); }
.track li.is-done span { color: var(--ink-3); }
.track li.is-now i { background: var(--ink); }
.track li.is-now span { color: var(--ink); }

@media (min-width: 1120px) {
  .track {
    position: fixed; z-index: 30;
    left: var(--gutter); top: 50%; transform: translateY(-50%);
  }
  .track ol {
    padding: 0;
    grid-template-columns: none; grid-auto-flow: row; gap: 0;
  }
  .track li { display: block; }
  .track button {
    display: grid; grid-template-columns: 26px 1fr; align-items: center;
    gap: .85rem; padding: .62rem 0;
  }
  .track i { height: 1px; width: 26px; }
  .track li.is-now i { height: 2px; width: 34px; }
  .track span { margin: 0; }
}

/* ── shell ─────────────────────────────────────────────────────────────── */
.shell {
  position: relative;
  padding: clamp(2.4rem, 7vw, 4.2rem) var(--gutter) clamp(4rem, 10vw, 7rem);
  display: grid; gap: clamp(3rem, 9vw, 4.5rem);
}
@media (min-width: 1120px) {
  .shell {
    grid-template-columns: minmax(0, 32rem) minmax(0, 24rem);
    justify-content: center;
    align-items: start;
    gap: clamp(2.5rem, 5.5vw, 6rem);
    padding-left: max(var(--gutter), 13rem);
    padding-right: var(--gutter);
    padding-block: clamp(1.75rem, 4vh, 3.25rem) clamp(2rem, 5vh, 3.5rem);
    min-height: calc(100svh - 11rem);
  }
}

/* ── a stage ───────────────────────────────────────────────────────────── */
.flow { display: grid; }
.js .flow { min-height: 26rem; }
.stage { max-width: 33rem; }
.js .flow > .stage { grid-area: 1 / 1; }
.js .flow > .stage:not(:first-child) { display: none; }
.js .flow.is-live > .stage { display: none; }
.js .flow.is-live > .stage.is-current { display: block; }
/* wire.mn's arrival, in full: opacity, an 18px rise and a 10px blur, all three
   on one duration and one curve. This *replaces* the two-movement version this
   surface argued for in v2 — the founder resolved the two recipes in the
   landing's favour for the whole site, and a page with two arrival curves
   reads as two pages.

   `backwards` rather than `both`, and the difference is not cosmetic: a
   filter left in the computed style — even `blur(0)` — makes the element a
   containing block and a stacking context for everything inside it, which on
   this page means the birthplace list. There is no delay to hold, so
   `backwards` costs nothing and the filter is gone the moment the stage has
   arrived. */
.js .stage.is-in { animation: arrive .8s var(--ease-arrive) backwards; }
@keyframes arrive {
  from { opacity: 0; transform: translateY(18px); filter: blur(10px); }
}

.stage__num {
  font: 400 .82rem/1 var(--serif);
  font-variant-numeric: tabular-nums;
  letter-spacing: .04em; color: var(--ink-3);
  margin-bottom: clamp(1rem, 2.4vw, 1.4rem);
}
/* Spectral 600, the site's display weight since the landing pass. 400 was what
   read as under-set there at 69px; at 40px it is less obviously wrong and
   still wrong — this is the only display line on the page, and the page is
   four of them in a row. */
.stage__q {
  font-family: var(--serif);
  font-weight: 600;
  font-size: clamp(1.72rem, 6.2vw, 2.5rem);
  line-height: 1.1;
  letter-spacing: -.018em;
  max-width: 17ch;
}
@media (min-width: 700px) { .stage__q { max-width: 21ch; } }
.stage__lede {
  margin-top: clamp(.95rem, 2.2vw, 1.3rem);
  font-size: 1.03em; line-height: 1.56;
  color: var(--ink-2);
  max-width: 31rem;
}

/* ── fields ────────────────────────────────────────────────────────────── */
.field { margin-top: clamp(2rem, 5vw, 2.9rem); }
.field__label {
  display: block;
  font: 500 .82rem/1.4 var(--sans);
  letter-spacing: .005em;
  color: var(--ink-3);
  margin-bottom: .95rem;
}
.field__hint {
  margin-top: .8rem;
  font: 400 .87rem/1.55 var(--sans);
  color: var(--ink-3);
  max-width: 27rem;
}
.field__hint.is-resolved {
  font: 400 1.06rem/1.4 var(--serif);
  font-style: italic;
  color: var(--ink);
}
/* --signal-text, not --signal: this is the one place on the site where the
   signal has to hold a sentence, and the coral cannot. See the token. */
.field__err {
  margin-top: .7rem;
  font: 400 .87rem/1.5 var(--sans);
  color: var(--signal-text);
  max-width: 27rem;
}
.field__err::before { content: "— "; }

/* A room, not a hairline — and now the room is the page's own second tier
   rather than the white wash that stood in for one. The rule is visible from
   across the page, the surface comes up under the value while you are in the
   field, and the typed value stays the loudest thing on screen. What it is
   deliberately not is a filled well with a rule under it: that is the shape
   every form kit ships, and this page is meant to read as paper. */
.input {
  appearance: none; -webkit-appearance: none;
  display: block; width: 100%;
  margin: 0; padding: .34em .3em .5em;
  background: linear-gradient(to top, rgba(250, 247, 241, 0) 0%, rgba(250, 247, 241, 0) 100%);
  border: 0;
  border-bottom: 1.5px solid var(--rule);
  color: var(--ink);
  font: 400 clamp(1.35rem, 4.6vw, 1.7rem)/1.3 var(--serif);
  letter-spacing: -.01em;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  transition: border-color .3s var(--ease-arrive), background .3s var(--ease-arrive);
}
/* The rule alone did not read as somewhere to type. A quiet italic in the
   field says what goes in it, and leaves the moment it is answered. */
.input::placeholder {
  color: var(--ink-4);
  font-style: italic;
  opacity: 1; /* Firefox dims placeholders by default */
}
.input:hover {
  border-bottom-color: var(--ink-3);
  background: linear-gradient(to top, rgba(250, 247, 241, .55) 0%, rgba(250, 247, 241, 0) 88%);
}
.input:focus {
  outline: none;
  border-bottom-color: var(--ink);
  background: linear-gradient(to top, var(--surface-lit) 0%, rgba(255, 253, 248, 0) 82%);
}
.input:focus-visible { outline: none; }
.field:focus-within .field__label { color: var(--ink-2); }
.field.is-bad .input { border-bottom-color: var(--signal-deep); }
.field.is-ok .input { border-bottom-color: var(--ink); }

/* ── the date and time control ─────────────────────────────────────────── */
.date {
  display: flex; align-items: flex-end; gap: .18em;
  font-size: clamp(1.85rem, 8vw, 2.6rem);
}
.date--time { font-size: clamp(1.85rem, 7.4vw, 2.45rem); }

.date__seg { position: relative; display: inline-flex; }
.date__seg::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1.5px;
  background: var(--rule);
  transition: background .25s var(--ease-arrive), height .25s var(--ease-arrive);
}
.date__seg:hover::after { background: var(--ink-3); }
.date__seg:focus-within::after { background: var(--ink); height: 2px; }
.field.is-ok .date__seg::after { background: var(--ink); }
.field.is-bad .date__seg::after { background: var(--signal-deep); }

.date__in {
  appearance: none; -webkit-appearance: none;
  background: none; border: 0; margin: 0;
  padding: .06em .12em .2em;
  color: var(--ink);
  font: 500 1em/1.15 var(--sans);
  font-variant-numeric: tabular-nums;
  letter-spacing: .012em;
  text-align: center;
  /* Sized for the placeholder, not the digits. YYYY / MM / DD are letters, and
     letters are wider than the tabular figures that replace them — at the old
     2.6ch the M's were being cut off at the right edge of the field. */
  width: 2.95ch;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  transition: background .25s var(--ease-arrive);
}
.date__seg--y .date__in { width: 4.95ch; }
.date__in::placeholder {
  color: var(--ink-4);
  font-weight: 400;
  font-size: .84em;
  letter-spacing: .02em;
}
.date__in:focus {
  outline: none;
  background: linear-gradient(to top, var(--surface-lit) 0%, rgba(255, 253, 248, 0) 82%);
}

.date__sep {
  font: 400 .8em/1 var(--sans);
  color: var(--ink-4);
  padding-bottom: .28em;
}

.date__mark {
  align-self: center; margin-left: .55em; margin-bottom: .1em;
  width: 15px; height: 15px; flex: none;
  opacity: 0; transform: scale(.5);
  transition: opacity .3s var(--ease), transform .35s var(--ease);
  background: var(--signal-deep);
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M2 8.6l4 4 8-9' fill='none' stroke='black' stroke-width='2.1' stroke-linecap='round' stroke-linejoin='round'/></svg>") center / contain no-repeat;
  mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M2 8.6l4 4 8-9' fill='none' stroke='black' stroke-width='2.1' stroke-linecap='round' stroke-linejoin='round'/></svg>") center / contain no-repeat;
}
.field.is-ok .date__mark { opacity: 1; transform: none; }

/* ── birthplace search ─────────────────────────────────────────────────── */
.place { position: relative; }
/* The one thing on this page that is genuinely floating, so it takes the full
   `--lift` rather than the plates' `--lift-sm` — the same distinction the
   landing draws between the object being offered and the things beside it.
   No border: a plate under a shadow does not also need an outline, and the
   hairline it had was the cool page's way of getting an edge it could not get
   from light. Rounded, so `overflow: hidden` does the clipping for the rows;
   the vertical scroll needs it anyway. */
.place__list {
  list-style: none; margin: 0; padding: 0;
  position: absolute; z-index: 25; left: 0; right: 0; top: calc(100% + 8px);
  max-height: 17rem; overflow: hidden auto;
  background: var(--surface-lit);
  border-radius: var(--radius);
  box-shadow: var(--lift);
}
.place__list li + li { border-top: 1px solid var(--rule-soft); }
.place__opt {
  display: block; width: 100%; text-align: left; cursor: pointer;
  appearance: none; background: none; border: 0;
  padding: .72rem .95rem;
  font: 400 .98rem/1.35 var(--sans);
  transition: background .16s var(--ease-arrive);
}
.place__opt small {
  display: block; margin-top: .16em;
  font-size: .8rem; color: var(--ink-3);
}
/* The list is already at `--surface-lit`, so a row under the pointer cannot
   light further — it warms instead, toward the field the plate is floating
   over. Same idea, opposite direction. */
.place__opt:hover, .place__opt.is-active { background: var(--sky-2); }
.place__opt.is-active { box-shadow: inset 3px 0 0 var(--signal-deep); }
/* Same fix as `.choice`, for the same reason: the active row's left bar and
   its warmed background are drawn square inside a rounded plate, so at the top
   and bottom the curve cut through both. Arrow-key to the first result and it
   showed. */
.place__list li:first-child .place__opt { border-radius: var(--radius) var(--radius) 0 0; }
.place__list li:last-child  .place__opt { border-radius: 0 0 var(--radius) var(--radius); }
.place__none {
  padding: .8rem .95rem;
  font: 400 .9rem/1.5 var(--sans); color: var(--ink-3);
}

.resolved { margin-top: 1.1rem; }
.resolved__main {
  font: 400 1.06rem/1.4 var(--serif); font-style: italic; color: var(--ink);
}
.resolved__sub {
  margin-top: .4rem;
  font: 400 .82rem/1.5 var(--sans); color: var(--ink-3);
}

/* ── the three answers about time ──────────────────────────────────────── */
/* An important choice given the weight of one — and the same "chosen"
   everywhere: lit surface, ink edge, the signal at the left. Night is not a
   selection state on this surface.

   One of the two places on the page where the reader is choosing between
   objects rather than reading, so it takes a plate. The three rows keep their
   hairlines and that is the right word for them here: the landing's own
   finding is that inside a surface a divided list reads as a list, where the
   same rules on the open field read as a wireframe — which is exactly what
   the 1px-gap grid this replaces was doing.

   `overflow: hidden` so the first and last rows' own edges are clipped to the
   plate's corners. The focus ring on the radio dot sits 17px in from the left
   and clears it. */
.choices {
  display: grid;
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--lift-sm);
  overflow: hidden;
}
.choice + .choice { border-top: 1px solid var(--rule-soft); }
.choice {
  position: relative; display: block; cursor: pointer;
  padding: 1.05rem 1.1rem 1.1rem;
  transition: background .22s var(--ease-arrive), box-shadow .22s var(--ease-arrive);
}
/* The end rows carry the plate's own corners, and this is a fix rather than a
   flourish. A chosen row is drawn with `inset 0 0 0 1px` — an inset shadow
   follows its *own* border-radius, so on a square row it drew a square ring
   that the plate's `overflow: hidden` then cut through at the curve: the ink
   edge ran to the corner and vanished into nothing, on the first and last rows
   only. Clipping cannot round an outline; the row has to be round. Same radius
   as the plate exactly, because there is no border or padding between them, so
   the two edges are coincident and the ring lands on the curve. */
.choice:first-of-type { border-radius: var(--radius) var(--radius) 0 0; }
.choice:last-of-type  { border-radius: 0 0 var(--radius) var(--radius); }
.choice:hover { background: var(--surface-lit); }
.choice input {
  position: absolute; opacity: 0; width: 1px; height: 1px;
  margin: 0; pointer-events: none;
}
.choice__body { display: block; padding-left: 1.85rem; }
.choice__body::before {
  content: ""; position: absolute; left: 1.1rem; top: 1.35rem;
  width: 13px; height: 13px; border-radius: 50%;
  border: 1px solid var(--ink-4);
  background: var(--surface-lit);
  transition: border-color .2s var(--ease-arrive), box-shadow .2s var(--ease-arrive);
}
.choice__body b {
  display: block; font: 500 1.02rem/1.35 var(--sans);
}
.choice__body i {
  display: block; margin-top: .22em; font-style: normal;
  font: 400 .87rem/1.45 var(--sans); color: var(--ink-3);
}
.choice input:focus-visible + .choice__body::before {
  outline: 2px solid var(--signal-deep); outline-offset: 3px;
}
.choice:has(input:checked) {
  background: var(--surface-lit);
  box-shadow: inset 3px 0 0 var(--signal-deep), inset 0 0 0 1px var(--ink);
}
.choice:has(input:checked) .choice__body::before {
  border-color: var(--signal-deep);
  box-shadow: inset 0 0 0 3.5px var(--signal-deep);
}

/* ── asides, one voice ─────────────────────────────────────────────────── */
/* Everything said beside a question stands behind the same left rule. The
   ink rule carries consequence; the soft rule carries reassurance. */
.reply { margin-top: clamp(1.6rem, 4vw, 2.2rem); }
/* The same arrival as a stage, shortened. An aside answers a radio the reader
   has just pressed, a few centimetres from where they are looking, so it comes
   in at .58s against the stage's .8s and travels less far — but on the same
   curve, and out of the same blur, because it is the same event happening
   smaller. */
.js .reply:not([hidden]) { animation: arrive-near .58s var(--ease-arrive) backwards; }
@keyframes arrive-near {
  from { opacity: 0; transform: translateY(10px); filter: blur(6px); }
}

.note {
  padding-left: clamp(1rem, 3vw, 1.4rem);
  border-left: 2px solid var(--ink);
  max-width: 31rem;
}
.note p { color: var(--ink-2); font-size: .99em; line-height: 1.62; }
.note p + p { margin-top: .8em; }

.hold {
  padding-left: clamp(1rem, 3vw, 1.4rem);
  border-left: 2px solid var(--rule);
  max-width: 31rem;
}
.hold p { font: 400 .93rem/1.6 var(--sans); color: var(--ink-2); }
.hold b { font-weight: 600; color: var(--ink); }
.hold__where { margin-top: .55em; color: var(--ink-3); }

.disclose {
  margin-top: clamp(1.7rem, 4vw, 2.3rem);
  border-top: 1px solid var(--rule);
  max-width: 31rem;
}
.disclose summary {
  list-style: none; cursor: pointer;
  padding: .95rem 1.6rem .95rem 0; position: relative;
  font: 400 .95rem/1.4 var(--sans); color: var(--ink-2);
}
.disclose summary::-webkit-details-marker { display: none; }
.disclose summary::after {
  content: ""; position: absolute; right: .25rem; top: 1.32rem;
  width: 9px; height: 9px; border-right: 1px solid var(--ink-3); border-bottom: 1px solid var(--ink-3);
  transform: rotate(45deg); transition: transform .3s var(--ease);
}
.disclose[open] summary::after { transform: rotate(-135deg); top: 1.5rem; }
.disclose summary:hover { color: var(--ink); }
.disclose__body { padding-bottom: 1.3rem; }
.disclose__body p { font-size: .97em; line-height: 1.62; color: var(--ink-2); max-width: 30rem; }
.disclose__body p + p { margin-top: .75em; }

/* ── review ────────────────────────────────────────────────────────────── */
.review { margin: clamp(2rem, 5vw, 2.8rem) 0 0; }
.review__row {
  display: grid; grid-template-columns: 4.6rem 1fr auto;
  align-items: baseline; gap: .8rem;
  padding: .95rem 0;
  border-top: 1px solid var(--rule);
}
.review__row:last-child { border-bottom: 1px solid var(--rule); }
/* Sentence case buys room as well as legibility here: the column is 4.6rem, and
   "Хэдэн цагт" only just cleared it as letterspaced caps. */
.review dt {
  font: 500 .8rem/1.5 var(--sans);
  letter-spacing: .005em;
  color: var(--ink-3);
}
.review dd { margin: 0; font-size: 1.02em; line-height: 1.45; }
.review dd em {
  display: block; margin-top: .1em;
  font-style: italic; color: var(--ink-3); font-size: .84em;
}

/* ── the two editions ──────────────────────────────────────────────────── */
/* The second place the reader is choosing between objects, and the more
   consequential one — it is where the money is agreed to. Both cards take
   *exactly* the same plate: §"The two launch products" is explicit that
   In-Depth must not look like the real product beside a downgraded Quick, and
   a raised card next to a recessed one would say precisely that. What tells
   them apart is the measure, which is the writing's own shape. It is the
   landing page's block, and it needs to be: this is the same pair of cards the
   customer was shown before they decided to come here. */
.editions { display: grid; gap: clamp(1rem, 2.4vw, 1.4rem); margin-top: clamp(2.2rem, 5vw, 3rem); }
.edition { position: relative; display: block; cursor: pointer; }
.edition input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }
.edition__body {
  display: block;
  padding: clamp(1.15rem, 2.6vw, 1.5rem) clamp(1.2rem, 2.8vw, 1.6rem) clamp(1.3rem, 2.8vw, 1.65rem);
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--lift-sm);
  transition: background .24s var(--ease-arrive), box-shadow .24s var(--ease-arrive);
}
.edition:hover .edition__body { background: var(--surface-lit); }
/* Chosen in the page's one grammar for it, over the plate rather than instead
   of it: the lift stays, so the card does not appear to sit down when it is
   picked. */
.edition:has(input:checked) .edition__body {
  background: var(--surface-lit);
  box-shadow: var(--lift-sm), inset 3px 0 0 var(--signal-deep), inset 0 0 0 1px var(--ink);
}
.edition input:focus-visible + .edition__body { outline: 2px solid var(--signal-deep); outline-offset: 3px; }
.edition__head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.edition__head b {
  font: 600 clamp(1.3rem, 3.6vw, 1.55rem)/1.2 var(--serif);
  letter-spacing: -.012em;
}
.edition__head em {
  font: 500 1rem/1 var(--sans); font-style: normal;
  font-variant-numeric: tabular-nums; color: var(--ink);
}
.edition__scope {
  display: block; margin-top: .7rem;
  font-size: .97em; line-height: 1.55; color: var(--ink-2);
}
/* The landing page's own treatment, because this is the same pair of products
   half a page apart in the customer's session — see `landing.css`'s `.secs` for
   why the measure bar that used to sit here is a named list now. Spans rather
   than an <ol>, because the whole card is inside a <label>. */
.secs {
  display: block; margin-top: 1rem; padding-top: .9rem;
  border-top: 1px solid var(--rule-soft);
}
.secs > span {
  display: flex; gap: .7rem;
  font: 400 .82rem/1.7 var(--sans);
  color: var(--ink-2);
}
.secs b {
  flex: none; min-width: 1.1rem; font-weight: 400;
  font-size: .7rem; color: var(--ink-4);
  font-variant-numeric: tabular-nums;
}
.edition__facts {
  display: block; margin-top: .85rem;
  font: 400 .84rem/1.5 var(--sans); color: var(--ink-3);
}

.upgrade {
  margin-top: 1.3rem;
  font: 400 .88rem/1.6 var(--sans); color: var(--ink-3);
  max-width: 31rem;
}

/* ── actions ───────────────────────────────────────────────────────────── */
.act {
  margin-top: clamp(2.2rem, 5.5vw, 3rem);
  display: flex; align-items: center; gap: 1.6rem; flex-wrap: wrap;
}
.act--pay { display: block; }
.act--pay .link { margin-top: 1.5rem; }
.act__fine {
  margin-top: 1.1rem; max-width: 31rem;
  font: 400 .82rem/1.62 var(--sans); color: var(--ink-3);
}
.act__fine a { text-decoration-color: var(--rule); text-underline-offset: .18em; }
.act__fine a:hover { color: var(--ink); }

.btn--pay { padding-inline: 2em; }
.btn--pay span { font-variant-numeric: tabular-nums; }

/* ═══════════════════════════════════════════════  THE PAYMENT  ════════
   Still the warm field, still the form's own typography. The QR is the only
   new object on the page and it is deliberately plain: this is the moment the
   customer decides Astral is real, and a decorated payment code reads as a
   worse payment code. The night room comes after, not here. */
.pay { margin-top: clamp(2.2rem, 5.5vw, 3rem); max-width: 31rem; }
.pay__h {
  font: 400 clamp(1.32rem, 3.3vw, 1.7rem)/1.22 var(--serif);
  letter-spacing: -.008em; color: var(--ink);
}
.pay__lede {
  margin-top: .7rem; max-width: 27rem;
  font: 400 .9rem/1.6 var(--sans); color: var(--ink-2);
}

.pay__code {
  margin: clamp(1.5rem, 4vw, 2rem) 0 0;
  display: flex; flex-direction: column; align-items: flex-start; gap: 1rem;
}
/* Sized in one place. The SVG carries a viewBox and no intrinsic size, so the
   code stays sharp at any width and the quiet zone is inside the box. */
.pay__qr {
  width: clamp(200px, 54vw, 244px); aspect-ratio: 1;
  background: var(--sky-3, #fff);
  border: 1px solid var(--rule-soft);
  padding: .55rem;
}
.pay__qr svg { display: block; width: 100%; height: 100%; }

.pay__status {
  display: flex; align-items: center; gap: .55rem;
  font: var(--label); letter-spacing: var(--track); text-transform: uppercase;
  color: var(--ink-3);
}
/* The only motion here, and it says one true thing: the server is still
   listening. It is not progress and does not pretend to measure anything. */
.pay__pulse {
  width: .42rem; height: .42rem; border-radius: 50%;
  background: var(--signal); flex: none;
  animation: pay-pulse 2.4s var(--ease-fade) infinite;
}
@keyframes pay-pulse { 0%, 100% { opacity: .25; } 50% { opacity: 1; } }

.pay__clock {
  margin-top: .9rem;
  font: 400 .82rem/1.6 var(--sans); color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}

.pay__banks { margin-top: clamp(1.6rem, 4vw, 2.1rem); }
.pay__banks-h {
  font: var(--label-sm); letter-spacing: var(--track); text-transform: uppercase;
  color: var(--ink-3);
}
.pay__bank-list {
  margin-top: .8rem; padding: 0; list-style: none;
  display: flex; flex-wrap: wrap; gap: .5rem;
}
/* The bank's name in Astral's own type. Their logos live on qpay.mn and are
   not fetched: the payment moment makes no third-party request. */
.pay__bank-list a {
  display: inline-block; padding: .58em 1.1em;
  border: 1px solid var(--rule);
  font: 500 .84rem/1 var(--sans); color: var(--ink);
  text-decoration: none;
  transition: border-color .2s var(--ease), background-color .2s var(--ease);
}
.pay__bank-list a:hover { border-color: var(--ink); background: var(--sky-3, #fff); }
.pay__bank-list li.is-extra { display: none; }
.pay__banks.is-open .pay__bank-list li.is-extra { display: block; }
.pay__banks .link { margin-top: .9rem; }

.pay__note {
  margin-top: 1.2rem; max-width: 29rem;
  font: 400 .86rem/1.6 var(--sans); color: var(--ink-2);
}
.pay__note.is-warn { color: var(--signal-deep); }
.pay [data-pay-refresh] { margin-top: 1.1rem; }
.pay [data-pay-cancel] { display: block; margin-top: 1.5rem; }

/* Local scaffolding. Rendered only when the server cannot take real money, and
   dressed so it can never be mistaken for part of the product. */
.pay__dev {
  display: block; margin-top: 1.4rem; padding: .5em 1em;
  border: 1px dashed var(--signal); background: none;
  font: 500 .74rem/1 var(--sans); color: var(--signal-deep);
  cursor: pointer;
}

@media (prefers-reduced-motion: reduce) {
  .pay__pulse { animation: none; opacity: .8; }
}

/* ═══════════════════════════════════════════════  THE FIGURE  ═════════ */
.scope { max-width: 33rem; }
@media (min-width: 1120px) {
  .scope { position: sticky; top: clamp(2.5rem, 10vh, 6rem); max-width: none; }
}

/* This one was a whole sentence set as a sign — "Astral юуг тайлж унших вэ" in
   letterspaced caps, which is a question wearing a heading's clothes. */
.scope__h {
  font: 500 .82rem/1.4 var(--sans);
  letter-spacing: .005em;
  color: var(--ink-3);
  margin-bottom: 1.3rem;
}
.scope__fig { margin: 0; width: min(300px, 74%); }
@media (min-width: 640px) { .scope__fig { width: min(340px, 60%); } }
@media (min-width: 1120px) { .scope__fig { width: 100%; max-width: 380px; } }
.scope__fig svg { width: 100%; height: auto; overflow: visible; }

.scope__count {
  margin-top: clamp(1.3rem, 3vw, 1.8rem);
  font: 400 1.02rem/1.45 var(--serif);
  color: var(--ink);
  max-width: 24rem;
}
.scope__note {
  margin-top: .6rem;
  font: 400 .85rem/1.6 var(--sans);
  color: var(--ink-3);
  max-width: 24rem;
}
/* --ink-3, not --ink-4. This is the sentence that says the figure is not the
   reader's chart, which is the whole truthfulness claim the figure rests on,
   and --ink-4 is documented as decoration only — it was carrying it at about
   2.6:1 on the cool field and would carry it at 2.4:1 here. */
.scope__fine {
  margin-top: 1.15rem;
  padding-top: .9rem;
  border-top: 1px solid var(--rule-soft);
  font: 400 .78rem/1.6 var(--sans);
  color: var(--ink-3);
  max-width: 24rem;
}

/* figure ink — the seventeen seats are ghosts before they are facts */
.sc-rim { fill: none; stroke: var(--ink); stroke-width: 1; opacity: .18; }
.sc-rim--in { opacity: .1; }

.sc-well {
  fill: none; stroke: var(--ink); stroke-width: 1; opacity: .26;
  transition: opacity .6s var(--ease), stroke .6s var(--ease), fill .6s var(--ease);
}
.sc-glyph {
  font-family: var(--sym); font-size: 19px;
  fill: var(--ink); text-anchor: middle; dominant-baseline: central;
  opacity: .12;
  transition: opacity .55s var(--ease);
  transition-delay: calc(var(--i, 0) * 42ms);
}
.sc-glyph--text { font-family: var(--sans); font-size: 11.5px; font-weight: 500; }

.sc-houses { opacity: 0; transition: opacity .7s var(--ease); }
.sc-hring { fill: none; stroke: var(--ink); stroke-width: 1; opacity: .2; }
.sc-hring--in { opacity: .12; }
.sc-spoke { stroke: var(--ink); stroke-width: 1; opacity: .28; }

.sc-chord {
  stroke: var(--ink); stroke-width: .6; opacity: 0; fill: none;
  transition: opacity .9s var(--ease);
}

/* determinate: the twelve that hold without a time */
.scope.is-positions .sc-slot:not(.sc-slot--timed) .sc-well { opacity: .5; }
.scope.is-positions .sc-slot:not(.sc-slot--timed) .sc-glyph { opacity: 1; }

/* determinate: the five that need one, plus the houses a time sets */
.scope.is-timed .sc-slot--timed .sc-well { opacity: .5; }
.scope.is-timed .sc-slot--timed .sc-glyph { opacity: 1; }
.scope.is-timed .sc-houses { opacity: 1; }

/* refused: named as omitted rather than left ambiguous */
.scope.is-untimed .sc-slot--timed .sc-well {
  opacity: .34; stroke-dasharray: 2.5 3;
}
.scope.is-untimed .sc-slot--timed .sc-glyph { opacity: .1; }

/* every pair measured */
.scope.is-web .sc-chord { opacity: .085; }
.scope.is-web .sc-chord--timed { opacity: 0; }
.scope.is-web.is-timed .sc-chord--timed { opacity: .085; }

/* ═══════════════════════════════════════════════  GENERATION  ═════════ */
/* The field changes once, here, because the configuring is over. Nothing on
   this screen reports on the work: no percentage, no invented sub-stages, and
   the only number is the clock, which is real elapsed time.

   What moves is light. The web inks in once on arrival, because the chart
   genuinely is calculated at that moment; after that a slow pass travels the
   relations and the room keeps its own weather. The ambient loops run on 15s,
   23s, 34s, 37s, 71s and 97s — no one of them is a multiple of another, so
   over a wait of any length the field never returns to an arrangement it has
   already held, and nothing on screen can be read as a cycle counting
   something out. */
.wait {
  position: fixed; inset: 0; z-index: 55;
  background:
    radial-gradient(120% 74% at 50% 8%, rgba(48, 40, 53, .85) 0%, rgba(23, 19, 25, 0) 64%),
    var(--night);
  color: var(--on-night);
  display: grid; place-items: center;
  padding: clamp(2rem, 7vw, 4rem) var(--gutter);
  /* The room is wider than the frame on purpose — the light bodies run past
     both edges. Clipping sideways keeps that from becoming a scrollbar. */
  overflow-x: hidden; overflow-y: auto;
  /* how far the pass down in the web is allowed to swing; the ready state
     opens it up, and the group opacities below carry the same lift smoothly.
     Nudged up with the warm dark, which reads a shade lighter than the cool
     one did at the same alpha — the reading surface's twin room made the same
     move for the same reason, and the two must stay in step. */
  --pass-lo: .16;
  --pass-hi: .56;
  /* The field change is quick and one-way — a light going out, not two rooms
     dissolved into each other. Everything else on this screen arrives after
     the daylight page is gone, so nothing is ever read through it. */
  animation: dim .5s cubic-bezier(.62, 0, .3, 1) both;
}
@keyframes dim { from { opacity: 0; } }

/* Weather. Two bodies of light crossing on unrelated periods, behind the
   content in the section's own stacking context so they never sit on the
   letterforms. Three stops rather than two, and the easing lives in the frames
   — a drift that gathers and lets go, never a there-and-back. */
.wait::before,
.wait::after {
  /* fixed, not absolute: the weather belongs to the room, so on a phone it
     holds still while the words scroll past it */
  content: ""; position: fixed; z-index: -1;
  border-radius: 50%; pointer-events: none;
}
.wait::before {
  left: -24%; right: -24%; top: -54%; height: 120%;
  background: radial-gradient(50% 50% at 50% 50%,
    rgba(74, 62, 80, .5) 0%, rgba(40, 33, 45, .2) 44%, rgba(23, 19, 25, 0) 73%);
  animation: weather-a 71s linear infinite;
}
.wait::after {
  left: 12%; right: -42%; top: 12%; height: 88%;
  background: radial-gradient(50% 50% at 50% 50%,
    rgba(56, 46, 62, .4) 0%, rgba(23, 19, 25, 0) 66%);
  animation: weather-b 97s linear infinite;
}
@keyframes weather-a {
  0%   { transform: translate3d(-4%, 2%, 0) scale(1);    animation-timing-function: cubic-bezier(.44, .05, .3, .97); }
  37%  { transform: translate3d(4%, -3%, 0) scale(1.09); animation-timing-function: cubic-bezier(.62, .02, .5, 1); }
  72%  { transform: translate3d(6%, 4%, 0) scale(1.03);  animation-timing-function: cubic-bezier(.32, .12, .4, .95); }
  100% { transform: translate3d(-4%, 2%, 0) scale(1); }
}
@keyframes weather-b {
  0%   { transform: translate3d(3%, -2%, 0) scale(1.05); animation-timing-function: cubic-bezier(.5, .04, .36, .98); }
  44%  { transform: translate3d(-6%, 3%, 0) scale(1);    animation-timing-function: cubic-bezier(.28, .14, .44, .96); }
  79%  { transform: translate3d(-2%, -4%, 0) scale(1.1); animation-timing-function: cubic-bezier(.58, .03, .48, 1); }
  100% { transform: translate3d(3%, -2%, 0) scale(1.05); }
}

/* Low in the room, the line the reading will arrive on. It draws once, then
   holds on a period of its own, and it promises nothing it cannot keep. The
   ground above it is the floor of the room, not a gauge: it never fills. */
.wait__horizon {
  position: fixed; left: 8%; right: 8%; bottom: 11%;
  height: 1px; z-index: -1;
  background: linear-gradient(90deg,
    rgba(240, 235, 227, 0) 0%,
    rgba(240, 235, 227, .3) 50%,
    rgba(240, 235, 227, 0) 100%);
  transform-origin: 50% 50%;
  pointer-events: none;
  animation:
    horizon-hold 23s 2.7s linear infinite,
    horizon-draw 2.15s .55s var(--settle) backwards;
  transition: left 2.4s var(--settle), right 2.4s var(--settle),
              filter 2.2s var(--settle);
}
/* The floor of the room. Elliptical rather than a band, so it has no edge of
   its own anywhere — a straight cut down the side reads as a panel, and this
   is meant to read as light lying on something. */
.wait__horizon::before {
  content: ""; position: absolute; left: -6%; right: -6%; bottom: 0;
  height: min(32vh, 270px);
  background: radial-gradient(54% 104% at 50% 100%,
    rgba(240, 235, 227, .075) 0%, rgba(240, 235, 227, .022) 46%, rgba(240, 235, 227, 0) 76%);
  animation: ground 37s linear infinite;
}
@keyframes horizon-draw { from { transform: scaleX(.16); opacity: 0; } }
@keyframes horizon-hold {
  0%   { opacity: 1;   animation-timing-function: cubic-bezier(.5, .06, .42, .96); }
  41%  { opacity: .52; animation-timing-function: cubic-bezier(.3, .1, .38, .98); }
  100% { opacity: 1; }
}
@keyframes ground {
  0%   { opacity: .7; transform: translate3d(-5%, 0, 0); animation-timing-function: cubic-bezier(.46, .04, .34, .97); }
  53%  { opacity: 1;  transform: translate3d(6%, 0, 0);  animation-timing-function: cubic-bezier(.34, .08, .42, .96); }
  100% { opacity: .7; transform: translate3d(-5%, 0, 0); }
}
/* Ready: the line the reading arrives on takes the width of the room, and the
   floor comes up with it — the filter carries both, so the drift underneath
   keeps running rather than being overridden mid-breath. */
.wait.is-ready .wait__horizon { left: 3%; right: 3%; filter: brightness(1.7); }

.wait__inner {
  display: grid; gap: clamp(2.2rem, 6vw, 3.5rem);
  width: 100%; max-width: 62rem;
  align-items: center;
}
@media (min-width: 900px) {
  .wait__inner { grid-template-columns: minmax(0, 22rem) minmax(0, 30rem); gap: clamp(3rem, 7vw, 6rem); justify-content: center; }
}
.wait__fig { margin: 0; width: min(268px, 66%); justify-self: start; position: relative; }
@media (min-width: 900px) { .wait__fig { width: 100%; } }
.wait__fig svg { width: 100%; height: auto; overflow: visible; }
/* The breath is asymmetric, the way a breath is: drawn in over two fifths and
   let go over three. Held off until the figure has finished arriving. */
.wait__fig svg { animation: breathe 15s 2.6s linear infinite; }
@keyframes breathe {
  0%   { opacity: 1;   transform: scale(1);     animation-timing-function: cubic-bezier(.52, .04, .44, .95); }
  41%  { opacity: .86; transform: scale(.991);  animation-timing-function: cubic-bezier(.26, .12, .36, .99); }
  100% { opacity: 1;   transform: scale(1); }
}
/* Ready: the room comes up behind the figure. Sits under the svg rather than
   over it, so no glyph is ever fogged by its own light. */
.wait__fig::before {
  content: ""; position: absolute; inset: -20%; z-index: -1;
  border-radius: 50%; pointer-events: none;
  background: radial-gradient(50% 50% at 50% 50%,
    rgba(226, 214, 222, .17) 0%, rgba(226, 214, 222, .06) 46%, rgba(226, 214, 222, 0) 72%);
  opacity: 0;
  transition: opacity 2.6s var(--settle);
}
.wait.is-ready .wait__fig::before { opacity: 1; }

.wait .sc-rim { stroke: var(--on-night); opacity: .2; transition: opacity 2.2s var(--settle); }
.wait .sc-rim--in { opacity: .1; }
.wait .sc-well { stroke: var(--on-night); opacity: .5; }
.wait .sc-glyph { fill: var(--on-night); opacity: 1; }
.wait .sc-hring, .wait .sc-spoke { stroke: var(--on-night); }
.wait .sc-chord { stroke: var(--on-night); opacity: .14; stroke-width: .68; }
.wait .is-untimed .sc-slot--timed .sc-well { opacity: .22; }
.wait .is-untimed .sc-slot--timed .sc-glyph { opacity: .1; }

/* ── the figure arrives, then lives ──────────────────────────────────────
   The ring first, then the seats one at a time round the wheel, then the web
   across them. It runs once, on arrival, and never again. */
.wait .sc-rim,
.wait .sc-houses { animation: ink 1.5s .5s var(--settle) backwards; }
.wait .sc-slot {
  transform-box: fill-box; transform-origin: 50% 50%;
  animation: seat 1.15s var(--settle) backwards;
  animation-delay: calc(.62s + var(--i, 0) * 40ms);
}
@keyframes ink { from { opacity: 0; } }
@keyframes seat { from { opacity: 0; transform: scale(.9); } }

/* The web draws slot by slot — every relation the Sun holds, then every one
   the Moon holds, and on round. Each line has its own measured length in
   `--len`, set once from its endpoints, so all 136 draw at one rate whatever
   their span. Dash length is exact, so nothing is left dotted at rest. */
.wait .sc-chord {
  stroke-dasharray: var(--len, 400);
  animation:
    chord-pass 34s linear infinite,
    chord-draw 1.5s var(--settle) backwards;
  animation-delay: calc(var(--c, 0) * -250ms), calc(1.15s + var(--c, 0) * 8ms);
}
@keyframes chord-draw { from { stroke-dashoffset: var(--len, 400); } }
/* A pass of light down the web. It is not a readout: it holds no position, it
   is never at rest, and it lights the same relations again on every turn.

   The tail is long on purpose — a third of the cycle. A short one leaves a
   single fan lit at a time, which sweeps the wheel like an arm and reads as
   the machine looking for something. Held this long, four or five seats are
   always glowing at different strengths, and it reads as light moving through
   a structure that was already finished. */
@keyframes chord-pass {
  0%   { opacity: var(--pass-lo); animation-timing-function: cubic-bezier(.36, .06, .3, .98); }
  5%   { opacity: var(--pass-hi); animation-timing-function: cubic-bezier(.42, .04, .5, .96); }
  38%  { opacity: var(--pass-lo); }
  100% { opacity: var(--pass-lo); }
}
/* Without a birth time, the relations that need one stay dark: no animation,
   so the static rule that holds them at nothing still governs. */
.wait .scope:not(.is-timed) .sc-chord--timed { animation: none; }

/* The whole-group lift the ready state rides on. A transition on the group
   multiplies through the pass without interrupting it, so nothing jumps. */
.wait .sc-chords { opacity: .62; transition: opacity 2.6s var(--settle); }
.wait .sc-slots  { opacity: .9;  transition: opacity 2.2s var(--settle); }
.wait.is-ready .sc-chords { opacity: 1; }
.wait.is-ready .sc-slots  { opacity: 1; }
.wait.is-ready .sc-rim    { opacity: .38; }

.wait__h {
  margin-top: 1rem;
  font-family: var(--serif);
  font-weight: 600;
  font-size: clamp(1.6rem, 5vw, 2.3rem);
  line-height: 1.16; letter-spacing: -.015em;
}
.wait__clock {
  margin-top: clamp(1.6rem, 4vw, 2.2rem);
  font: 500 clamp(1.9rem, 6vw, 2.6rem)/1 var(--sans);
  font-variant-numeric: tabular-nums;
  letter-spacing: .01em;
}
.wait__clock i {
  font-style: normal; font-size: .82rem; font-weight: 400;
  letter-spacing: .005em;
  color: var(--on-night-2); margin-left: .7em;
}
.wait__est {
  margin-top: .9rem;
  font: 400 .93rem/1.6 var(--sans); color: var(--on-night-2);
  max-width: 27rem;
}
.wait__leave {
  margin-top: clamp(1.8rem, 4vw, 2.4rem);
  padding-top: 1.3rem; border-top: 1px solid var(--rule-night);
  font: 400 .88rem/1.65 var(--sans); color: var(--on-night-2);
  max-width: 27rem;
}
.wait__go { margin-top: clamp(1.8rem, 4vw, 2.4rem); }
.btn--reveal { padding: 1.15em 2.1em; }

/* ── the words arrive ────────────────────────────────────────────────────
   Two movements on purpose, not one. The line is fully lit at .6s and goes on
   travelling until 1.45s — so it is read before it has settled, and it settles
   rather than stops. Fade and travel on one curve and one duration is the
   motion every generated page ships. */
.wait__type > * {
  animation:
    type-lit .6s var(--fade) backwards,
    type-set 1.45s var(--settle) backwards;
}
@keyframes type-lit { from { opacity: 0; } }
@keyframes type-set { from { transform: translate3d(0, 14px, 0); } }

/* After the figure: the state, the sentence, the clock, the estimate, the
   reassurance — in the order they are worth reading. */
[data-wait-working] > *:nth-child(1) { animation-delay: 1.3s; }
[data-wait-working] > *:nth-child(2) { animation-delay: 1.44s; }
[data-wait-working] > *:nth-child(3) { animation-delay: 1.72s; }
[data-wait-working] > *:nth-child(4) { animation-delay: 1.86s; }
[data-wait-working] > *:nth-child(5) { animation-delay: 2s; }

/* The readiness state that used to follow this one is gone: generation is
   synchronous, so the browser leaves for the private link the moment the last
   section lands, and the arrival is run once by the reading surface. Its
   staggered entrance rules went with it rather than being left to match markup
   that no longer exists. */

/* ── a short room ────────────────────────────────────────────────────────
   Last in the section so it can take back what the rules above set. A short
   viewport has no spare floor: the words would run past the bottom and the
   horizon would end up as a hairline behind the last paragraph. The room
   tightens and the figure gives up the height, staying the largest thing on
   screen either way. */
@media (max-height: 720px) {
  .wait { padding-block: clamp(1.1rem, 3vh, 1.8rem); }
  .wait__inner { gap: clamp(1.5rem, 4vw, 2.4rem); }
  .wait__horizon { bottom: 3.5%; }
  .wait__horizon::before { height: 16vh; }
}
@media (max-height: 720px) and (max-width: 899px) {
  .wait__fig { width: min(186px, 48%); }
}

/* ── footer ────────────────────────────────────────────────────────────── */
/* Same correction as `.scope__fine`: this is a disclosure, not decoration, and
   --ink-4 cannot hold running text on either field. */
.foot {
  padding: 0 var(--gutter) clamp(2rem, 5vw, 3rem);
  font: 400 .78rem/1.6 var(--sans); color: var(--ink-3);
}
.foot__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-3); margin-right: .7em;
}

/* ── reduced motion ────────────────────────────────────────────────────── */
/* The still page is the finished composition. `animation: none` rather than a
   near-zero duration, because several of these hold their opening frame during
   a delay, and a shortened delay would still blink the figure and the words in.
   What is left is the room with its weather settled and the web already
   drawn — the same picture the motion was travelling towards. */
@media (prefers-reduced-motion: reduce) {
  .wait, .wait::before, .wait::after,
  .wait__horizon, .wait__horizon::before,
  .wait__fig svg,
  .wait .sc-rim, .wait .sc-houses, .wait .sc-slot, .wait .sc-chord,
  .wait__type > * { animation: none; }
  /* the group lift stands in for the pass, so the web reads at the weight it
     had before any of this moved — and the untimed rules still govern which
     relations are drawn at all */
  .wait .sc-chords { opacity: 1; }
}
