Shuffle
React + Tailwind
const T = 3.8;
const N = 10;
const HALF = N / 2;
const pct = (f: number) => `${(f * 100).toFixed(2)}%`;
// Riffle order: the halves drop in turn from the bottom, left first.
const order = (i: number) => (i < HALF ? 2 * i : 2 * (i - HALF) + 1);
const landAt = (k: number) => 0.21 + k * 0.034;
const tf = (x: number, y: number, r: number, f = 0) => `transform: translate(${x.toFixed(1)}px, ${y.toFixed(1)}px) rotate(${r.toFixed(1)}deg) rotateY(${f}deg)`;
function cardFrames(i: number) {
const k = order(i);
const side = i < HALF ? -1 : 1;
const j = i < HALF ? i : i - HALF;
const pile = tf(side * 64 - side * j * 0.4, 6 - j * 0.9, side * 13);
const jx = (((k * 7) % 5) - 2) * 1.3;
const jr = (((k * 5) % 7) - 3) * 1.4;
const landed = tf(jx, -k * 0.7, jr);
const squared = tf(0, -k * 0.7, 0);
const top = i === N - 1;
return [
`0%, ${pct(0.05)} { ${tf(0, -i * 0.7, 0)}; z-index: ${i}; animation-timing-function: cubic-bezier(0.5, 0, 0.3, 1); }`,
`${pct(0.17)}, ${pct(landAt(k) - 0.001)} { ${pile}; z-index: ${i}; animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); }`,
`${pct(landAt(k))} { ${pile}; z-index: ${20 + k}; animation-timing-function: cubic-bezier(0.3, 0, 0.4, 1.3); }`,
`${pct(landAt(k) + 0.045)}, ${pct(0.58)} { ${landed}; z-index: ${20 + k}; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); }`,
...(top
? [
`${pct(0.66)}, ${pct(0.7)} { ${squared}; z-index: ${20 + k}; animation-timing-function: cubic-bezier(0.4, 0, 0.3, 1); }`,
`${pct(0.77)}, ${pct(0.87)} { ${tf(0, -k * 0.7 - 10, 0, 180)}; z-index: ${20 + k}; animation-timing-function: cubic-bezier(0.4, 0, 0.3, 1); }`,
`${pct(0.94)} { ${tf(0, -k * 0.7, 0, 360)}; z-index: ${20 + k}; }`,
`100% { ${tf(0, -i * 0.7, 0, 360)}; z-index: ${i}; }`,
]
: [`${pct(0.66)}, ${pct(0.95)} { ${squared}; z-index: ${20 + k}; }`, `100% { ${tf(0, -i * 0.7, 0)}; z-index: ${i}; }`]),
].join(' ');
}
const css = `
.loader-shuffle { --stock: light-dark(#fbfcfd, #e5e6e9); --ink: light-dark(#373a40, #121417); --back: light-dark(#50535b, #393c41); position: relative; width: 230px; height: 176px; perspective: 700px; }
.loader-shuffle__card { position: absolute; left: 83px; top: 44px; width: 64px; height: 90px; transform-style: preserve-3d; animation: ${T}s infinite; }
.loader-shuffle__back, .loader-shuffle__face { position: absolute; inset: 0; border-radius: 5px; backface-visibility: hidden; box-shadow: 0 0 0 0.5px color-mix(in oklab, var(--ink) 30%, transparent), 0 1px 2px light-dark(rgba(0, 0, 0, 0.09), rgba(0, 0, 0, 0.3)); }
/* The back: white border round a fine lattice, framed by a hairline. */
.loader-shuffle__back { background: repeating-linear-gradient(45deg, var(--back) 0 1px, transparent 1px 4.5px) 6px 6px / 52px 78px no-repeat, repeating-linear-gradient(-45deg, var(--back) 0 1px, transparent 1px 4.5px) 6px 6px / 52px 78px no-repeat, var(--stock); outline: 1px solid var(--back); outline-offset: -7px; }
.loader-shuffle__face { transform: rotateY(180deg); background: var(--stock); color: var(--ink); font-family: Georgia, 'Times New Roman', serif; }
.loader-shuffle__index { position: absolute; left: 5px; top: 4px; display: grid; justify-items: center; font-size: 13px; font-weight: 700; line-height: 1; }
.loader-shuffle__index small { font-size: 10px; }
.loader-shuffle__index--low { left: auto; top: auto; right: 5px; bottom: 4px; rotate: 180deg; }
.loader-shuffle__pip { position: absolute; inset: 0; display: grid; place-items: center; font-size: 36px; line-height: 1; }
${Array.from({ length: N }, (_, i) => `.loader-shuffle__card:nth-child(${i + 1}) { animation-name: loader-shuffle-card-${i}; } @keyframes loader-shuffle-card-${i} { ${cardFrames(i)} }`).join('\n ')}
@media (prefers-reduced-motion: reduce) {
.loader-shuffle__card { animation: none !important; }
${Array.from({ length: N }, (_, i) => `.loader-shuffle__card:nth-child(${i + 1}) { transform: translate(${(i < HALF ? -1 : 1) * 30}px, ${-(i % HALF) * 0.7}px) rotate(${(i < HALF ? -1 : 1) * 6}deg); }`).join(' ')}
}
`;
export function Preview() {
return (
<>
<style href="kit-loaders-shuffle" precedence="kit">
{css}
</style>
<div className="loader-shuffle" role="status" aria-label="Loading">
{Array.from({ length: N }, (_, i) => (
<span key={i} className="loader-shuffle__card" aria-hidden="true">
<span className="loader-shuffle__back" />
{i === N - 1 && (
<span className="loader-shuffle__face">
<span className="loader-shuffle__index">
A<small>♠</small>
</span>
<span className="loader-shuffle__pip">♠</span>
<span className="loader-shuffle__index loader-shuffle__index--low">
A<small>♠</small>
</span>
</span>
)}
</span>
))}
</div>
</>
);
}Markup where the component goes, CSS in your stylesheet. No JavaScript.
HTML
<div class="loader-shuffle" role="status" aria-label="Loading">
<span class="loader-shuffle__card" aria-hidden="true">
<span class="loader-shuffle__back"></span>
</span>
<span class="loader-shuffle__card" aria-hidden="true">
<span class="loader-shuffle__back"></span>
</span>
<span class="loader-shuffle__card" aria-hidden="true">
<span class="loader-shuffle__back"></span>
</span>
<span class="loader-shuffle__card" aria-hidden="true">
<span class="loader-shuffle__back"></span>
</span>
<span class="loader-shuffle__card" aria-hidden="true">
<span class="loader-shuffle__back"></span>
</span>
<span class="loader-shuffle__card" aria-hidden="true">
<span class="loader-shuffle__back"></span>
</span>
<span class="loader-shuffle__card" aria-hidden="true">
<span class="loader-shuffle__back"></span>
</span>
<span class="loader-shuffle__card" aria-hidden="true">
<span class="loader-shuffle__back"></span>
</span>
<span class="loader-shuffle__card" aria-hidden="true">
<span class="loader-shuffle__back"></span>
</span>
<span class="loader-shuffle__card" aria-hidden="true">
<span class="loader-shuffle__back"></span>
<span class="loader-shuffle__face">
<span class="loader-shuffle__index">
A
<small>♠</small>
</span>
<span class="loader-shuffle__pip">♠</span>
<span class="loader-shuffle__index loader-shuffle__index--low">
A
<small>♠</small>
</span>
</span>
</span>
</div>CSS
.loader-shuffle { --stock: light-dark(#fbfcfd, #e5e6e9); --ink: light-dark(#373a40, #121417); --back: light-dark(#50535b, #393c41); position: relative; width: 230px; height: 176px; perspective: 700px; }
.loader-shuffle__card { position: absolute; left: 83px; top: 44px; width: 64px; height: 90px; transform-style: preserve-3d; animation: 3.8s infinite; }
.loader-shuffle__back, .loader-shuffle__face { position: absolute; inset: 0; border-radius: 5px; backface-visibility: hidden; box-shadow: 0 0 0 0.5px color-mix(in oklab, var(--ink) 30%, transparent), 0 1px 2px light-dark(rgba(0, 0, 0, 0.09), rgba(0, 0, 0, 0.3)); }
/* The back: white border round a fine lattice, framed by a hairline. */
.loader-shuffle__back { background: repeating-linear-gradient(45deg, var(--back) 0 1px, transparent 1px 4.5px) 6px 6px / 52px 78px no-repeat, repeating-linear-gradient(-45deg, var(--back) 0 1px, transparent 1px 4.5px) 6px 6px / 52px 78px no-repeat, var(--stock); outline: 1px solid var(--back); outline-offset: -7px; }
.loader-shuffle__face { transform: rotateY(180deg); background: var(--stock); color: var(--ink); font-family: Georgia, 'Times New Roman', serif; }
.loader-shuffle__index { position: absolute; left: 5px; top: 4px; display: grid; justify-items: center; font-size: 13px; font-weight: 700; line-height: 1; }
.loader-shuffle__index small { font-size: 10px; }
.loader-shuffle__index--low { left: auto; top: auto; right: 5px; bottom: 4px; rotate: 180deg; }
.loader-shuffle__pip { position: absolute; inset: 0; display: grid; place-items: center; font-size: 36px; line-height: 1; }
.loader-shuffle__card:nth-child(1) { animation-name: loader-shuffle-card-0; } @keyframes loader-shuffle-card-0 { 0%, 5.00% { transform: translate(0.0px, 0.0px) rotate(0.0deg) rotateY(0deg); z-index: 0; animation-timing-function: cubic-bezier(0.5, 0, 0.3, 1); } 17.00%, 20.90% { transform: translate(-64.0px, 6.0px) rotate(-13.0deg) rotateY(0deg); z-index: 0; animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 21.00% { transform: translate(-64.0px, 6.0px) rotate(-13.0deg) rotateY(0deg); z-index: 20; animation-timing-function: cubic-bezier(0.3, 0, 0.4, 1.3); } 25.50%, 58.00% { transform: translate(-2.6px, 0.0px) rotate(-4.2deg) rotateY(0deg); z-index: 20; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } 66.00%, 95.00% { transform: translate(0.0px, 0.0px) rotate(0.0deg) rotateY(0deg); z-index: 20; } 100% { transform: translate(0.0px, 0.0px) rotate(0.0deg) rotateY(0deg); z-index: 0; } }
.loader-shuffle__card:nth-child(2) { animation-name: loader-shuffle-card-1; } @keyframes loader-shuffle-card-1 { 0%, 5.00% { transform: translate(0.0px, -0.7px) rotate(0.0deg) rotateY(0deg); z-index: 1; animation-timing-function: cubic-bezier(0.5, 0, 0.3, 1); } 17.00%, 27.70% { transform: translate(-63.6px, 5.1px) rotate(-13.0deg) rotateY(0deg); z-index: 1; animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 27.80% { transform: translate(-63.6px, 5.1px) rotate(-13.0deg) rotateY(0deg); z-index: 22; animation-timing-function: cubic-bezier(0.3, 0, 0.4, 1.3); } 32.30%, 58.00% { transform: translate(2.6px, -1.4px) rotate(0.0deg) rotateY(0deg); z-index: 22; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } 66.00%, 95.00% { transform: translate(0.0px, -1.4px) rotate(0.0deg) rotateY(0deg); z-index: 22; } 100% { transform: translate(0.0px, -0.7px) rotate(0.0deg) rotateY(0deg); z-index: 1; } }
.loader-shuffle__card:nth-child(3) { animation-name: loader-shuffle-card-2; } @keyframes loader-shuffle-card-2 { 0%, 5.00% { transform: translate(0.0px, -1.4px) rotate(0.0deg) rotateY(0deg); z-index: 2; animation-timing-function: cubic-bezier(0.5, 0, 0.3, 1); } 17.00%, 34.50% { transform: translate(-63.2px, 4.2px) rotate(-13.0deg) rotateY(0deg); z-index: 2; animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 34.60% { transform: translate(-63.2px, 4.2px) rotate(-13.0deg) rotateY(0deg); z-index: 24; animation-timing-function: cubic-bezier(0.3, 0, 0.4, 1.3); } 39.10%, 58.00% { transform: translate(1.3px, -2.8px) rotate(4.2deg) rotateY(0deg); z-index: 24; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } 66.00%, 95.00% { transform: translate(0.0px, -2.8px) rotate(0.0deg) rotateY(0deg); z-index: 24; } 100% { transform: translate(0.0px, -1.4px) rotate(0.0deg) rotateY(0deg); z-index: 2; } }
.loader-shuffle__card:nth-child(4) { animation-name: loader-shuffle-card-3; } @keyframes loader-shuffle-card-3 { 0%, 5.00% { transform: translate(0.0px, -2.1px) rotate(0.0deg) rotateY(0deg); z-index: 3; animation-timing-function: cubic-bezier(0.5, 0, 0.3, 1); } 17.00%, 41.30% { transform: translate(-62.8px, 3.3px) rotate(-13.0deg) rotateY(0deg); z-index: 3; animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 41.40% { transform: translate(-62.8px, 3.3px) rotate(-13.0deg) rotateY(0deg); z-index: 26; animation-timing-function: cubic-bezier(0.3, 0, 0.4, 1.3); } 45.90%, 58.00% { transform: translate(0.0px, -4.2px) rotate(-1.4deg) rotateY(0deg); z-index: 26; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } 66.00%, 95.00% { transform: translate(0.0px, -4.2px) rotate(0.0deg) rotateY(0deg); z-index: 26; } 100% { transform: translate(0.0px, -2.1px) rotate(0.0deg) rotateY(0deg); z-index: 3; } }
.loader-shuffle__card:nth-child(5) { animation-name: loader-shuffle-card-4; } @keyframes loader-shuffle-card-4 { 0%, 5.00% { transform: translate(0.0px, -2.8px) rotate(0.0deg) rotateY(0deg); z-index: 4; animation-timing-function: cubic-bezier(0.5, 0, 0.3, 1); } 17.00%, 48.10% { transform: translate(-62.4px, 2.4px) rotate(-13.0deg) rotateY(0deg); z-index: 4; animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 48.20% { transform: translate(-62.4px, 2.4px) rotate(-13.0deg) rotateY(0deg); z-index: 28; animation-timing-function: cubic-bezier(0.3, 0, 0.4, 1.3); } 52.70%, 58.00% { transform: translate(-1.3px, -5.6px) rotate(2.8deg) rotateY(0deg); z-index: 28; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } 66.00%, 95.00% { transform: translate(0.0px, -5.6px) rotate(0.0deg) rotateY(0deg); z-index: 28; } 100% { transform: translate(0.0px, -2.8px) rotate(0.0deg) rotateY(0deg); z-index: 4; } }
.loader-shuffle__card:nth-child(6) { animation-name: loader-shuffle-card-5; } @keyframes loader-shuffle-card-5 { 0%, 5.00% { transform: translate(0.0px, -3.5px) rotate(0.0deg) rotateY(0deg); z-index: 5; animation-timing-function: cubic-bezier(0.5, 0, 0.3, 1); } 17.00%, 24.30% { transform: translate(64.0px, 6.0px) rotate(13.0deg) rotateY(0deg); z-index: 5; animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 24.40% { transform: translate(64.0px, 6.0px) rotate(13.0deg) rotateY(0deg); z-index: 21; animation-timing-function: cubic-bezier(0.3, 0, 0.4, 1.3); } 28.90%, 58.00% { transform: translate(0.0px, -0.7px) rotate(2.8deg) rotateY(0deg); z-index: 21; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } 66.00%, 95.00% { transform: translate(0.0px, -0.7px) rotate(0.0deg) rotateY(0deg); z-index: 21; } 100% { transform: translate(0.0px, -3.5px) rotate(0.0deg) rotateY(0deg); z-index: 5; } }
.loader-shuffle__card:nth-child(7) { animation-name: loader-shuffle-card-6; } @keyframes loader-shuffle-card-6 { 0%, 5.00% { transform: translate(0.0px, -4.2px) rotate(0.0deg) rotateY(0deg); z-index: 6; animation-timing-function: cubic-bezier(0.5, 0, 0.3, 1); } 17.00%, 31.10% { transform: translate(63.6px, 5.1px) rotate(13.0deg) rotateY(0deg); z-index: 6; animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 31.20% { transform: translate(63.6px, 5.1px) rotate(13.0deg) rotateY(0deg); z-index: 23; animation-timing-function: cubic-bezier(0.3, 0, 0.4, 1.3); } 35.70%, 58.00% { transform: translate(-1.3px, -2.1px) rotate(-2.8deg) rotateY(0deg); z-index: 23; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } 66.00%, 95.00% { transform: translate(0.0px, -2.1px) rotate(0.0deg) rotateY(0deg); z-index: 23; } 100% { transform: translate(0.0px, -4.2px) rotate(0.0deg) rotateY(0deg); z-index: 6; } }
.loader-shuffle__card:nth-child(8) { animation-name: loader-shuffle-card-7; } @keyframes loader-shuffle-card-7 { 0%, 5.00% { transform: translate(0.0px, -4.9px) rotate(0.0deg) rotateY(0deg); z-index: 7; animation-timing-function: cubic-bezier(0.5, 0, 0.3, 1); } 17.00%, 37.90% { transform: translate(63.2px, 4.2px) rotate(13.0deg) rotateY(0deg); z-index: 7; animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 38.00% { transform: translate(63.2px, 4.2px) rotate(13.0deg) rotateY(0deg); z-index: 25; animation-timing-function: cubic-bezier(0.3, 0, 0.4, 1.3); } 42.50%, 58.00% { transform: translate(-2.6px, -3.5px) rotate(1.4deg) rotateY(0deg); z-index: 25; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } 66.00%, 95.00% { transform: translate(0.0px, -3.5px) rotate(0.0deg) rotateY(0deg); z-index: 25; } 100% { transform: translate(0.0px, -4.9px) rotate(0.0deg) rotateY(0deg); z-index: 7; } }
.loader-shuffle__card:nth-child(9) { animation-name: loader-shuffle-card-8; } @keyframes loader-shuffle-card-8 { 0%, 5.00% { transform: translate(0.0px, -5.6px) rotate(0.0deg) rotateY(0deg); z-index: 8; animation-timing-function: cubic-bezier(0.5, 0, 0.3, 1); } 17.00%, 44.70% { transform: translate(62.8px, 3.3px) rotate(13.0deg) rotateY(0deg); z-index: 8; animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 44.80% { transform: translate(62.8px, 3.3px) rotate(13.0deg) rotateY(0deg); z-index: 27; animation-timing-function: cubic-bezier(0.3, 0, 0.4, 1.3); } 49.30%, 58.00% { transform: translate(2.6px, -4.9px) rotate(-4.2deg) rotateY(0deg); z-index: 27; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } 66.00%, 95.00% { transform: translate(0.0px, -4.9px) rotate(0.0deg) rotateY(0deg); z-index: 27; } 100% { transform: translate(0.0px, -5.6px) rotate(0.0deg) rotateY(0deg); z-index: 8; } }
.loader-shuffle__card:nth-child(10) { animation-name: loader-shuffle-card-9; } @keyframes loader-shuffle-card-9 { 0%, 5.00% { transform: translate(0.0px, -6.3px) rotate(0.0deg) rotateY(0deg); z-index: 9; animation-timing-function: cubic-bezier(0.5, 0, 0.3, 1); } 17.00%, 51.50% { transform: translate(62.4px, 2.4px) rotate(13.0deg) rotateY(0deg); z-index: 9; animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 51.60% { transform: translate(62.4px, 2.4px) rotate(13.0deg) rotateY(0deg); z-index: 29; animation-timing-function: cubic-bezier(0.3, 0, 0.4, 1.3); } 56.10%, 58.00% { transform: translate(1.3px, -6.3px) rotate(0.0deg) rotateY(0deg); z-index: 29; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } 66.00%, 70.00% { transform: translate(0.0px, -6.3px) rotate(0.0deg) rotateY(0deg); z-index: 29; animation-timing-function: cubic-bezier(0.4, 0, 0.3, 1); } 77.00%, 87.00% { transform: translate(0.0px, -16.3px) rotate(0.0deg) rotateY(180deg); z-index: 29; animation-timing-function: cubic-bezier(0.4, 0, 0.3, 1); } 94.00% { transform: translate(0.0px, -6.3px) rotate(0.0deg) rotateY(360deg); z-index: 29; } 100% { transform: translate(0.0px, -6.3px) rotate(0.0deg) rotateY(360deg); z-index: 9; } }
@media (prefers-reduced-motion: reduce) {
.loader-shuffle__card { animation: none !important; }
.loader-shuffle__card:nth-child(1) { transform: translate(-30px, 0px) rotate(-6deg); } .loader-shuffle__card:nth-child(2) { transform: translate(-30px, -0.7px) rotate(-6deg); } .loader-shuffle__card:nth-child(3) { transform: translate(-30px, -1.4px) rotate(-6deg); } .loader-shuffle__card:nth-child(4) { transform: translate(-30px, -2.0999999999999996px) rotate(-6deg); } .loader-shuffle__card:nth-child(5) { transform: translate(-30px, -2.8px) rotate(-6deg); } .loader-shuffle__card:nth-child(6) { transform: translate(30px, 0px) rotate(6deg); } .loader-shuffle__card:nth-child(7) { transform: translate(30px, -0.7px) rotate(6deg); } .loader-shuffle__card:nth-child(8) { transform: translate(30px, -1.4px) rotate(6deg); } .loader-shuffle__card:nth-child(9) { transform: translate(30px, -2.0999999999999996px) rotate(6deg); } .loader-shuffle__card:nth-child(10) { transform: translate(30px, -2.8px) rotate(6deg); }
}Goal: Create a "Shuffle" loader.
Visual behavior: A deck is cut, riffled together card by card, squared up, and the top card flips over to show an ace.
Technique:
- Pure CSS: every motion is a keyframe animation, so the plain HTML version needs no script.
- Each of the ten cards has its own generated keyframes: the cut into two piles, its slot in the riffle order (the halves drop in turn from the bottom), then square-up.
- Cards land with a little scatter of position and angle and a raised z-index, so the riffle builds a real interleaved stack.
- The top card is two faces in preserve-3d with backface-visibility hidden; it lifts, turns over to the ace and turns back before the next cut.
Accessibility: The root has role="status" and aria-label="Loading"; everything inside it is aria-hidden. Under prefers-reduced-motion the two halves sit fanned apart, ready to riffle.
My stack: {{USER_STACK}}
My styling: {{USER_STYLING}}
My constraints: {{USER_CONSTRAINTS}}
My colors: neutral only. Black, white and grays, no hues. Use the color values above as written, or my project's closest neutral tokens.
Return a single self-contained component. No dependencies.Goal: Create a "Shuffle" loader.
Visual behavior: A deck is cut, riffled together card by card, squared up, and the top card flips over to show an ace.
Technique:
- Pure CSS: every motion is a keyframe animation, so the plain HTML version needs no script.
- Each of the ten cards has its own generated keyframes: the cut into two piles, its slot in the riffle order (the halves drop in turn from the bottom), then square-up.
- Cards land with a little scatter of position and angle and a raised z-index, so the riffle builds a real interleaved stack.
- The top card is two faces in preserve-3d with backface-visibility hidden; it lifts, turns over to the ace and turns back before the next cut.
Accessibility: The root has role="status" and aria-label="Loading"; everything inside it is aria-hidden. Under prefers-reduced-motion the two halves sit fanned apart, ready to riffle.
My stack: {{USER_STACK}}
My styling: {{USER_STYLING}}
My constraints: {{USER_CONSTRAINTS}}
My colors: use my project's existing color tokens. The color values above are grayscale placeholders. Map each one by its role (surface, text, border, accent, glow) to my theme, and keep their relative lightness so contrast and the effect still read.
Return a single self-contained component. No dependencies.Paste into Claude, ChatGPT or Cursor after filling in {{USER_STACK}}, {{USER_STYLING}} and {{USER_CONSTRAINTS}}. Neutral keeps the greys you see here; Match my project asks the model to map them onto your own colour tokens.
Free to use in any project, commercial included. MIT-0, no attribution required. Licence
About this component
A deck is cut, riffled together card by card, squared up, and the top card flips over to show an ace.
- Dependencies
- None
- Difficulty
- Advanced
- Triggers
- continuous, loop, 3d, delay stagger
- Techniques
- keyframes, preserve 3d, 3d transform, perspective
- Reduced motion
- Respected
- Theme
- Light and dark (follows your page's color-scheme)
The root has role="status" and aria-label="Loading"; everything inside it is aria-hidden. Under prefers-reduced-motion the two halves sit fanned apart, ready to riffle.