/* ==========================================================================
   Yari Stats: the pinned, scroll-scrubbed timeline

   Carroll's mechanic, not an approximation of it. The stage pins to the
   viewport and the entire reveal is scrubbed against scroll position, so the
   sequence is driven by the wheel and stops dead when the wheel stops.

   Their arithmetic, from js/..._project__js_gsap_animation.min.js:
     the timeline spans 200vh and holds 17.2 time units
     the pin engages at progress 0.5 and releases at 1.0
   So the first half plays while the section scrolls in, and the whole reveal
   happens while the page is locked and motionless. That lock is the effect.

   Ten steps as progress windows (their unit range / 17.2):
     1  0.000 - 0.233  panel drops in from 440px above     LINEAR
     2  0.233 - 0.465  the mark inside it scales away
     3  0.465 - 0.523  HOLD. the pin engages inside this
     4  0.523 - 0.581  clip-path wipes open from inset 24%
     5  0.581 - 0.640  HOLD on the open slab
     6  0.640 - 0.698  the panel ground dissolves to nothing
     7  0.698 - 0.756  cards re-gutter, 50% flush to 48% + 30px
     8  0.756 - 0.791  figures rise, character by character
     9  0.791 - 0.826  notes rise, one beat behind
    10  0.826 - 1.000  the cover collapses its 400px tail

   Steps 3 and 5 tween to values already held. In a scrubbed timeline that is
   how you buy dwell: nothing moves, so the state gets read.

   The CSS default in stats.css is the FINISHED state. .yp-scrub, added by
   stats-scroll.js only when it will actually drive the section, is what winds
   it back. No JS, narrow viewport, or reduced motion all leave it complete.
   ========================================================================== */

/* ---- the thing that actually makes the pin work -------------------------
   Divi sets overflow-x:auto on #page-container. Any ancestor whose overflow
   is not visible becomes the scroll container that position:sticky sticks
   INSIDE, and since #page-container is 13000px tall and never scrolls itself,
   the stage had nothing to stick to and simply scrolled away. The section
   looked correct at rest, which is what made this hard to see.

   overflow:clip clips exactly the same but does NOT establish a scroll
   container, so sticky resolves against the viewport again.

   BOTH axes have to be set together. Setting only overflow-x:clip against
   Divi's overflow-y computes back to hidden, because the axes coerce each
   other when they disagree, and hidden is still a scroll container.

   Scoped to the body class so pages with no pinned section are untouched. */
body.home #page-container { overflow: clip; }
body.home #page-container,
body.home #et-main-area { overflow: clip; }

/* The runway the stage sticks inside. 300vh carries the 200vh timeline plus
   the pinned viewport sitting on top of it. */
.yp-stats.yp-scrub { padding-bottom: 0; }

/* 200vh: one viewport of stage plus one of pinned travel. The first half of
   the timeline is paid for by the section's own approach up the screen, so it
   does not need page of its own. 300vh here bought a dead third at the top. */
.yp-stats.yp-scrub .yp-stats-runway {
  position: relative;
  /* One viewport of stage plus one of pinned travel. The stage is taken out of
     flow while pinned, so the runway is what holds the section open. */
  height: 200vh;
}

/* The oversized name is set nowrap and runs wider than the frame by design.
   Without this it escapes the parked stage and leaves a stray glyph sitting on
   the ground between this section and the next. */
.yp-stats.yp-scrub .yp-stats-cover { overflow: hidden; }

/* The pin is driven by JS toggling .is-pinned, NOT by position:sticky.

   Sticky sticks inside the nearest ancestor whose overflow is not visible,
   and this Divi shell has three of them: #page-container (inline, written by
   Divi's own script, so no stylesheet can beat it), body, and previously the
   section itself. The stage had a 13000px non-scrolling box to stick inside
   rather than the viewport, so it looked right at rest and then scrolled away.

   position:fixed answers to none of that. The cost is that the script has to
   hold the horizontal geometry itself, which it does from the runway's box. */
.yp-stats.yp-scrub .yp-stats-stage {
  position: relative;
  height: 100vh;
  display: flex;
  align-items: center;
  overflow: hidden;
}

.yp-stats.yp-scrub .yp-stats-stage.is-pinned {
  position: fixed;
  top: 0;
  z-index: 1;
}

/* After the run the stage parks at the foot of its runway instead of jumping
   back to the top of it.

   It also stops being a viewport tall box. While pinned the stage is 100vh so
   the composition sits centred in the screen, but once parked that leaves a
   couple of hundred pixels of empty ground below the picture that the reader
   has to scroll through before the next section arrives. Carroll closes the
   same gap with their last timeline step, which collapses the cover's 400px
   tail as the pin releases. Hugging the content does the same job. */
