/* ============================================================================
   motion.css — THE KEYFRAMES, THE TRANSITION CLASSES, AND THE DESIGN LAW
   Plus the prefers-reduced-motion block, at the end.

   READ THE HANDOVER BLOCK NEAR THE BOTTOM BEFORE EDITING A REVEAL. js/reveal.js
   now drives the copy and glyph reveals through anime.js and this file switches
   those specific rules off under `html.js-motion`. Everything here is still the
   truth for no-JS, no-anime, a motion module that threw, and reduced motion —
   and the aperture, hairline, silhouette, strike, lid, route and lay-down chrome
   are still owned here outright. THE LAW BELOW BINDS BOTH IMPLEMENTATIONS.

   THE SIGNATURE IS BLUR-INTO-FOCUS. Every reveal on this site is
   `filter: blur(N) → blur(0)` with `opacity: 0 → 1` and ZERO TRANSLATE.

   PROHIBITION, load-bearing. No display type on this site ever travels
   vertically. There is no `translateY(110%)` inside an `overflow:hidden` mask,
   no per-word slide-up, no line-mask reveal of any kind — that is the rejected
   sites' one shared move and reproducing it forfeits the whole distinctness
   argument (spec §10: "Splits to glyphs for a blur, not to words for a
   translateY mask. Zero vertical travel on any display type.").

   FOUR CURVES ONLY, all monotone, zero overshoot anywhere:
     --e-lay   arrives    --e-fall  leaves    --e-grade  grades    --e-roll  mechanical
   No bounce, no spring, no control point above 1. Durations come from the
   --d-* tokens. NOTE: those tokens carry their `ms` unit (--d-lay: 940ms), so
   they are used directly as times. Multiplying one by a unit — `calc(var(--d-lay)
   * 1ms)` — is ms x ms, which is invalid and silently voids the WHOLE
   declaration (animation-name resolves to `none` and the element stays at
   opacity 0). Multiply by a plain number only: `calc(var(--d-lay) * 1)`.

   TRIGGER CONTRACT. Reveals hang off `.leaf__body[data-live]`, the flag
   js/leaves.js already owns, so a leaf replays its reveal every time it becomes
   the live leaf and nothing needs a second vocabulary. Corner chrome is
   anchored to page load instead, because the lay-down starts at frame one and
   nothing is waiting on it. Every reveal uses `animation-fill-mode: both`,
   which is what lets the reduced-motion block collapse durations to nothing
   and land on the end state rather than the start state.
   ========================================================================== */

/* ── Keyframes ───────────────────────────────────────────────────────────── */

@keyframes settle {
  from { opacity: 0; filter: blur(var(--blur, 6px)) }
  to   { opacity: 1; filter: blur(0) }
}

/* Pins: fade + scale 0.9 → 1. NO BOUNCE — "zero overshoot anywhere" is a
   stated property of this motion system and one exception makes it a claim. */
@keyframes rise {
  from { opacity: 0; transform: scale(.9) }
  to   { opacity: 1; transform: scale(1) }
}

/* The aperture slit, opening from an element's own horizontal centre line. */
@keyframes slit {
  from { clip-path: inset(50% 0) }
  to   { clip-path: inset(0 0) }
}

/* The photograph is still settling after the aperture has stopped. */
@keyframes counter {
  from { transform: scale(1.09) }
  to   { transform: scale(1) }
}

@keyframes cut  { from { --cut-t: 0 } to { --cut-t: 1 } }
@keyframes hair { from { --hair: 0 }  to { --hair: 1 } }
@keyframes draw { from { --draw: 0% } to { --draw: 100% } }

@keyframes trace  { from { stroke-dashoffset: var(--len, 1000) } to { stroke-dashoffset: 0 } }
@keyframes strike { from { transform: scaleX(0) } to { transform: scaleX(1) } }

@keyframes inner-in { to { opacity: 1 } }

/* ── Lay-down, step 6: corner chrome arrives ─────────────────────────────────
   mark → Menu pill → status pill → rotated mark → micro-label at
   --d-chrome-stagger (60 ms), each opacity 0→1 with blur(7px)→0 over
   --d-chrome (520 ms) on --e-lay, once the sheet has finished shrinking. */

