Skip to content

pastetaste

Spiral Deck

MediaAnimationInteraction

A stack of photo cards that deals itself along a spiral into a fanned wheel. Each change swings the next card in from the fan; at the end of a lap the cards spiral back into the deck.

Paste it into Claude Code: it installs the component, asks for your brand and content, and places it on the page you name.

Install

Or by hand, in a Next.js project with shadcn set up. It writes components/pastetaste/spiral-deck.tsx.

npx shadcn@latest add https://pastetaste.com/r/spiral-deck.json

Prompt

Install the pastetaste component "Spiral Deck" (spiral-deck) into this Next.js project and use it.

What it is: A stack of photo cards that deals itself along a spiral into a fanned wheel. Each change swings the next card in from the fan; at the end of a lap the cards spiral back into the deck.

Before you place it, ask me (one short message, in my language; "skip" = sensible default): which page and section it goes in, my brand colour (hex), the text, labels or images it should show, and whether I want my logo or my own copy in the demo content. Then:

Steps:
1. If the project has no components.json, run: npx shadcn@latest init -y -d. Then run: npx shadcn@latest add https://pastetaste.com/r/spiral-deck.json
   (it writes components/pastetaste/spiral-deck.tsx; install any npm packages it reports).
2. Import it: import { SpiralDeck } from "@/components/pastetaste/spiral-deck" (check the file for the exact named export).
3. Put it where it fits on the page I name, keep the rest of the page untouched, and pass props for my colours instead of editing the installed file.
4. It does not depend on Tailwind; colours come from CSS variables (--pt-*) or props.
5. It supports prefers-reduced-motion with a still state; do not remove it.

Props (all optional unless the type has no default):
- cards: { src: string; alt: string; title?: string; meta?: string; position?: string }[] — The cards, front of the deck first. title and meta are the caption under the focused card (meta in mono after the title); position is the image's object-position.
- defaultIndex: number = 0 — Card shown first.
- onIndexChange: (index: number) => void — Called when the focused card changes.
- intro: boolean = true — Start as a stacked deck and deal it along the spiral the first time the stage is 35% on screen.
- autoplay: number = 0 — Milliseconds between cards; 0 is off. Holds on hover, focus, drag, off-screen and in hidden tabs, adds a Pause button, and stays off under reduced motion.
- wrap: 'spiral' | 'wheel' = 'spiral' — After the last card: gather every card back along the spiral and deal again from the first, or keep turning the wheel.
- step: number = 20 — Degrees between fanned cards.
- curve: number = 1.25 — Fan wheel radius in card heights; smaller is a tighter fan.
- reach: number = 3 — Cards visible in the fan to the right of the focused one.
- aspect: number = 0.8 — Card width over height.
- size: number = 0.62 — Card height as a fraction of the stage height (also capped by the stage width).
- deckScale: number = 0.36 — Size of the stacked deck relative to a focused card.
- duration: number = 0.95 — Seconds a swap to the next card takes (expo-out).
- dealDuration: number = 1.6 — Seconds the top card takes to travel the spiral when dealing; the others follow 70 ms apart.
- backdrop: ReactNode — Content centred behind the cards, e.g. a large headline the deck sits on. It can read the --sd-spread variable (0 stacked, 1 spread) to fade or move with the deal.
- controls: boolean = true — Previous, next, counter, Stack/Spread and Pause buttons at the bottom of the stage.
- label: string = 'Card deck' — Accessible name of the carousel.
- className / style: string / CSSProperties — On the root. The root fills its parent's height (min 420 px), so give the parent a height.

Live preview: https://pastetaste.com/c/spiral-deck
Then run the dev server, check it in the browser at desktop and mobile width, and tell me the page URL.

Props 18

PropTypeDefaultDescription
cards{ src: string; alt: string; title?: string; meta?: string; position?: string }[]-The cards, front of the deck first. title and meta are the caption under the focused card (meta in mono after the title); position is the image's object-position.
defaultIndexnumber0Card shown first.
onIndexChange(index: number) => void-Called when the focused card changes.
introbooleantrueStart as a stacked deck and deal it along the spiral the first time the stage is 35% on screen.
autoplaynumber0Milliseconds between cards; 0 is off. Holds on hover, focus, drag, off-screen and in hidden tabs, adds a Pause button, and stays off under reduced motion.
wrap'spiral' | 'wheel''spiral'After the last card: gather every card back along the spiral and deal again from the first, or keep turning the wheel.
stepnumber20Degrees between fanned cards.
curvenumber1.25Fan wheel radius in card heights; smaller is a tighter fan.
reachnumber3Cards visible in the fan to the right of the focused one.
aspectnumber0.8Card width over height.
sizenumber0.62Card height as a fraction of the stage height (also capped by the stage width).
deckScalenumber0.36Size of the stacked deck relative to a focused card.
durationnumber0.95Seconds a swap to the next card takes (expo-out).
dealDurationnumber1.6Seconds the top card takes to travel the spiral when dealing; the others follow 70 ms apart.
backdropReactNode-Content centred behind the cards, e.g. a large headline the deck sits on. It can read the --sd-spread variable (0 stacked, 1 spread) to fade or move with the deal.
controlsbooleantruePrevious, next, counter, Stack/Spread and Pause buttons at the bottom of the stage.
labelstring'Card deck'Accessible name of the carousel.
className / stylestring / CSSProperties-On the root. The root fills its parent's height (min 420 px), so give the parent a height.
View all →