Sequenced scrub
Each child animates over its own slice of the pin. Choreography without a timeline.
First this
then this
then this
<div data-sv data-sv-pin="250vh">
<div class="sv-stage grid place-items-center">
<div class="sv-range sv-range-rise grid gap-3">
<h2 data-sv-from="0" data-sv-to=".4" class="text-4xl font-bold">First</h2>
<p data-sv-from=".3" data-sv-to=".7">Second</p>
<p data-sv-from=".6" data-sv-to="1">Third</p>
</div>
</div>
</div>
<!-- needs calc() division by var: Chrome 112 / Safari 16.4 / FF 112.
--sv-r is a registered property (initial-value: 1), so an engine
that can't compute the division settles there instead of turning
invalid; still consume it as var(--sv-r, 1). -->
<div data-sv data-sv-pin="250vh"> <!-- the pin helper owns the height -->
<div class="sv-stage"> <!-- the sticky viewport, from pin.css -->
<div class="sv-range sv-range-rise">
<h2 style="--sv-from: 0; --sv-to: .4">First</h2>
<p style="--sv-from: .3; --sv-to: .7">Second</p>
<p style="--sv-from: .6; --sv-to: 1">Third</p>
</div>
</div>
</div>
/* styles/pin.css ships it; the mechanism, if you want it inline: */
/* the clock lives on the container at zero specificity, so your own rule on
that container (--sv-clock: var(--sv-t)) or an inline style wins */
:where(.sv .sv-range) {
--sv-clock: var(--sv-pin, var(--sv-t, 0));
}
.sv .sv-range > * {
--sv-r: clamp(0, calc((var(--sv-clock) - var(--sv-from, 0)) /
(var(--sv-to, 1) - var(--sv-from, 0))), 1);
}
/* consume --sv-r however you like, ALWAYS with a fallback of 1, and floor
an opacity so unread text still meets 4.5:1 (the presets floor at .55): */
.mine > * { opacity: calc(.55 + .45 * var(--sv-r, 1)); scale: calc(.8 + var(--sv-r, 1) * .2); }
/* the same sheet's reduced-motion override, last so it wins on source order: */
@media (prefers-reduced-motion: reduce) {
.sv .sv-range > * { --sv-r: 1; }
}
/* the same under html[data-sv-motion="reduce"], the site's own switch */
:where([data-sv-motion="reduce"]) .sv .sv-range > * { --sv-r: 1; }
<Track pin="250vh">
<div className="sv-stage grid place-items-center">
<div className="sv-range sv-range-rise grid gap-3">
{steps.map((s, i, all) => (
<Step key={s.id} style={{ '--sv-from': i / all.length, '--sv-to': (i + 1.6) / all.length } as React.CSSProperties} {...s} />
))}
</div>
</div>
</Track>
// JS twin for canvas/WebGL consumers:
// track(el, { pin: '250vh', onPin: (p) => uniform.set(mapRange(p, 0.3, 0.7)) })