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.--u, tied to the canvas, so the tetrahedron is the same share of a gallery card, the editor and a recording canvas. The numbers above are those units.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 {
/* one base unit: every length below is a multiple of it, so the tetrahedron is the same share of a
card, the editor, a full screen and a recording canvas */
--u: 0.4vmin;
perspective: calc(800 * var(--u));
}
.tetra {
--a: calc(140 * var(--u)); /* edge length */
--r: calc(40.41 * var(--u)); /* floor inradius = a / (2√3) */
position: relative;
width: var(--a);
height: calc(121.24 * var(--u)); /* face height = a × √3 / 2 */
transform-style: preserve-3d;
transform-origin: 50% calc(92.66 * var(--u)); /* the centroid: it stands a·√(2/3) = 114.31 units tall, a quarter of that up */
/* it turns round the centroid, 32 units below the middle of its box. The eye reads the middle
of what it sees, and over a turn that sits on average 5 units above the centroid (the apex
reaches further than the floor), so the box is lifted by 27 units, not 32, and the tumble as
seen is what sits in the middle of the canvas */
translate: 0 calc(-27 * var(--u));
/* the loop starts 14.5s into the turn: a pose seen square, centred on the canvas, which is what
a paused card shows. The core starts at the same moment, so it still undoes the tumble */
animation: tumble 16s linear -14.5s 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.5 * var(--u)), var(--edge) calc(50% - 1.5 * var(--u)) 50%, transparent 50%) left / 50% 100% no-repeat,
linear-gradient(to top right, transparent calc(50% - 1.5 * var(--u)), var(--edge) calc(50% - 1.5 * var(--u)) 50%, transparent 50%) right / 50% 100% no-repeat,
linear-gradient(var(--edge), var(--edge)) bottom / 100% calc(1.5 * var(--u)) 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: calc(92.66 * var(--u));
left: 50%;
width: calc(40 * var(--u));
height: calc(40 * var(--u));
margin: calc(-20 * var(--u)) 0 0 calc(-20 * var(--u));
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 -14.5s 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; }
}