scrollvars / fx
docs · demo · bench · llms.txt

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
<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); }
<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 — full core 3.9 KB gzip. All effects respect prefers-reduced-motion and render complete without JS.