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.
Cover and pages hinge on the spine, each opening to a slightly different angle.
Your edited version
transform-origin: left — the spine is the hinge.--n. One unit of translateZ per sheet stops them z-fighting, and never less than 1px (max(1px, var(--u))): on a small card one unit is under a pixel.var(--end), computed per sheet, so the pages fan out.--open, registered with @property so it can be animated, runs 0 → 1 and back. Each sheet turns over its own slice of it, the cover’s first and the bottom page’s last. Played backwards the slices come in the opposite order by themselves, so the book shuts bottom page first and the cover comes down last, on top. (A per-sheet animation-delay cannot do this: it keeps the cover first both ways, so the cover shuts under pages that are still open and they pass through it.)--u, so the book is the same share of a gallery card, the editor and a recording canvas.translate on the book follows --open: it slides the spine from the middle of the shut book to the middle of the canvas as the book opens, and a paused card, which shows the shut book, shows it centred. The padding above it does the same for the lift.transform-origin: leftvar() end angle per sheetone @property --open, a staggered slice per sheet<div class="scene">
<div class="book">
<i style="--n:0"></i>
<i style="--n:1"></i>
<i style="--n:2"></i>
<i style="--n:3"></i>
<i style="--n:4" class="cover"></i>
</div>
</div>
.scene {
/* one base unit: every length below is a multiple of it, so the book is the same share of a
card, the editor, a full screen and a recording canvas */
--u: 0.28vmin;
display: grid;
place-items: center;
/* the pages lift toward you as they turn, and the perspective magnifies them upward: room
above the book splits that between the shut book and the open one */
padding-top: calc(30 * var(--u));
perspective: calc(1000 * var(--u));
}
.book {
position: relative;
width: calc(130 * var(--u));
height: calc(175 * var(--u));
border-radius: calc(2 * var(--u)) calc(8 * var(--u)) calc(8 * var(--u)) calc(2 * var(--u));
background: #4a3a99; /* back cover */
transform-style: preserve-3d;
/* the pages swing left past the spine, so the book slides right as it opens: shut, the book
is centred, and open, the spine is in the middle and the spread is centred */
transform: translate(calc(60 * var(--u) * var(--open) + var(--u)), calc(-6 * var(--u))) rotateX(24deg) rotateY(-12deg);
animation: open 3.6s ease-in-out infinite alternate;
}
.book i {
--end: calc(-118deg - var(--n) * 12deg);
position: absolute;
inset: calc(3 * var(--u)) calc(3 * var(--u)) calc(3 * var(--u)) 0;
border-radius: 0 calc(5 * var(--u)) calc(5 * var(--u)) 0;
background: linear-gradient(90deg, #cfd3e6, #fff 14%);
transform-origin: left center;
/* a sheet turns while --open runs through its slice: the cover's (n = 4) starts at 0, and each
sheet under it starts 0.04 later; every slice is 0.84 long */
--from: calc((4 - var(--n)) * 0.04);
transform: translateZ(calc(var(--n) * max(1px, var(--u)))) rotateY(calc(var(--end) * clamp(0, (var(--open) - var(--from)) / 0.84, 1)));
}
.book .cover {
inset: 0;
border-radius: calc(2 * var(--u)) calc(8 * var(--u)) calc(8 * var(--u)) calc(2 * var(--u));
background: linear-gradient(90deg, #4a3a99, #8b6cff 12%);
}
/* how far open the book is, from 0 (shut) to 1; registered, so it animates as a number */
@property --open {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
@keyframes open {
0%, 10% { --open: 0; }
90%, 100% { --open: 1; }
}