html.js .card__inner .chrome {
  --blur: 7px;
  opacity: 0;
  animation: settle calc(var(--d-chrome) * 1) var(--e-lay) both;
}

html.js .card__inner .chrome--mark  { animation-delay: calc(var(--d-lay) * 1 + var(--d-chrome-stagger) * 0) }
html.js .card__inner .pill--menu    { animation-delay: calc(var(--d-lay) * 1 + var(--d-chrome-stagger) * 1) }
html.js .card__inner .pill--index   { animation-delay: calc(var(--d-lay) * 1 + var(--d-chrome-stagger) * 2) }
html.js .card__inner .mark-rotated  { animation-delay: calc(var(--d-lay) * 1 + var(--d-chrome-stagger) * 3) }
html.js .card__inner .note          { animation-delay: calc(var(--d-lay) * 1 + var(--d-chrome-stagger) * 4) }

/* Failsafe, not a second implementation. css/card.css parks `.card__inner` at
   opacity 0 and js/main.js reveals it as the sheet lands. This asserts the same
   end state at --d-lay with `forwards` fill, so a lay-down that never runs
   cannot leave the card's interior invisible. During the delay the animation
   contributes nothing, so main.js's earlier reveal still wins. */
html.js .card__inner {
  animation: inner-in 1ms linear calc(var(--d-lay) * 1) forwards;
}

/* ── The leaf swap: 380 out / 90 hold / 720 in ───────────────────────────── */

.leaf__body {
  transition:
    opacity calc(var(--d-leaf-out) * 1) var(--e-fall),
    visibility 0s linear calc(var(--d-leaf-out) * 1);
}

.leaf__body[data-live] {
  transition:
    opacity calc(var(--d-leaf-in) * 1) var(--e-lay) calc(var(--d-leaf-hold) * 1),
    visibility 0s linear 0s;
}

/* ── The photo-over-giant-serif device (spec §7) ─────────────────────────────
   Glyphs blur in ordered OUTWARD FROM THE MIDDLE GLYPH at --d-glyph-stagger
   (46 ms), --d-glyph (760 ms) each, blur(8px)→0, zero translate. The word
   appears to grow out from behind where the panel will be, in both directions.
   The stagger order is CAUSED BY THE LAYOUT, not chosen.

   Blur is capped at 8px, not 14px: animated `filter` on nine spans at up to
   19rem is the one place this design can drop frames on integrated graphics. */

.word .glyphs > span { --blur: 8px; opacity: 0 }

.leaf__body[data-live] .word .glyphs > span {
  animation: settle calc(var(--d-glyph) * 1) var(--e-lay) both;
  will-change: opacity, filter;
}

/* PROTECTED — nine glyphs, centre glyph 5 */
.word[data-word="protected"] .glyphs > span:nth-child(5)              { animation-delay: calc(var(--d-glyph-stagger) * 0) }
.word[data-word="protected"] .glyphs > span:is(:nth-child(4), :nth-child(6)) { animation-delay: calc(var(--d-glyph-stagger) * 1) }
.word[data-word="protected"] .glyphs > span:is(:nth-child(3), :nth-child(7)) { animation-delay: calc(var(--d-glyph-stagger) * 2) }
.word[data-word="protected"] .glyphs > span:is(:nth-child(2), :nth-child(8)) { animation-delay: calc(var(--d-glyph-stagger) * 3) }
.word[data-word="protected"] .glyphs > span:is(:nth-child(1), :nth-child(9)) { animation-delay: calc(var(--d-glyph-stagger) * 4) }

/* YOURS — five glyphs, centre glyph 3 */
.word[data-word="yours"] .glyphs > span:nth-child(3)              { animation-delay: calc(var(--d-glyph-stagger) * 0) }
.word[data-word="yours"] .glyphs > span:is(:nth-child(2), :nth-child(4)) { animation-delay: calc(var(--d-glyph-stagger) * 1) }
.word[data-word="yours"] .glyphs > span:is(:nth-child(1), :nth-child(5)) { animation-delay: calc(var(--d-glyph-stagger) * 2) }

