Hover card fan
Pure CSSThree overlapping cards: the one you point at straightens and comes toward you while the others dim and lean away. Static strips catch the pointer, so nothing flickers.
Five cards share one pivot below the hand and fan out by index.
Your edited version
transform-origin: 50% 150% moves the pivot below the card, so rotateZ swings it along an arc instead of spinning it in place.rotateZ(calc(var(--i) * 15deg)) fans symmetrically around an upright middle card, and the loop swings between that and the gathered 7°.translateZ per card gives each its own depth, which avoids flicker where they overlap.--u, so the hand is the same share of a gallery card, the editor and a recording canvas.translateY and the open hand and the gathered one share the miss rather than one of them being centred and the other not.transform-origin far below the cardrotateZ(var(--i) × step)translateZ per card to avoid z-fighting<div class="scene">
<div class="hand">
<i style="--i:-2">A<small></small></i>
<i style="--i:-1">K<small></small></i>
<i style="--i:0">Q<small></small></i>
<i style="--i:1">J<small></small></i>
<i style="--i:2">10<small></small></i>
</div>
</div>
.scene {
/* one base unit: every length below is a multiple of it, so the hand is the same share of a
card, the editor, a full screen and a recording canvas */
--u: 0.28vmin;
display: grid;
place-items: center;
perspective: calc(800 * var(--u));
}
.hand {
position: relative;
width: calc(110 * var(--u));
height: calc(156 * var(--u));
transform-style: preserve-3d;
/* the outer cards swing down as well as out, and their shadows hang lower still: the lift
splits the difference between the closed hand and the open one, so neither sits off centre */
transform: translateY(calc(-12 * var(--u))) rotateX(26deg);
}
.hand i {
position: absolute;
inset: 0;
padding: calc(8 * var(--u)) calc(12 * var(--u));
border-radius: calc(10 * var(--u));
background: linear-gradient(160deg, #fff, #d9dcec);
color: #14172b;
font: 800 calc(26 * var(--u))/1 Inter, system-ui;
box-shadow: 0 calc(8 * var(--u)) calc(16 * var(--u)) calc(-8 * var(--u)) #000;
/* the pivot is half a card below the bottom edge. Further down flattens the arc and throws
the outer cards wider — at 170% the open hand ran off the sides of the canvas. */
transform-origin: 50% 150%;
animation: fan 3s ease-in-out infinite alternate;
}
/* the spade under each index is drawn, not typed: ♠ is in neither font an export carries, so every
device, and the export service, drew a spade of its own. One shape, as a mask, in the card's ink */
.hand small {
display: block;
width: calc(13 * var(--u));
height: calc(14 * var(--u));
margin-top: calc(4 * var(--u));
background: currentColor;
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 22'%3E%3Cpath d='M10 0C6 5 0 8 0 12.5 0 15.6 2.4 17.6 5 17.6c1.8 0 3.3-.8 4.2-2-.3 2.4-1.2 4.2-2.7 5.9h7c-1.5-1.7-2.4-3.5-2.7-5.9.9 1.2 2.4 2 4.2 2 2.6 0 5-2 5-5.1C20 8 14 5 10 0z'/%3E%3C/svg%3E") center / contain no-repeat;
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 22'%3E%3Cpath d='M10 0C6 5 0 8 0 12.5 0 15.6 2.4 17.6 5 17.6c1.8 0 3.3-.8 4.2-2-.3 2.4-1.2 4.2-2.7 5.9h7c-1.5-1.7-2.4-3.5-2.7-5.9.9 1.2 2.4 2 4.2 2 2.6 0 5-2 5-5.1C20 8 14 5 10 0z'/%3E%3C/svg%3E") center / contain no-repeat;
}
@keyframes fan {
/* never closed flat: even the gathered hand is spread 7deg a card, enough to show every corner index, so a paused card shows a fan */
0%, 15% { transform: translateZ(calc(var(--i) * 1 * var(--u))) rotateZ(calc(var(--i) * 7deg)); }
85%, 100% { transform: translateZ(calc(var(--i) * 6 * var(--u))) rotateZ(calc(var(--i) * 15deg)); }
}