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.
Eight triangles hinged on a square equator, leaning in by 35.26° so they meet at two apexes. One rule builds all of them: scaleY(-1) flips the bottom four.
Your edited version
clip-path), standing on one edge of the square "equator".transform-origin: 50% 100%) and lean it in by 90° − atan(√2) ≈ 35.26°: exactly enough for four tips to meet on the axis.--s: -1 flips the triangle to point down with scaleY(-1) and reverses the lean, so one line of CSS builds all eight faces.clip-path also clips borders away, so the slanted edges are painted: a to top left gradient's 50% line runs exactly along its box diagonal.clip-path triangleslean = 90° − atan(√2)--s: 1 / −1 flips half the facesedges drawn with "to corner" gradients<div class="scene">
<div class="octa">
<i style="--i:0; --s:1"></i>
<i style="--i:1; --s:1"></i>
<i style="--i:2; --s:1"></i>
<i style="--i:3; --s:1"></i>
<i style="--i:0; --s:-1"></i>
<i style="--i:1; --s:-1"></i>
<i style="--i:2; --s:-1"></i>
<i style="--i:3; --s:-1"></i>
</div>
</div>
.scene {
perspective: 800px;
}
.octa {
--a: 110px; /* edge length */
position: relative;
width: var(--a);
height: var(--a);
transform-style: preserve-3d;
animation: spin 12s linear infinite;
}
.octa i {
--c: 139 108 255;
--alpha: 0.22;
--edge: rgb(var(--c) / 0.85);
position: absolute;
left: 0;
bottom: 50%; /* bottom edge on the equator, through the centre */
width: var(--a);
height: 95.26px; /* triangle height = a × √3 / 2 */
transform-origin: 50% 100%;
transform:
rotateY(calc(var(--i) * 90deg))
translateZ(calc(var(--a) / 2))
rotateX(calc(var(--s) * 35.26deg)) /* lean in: 90deg - atan(√2) */
scaleY(var(--s)); /* -1 flips the bottom four downward */
clip-path: polygon(50% 0, 0 100%, 100% 100%);
background:
linear-gradient(to top left, transparent calc(50% - 1.5px), var(--edge) calc(50% - 1.5px) 50%, transparent 50%) left / 50% 100% no-repeat,
linear-gradient(to top right, transparent calc(50% - 1.5px), var(--edge) calc(50% - 1.5px) 50%, transparent 50%) right / 50% 100% no-repeat,
linear-gradient(var(--edge), var(--edge)) bottom / 100% 1.5px no-repeat,
linear-gradient(to top, rgb(var(--c) / var(--alpha)), rgb(var(--c) / 0.08));
}
.octa i:nth-child(n + 5) {
--c: 46 230 214;
}
/* checkerboard the brightness so neighbouring faces always differ */
.octa i:nth-child(-n + 4):nth-child(odd),
.octa i:nth-child(n + 5):nth-child(even) {
--alpha: 0.42;
}
@keyframes spin {
from { transform: rotateZ(14deg) rotateX(-16deg) rotateY(0deg); }
to { transform: rotateZ(14deg) rotateX(-16deg) rotateY(360deg); }
}
@use 'sass:math';
$a: 92px; // edge length
$t: $a * math.div(math.sqrt(3), 2); // height of one equilateral face
// A face meets the base plane at atan(sqrt 2) = 54.74deg. Faces start upright, so they lean the rest.
$lean: 90deg - math.atan(math.sqrt(2)); // 35.26deg
$edge: color-mix(in srgb, var(--c) 85%, transparent);
.d-octa {
position: relative;
width: $a;
height: $a;
transform-style: preserve-3d;
animation: d-octa-spin 12s linear infinite;
// Every face is the same upward triangle standing on the square "equator", hinged on its
// bottom edge. --s is 1 for the top pyramid and -1 for the bottom one: it flips the triangle
// downwards (scaleY) and reverses the lean, so one rule builds all eight faces.
i {
--c: var(--accent);
--a: 22%;
position: absolute;
left: 0;
bottom: 50%;
width: $a;
height: $t;
transform-origin: 50% 100%;
transform: rotateY(calc(var(--i) * 90deg)) translateZ($a * 0.5) rotateX(calc(var(--s) * #{$lean})) scaleY(var(--s));
clip-path: polygon(50% 0, 0 100%, 100% 100%);
// Borders are clipped away with the corners, so the edges are drawn: a "to corner" gradient's
// 50% line runs exactly along the diagonal of its box, and each half of the face gets one.
background:
linear-gradient(to top left, transparent calc(50% - 1.5px), $edge calc(50% - 1.5px), $edge 50%, transparent 50%) left /
50% 100% no-repeat,
linear-gradient(to top right, transparent calc(50% - 1.5px), $edge calc(50% - 1.5px), $edge 50%, transparent 50%)
right / 50% 100% no-repeat,
linear-gradient($edge, $edge) bottom / 100% 1.5px no-repeat,
linear-gradient(to top, color-mix(in srgb, var(--c) var(--a), transparent), color-mix(in srgb, var(--c) 8%, transparent));
// checkerboard the brightness so neighbouring faces (also across the equator) differ
&:nth-child(-n + 4):nth-child(odd),
&:nth-child(n + 5):nth-child(even) {
--a: 42%;
}
&:nth-child(n + 5) {
--c: var(--accent-2);
}
}
}
@keyframes d-octa-spin {
from {
transform: rotateZ(14deg) rotateX(-16deg) rotateY(0deg);
}
to {
transform: rotateZ(14deg) rotateX(-16deg) rotateY(360deg);
}
}