/* The aperture: --cut-t 0 → 1 over --d-cut (860 ms) --e-grade at
   --d-cut-delay (240 ms), the <img> counter-scaling 1.09 → 1.000 over
   --d-cut-img (1400 ms) on the same curve, and the 1px hairline drawing last
   at +700 ms over --d-cut-hair (300 ms). */

.leaf__body[data-live] .word .cut {
  animation:
    cut  calc(var(--d-cut) * 1)      var(--e-grade) calc(var(--d-cut-delay) * 1) both,
    hair calc(var(--d-cut-hair) * 1) var(--e-lay)   700ms                          both;
}

.leaf__body[data-live] .word .cut img {
  animation: counter calc(var(--d-cut-img) * 1) var(--e-grade) calc(var(--d-cut-delay) * 1) both;
}

/* Leaf 08's slit is the slowest of the two, so the faces arrive as the page's
   final beat. The second duration in the list is the hairline's, unchanged. */
#body-08 .word .cut {
  animation-duration: 980ms, calc(var(--d-cut-hair) * 1);
}

/* ── Shared copy reveals ─────────────────────────────────────────────────────
   One gesture, one curve. Delays place each object in the leaf's own order. */

.leaf__body[data-live] :is(
  .lede, .above, .body, .forms, .statement, .plate-name, .label, .sub, .caps,
  .quote, a.pill, address, .locations, table caption
) {
  animation: settle calc(var(--d-chrome) * 1) var(--e-lay) both;
}

.leaf__body[data-live] .lede        { animation-delay: calc(var(--d-glyph) * 1) }
.leaf__body[data-live] .body        { animation-delay: 240ms }
.leaf__body[data-live] .forms       { animation-delay: 180ms }
.leaf__body[data-live] a.pill       { animation-delay: 420ms }
.leaf__body[data-live] table caption { animation-delay: 120ms }

/* ── Leaf 02 — statement with photographs set between the words ──────────────
   Each inset runs the aperture slit from its own vertical centre, staggered
   120 ms in reading order, 700 ms each. The statement's own words cannot be
   staggered from CSS: index.html sets the headline as text nodes around three
   <img>, with no per-word spans to key off, so the sentence blurs in as one
   object and the insets carry the sequence. */

.statement .inset { clip-path: inset(50% 0) }

.leaf__body[data-live] .statement .inset {
  animation: slit 700ms var(--e-grade) both;
}
.leaf__body[data-live] .statement .inset:nth-of-type(1) { animation-delay: 0ms }
.leaf__body[data-live] .statement .inset:nth-of-type(2) { animation-delay: 120ms }
.leaf__body[data-live] .statement .inset:nth-of-type(3) { animation-delay: 240ms }

/* Manifest rows: hairline --draw 0% → 100%, --d-row (380 ms),
   --d-row-stagger (40 ms). css/table.css consumes --draw for the rule. */

.leaf__body[data-live] :is(.manifest, table.index) tbody tr {
  --blur: 5px;
  animation:
    draw   calc(var(--d-row) * 1) var(--e-grade) both,
    settle calc(var(--d-row) * 1) var(--e-lay)   both;
}

.leaf__body[data-live] :is(.manifest, table.index) tbody tr:nth-child(1)  { animation-delay: calc(var(--d-row-stagger) * 0) }
.leaf__body[data-live] :is(.manifest, table.index) tbody tr:nth-child(2)  { animation-delay: calc(var(--d-row-stagger) * 1) }
.leaf__body[data-live] :is(.manifest, table.index) tbody tr:nth-child(3)  { animation-delay: calc(var(--d-row-stagger) * 2) }
.leaf__body[data-live] :is(.manifest, table.index) tbody tr:nth-child(4)  { animation-delay: calc(var(--d-row-stagger) * 3) }
.leaf__body[data-live] :is(.manifest, table.index) tbody tr:nth-child(5)  { animation-delay: calc(var(--d-row-stagger) * 4) }
.leaf__body[data-live] table.index tbody tr:nth-child(6)  { animation-delay: calc(var(--d-row-stagger) * 5) }
.leaf__body[data-live] table.index tbody tr:nth-child(7)  { animation-delay: calc(var(--d-row-stagger) * 6) }
.leaf__body[data-live] table.index tbody tr:nth-child(8)  { animation-delay: calc(var(--d-row-stagger) * 7) }
.leaf__body[data-live] table.index tbody tr:nth-child(9)  { animation-delay: calc(var(--d-row-stagger) * 8) }
.leaf__body[data-live] table.index tbody tr:nth-child(10) { animation-delay: calc(var(--d-row-stagger) * 9) }

