CardTrain
svganimationbackgroundAn endless train of tilting cards flowing along a wave — pure canvas 2D, no WebGL, fully prop-driven.
An endless train of tilting cards flowing along a wave — pure canvas 2D, no WebGL, fully prop-driven.
Playground
Every card dealt,
every promise kept
Always be
shipping.
Tweak the props in the toolbox — the preview updates in realtime. “Copy code” gives you the exact JSX for the current tweaks.
Installation
Requires the shadcn CLI. Run this from your project root — it writes the component source, then you import and go.
npx shadcn@latest add "https://webcules.com/r/card-train.json"
The command installs the component source (plus its engine / type files when the component ships one). No other dependencies — everything is dependency-free canvas, SVG and CSS.
Usage
Import the component and drop it in — every prop in the toolbox is a regular React prop:
import { CardTrain } from "@webcules/ui/components/ui/card-train";export function Hero() {return (<section className="relative h-[600px] overflow-hidden"><CardTrain className="absolute inset-0" /><h1 className="absolute bottom-16 left-16 text-6xl">Always be shipping</h1></section>);}
Highlights
- An endless train of overlapping cards flows along a traveling wave, each card flipping through 3D as it rides the rail — the "shipping" hero, prop-driven
- Hand-rolled perspective projection in canvas 2D: crests sit closer and larger, troughs recede and dim toward the backdrop — no WebGL, no dependencies
- Shadows fall card-to-card, so the stack reads as layered paper, not flat sprites; colors cycle per card from any palette you pass
- Seamless loop (cards spawn off-canvas), deterministic seed, aria-hidden, one settled frame under prefers-reduced-motion
Example — Candy — the reference look
Rose / periwinkle / lilac cards on warm paper-gray. Compose the copy like the train wants it: headline bottom-left, tagline top-right.
<CardTraincolors={["#f4a7b3", "#a9b6e6", "#d9aee0"]}background="#e8e6e2"/>
Example — Sunset — warmer, friendlier
Coral / amber / rose cards on warm cream — same geometry, different mood.
<CardTraincolors={["#f2977f", "#f2c14e", "#e88aa0"]}background="#faf3ea"/>
Example — Midnight — dark heroes
Indigo / steel-blue / violet cards with brighter gradients on deep navy. Bump depth so the crest scale carries the dark scene.
<CardTraincolors={["#5b6bd6", "#7f9bd9", "#9a7fd6"]}background="#12141f"depth={0.35}/>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| colors | string[] | string | "#f4a7b3,#a9b6e6,#d9aee0" | Per-card color cycle — the train walks it by index (in code also accepts an array). |
| background | string | "#e8e6e2" | Backdrop the train rides on. Use "transparent" to let the page show through. |
| cardSize | number | 150 | Base card edge in px — auto-scales down on narrow containers. |
| count | number | 26 | Minimum cards in the stream — auto-raised to fill the width. |
| overlap | number | 0.65 | Fraction of each card hidden by the next — high values read as one ribbon. |
| amplitude | number | 0.18 | Wave height as a fraction of the container height. |
| wavelength | number | 1.4 | Wave length as a fraction of the container width. |
| speed | number | 0.12 | Stream speed in container-widths per second; 0 = frozen. |
| tilt | number | 1 | Flip intensity — 0 rides a flat rail, 1 is the cinematic default. |
| depth | number | 0.25 | Perspective amount — crest scale plus trough dimming. |
| confetti | boolean | true | Drifting square-shard ambience layer around the train. |
| confettiCount | number | 24 | How many confetti squares drift. |
| seed | number | 1 | Deterministic confetti field — same seed, same sky. |
Notes
- colors takes a comma-separated string (as the toolbox emits) or a real array — same result.
- The wave itself glides slowly forward; the train rides it, so cards bob gently as they travel — speed={0} freezes both.
- cardSize auto-scales down on narrow containers (capped at ~10% of the width) so the train stays proportional on phones.
- Confetti mostly sits behind the train; a few squares drift in front to keep the scene airy.
- Under prefers-reduced-motion it renders one settled static frame; the loop pauses on hidden tabs.
