Solar system
Pure CSSOrbits are rings on a tilted plane, each spinning at its own speed. Every planet undoes both rotations, so it always faces the camera.
Sweep across the floor. Tiles rise instantly and sink slowly, leaving a trail.
Your edited version
:hover works on the tile you actually see under the pointer, even on a tilted plane.::before plate lifts. If the hovered element moved, it would slide out from under the pointer and flicker.:hover rule overrides transition-duration to 0.08s — that one applies when the hover starts.--u, tied to the canvas: a tile is 46 units square and its plate rises 46, so the floor is the same share of a gallery card, the editor and a recording canvas.hit-testing works in 3Dfast transition in, slow transition outisometric floor<div class="scene">
<div class="floor" tabindex="0" role="img" aria-label="Floor of tiles; each rises as the pointer passes over it">
<i></i><i></i><i></i><i></i><i></i>
<i></i><i></i><i></i><i></i><i></i>
<i></i><i></i><i></i><i></i><i></i>
<i></i><i></i><i></i><i></i><i></i>
<i></i><i></i><i></i><i></i><i></i>
</div>
</div>
.scene {
/* one base unit: every length below is a multiple of it, so the floor is the same share of a
gallery card, the editor, a full screen and a recording canvas */
--u: 0.24vmin;
perspective: calc(900 * var(--u));
}
.floor {
display: grid;
grid-template-columns: repeat(5, calc(46 * var(--u)));
gap: calc(5 * var(--u));
transform-style: preserve-3d;
/* the camera magnifies the near half of the tilted floor, so at rest it draws further below its
own middle than above; a raised plate at the far corner reaches the other way, up the screen.
This small lift splits the two, so the floor at rest and the floor with its highest plate up
are both centred to within a few units */
transform: translateY(calc(-6 * var(--u))) rotateX(56deg) rotateZ(-45deg);
/* same plane as its cells: keep the floor itself out of hit-testing, or hover misses in patches */
pointer-events: none;
}
/* the cell is a fixed hit target; only its ::before plate moves */
.floor i {
pointer-events: auto;
position: relative;
height: calc(46 * var(--u));
transform-style: preserve-3d;
}
.floor i::before {
content: '';
position: absolute;
inset: 0;
border-radius: calc(7 * var(--u));
background: rgb(139 108 255 / 0.35);
border: calc(1 * var(--u)) solid rgb(139 108 255 / 0.7);
pointer-events: none;
/* slow on the way down */
transition: transform 1.4s ease-out, background 1.4s ease-out;
}
.floor i:hover::before {
background: #2ee6d6;
transform: translateZ(calc(46 * var(--u)));
/* instant on the way up */
transition-duration: 0.08s;
}
/* the keyboard: one stop for the floor, and the middle plate rises as it does under the pointer */
.floor:focus-visible i:nth-child(13)::before {
background: #2ee6d6;
transform: translateZ(calc(46 * var(--u)));
transition-duration: 0.08s;
}