/* ===========================================================================
   leaf.css — the eight leaves.

   Two things live here and nothing else:

   1. The `<section>` spacers. The document scrolls natively — no wheel
      multiplier, no transform-translated reel, no scroll-behavior, nothing
      hijacked. Eight real 100svh landmarks provide genuine scroll length, a
      working scrollbar, keyboard paging, Find-in-page and position:sticky if
      it is ever needed. The card only ever READS window.scrollY.

   2. The ink declarations. Type colour is never tweened across hundreds of
      inherited nodes. The ground cross-fades (card.css) while each element
      carrying [data-plate] declares its own --on-plate / --on-soft / --on-rule
      locally, so ink changes at the moment the leaf swaps.
   ======================================================================== */


/* === 3.4 Section spacers ================================================= */

.leaf{
  position: relative;
  min-height: 100svh;
}

/* A leaf that scrubs something across its own length needs more than one
   viewport of scroll to scrub it with. The card is `position: fixed`, so for as
   long as this spacer is under the viewport the leaf stays on screen and the
   scroll only advances what is INSIDE it — the pin, and the scrub, and then the
   release when the spacer ends. That is the whole pin/scrub/unpin cycle, with no
   pinning library and nothing to synchronise: 300svh of spacer is 200svh of
   sweep for the line-up's arc, then leaf 04 takes the card.

   Anything given this attribute must be driven by section progress, not by a
   one-shot reveal, or it will simply sit still for three screens. */
.leaf[data-span="carousel"]{
  min-height: 380svh;
}
/* Ten testimonials need longer than nine cards: ~40svh of scroll each, so a
   quote is on screen long enough to actually be read. */
.leaf[data-span="stack"]{
  min-height: 420svh;
}
/* A leaf that scrubs nothing but still needs to be SEEN. One viewport of spacer
   means one flick of the wheel takes you past it — the locations table was being
   scrolled through unintentionally. Twice the spacer is twice the dwell, with no
   change to what is on it. */
.leaf[data-span="dwell"]{
  min-height: 210svh;
}


/* === 3.3 Ink =============================================================== */

/* Default ink — foam — so the card and its chrome are legible on the very
   first frame, before plate.js has stamped [data-plate] anywhere. Declared on
   `html` rather than on `.card` so that a [data-plate] written to either the
   root or the card itself always wins: a declaration on an element beats
   inheritance regardless of specificity. This block must stay ABOVE the
   attribute selectors below. */
html{
  --on-plate: var(--stencil);
  --on-soft:  var(--stencil-3);
  --on-rule:  var(--rule);
  --plate-now: var(--plate-foam);
}

/* The rule is mechanical: if the ground is not --plate-foam, secondary text is
   --stencil-2. --stencil-3 measures 4.84:1 on foam and fails AA on seal
   (4.25), cream (4.37) and kraft (4.27) — it is legal on foam and nowhere
   else. */
[data-plate="foam"]   { --on-plate:var(--stencil); --on-soft:var(--stencil-3); --on-rule:var(--rule) }
[data-plate="kraft"]  { --on-plate:var(--stencil); --on-soft:var(--stencil-2); --on-rule:var(--rule) }
[data-plate="seal"]   { --on-plate:var(--stencil); --on-soft:var(--stencil-2); --on-rule:var(--rule) }
[data-plate="cream"]  { --on-plate:var(--stencil); --on-soft:var(--stencil-2); --on-rule:var(--rule) }
[data-plate="carbon"] { --on-plate:var(--carbon-type); --on-soft:var(--carbon-soft); --on-rule:var(--rule-dark) }

/* The ground itself, as a variable, declared exactly where the ink is. The PBE
   tiles knock their letters out of the plate they sit on, so they need to name
   the current ground — and they must invert with it on the carbon leaf. */
[data-plate="foam"]   { --plate-now: var(--plate-foam) }
[data-plate="kraft"]  { --plate-now: var(--plate-kraft) }
[data-plate="seal"]   { --plate-now: var(--plate-seal) }
[data-plate="cream"]  { --plate-now: var(--plate-cream) }
[data-plate="carbon"] { --plate-now: var(--plate-carbon) }


/* === 3.4 The leaf bodies, once relocated into the stage =================== */

/* At boot leaves.js moves each `.leaf__body` into #stage with appendChild — a
   real DOM move, not a clone. Exactly one copy of every string exists in the
   DOM, in both JS and no-JS modes. The emptied `<section>` stays behind purely
   as a 100svh scroll spacer, keeping its id, aria-labelledby and data-plate;
   the wrapper keeps its own id, so anchors and aria references survive. */

#stage > .leaf__body{
  position: absolute;
  inset: 0;
}