.yp-stats.yp-scrub .yp-stats-stage.is-parked {
  position: absolute;
  bottom: 0;
  /* height MUST match the pinned height. While pinned the stage is a viewport
     tall and the cover sits centred in it; parking it at its content height
     put the cover 110px lower, and the release read as a hard downward jerk
     because the cover moved 110px against 15px of scroll.

     With both states a viewport tall, the parked block occupies exactly the
     last screen of the runway, which at the release moment IS the viewport, so
     the cover lands on the same pixel it was pinned at. The handover becomes
     invisible. */
  height: 100vh;
  /* left AND right, not width. An absolutely positioned box with neither
     collapses to shrink-to-fit, and since every child inside it is absolute
     too there is nothing left to measure: the whole composition went to zero
     width and the section rendered as an empty band. The script clears the
     inline left/width it uses while pinned, so the parked state has to supply
     its own horizontal anchoring. */
  left: 0;
  right: 0;
}

/* Every animated value arrives as a custom property written once per frame.
   Interpolation in JS, painting in CSS: no longhand style writes, and the
   whole resting state stays legible here instead of buried in the script. */

/* (0) the photo group settles from scale(.8) translateY(50%) */
.yp-stats.yp-scrub .yp-stats-animate {
  transform: translateY(calc(50% * var(--s-photo, 0)))
             scale(calc(1 - 0.2 * var(--s-photo, 0)));
}

/* (1) the panel drops in, (4) wipes open, (6) loses its ground */
.yp-stats.yp-scrub .yp-stats-records {
  transform: translateY(-50%) translateY(calc(-100% * var(--s-panel, 0)));
  clip-path: inset(0 var(--s-wipe, 0%));
  background-color: rgb(var(--yp-ink-rgb) / var(--s-ground, 0));
}

/* (2,3) the mark inside the panel scales away, then holds at nothing */
.yp-stats.yp-scrub .yp-stats-mark {
  transform: translate(-50%, -50%) scale(var(--s-mark, 0));
  opacity: var(--s-mark, 0);
}

/* (7) the flush 2x2 opens its gutters. Their tween writes width and
   margin-bottom; same two properties here, same end values. */
/* THE SPLIT. This is the move the whole section exists for: one solid black
   block that visibly divides into four tiles with the photograph showing
   between them. Their tween is 50% -> 48% + 30px, which on their composition
   reads because their panel ground dissolves at the same time. Ours opens
   wider, because a 2% gutter is a seam, not a division. */
.yp-stats.yp-scrub .yp-stats-card {
  width: calc(50% - 5% * var(--s-gutter, 0));
  height: calc(50% - 4% * var(--s-gutter, 0));
  margin-bottom: calc(30px * var(--s-gutter, 0));
}

/* (8) figures rise out of their mask, one character at a time */
.yp-stats.yp-scrub .yp-stats-plain { display: none; }
.yp-stats.yp-scrub .yp-stats-chars { display: inline; }
.yp-stats.yp-scrub .yp-stats-chars span {
  transform: translateY(calc(100% * var(--s-ch, 0)));
  opacity: calc(1 - var(--s-ch, 0));
}

/* (9) notes follow */
.yp-stats.yp-scrub .yp-stats-note {
  transform: translateY(calc(20px * var(--s-note, 0)));
  opacity: calc(1 - var(--s-note, 0));
}

/* (10) the cover collapses the tail it was holding open */
.yp-stats.yp-scrub .yp-stats-cover {
  padding-bottom: calc(var(--s-tail, 0) * clamp(60px, 10vh, 200px));
}

/* ---- the hand back ------------------------------------------------------
   A pin release is always a step change in velocity: the composition is
   stationary one frame and moving at full scroll speed the next. Position is
   continuous, but the eye still reads the change.

   Carroll cover the same moment by collapsing their 400px tail through the
   last three timeline units, so the section is visibly closing before it lets
   go. This does it by easing the composition back and down in presence across
   the final stretch, so by the time the pin releases the block is already
   receding and the switch to scrolling is the end of a movement rather than
   the start of one. Opacity and scale only: neither affects layout, so the
   position continuity measured at the boundary is untouched. */
.yp-stats.yp-scrub .yp-stats-cover {
  /* Subtle on purpose. Enough that the block reads as receding before the pin
     lets go, not so much that a visibly washed out composition then scrolls
     past. 0.38 was too much: it left the section at 62 percent as it exited. */
  opacity: calc(1 - 0.2 * var(--s-exit, 0));
  transform: scale(calc(1 - 0.022 * var(--s-exit, 0)));
  transform-origin: 50% 35%;
}

/* Nothing here transitions: every value is scrubbed, and a transition on top
   of a scrub produces lag against the wheel rather than smoothing. */
.yp-stats.yp-scrub .yp-stats-animate,
.yp-stats.yp-scrub .yp-stats-records,
.yp-stats.yp-scrub .yp-stats-mark,
.yp-stats.yp-scrub .yp-stats-card,
.yp-stats.yp-scrub .yp-stats-chars span,
.yp-stats.yp-scrub .yp-stats-note { transition: none; }

@media (prefers-reduced-motion: reduce) {
  .yp-stats.yp-scrub .yp-stats-runway { height: auto; }
  .yp-stats.yp-scrub .yp-stats-stage { position: relative; height: auto; }
}
