← Learn · Prompt · 19 October 2026 · 10 min

The Prompt Behind My Second Hero: A Deck of Reels Dealt Into a Fan That Lifts Into a Turning Wheel

A copy-pasteable prompt and the code concepts behind the Deal hero: Reel cards dealt face down, flipped into a fan while the loader counts, then lifted into a wheel of playing videos rising out of the clouds.

My homepage opens with a small tilted ring of Reels that expands into a 3D carousel. I wrote up the prompt and the code behind that one. This post is about the second hero I built to compete with it, and you can see it live at avivashishta.com/test. It is the same homepage, word for word, with only the hero swapped, so you can open both in two tabs and compare.

The Deal hero starts like a card trick. While the counter climbs, a deck flies up from below the screen. Every card arrives face down, flips face up mid-flight and lands in a fan. When the loader hits 100, the fan does not get replaced. It lifts, tips back, opens into a wider wheel of bigger cards and starts turning, while the sky and the cloud bank arrive around it and the Reels near the top start playing.

The short version: the deck is a full 360 degree wheel pinned to a pivot below the screen, so only its top third is ever visible. Each card has one home angle on that wheel. The homepage's own loader already animates two numbers, formation and expansion. Map formation to the deal and expansion to the lift, and the whole sequence comes from one transform per card.

The prompt

Paste this into Claude Code, Cursor or Replit. It describes the behaviour rather than my file layout, so it works in any React project.

Build a homepage hero where a deck of vertical video cards is dealt into a fan while the page loads, then lifts into a slowly turning wheel. React, Motion (framer-motion), plain CSS 3D transforms. No WebGL, no Three.js.

THE WHEEL: 34 cards (9:16) sit on a full circle around a pivot point about 500px below the centre of the hero, each one standing on the rim with its bottom toward the pivot. Card i has a home angle of i * 360 / 34, where 0 is straight up. Because the pivot is below the screen, only the cards within about 100 degrees of the top are ever visible. Hide the rest with visibility: hidden, which means there is never a seam where cards wrap around.

ONE TRANSFORM PER CARD, in this order: translate3d(0, pivotY, depth) then rotateZ(angle) then translate3d(0, -radius, 0) then rotateY(flip) then scale(s). Move to the pivot, rotate around it, push out along the rotated axis. Give each card a small negative z of its distance from the top, so the top card always overlaps its neighbours symmetrically.

TWO MOTION VALUES DRIVE EVERYTHING: deal (0 to 1) and lift (0 to 1).

THE DEAL (the loader): order cards by their distance from the top, so the top card is dealt first and the deal spreads outward to both sides at once. Each card gets its own slice of the deal: p = easeOutCubic((deal - order * 0.6) / 0.4). While p runs 0 to 1, the card's angle sweeps from 70 degrees to the right of its home angle down to the home angle, so every card travels left and the whole deck moves one way, its distance from the pivot from 0 to the radius (starting 140px low so it comes up from below the screen), rotateY goes from 180 to 0 so it flips face up mid-flight, and scale from 0.7 to 1. Give every card a back face (an ink panel with a serif monogram) so the flip shows something. The deal runs about 2.6 seconds, linear, while a real progress counter loads the covers and video previews.

THE LIFT (the reveal): starts only when the deal is finished AND the assets are ready. Over about 1.9 seconds with a strong ease (cubic-bezier .5, 0, .15, 1), the cards grow 35 percent and the wheel grows with them: the radius goes from 580 to 1010 so the bigger cards get room instead of crowding, and the pivot drops from 500 to 814 so the top card stays clear of the frame. The whole scene also tips back 12 degrees around X, scales up 14 percent, and every card's angle gets a 28 degree turn to the left so the wheel visibly turns as it opens, in the same direction it will keep drifting. Never let the opening turn and the drift go opposite ways, or the wheel jerks backwards as it settles. After the lift the wheel keeps turning slowly on its own (7 degrees per second, scaled by lift so it ramps in).

THE WORLD: as the lift starts, a painted sky fades in behind the wheel and a cloud bank rises in front of its lower half. The clouds are fully opaque, so the wheel rises out of them and never shows through them. Header and headline start about a second into the lift, while the wheel is still moving.

VIDEO: each card has a cover image and a muted, looping, inline video with preload none. After the lift passes 0.6, cards within 40 degrees of the top play; everything else pauses. The video starts at opacity 0 and fades in only on its playing event, so there is never a black frame between cover and video.

INTERACTION: drag, wheel and swipe move a target angle, and the visible angle eases toward it with an exponential follow (1 - exp(-dt * 9)). On release, keep the drag velocity and let it decay (velocity *= exp(-dt * 3.2)) so a flick glides. Ignore the velocity if the pointer paused for more than 80ms before release. The cursor never tilts the cards: the deck moves only on its own or under a drag. A drag must not trigger a card's link click.

PERFORMANCE: one requestAnimationFrame loop for all cards. Keep a cache of the last value written for every style and skip the write when nothing changed. Decode every cover with img.decode() before the deal starts, so no card pops in mid-flight. Size the scene to the hero frame, not the window, so the lifted wheel clears the frame's top edge.

ACCESSIBILITY AND ESCAPES: Escape, Tab and a visible skip button end the entrance instantly. With prefers-reduced-motion both values jump to 1 and nothing moves on its own. Every card is a real link with a text label for screen readers.

Show me the pose code for one card first, then the deal, then the lift, then video and gestures.

Concept 1: a fan is a wheel you only see the top of

