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.
Six tiles that lift and flip to show their back. Odd tiles turn sideways, even ones head over heels, from a single custom property holding the rotation.
Your edited version
.tile-inner wrapper inside it flips, on :hover / :focus-visible.--flip, holds the whole rotation function (rotateY(180deg) or rotateX(180deg)), set once per tile so a single rule can flip every tile differently.--flip in its resting state, so once the tile turns, the back lands right-way up instead of mirrored.backface-visibility: hidden on both faces means only the one currently facing you is ever visible.translateZ(30px) before the flip keeps the tile from clipping into its neighbours mid-turn.a transform function stored in --flipstatic tile, flipping innercontainer pointer-events: nonebackface-visibility: hidden<div class="scene">
<div class="grid">
<div class="tile" tabindex="0" style="--c:#8b6cff">
<div class="tile-inner"><b>01</b><span>tilt</span></div>
</div>
<div class="tile" tabindex="0" style="--c:#ff4d9d">
<div class="tile-inner"><b>02</b><span>flip</span></div>
</div>
<div class="tile" tabindex="0" style="--c:#2ee6d6">
<div class="tile-inner"><b>03</b><span>spin</span></div>
</div>
<div class="tile" tabindex="0" style="--c:#ffb547">
<div class="tile-inner"><b>04</b><span>lift</span></div>
</div>
<div class="tile" tabindex="0" style="--c:#8b6cff">
<div class="tile-inner"><b>05</b><span>fold</span></div>
</div>
<div class="tile" tabindex="0" style="--c:#ff4d9d">
<div class="tile-inner"><b>06</b><span>zoom</span></div>
</div>
</div>
</div>
.scene {
perspective: 800px;
}
.grid {
display: grid;
grid-template-columns: repeat(3, 62px);
gap: 9px;
transform-style: preserve-3d;
transform: rotateX(16deg);
/* the grid and its tiles are coplanar: take the grid out of hit-testing so the
pointer always resolves to a tile, never something ambiguous between them */
pointer-events: none;
}
.tile {
--flip: rotateY(180deg); /* odd tiles flip sideways */
position: relative;
height: 62px;
outline: none;
cursor: pointer;
pointer-events: auto;
transform-style: preserve-3d;
}
.tile:nth-child(even) {
--flip: rotateX(180deg); /* even tiles flip head over heels */
}
.tile-inner {
position: absolute;
inset: 0;
pointer-events: none;
transform-style: preserve-3d;
transition: transform 0.6s cubic-bezier(0.3, 1.25, 0.5, 1);
}
.tile-inner > * {
position: absolute;
inset: 0;
display: grid;
place-items: center;
border-radius: 10px;
backface-visibility: hidden;
}
/* front */
.tile-inner b {
color: #eceefb;
font-size: 20px;
font-weight: 900;
background: color-mix(in srgb, var(--c) 26%, transparent);
border: 1px solid color-mix(in srgb, var(--c) 75%, transparent);
box-shadow: inset 0 0 24px color-mix(in srgb, var(--c) 30%, transparent);
}
/* back: pre-turned with the same rotation the tile will make, so it lands the right way up */
.tile-inner span {
background: linear-gradient(135deg, var(--c), color-mix(in srgb, var(--c) 45%, #000));
color: #fff;
font-size: 12px;
font-weight: 800;
letter-spacing: 0.04em;
transform: var(--flip);
}
.tile:hover .tile-inner,
.tile:focus-visible .tile-inner {
/* lift first (toward the viewer), then flip in place */
transform: translateZ(30px) var(--flip);
}
@use '../mixins' as *;
.d-flipgrid {
display: grid;
grid-template-columns: repeat(3, 62px);
gap: 9px;
transform-style: preserve-3d;
transform: rotateX(16deg);
// The grid and its tiles share one 3D plane, and coplanar surfaces have no stable hit-test
// order. Take the grid out of hit-testing so the pointer always lands on a tile.
pointer-events: none;
// The tile is the hit target and never moves; only __inner flips.
&__tile {
// odd tiles flip sideways, even tiles flip head over heels
--flip: rotateY(180deg);
position: relative;
height: 62px;
outline: none;
cursor: pointer;
pointer-events: auto;
transform-style: preserve-3d;
&:nth-child(even) {
--flip: rotateX(180deg);
}
}
&__inner {
position: absolute;
inset: 0;
pointer-events: none;
transform-style: preserve-3d;
transition: transform 0.6s cubic-bezier(0.3, 1.25, 0.5, 1);
> * {
position: absolute;
inset: 0;
display: grid;
place-items: center;
border-radius: 10px;
backface-visibility: hidden;
}
// front
b {
color: var(--text);
font-size: 20px;
font-weight: 900;
@include face(var(--c), 26%);
}
// back: pre-turned with the same rotation the tile will make, so it lands the right way up
span {
background: linear-gradient(135deg, var(--c), color-mix(in srgb, var(--c) 45%, #000));
color: #fff;
font-size: 12px;
font-weight: 800;
letter-spacing: 0.04em;
transform: var(--flip);
}
}
&__tile:hover &__inner,
&__tile:focus-visible &__inner {
// lift first (toward the viewer), then flip in place
transform: translateZ(30px) var(--flip);
}
}