Twisting puzzle cube
Pure CSSA 3×3×3 cube built as three layers that take turns twisting a quarter turn. Each layer is one flat box with the stickers painted on by gradients.
Hover to blow the faces apart. One custom property moves all six.
Your edited version
translateZ(var(--d)) instead of a fixed half-side distance.--d — one declaration moves all six faces.transition on transform, the change animates even though only a custom property was touched.--u, tied to the canvas: the side is 110 units, so a closed cube would have --d at 55; it rests at 64, the faces standing a little apart so even a paused cube shows six loose panels, and hover pushes it to 108. The cube, and how far it flies apart, are the same share of a gallery card, the editor and a recording canvas.translateZ(var(--d))transition on a var()-driven transformspin continues underneath<div class="scene" tabindex="0" role="img" aria-label="Cube whose faces fly apart on hover or focus">
<div class="cube">
<div></div><div></div><div></div>
<div></div><div></div><div></div>
</div>
</div>
.scene {
/* one base unit: every length below is a multiple of it, so the cube is the same share of a
card, the editor, a full screen and a recording canvas */
--u: 0.28vmin;
--s: calc(110 * var(--u));
/* distance of each face from the centre: at rest a little more than half the side, so the
faces stand just apart and even a paused cube shows it is six loose panels */
--d: calc(64 * var(--u));
display: grid;
place-items: center;
/* a hit area wider than the cube, so the pointer does not fall off it between the faces */
width: calc(300 * var(--u));
height: calc(300 * var(--u));
perspective: calc(800 * var(--u));
cursor: pointer;
}
.scene:hover,
.scene:focus {
--d: calc(108 * var(--u));
}
.cube {
position: relative;
width: var(--s);
height: var(--s);
transform-style: preserve-3d;
animation: spin 10s linear infinite;
}
.cube > * {
position: absolute;
inset: 0;
border-radius: calc(6 * var(--u));
background: rgb(255 77 157 / 0.35);
border: calc(1 * var(--u)) solid rgb(255 77 157 / 0.85);
transition: transform 0.6s cubic-bezier(0.3, 1.5, 0.5, 1);
}
.cube > :nth-child(1) { transform: rotateY(0deg) translateZ(var(--d)); }
.cube > :nth-child(2) { transform: rotateY(90deg) translateZ(var(--d)); }
.cube > :nth-child(3) { transform: rotateY(180deg) translateZ(var(--d)); }
.cube > :nth-child(4) { transform: rotateY(-90deg) translateZ(var(--d)); }
.cube > :nth-child(5) { transform: rotateX(90deg) translateZ(var(--d)); }
.cube > :nth-child(6) { transform: rotateX(-90deg) translateZ(var(--d)); }
/* The top and bottom are always seen at a slant, squeezed flat, and their edges sit on the
same spot as the side faces' edges: two lines there step over each other and read as dashes.
So only the sides draw the edge line. */
.cube > :nth-child(n + 5) { border-color: transparent; }
@keyframes spin {
from { transform: rotateX(-24deg) rotateY(-35deg); }
to { transform: rotateX(-24deg) rotateY(325deg); }
}