My first version was a real fan: 14 cards spread across 150 degrees. It looked great until you dragged it, because a fan has ends. When a card scrolled off one end it had to reappear at the other, and no amount of fading hid that jump.

The fix was to stop building a fan. The deck is a full wheel of 34 cards around a pivot 500px below the centre of the hero (it drops further during the lift). With that geometry, the bottom half of the wheel is always off screen or behind the clouds, so the visible top looks exactly like a fan, and it can turn forever without a seam.

const step = 360 / cards.length;
// 0 is straight up; -180..180 either side.
const home = wrap(i * step - spin + kick);
const away = Math.abs(home);
const visible = p > 0 && away < 105;

Concept 2: pivot, rotate, push out

Every card's position is one transform string, and the order is the whole trick:

const transform =
  `translate3d(0, ${pivot}px, ${-away * .5}px)` + // go to the pivot, top card frontmost
  ` rotateZ(${home + (1 - p) * 70}deg)` +          // sweep left around it into place
  ` translate3d(0, ${-radius * p + (1 - p) * 140}px, 0)` + // out to the rim, from below
  ` rotateY(${(1 - p) * 180}deg)` +               // flip face up as it lands
  ` scale(${(.7 + .3 * p) * grow})`;

Read it right to left and it is how you would deal a card by hand: hold it at the deck, flip it, slide it out, rotate it into the fan. Every card starts 70 degrees to the right of its place and sweeps left into it. In my first version, the fan opened outward from the middle, so half the cards went right and half went left, and then the wheel turned left. That half second of rightward motion was the thing that made it feel unsmooth. One direction, all the way through, from the first card to the idle drift. The z offset matters more than it looks. Without it, card 0 overlaps card 33 and the fan has a visible seam at the top. With -away * .5, the card nearest the top always sits in front of its neighbours on both sides.

Concept 3: deal from the middle out

The ring hero deals cards around the circle in order. For a fan that reads wrong, because your eye goes to the top first. So each card's slot in the timeline comes from its distance to the top:

const order = Math.min(away, 100) / 100;          // 0 at the top, 1 at the edges
const p = outCubic(span(deal, order * .6, order * .6 + .4));

Each card spends 40 percent of the deal moving and starts at its own point in the first 60 percent. The top card is dealt first, and the deal spreads to both sides at once, which makes it look symmetrical and deliberate.

Concept 4: the loader already tells you when to lift

I didn't write a new timeline. The homepage's loader animates two motion values: formation while it counts, and expansion when the ring opens. It also handles the counter, the skip button, Escape, reduced motion and the moment the header and headline come in. The Deal hero just reads the same two numbers:

const dealt = formation.get(), lift = expansion.get();
// Bigger cards need a bigger wheel, and a bigger wheel needs a lower pivot to stay in frame.
const radius = 580 + 430 * lift, pivot = 500 + 314 * lift, grow = 1 + .35 * lift;
const kick = -28 * lift; // left, the same way the wheel drifts afterwards
stage.style.transform =
  `translateY(${60 - 10 * lift}px) rotateX(${12 * lift}deg) scale(${scale() * (1 + .14 * lift)})`;

The growth is part of the same motion. The deal packs small cards tightly, because a dense fan reads as a deck. The resting wheel needs the opposite: Reels big enough to watch, with a little air between them. Growing the cards, the radius and the pivot drop on the same lift value means the deck loosens into the carousel in one continuous move, and there's never a moment where it resizes.

That is why the two heroes can share one page. Everything that makes the entrance feel finished (the real progress counter, the brand word flying into the header, the frame drawing in) belongs to the loader, and both heroes plug into it.

Concept 5: the clouds are solid, the videos fade in

Two small details made it feel like one world rather than cards over a picture.

The cloud bank is fully opaque on this hero. On the ring, clouds at 92 percent read as mist. On a wheel whose lower half is always behind them, any transparency shows the cards through them, and the wheel stops looking like it rises out of the sky.

Videos never pop. Each card shows its cover, and the video sits on top at opacity 0 until the browser fires playing:

.preview { opacity: 0; transition: opacity .5s ease; }
.preview[data-playing] { opacity: 1; }

Calling play() doesn't mean a frame is ready. Waiting for the event is the difference between a crossfade and a black flash.

Concept 6: smooth is mostly about what you don't do

The whole hero runs in one requestAnimationFrame loop that writes inline styles, with a few rules:

  • Write only what changed. A Map remembers the last value of every style per card and skips the write if it's the same. Settled and off-screen cards cost nothing.
  • Hide, don't fade, what's out of view. Cards past 105 degrees get visibility: hidden, so the compositor skips them entirely.
  • Decode before you deal. img.decode() on every cover before the clock starts means no card arrives as a blank rectangle.
  • No cursor tilt. I tried a few degrees of parallax on mouse move and took it out. The cards should move because the deck is turning, not because your hand drifted across the trackpad.
  • Follow, don't snap. The visible angle eases toward the target with 1 - Math.exp(-dt * 9), which stays the same at any frame rate. Flicks keep their velocity and decay smoothly, and a pause before letting go cancels the flick, so a slow, careful drag stops exactly where you left it.
  • Size to the frame. The scene scales to the hero frame's size, not the window's. On a short laptop screen the lifted wheel would otherwise poke out of the top of the frame.

Ring or deal?

The ring is about immersion: you end up inside a room of Reels. The deal is about presentation: a hand of cards held up for you to pick from, and it shows more covers at once at a readable size. I haven't decided which one stays. Open the homepage and the Deal version side by side and tell me which one you'd keep.