/* ── Leaf 03 — the band ─────────────────────────────────────────────────────
   The band's X translate is a raw 1:1 scrub against this section's own
   rawProgress with zero added easing (js/band.js) — mechanical horizontal
   against the soft damped vertical, which is what makes both legible. Nothing
   here may add a transition to it, or the scrub stops being 1:1. */

.band { transition: none }

.leaf__body[data-live] .panel {
  --blur: 6px;
  animation: settle calc(var(--d-row) * 1) var(--e-lay) both;
}
.leaf__body[data-live] .panel:nth-child(1) { animation-delay: 0ms }
.leaf__body[data-live] .panel:nth-child(2) { animation-delay: calc(var(--d-row-stagger) * 1) }
.leaf__body[data-live] .panel:nth-child(3) { animation-delay: calc(var(--d-row-stagger) * 2) }
.leaf__body[data-live] .panel:nth-child(4) { animation-delay: calc(var(--d-row-stagger) * 3) }
.leaf__body[data-live] .panel:nth-child(5) { animation-delay: calc(var(--d-row-stagger) * 4) }
.leaf__body[data-live] .panel:nth-child(6) { animation-delay: calc(var(--d-row-stagger) * 5) }
.leaf__body[data-live] .panel:nth-child(7) { animation-delay: calc(var(--d-row-stagger) * 6) }
.leaf__body[data-live] .panel:nth-child(8) { animation-delay: calc(var(--d-row-stagger) * 7) }
.leaf__body[data-live] .panel:nth-child(9) { animation-delay: calc(var(--d-row-stagger) * 8) }

/* ── Leaf 04 — the silhouette window ────────────────────────────────────────
   Animating --draw gives a genuinely soft directional wipe that clip-path
   cannot produce, using the feathered alpha those mask PNGs were authored for.
   The mask-image/mask-composite declaration itself is css/table.css's. */

.silhouette {
  transition: --draw calc(var(--d-mask) * 1) var(--e-grade);
}

.leaf__body[data-live] .silhouette { --draw: 100% }

/* Cross-dissolve when the active row changes, 520 ms. */
.silhouette img {
  transition: opacity 520ms var(--e-grade);
}

/* ── Leaf 05 — the card opens around the photograph ─────────────────────────
   Same aperture animation as the hero, run on the --plate-cream overlay whose
   hole grows to the photo's rect, so the card reveals the image rather than the
   image being placed on the card. 880 ms --e-grade. */

.leaf__body[data-live] .plate[data-aperture] {
  animation: cut 880ms var(--e-grade) both;
}

.leaf__body[data-live] .plate-name {
  animation-duration: calc(var(--d-glyph) * 1);
  animation-delay: 300ms;
}

/* ── Leaf 06 — the strike rule and the evidence ─────────────────────────────
   First of --film-red's exactly two appearances on the entire site. Draws
   left-to-right scaleX(0)→1 over 380 ms, 620 ms after the headline lands. The
   rule's geometry is declared here so the grace note cannot go missing. */

.struck {
  position: relative;
  white-space: nowrap;
}

.struck::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 52%;
  height: 2px;
  background: var(--film-red);
  transform: scaleX(0);
  transform-origin: left center;
  pointer-events: none;
}

/* THREE RULES NOW, NOT ONE, drawn left to right in reading order. index.html
   strikes "menu", "queue" and "recordings" — each is a thing you do not get, and
   the rule through it is what says so. Drawing all three at once read as a single
   graphic event; 90ms apart reads as someone crossing them off a list.

   NOTE ON --film-red: css/tokens.css declares exactly two permitted uses of it on
   the whole site, and this is now three rules plus the reserved phone dot. That
   ceiling was a design decision and the client has since asked for the other two
   strikes directly, so the token's own comment is the thing that is out of date —
   not this. Still the only red on the page, still confined to the carbon leaf. */
