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.
Four equilateral triangles: three hinged on the edges of the fourth and leaned in by 19.47°, the angle at which their tips meet. A glowing core turns back against the tumble to stay round.
Your edited version
a wide and a × √3 / 2 tall, cut with clip-path: polygon(50% 0, 0 100%, 100% 100%).rotateY(i × 120deg) translateZ(r), where r = a / (2√3) is the distance from the floor's centre to the middle of an edge.atan(2√2) ≈ 70.53°, so an upright face leans the other 19.47°, and the three tips meet exactly above the centre. The floor is the same triangle leaned all the way, 90°, so its tip lands on the back corner.transform-origin), not round the box centre, or it wobbles as it turns.clip-path triangleslean = 90° − atan(2√2)transform-origin on the centroidinverse keyframes billboard<div class="scene">
<div class="tetra">
<i style="--i:0"></i>
<i style="--i:1"></i>
<i style="--i:2"></i>
<b></b>
<u></u>
</div>
</div>
.scene {
perspective: 800px;
}
.tetra {
--a: 140px; /* edge length */
--r: 40.41px; /* floor inradius = a / (2√3) */
position: relative;
width: var(--a);
height: 121.24px; /* face height = a × √3 / 2 */
transform-style: preserve-3d;
transform-origin: 50% 92.66px; /* the centroid: it stands a·√(2/3) = 114.31px tall, a quarter of that up */
animation: tumble 16s linear infinite;
}
.tetra i,
.tetra b {
--c: 46 230 214;
--alpha: 0.34;
--edge: rgb(var(--c) / 0.95);
position: absolute;
inset: 0;
transform-origin: 50% 100%; /* the hinge: the bottom edge, on the floor */
clip-path: polygon(50% 0, 0 100%, 100% 100%);
/* borders are clipped away, so the edges are painted: a "to corner" gradient's 50% line is its box diagonal */
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(90deg, transparent 42%, rgb(255 255 255 / 0.16) 50%, transparent 58%),
linear-gradient(to top, rgb(var(--c) / var(--alpha)), rgb(var(--c) / 0.08));
}
/* the sides: 120deg apart, out to an edge, leaned in by 90deg - atan(2√2) = 19.47deg */
.tetra i {
transform: rotateY(calc(var(--i) * 120deg)) translateZ(var(--r)) rotateX(19.47deg);
}
.tetra i:nth-child(2) { --c: 139 108 255; --alpha: 0.44; }
.tetra i:nth-child(3) { --c: 255 77 157; --alpha: 0.3; }
/* the floor: the same triangle on the front edge, folded flat */
.tetra b {
--c: 139 108 255;
--alpha: 0.26;
transform: translateZ(var(--r)) rotateX(90deg);
}
/* the core sits on the centroid */
.tetra u {
position: absolute;
top: 92.66px;
left: 50%;
width: 40px;
height: 40px;
margin: -20px 0 0 -20px;
border-radius: 50%;
background: radial-gradient(circle, #fff 0 8%, #ffcb7e 22%, rgb(255 181 71 / 0.55) 42%, transparent 70%);
animation: face-you 16s linear infinite, pulse 2.4s ease-in-out infinite alternate;
}
/* -24deg + 360deg = 336deg: the end pose equals the start pose */
@keyframes tumble {
from { transform: rotateX(-24deg) rotateY(0deg) rotateZ(0deg); }
to { transform: rotateX(336deg) rotateY(360deg) rotateZ(360deg); }
}
/* the exact inverse of the tumble: the same turns backwards, in reverse order */
@keyframes face-you {
from { transform: rotateZ(0deg) rotateY(0deg) rotateX(24deg); }
to { transform: rotateZ(-360deg) rotateY(-360deg) rotateX(-336deg); }
}
@keyframes pulse {
from { opacity: 0.7; scale: 0.85; }
to { opacity: 1; scale: 1.1; }
}
@use 'sass:math';
$a: 120px; // edge length
$t: $a * math.div(math.sqrt(3), 2); // height of one equilateral face (103.92px)
$r: math.div($a, 2 * math.sqrt(3)); // base inradius: centre of the floor to the middle of an edge (34.64px)
// A face meets the floor at atan(2√2) = 70.53deg. Faces start upright, so they lean the rest.
$lean: 90deg - math.atan(2 * math.sqrt(2)); // 19.47deg
$H: $a * math.sqrt(math.div(2, 3)); // how tall it stands (97.98px)
$mid: $t - $H * 0.25; // the centroid, a quarter of the way up, measured from the box top
$edge: color-mix(in srgb, var(--c) 85%, #fff);
$spin: 16s;
// A glass tetrahedron with a glowing core. Every face is the same triangle hinged on one edge of
// the floor triangle: the three sides lean in by 19.47deg, the floor "leans" all the way, 90deg.
.d-tetra {
position: relative;
width: $a;
height: $t;
transform-style: preserve-3d;
transform-origin: 50% $mid; // tumble round the centroid, not the box centre
animation: d-tetra-tumble $spin linear infinite;
i,
b {
--c: var(--accent-2);
--a: 34%;
position: absolute;
inset: 0;
transform-origin: 50% 100%; // the hinge: the bottom edge, on the floor
clip-path: polygon(50% 0, 0 100%, 100% 100%);
// clip-path cuts borders away, so the edges are painted: 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(90deg, transparent 42%, rgb(255 255 255 / 0.16) 50%, transparent 58%),
linear-gradient(to top, color-mix(in srgb, var(--c) var(--a), transparent), color-mix(in srgb, var(--c) 8%, transparent));
}
// the three sides: turned 120deg apart, stepped out to an edge, leaned in
i {
transform: rotateY(calc(var(--i) * 120deg)) translateZ($r) rotateX($lean);
&:nth-child(2) {
--c: var(--accent);
--a: 44%;
}
&:nth-child(3) {
--c: var(--hot);
--a: 30%;
}
}
// the floor: the same triangle on the front edge, folded flat (its tip points to the back corner)
b {
--c: var(--accent);
--a: 26%;
transform: translateZ($r) rotateX(90deg);
}
// the core sits on the centroid and turns back against the tumble, so it always faces you
u {
position: absolute;
top: $mid;
left: 50%;
width: 34px;
height: 34px;
margin: -17px 0 0 -17px;
border-radius: 50%;
background: radial-gradient(
circle,
#fff 0 8%,
color-mix(in srgb, var(--warm) 70%, #fff) 22%,
color-mix(in srgb, var(--warm) 55%, transparent) 42%,
transparent 70%
);
animation:
d-tetra-face $spin linear infinite,
d-tetra-pulse 2.4s ease-in-out infinite alternate;
}
}
// -24deg + 360deg = 336deg: the end pose equals the start pose
@keyframes d-tetra-tumble {
from {
transform: rotateX(-24deg) rotateY(0deg) rotateZ(0deg);
}
to {
transform: rotateX(336deg) rotateY(360deg) rotateZ(360deg);
}
}
// the exact inverse: the same three turns backwards, in reverse order
@keyframes d-tetra-face {
from {
transform: rotateZ(0deg) rotateY(0deg) rotateX(24deg);
}
to {
transform: rotateZ(-360deg) rotateY(-360deg) rotateX(-336deg);
}
}
@keyframes d-tetra-pulse {
from {
opacity: 0.7;
scale: 0.85;
}
to {
opacity: 1;
scale: 1.1;
}
}