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.
Six side panels in a closed ring plus two hexagon caps cut with clip-path. The apothem, (side / 2) / tan(30°), is how far each panel steps out.
Your edited version
rotateY(i × 60deg), then translateZ by the apothem, the distance from the centre to the middle of a side.(side / 2) / tan(180° / 6). For 60px panels that is 51.96px. Any less and the panels cross; any more and the corners open.2 × side by 2 × apothem box cut to a hexagon with clip-path. clip-path would flatten a 3D container, but the caps have no 3D children, so it is safe here.rotateX(90deg) lays a cap flat; translateZ(height / 2) then lifts it along its new normal to the top (and -90deg for the bottom).rotateY(i × 60°) + translateZ(apothem)clip-path: polygon() capsrotateX(90deg) to lay a cap flattwo-axis tumble<div class="scene">
<div class="prism">
<i style="--i:0"></i>
<i style="--i:1"></i>
<i style="--i:2"></i>
<i style="--i:3"></i>
<i style="--i:4"></i>
<i style="--i:5"></i>
<b></b>
<b></b>
</div>
</div>
.scene {
perspective: 800px;
}
.prism {
--w: 60px; /* one side of the hexagon = one panel */
--h: 110px;
--r: 51.96px; /* apothem = (w / 2) / tan(180deg / 6) */
position: relative;
width: var(--w);
height: var(--h);
transform-style: preserve-3d;
animation: tumble 18s linear infinite;
}
.prism i {
position: absolute;
inset: 0;
background: rgb(139 108 255 / 0.24);
border: 1px solid rgb(139 108 255 / 0.75);
box-shadow: inset 0 0 24px rgb(139 108 255 / 0.3);
/* turn to face outward, THEN step out along that direction */
transform: rotateY(calc(var(--i) * 60deg)) translateZ(var(--r));
}
.prism i:nth-child(even) {
background: rgb(46 230 214 / 0.2);
border-color: rgb(46 230 214 / 0.75);
box-shadow: inset 0 0 24px rgb(46 230 214 / 0.3);
}
/* caps: a regular hexagon's corner radius equals its side, so the box is 2w by 2r */
.prism b {
position: absolute;
left: calc(50% - var(--w));
top: calc(50% - var(--r));
width: calc(var(--w) * 2);
height: calc(var(--r) * 2);
clip-path: polygon(25% 0, 75% 0, 100% 50%, 75% 100%, 25% 100%, 0 50%);
background: radial-gradient(circle, rgb(255 77 157 / 0.12) 20%, rgb(255 77 157 / 0.55));
transform: rotateX(90deg) translateZ(calc(var(--h) / 2));
}
.prism b + b {
transform: rotateX(-90deg) translateZ(calc(var(--h) / 2));
}
/* -25deg + 360deg = 335deg: the end pose equals the start pose, so the loop is seamless */
@keyframes tumble {
from { transform: rotateX(-25deg) rotateY(0deg); }
to { transform: rotateX(335deg) rotateY(360deg); }
}
@use 'sass:math';
@use '../mixins' as *;
$n: 6;
$w: 52px; // width of one side panel = side of the hexagon
$h: 96px;
$r: math.div($w * 0.5, math.tan(math.div(180deg, $n))); // apothem: centre to the middle of a side
$R: $w; // a regular hexagon's corner radius equals its side
.d-prism {
position: relative;
width: $w;
height: $h;
transform-style: preserve-3d;
animation: d-prism-tumble 18s linear infinite;
// side panels: a six-panel ring, exactly like a carousel with no gaps
i {
position: absolute;
inset: 0;
transform: rotateY(calc(var(--i) * #{math.div(360deg, $n)})) translateZ($r);
@include face(var(--accent), 24%);
&:nth-child(even) {
@include face(var(--accent-2), 20%);
}
}
// caps: a flat-topped hexagon cut out of a 2R x 2r box, laid flat on each end.
// clip-path is safe here because the caps are leaves (no 3D children to flatten).
b {
position: absolute;
left: calc(50% - #{$R});
top: calc(50% - #{$r});
width: $R * 2;
height: $r * 2;
clip-path: polygon(25% 0, 75% 0, 100% 50%, 75% 100%, 25% 100%, 0 50%);
background: radial-gradient(
circle,
color-mix(in srgb, var(--hot) 12%, transparent) 20%,
color-mix(in srgb, var(--hot) 55%, transparent) 100%
);
transform: rotateX(90deg) translateZ($h * 0.5);
& + b {
transform: rotateX(-90deg) translateZ($h * 0.5);
}
}
}
@keyframes d-prism-tumble {
from {
transform: rotateX(-25deg) rotateY(0deg);
}
to {
transform: rotateX(335deg) rotateY(360deg);
}
}