/* motion.css — the scroll choreography's static half.
 *
 * EVERY RULE HERE DESCRIBES THE PAGE AT REST, IN ITS FINISHED STATE. Nothing
 * is hidden, shrunk or moved by this file. The from-states are set by
 * src/motion.js and only after it has decided the visitor wants motion, so a
 * page with scripting off, a page that fails to reach esm.sh, and a page
 * asking for reduced motion are all the same readable page the design was
 * approved as. Animating "into view" from CSS is how a site ends up with
 * permanently invisible copy when one import 404s.
 *
 * There are no glows, no particles, no ambient blurs and no luminous effects
 * in here, and nothing scales type. The word reveal is translation inside a
 * mask; the font size never changes. */

/* ── the masked word reveal ───────────────────────────────────────────
   One wrapper per word, built by script. The wrapper is the mask and the
   inner span is the thing that travels: translateY(110%) → 0.

   THE PADDING AND THE NEGATIVE MARGIN ARE LOAD-BEARING. An inline-block
   with overflow:hidden clips at the line box, and the line box stops at the
   baseline plus whatever the font's descent happens to be. On a heavy
   display face at a tight line-height that lands right through the tails of
   g, p, y and Q. The padding opens the mask below the baseline and the
   negative margin takes that space back out of the layout, so descenders
   survive and the line's position and leading are untouched. */
.w {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
  padding-bottom: 0.18em;
  margin-bottom: -0.18em;
}
.w > span {
  display: inline-block;
  will-change: transform;
  backface-visibility: hidden;
}

/* ── the scrubbed reveal on the split artwork ─────────────────────────
   At rest this is the artwork exactly as it shipped. motion.js narrows the
   inset and scales the image, then opens it back up as the page scrolls.
   This is artwork, not type. */
/* The caret that sits at the end of the recipient id while it types. It is
   a plain 1px rule, shown only during that state. */
.ui-field { position: relative; }
.ui-field .caret {
  display: none;
  width: 1px;
  height: 1em;
  margin-left: 1px;
  background: currentColor;
  vertical-align: -0.12em;
}
.is-typing .ui-field .caret { display: inline-block; animation: caret 1s steps(1) infinite; }
@keyframes caret { 50% { opacity: 0; } }

/* Counting figures must not reflow the row they sit in as digits change. */
[data-count] { font-variant-numeric: tabular-nums; }

/* ── the converging composition ───────────────────────────────────────
   The props already sit outside the slab. They are only allowed to arrive
   from further out, never to fly across the page. */
[data-converge] { will-change: transform; }

/* ── reduced motion ───────────────────────────────────────────────────
   motion.js returns before building anything when this is set, so there is
   nothing to switch off; the words are never split and the headings are
   ordinary headings. These rules cover the micro-interactions that live in
   CSS rather than in the timelines. */
@media (prefers-reduced-motion: reduce) {
  .is-typing .ui-field .caret { animation: none; }
  .w > span, .rw { will-change: auto; }
}

/* ── the resolving statements ─────────────────────────────────────────
   Deliberately NOT masked. These words rotate and skew, and a mask would
   clip them against their own box while they are out of true. */
.rw { display: inline-block; will-change: transform, filter; backface-visibility: hidden; }

/* ── the pinned story's changing phrase ───────────────────────────────
   One slot, three phrases stacked in it. The slot is sized by the tallest,
   so the device beside it never shifts when the narrative changes, and it
   masks so a phrase leaving upward is clipped rather than escaping into the
   copy above. Only the phrases move; the heading itself never does. */
.story-head { position: relative; }
.story-slot { position: relative; display: block; overflow: hidden; }
.story-line { display: block; }
.story-line + .story-line { position: absolute; inset: 0; }

/* While pinned the card must not be taller than the screen, or its later
   states build below the fold where nobody sees them. */
@media (min-width: 861px) {
  [data-scene="pin-rewards"] .row { align-items: center; }
  [data-scene="pin-rewards"] .card { max-height: 76vh; }
  [data-scene="pin-rewards"] .card .ui { max-height: 76vh; overflow: hidden; }
}