/* THE LEAF THAT DOES NOT FIT, marked by js/fitleaf.js after measuring its
   content against the stage. It is never set on a leaf that fits, which is every
   leaf on a laptop and most of them on a big phone.

   THE SCROLLING ITSELF IS NOT HERE. css/compose.css already gives every leaf
   body `overflow: hidden auto` with chaining left on — read the note there
   before changing either. All this adds is what a leaf needs once it is actually
   being scrolled: momentum on iOS, and enough padding at the foot that the last
   line clears the card's bottom chrome instead of ending under the pill.

   The scrollbar is deliberately not hidden. This is the only scrollable region
   inside the card and the one place a reader has no other cue that there is
   more; styling it away would remove the single affordance. */
#stage > .leaf__body[data-overflows]{
  -webkit-overflow-scrolling: touch;
  padding-bottom: var(--s-4);
}

/* NEVER `display:none`. `visibility:hidden` keeps the text in the
   accessibility tree and findable by Find-in-page, and correctly removes its
   descendants from the tab order — exactly the pair of behaviours needed.
   a11y.js bridges focus and selection back to the owning section's offsetTop,
   because the browser cannot scroll a match inside a hidden leaf into view.

   The swap is 380 out / 90 hold / 720 in, encoded here in the two states:
   fading out leaves decisively on --e-fall and only then flips visibility;
   fading in arrives on --e-lay after the hold, with visibility restored at
   once so the incoming text is never mid-fade and unreachable. */

.leaf__body{
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: scale(var(--leaf-depth-out));
  transition: opacity var(--d-leaf-out) var(--e-fall),
              transform var(--d-leaf-out) var(--e-fall),
              visibility 0s linear var(--d-leaf-out);
}
.leaf__body[data-live]{
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: scale(1);
  transition: opacity var(--d-leaf-in) var(--e-lay),
              transform var(--d-leaf-in) var(--e-lay),
              visibility 0s linear 0s;
}

/* THE HOLD IS GONE FROM BOTH ENDS. js/leaves.js now brings the incoming leaf
   live at --d-leaf-lead rather than at out+hold, and the incoming transition no
   longer carries a --d-leaf-hold delay of its own. Between them those two waits
   left the stage genuinely empty for ~190 ms on every leaf change. The bodies
   are `position:absolute; inset:0` siblings, so they were always able to
   overlap — nothing was stacking them, only the timing kept them apart.

   A leaf that has never been live must still come from BEHIND rather than from
   in front, or the first leaf of the page arrives backwards. `.leaf__body`'s
   resting transform above is the departing state; this is the waiting one. */
.leaf__body:not([data-live]):not([data-leaving]){
  transform: scale(var(--leaf-depth-in));
}

/* The departing leaf must sit UNDER the arriving one for the overlap to read as
   a dissolve rather than a fight. */
.leaf__body[data-leaving]{ z-index: 0 }
.leaf__body[data-live]{ z-index: 1 }

/* ── js-transit: js/transit.js owns the swap ────────────────────────────────
   Everything above is the fallback — no JS, no anime.js, reduced motion, or a
   transit module that threw on boot. When transit.js IS live it drives opacity,
   transform and clip-path per leaf PAIR, so the single shared cross-dissolve
   above has to get out of the way: a CSS transition on the same properties
   would fire again on every inline write and double-animate against the
   timeline. Same handover pattern as css/motion.css's reveal block.

   visibility stays CSS's, but both states become visible with no delay — the
   departing leaf has to remain on screen for as long as its own gesture lasts,
   and transit.js decides when that is by removing `data-leaving` itself. */
html.js-transit .leaf__body{
  transition: none;
}

/* BOTH ON-STAGE STATES RESOLVE TO opacity 1 HERE, AND THAT IS THE FAIL-SAFE.
   transit.js writes inline opacity and animates it, and inline beats this rule,
   so in normal operation this value is never what you see. It matters when
   something goes wrong: if a gesture throws, or anime.js never loads, a leaf
   left to CSS is VISIBLE rather than blank.

   The reason the transition above must be `none` and this must be `1`: with the
   old `[data-live] { opacity: 1 }` plus a disabled transition, a leaf snapped to
   fully opaque the instant the attribute landed, and anime then animated it from
   1 to 1 — the gesture ran and nothing moved. transit.js now parks the element
   at 0 synchronously inside the same task that sets `data-live`, before the
   browser paints, so there is no flash of the CSS value either. */
html.js-transit .leaf__body:is([data-live], [data-leaving]){
  opacity: 1;
  visibility: visible;
  transition: none;
}

/* A leaf that is neither live nor leaving is genuinely off stage. The 0s delay
   matters: with transit.js owning the fade, there is no outgoing opacity
   transition left for visibility to wait on. */
html.js-transit .leaf__body:not([data-live]):not([data-leaving]){
  visibility: hidden;
  transition: none;
}