.leaf__body[data-live] .struck::after {
  animation: strike 380ms var(--e-lay) 620ms both;
}

.leaf__body[data-live] .struck:nth-of-type(2)::after { animation-delay: 710ms }
.leaf__body[data-live] .struck:nth-of-type(3)::after { animation-delay: 800ms }

.leaf__body[data-live] .quote { animation-delay: 420ms }

.leaf__body[data-live] .insets li {
  --blur: 6px;
  animation: settle calc(var(--d-row) * 1) var(--e-lay) both;
}
.leaf__body[data-live] .insets li:nth-child(1) { animation-delay: 620ms }
.leaf__body[data-live] .insets li:nth-child(2) { animation-delay: 690ms }
.leaf__body[data-live] .insets li:nth-child(3) { animation-delay: 760ms }

/* The five-band carbon lid. Tint-to-tint changes are the plain ground
   cross-fade; INTO AND OUT OF CARBON ONLY these five bands run on top of it.
   In: scaleY(0)→1 from transform-origin top, top band first, --d-lid-stagger
   40 ms, --d-lid 460 ms each, --e-grade — a lid closing. Out: the same bands
   reverse from transform-origin bottom, --d-lid-off 340 ms each, --e-fall.
   THE LID COMES OFF FASTER THAN IT WENT ON. transform-origin is deliberately
   not in the transition list: it flips discretely at the state change, which is
   exactly the behaviour the two directions need. */

.lid > i {
  transform: scaleY(0);
  transform-origin: bottom;
  transition: transform calc(var(--d-lid-off) * 1) var(--e-fall);
}

:is(.lid[data-on], [data-plate="carbon"] .lid) > i {
  transform: scaleY(1);
  transform-origin: top;
  transition: transform calc(var(--d-lid) * 1) var(--e-grade);
}

:is(.lid[data-on], [data-plate="carbon"] .lid) > i:nth-child(1) { transition-delay: calc(var(--d-lid-stagger) * 0) }
:is(.lid[data-on], [data-plate="carbon"] .lid) > i:nth-child(2) { transition-delay: calc(var(--d-lid-stagger) * 1) }
:is(.lid[data-on], [data-plate="carbon"] .lid) > i:nth-child(3) { transition-delay: calc(var(--d-lid-stagger) * 2) }
:is(.lid[data-on], [data-plate="carbon"] .lid) > i:nth-child(4) { transition-delay: calc(var(--d-lid-stagger) * 3) }
:is(.lid[data-on], [data-plate="carbon"] .lid) > i:nth-child(5) { transition-delay: calc(var(--d-lid-stagger) * 4) }

/* Coming off, the bottom band goes first — the room brightens as the lid lifts. */
.lid > i:nth-child(5) { transition-delay: calc(var(--d-lid-stagger) * 0) }
.lid > i:nth-child(4) { transition-delay: calc(var(--d-lid-stagger) * 1) }
.lid > i:nth-child(3) { transition-delay: calc(var(--d-lid-stagger) * 2) }
.lid > i:nth-child(2) { transition-delay: calc(var(--d-lid-stagger) * 3) }
.lid > i:nth-child(1) { transition-delay: calc(var(--d-lid-stagger) * 4) }

/* ── Leaf 07 — Texas ───────────────────────────────────────────────────────
   Route draws 900 ms --e-grade. Pins fade + scale 0.9→1, 260 ms, 70 ms
   stagger, --e-lay, no bounce. Rows blur in bottom-to-top, 52 ms stagger. */

.map .route {
  /* hypotenuse of the Houston → El Paso line in the 424×400 viewBox */
  --len: 366;
  stroke-dasharray: var(--len);
  stroke-dashoffset: var(--len);
}

.leaf__body[data-live] .map .route {
  animation: trace 900ms var(--e-grade) both;
}

.map .pin {
  transform-box: fill-box;
  transform-origin: center;
  opacity: 0;
}

