ScrollVars / fx
docs · demo · bench · llms.txt · GitHub

Deck spread

A centered card deck deals itself into the grid. On arrival or scrubbed.

Use it for: Feature cards, pricing tiers, portfolio grids that deserve an entrance.
Knobs: --sv-mid = (N−1)/2, --sv-order per card, --sv-gap; map --sv-spread yourself to scrub

01
02
03

Install: npx scrollvars add deck-spread
Styles: import 'scrollvars/styles/core.css' · import 'scrollvars/styles/pin.css'

<section data-sv class="py-24">
  <div class="sv-spread sv-spread-in [--sv-gap:14px]">
    <div class="[--sv-order:0] rounded-xl border p-8">01</div>
    <div class="[--sv-order:1] rounded-xl border p-8">02</div>
    <div class="[--sv-order:2] rounded-xl border p-8">03</div>
  </div>
</section>
<section data-sv>
  <div class="sv-spread sv-spread-in">
    <div style="--sv-order: 0">…</div>
    <div style="--sv-order: 1">…</div>
    <div style="--sv-order: 2">…</div>
  </div>
</section>

/* styles/core.css ships it; the math, if you want it inline: */
.sv-spread { display: flex; gap: var(--sv-gap, 16px); justify-content: center; }
.sv-spread > * { --sv-d: calc(var(--sv-order, 0) - var(--sv-mid, 1));
  translate: calc(var(--sv-d) * (var(--sv-spread, 0) - 1) * (100% + var(--sv-gap, 16px))) 0;
  rotate: calc(var(--sv-d) * (1 - var(--sv-spread, 0)) * -5deg); }

/* scrub instead of play: */
.mine > * { --sv-spread: clamp(0, calc(var(--sv-t) * 2), 1); }

/* the same sheet's reduced-motion override, last so it wins on source order: */
@media (prefers-reduced-motion: reduce) {
  .sv-spread > *,
  .sv-on .sv .sv-spread.sv-spread-in > * {
    translate: none; rotate: none; transition: none; }
}
/* the same under html[data-sv-motion="reduce"], the site's own switch */
:where([data-sv-motion="reduce"]) .sv-spread > *, .sv-on:where([data-sv-motion="reduce"]) .sv .sv-spread.sv-spread-in > * { translate: none; rotate: none; transition: none; }
<Track>
  <div className="sv-spread sv-spread-in">
    {cards.map((c, i) => (
      <Item key={c.id} order={i}><Card {...c} /></Item>
    ))}
  </div>
</Track>
{/* Item carries the vars as props. No style attr; sv-spread targets
   direct children, and the Item wrapper IS the direct child. */}

Engine: npm i scrollvars, 12.9 KB gzip as ESM (this page's fx/sv.js IIFE: 13.1 KB). All effects respect prefers-reduced-motion and render complete without JS.