.leaf__body[data-live] .map .pin {
  animation: rise 260ms var(--e-lay) both;
}
.leaf__body[data-live] .map .pin:nth-of-type(1) { animation-delay: 300ms }
.leaf__body[data-live] .map .pin:nth-of-type(2) { animation-delay: 370ms }
.leaf__body[data-live] .map .pin:nth-of-type(3) { animation-delay: 440ms }
.leaf__body[data-live] .map .pin:nth-of-type(4) { animation-delay: 510ms }
.leaf__body[data-live] .map .pin:nth-of-type(5) { animation-delay: 580ms }

.leaf__body[data-live] .locations li {
  --blur: 5px;
  animation: settle calc(var(--d-row) * 1) var(--e-lay) both;
}
.leaf__body[data-live] .locations li:nth-child(5) { animation-delay: 0ms }
.leaf__body[data-live] .locations li:nth-child(4) { animation-delay: 52ms }
.leaf__body[data-live] .locations li:nth-child(3) { animation-delay: 104ms }
.leaf__body[data-live] .locations li:nth-child(2) { animation-delay: 156ms }
.leaf__body[data-live] .locations li:nth-child(1) { animation-delay: 208ms }

/* ── Leaf 08 — the colophon ────────────────────────────────────────────────
   Three quiet columns, blur in at 60 ms stagger, 440 ms each. */

.leaf__body[data-live] .colophon .col {
  --blur: 5px;
  animation: settle 440ms var(--e-lay) both;
}
.leaf__body[data-live] .colophon .col:nth-child(1) { animation-delay: 900ms }
.leaf__body[data-live] .colophon .col:nth-child(2) { animation-delay: 960ms }
.leaf__body[data-live] .colophon .col:nth-child(3) { animation-delay: 1020ms }

/* ── Per-leaf callout entry offsets (consumed by css/callout.css) ──────────── */

#body-01 .callout { --callout-in: 1100ms }
#body-02 .callout { --callout-in: 180ms }

/* ============================================================================
   THE FLOW FIELD lives in js/flow.js and is deliberately NOT in this file.
   It puts the card on the same continuous scroll scalars that already drive the
   backdrop — velocity, momentum bend, and progress through the live leaf — so
   the card and the world move under one force instead of the card blinking on
   timers in front of a world that flows.

   It writes finished `scale` / `translate` values straight onto the four things
   that move (the stage, the live leaf, the corner chrome) rather than
   publishing custom properties for CSS here to read. That is a performance
   decision, not a style one: custom properties inherit, so a variable set on
   `.card__inner` invalidates the inherited-property computation for its whole
   subtree — 344 nodes on this page, every frame of every scroll, to move 7
   elements. Every term rests at exactly 0, so a still page is at identity and
   js/ribbon.js and js/cube.js can still measure real rects.

   If you are looking for the amplitudes, they are the named constants at the
   top of js/flow.js.
   ========================================================================== */

/* ============================================================================
   HANDOVER — the reveals js/reveal.js now owns
   `html.js-motion` is set by js/main.js only once the motion engine has bound
   and the leaves have been armed. Everything above this block is still the
   truth for: no JS, no anime.js, a motion module that threw on boot, and the
   `prefers-reduced-motion` path below. Nothing here is a second implementation
   of a reveal — it only stops CSS asserting the ones JS drives.

   WHY IT HAS TO BE SWITCHED OFF RATHER THAN LEFT TO LOSE. A CSS `animation`
   with `fill-mode: both` sits in the animation origin, which outranks the
   author origin — including inline styles. reveal.js parks and drives these
   elements with inline `opacity` / `filter`, so any surviving `animation`
   here would simply overwrite it every frame and nothing would move.

   The keyframes above are NOT dead: `cut`, `hair`, `draw`, `trace`, `slit`,
   `strike`, `counter`, the carbon lid and the lay-down chrome all stay in CSS
   and are untouched by reveal.js. So does `settle` itself, for the fallbacks.
   ========================================================================== */

html.js-motion .leaf__body[data-live] :is(
  .word .glyphs > span,
  .statement, .statement .clause,
  .lede, .above, .body, .forms, .plate-name, .label, .sub, .caps, .quote,
  a.pill, address, .locations, .locations li, table caption,
  .panel, .insets li, .colophon .col, .map .pin,
  .proof, .about__photo, .reasons > li, .facts > li,
  .lineup__end, .about__more, .testify__label
) {
  animation: none;
}

/* THE GLYPHS ARE THE EXCEPTION IN THIS BLOCK: they are not handed to reveal.js.
   js/word.js has owned them since the beginning and computes the centre-out
   stagger correctly — but `animation: settle` above sits in the animation
   origin, which outranks the inline opacity/filter word.js writes per glyph, so
   that stagger was calculated and thrown away on every leaf entry and the hero
   resolved as one flat block. Switching the animation off is the whole fix.

   css/word.css also hangs a `transition` on these spans for a third version of
   the same reveal. It never ran either, for the same reason — and now that the
   animation is gone it WOULD, firing again on every inline write word.js makes
   and double-animating each glyph against its own tween. Hence both, together. */
html.js-motion .glyphs > span {
  transition: none;
}

/* The overlay rows, for the same reason: css/menu.css's nth-child table stops
   at `.links > li:nth-child(3)` and the markup ships five, so the last two took
   the 0s default and the menu opened bottom-up. reveal.js staggers them from the
   list length instead. css/menu.css keeps the clip-path growth and everything
   else about the overlay. */
html.js-motion .menu:is([data-live], [data-open]) :is(.index, .links) > li,
html.js-motion .menu:is([data-live], [data-open]) .phone {
  animation: none;
}

/* ============================================================================
   prefers-reduced-motion: reduce
   Reduced, then removed only where reduction is meaningless. THE COMPOSITION
   NEVER CHANGES: every element stays exactly where it is, at full opacity, in
   focus, fully legible. Nothing is hidden and nothing is removed from the
   accessibility tree.

   Because every reveal above sets `animation-fill-mode: both`, collapsing
   duration and delay to a single millisecond lands each element on its END
   keyframe. The explicit assertions that follow cover the four things that are
   not animations: registered custom properties whose initial value is the
   closed state, dash offsets, transforms held at zero, and blurs.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    transition-delay: 0ms !important;
  }

  /* Apertures render open and hairlines render drawn. These properties are
     registered `inherits:false`, so each is asserted on the element that
     consumes it — setting them on :root would do nothing at all. */
  .word .cut,
  .plate[data-aperture] {
    --cut-t: 1;
    --hair: 1;
  }

  .silhouette,
  .manifest tbody tr,
  table.index tbody tr {
    --draw: 100%;
  }

  /* All filter: blur() reveals dropped. */
  .word .glyphs > span,
  .card__inner .chrome,
  .statement,
  .lede,
  .above,
  .body,
  .forms,
  .plate-name,
  .label,
  .sub,
  .caps,
  .quote,
  a.pill,
  address,
  .locations,
  .locations li,
  .panel,
  .insets li,
  .colophon .col,
  .manifest tbody tr,
  table.index tbody tr,
  .callout .num,
  .menu .index > li,
  .menu .links > li,
  .menu .phone {
    filter: none !important;
    opacity: 1;
  }

  /* Slits render open. */
  .statement .inset {
    clip-path: inset(0 0) !important;
  }

  /* Counter-scale renders settled. */
  .word .cut img {
    transform: none !important;
  }

  /* Callout leaders and the Texas route render at their end state. */
  .callout .lead,
  .map .route {
    stroke-dashoffset: 0 !important;
  }

  .callout .tick {
    transform: scaleY(1) !important;
  }

  .map .pin {
    opacity: 1;
    transform: none !important;
  }

  /* The strike rule is information, not decoration — it renders struck. */
  .struck::after {
    transform: scaleX(1) !important;
  }

  /* The carbon lid becomes an instant ground swap; the bands never animate,
     and the plain ground cross-fade underneath is what changes the card. */
  .lid > i {
    transition: none !important;
  }

  /* The overlay opens and closes without the pill-to-card growth. */
  .menu:is([data-live], [data-open]) {
    clip-path: inset(0 0 0 0 round var(--menu-r)) !important;
  }

  /* The card's interior must never be left waiting on a collapsed animation. */
  html.js .card__inner {
    opacity: 1;
  }
}
