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.
A thick eight-tooth gear driving a smaller five-tooth one the other way. Each face is one clip-path polygon, and every straight edge of it gets a standing rectangle, so the gear has a real rim and real teeth.
Your edited version
clip-path: polygon(): eight rectangular teeth on a circle, and a straight chord across each gap. Two copies, front and back, are pushed apart with translateZ(±8). That is the whole silhouette for four elements, where teeth built as boxes would cost dozens.rotateZ(i × 45deg) translateY(-80) rotateX(90deg), its two flanks are translate(±11, -71) rotateY(90deg) in the same turned frame, and the rim chord in the gap after it sits at the half step. The chord is exactly as long and as far out as the clip-path edge it stands on, so the pieces meet with no seam.-576deg, 8/5 of a turn the other way, in the same 12 s. 576° is exactly eight teeth of a five-tooth gear, so the end pose is the start pose and the linear loop has no join.rotateX(55deg) on a wrapper and only the gears spin inside it, both with preserve-3d. Every length is a multiple of one base unit, --u, so the pair is the same share of a gallery card, the editor and a recording canvas; the numbers above are those units.clip-path: polygon() facestanding rectangles on every edgeradii in the ratio of tooth countscounter-spin at -8/5 of a turn<div class="scene">
<div class="rig">
<div class="gear big">
<b class="face front"></b>
<b class="face back"></b>
<i class="tip" style="--i:0"></i><i class="fl l" style="--i:0"></i><i class="fl r" style="--i:0"></i><i class="rim" style="--i:0"></i>
<i class="tip" style="--i:1"></i><i class="fl l" style="--i:1"></i><i class="fl r" style="--i:1"></i><i class="rim" style="--i:1"></i>
<i class="tip" style="--i:2"></i><i class="fl l" style="--i:2"></i><i class="fl r" style="--i:2"></i><i class="rim" style="--i:2"></i>
<i class="tip" style="--i:3"></i><i class="fl l" style="--i:3"></i><i class="fl r" style="--i:3"></i><i class="rim" style="--i:3"></i>
<i class="tip" style="--i:4"></i><i class="fl l" style="--i:4"></i><i class="fl r" style="--i:4"></i><i class="rim" style="--i:4"></i>
<i class="tip" style="--i:5"></i><i class="fl l" style="--i:5"></i><i class="fl r" style="--i:5"></i><i class="rim" style="--i:5"></i>
<i class="tip" style="--i:6"></i><i class="fl l" style="--i:6"></i><i class="fl r" style="--i:6"></i><i class="rim" style="--i:6"></i>
<i class="tip" style="--i:7"></i><i class="fl l" style="--i:7"></i><i class="fl r" style="--i:7"></i><i class="rim" style="--i:7"></i>
</div>
<div class="gear small">
<b class="face front"></b>
<b class="face back"></b>
<i class="tip" style="--i:0"></i><i class="fl l" style="--i:0"></i><i class="fl r" style="--i:0"></i><i class="rim" style="--i:0"></i>
<i class="tip" style="--i:1"></i><i class="fl l" style="--i:1"></i><i class="fl r" style="--i:1"></i><i class="rim" style="--i:1"></i>
<i class="tip" style="--i:2"></i><i class="fl l" style="--i:2"></i><i class="fl r" style="--i:2"></i><i class="rim" style="--i:2"></i>
<i class="tip" style="--i:3"></i><i class="fl l" style="--i:3"></i><i class="fl r" style="--i:3"></i><i class="rim" style="--i:3"></i>
<i class="tip" style="--i:4"></i><i class="fl l" style="--i:4"></i><i class="fl r" style="--i:4"></i><i class="rim" style="--i:4"></i>
</div>
</div>
</div>
.scene {
/* one base unit: every length below is a multiple of it, so the pair is the same share of a
card, the editor, a full screen and a recording canvas */
--u: 0.38vmin;
position: relative;
width: calc(216 * var(--u));
height: calc(216 * var(--u));
perspective: calc(800 * var(--u));
}
/* the static tilt; the gears spin inside it. The inner translate, in the gears' own plane,
centres the pair (the small gear hangs off the big one's top right); the outer one, in screen
space, is the last few units the perspective pulls it off by (the near, bottom side is drawn
larger than the far one) */
.rig {
position: absolute;
inset: 0;
transform-style: preserve-3d;
transform: translate(calc(5 * var(--u)), calc(-8 * var(--u))) rotateX(55deg) translate(calc(-28 * var(--u)), calc(28 * var(--u)));
}
.gear {
position: absolute;
top: 50%;
left: 50%;
transform-style: preserve-3d;
animation: 12s linear infinite;
}
/* 8 teeth, tip radius 80, root 62: a 160 box. The tooth step is 45deg */
.big {
--step: 45deg;
--rt: 80;
--rm: 71; /* half way between root and tip: where a flank's middle sits */
--rw: 27.13; /* the rim chord in a gap: its length ... */
--rd: 61.49; /* ... and how far out it stands */
width: calc(160 * var(--u));
height: calc(160 * var(--u));
margin: calc(-80 * var(--u)) 0 0 calc(-80 * var(--u));
animation-name: gear-cw;
}
/* 5 teeth, tip 53, root 35: a 106 box, its centre 117 up-right of the big one's at 45deg
(117 × sin 45 = 82.7). Pitch radii 72 + 45 = 117 is what makes the teeth mesh */
.small {
--step: 72deg;
--rt: 53;
--rm: 44;
--rw: 23.35;
--rd: 34.78;
width: calc(106 * var(--u));
height: calc(106 * var(--u));
margin: calc(-135.7 * var(--u)) 0 0 calc(29.7 * var(--u));
animation-name: gear-ccw;
}
/* the two faces: one polygon each, 8 apart in depth */
.face {
position: absolute;
inset: 0;
}
.big .face { clip-path: polygon(43.13% 11.25%, 43.13% 0%, 56.88% 0%, 56.88% 11.25%, 72.54% 17.74%, 80.49% 9.78%, 90.22% 19.51%, 82.26% 27.46%, 88.75% 43.13%, 100% 43.13%, 100% 56.88%, 88.75% 56.88%, 82.26% 72.54%, 90.22% 80.49%, 80.49% 90.22%, 72.54% 82.26%, 56.88% 88.75%, 56.88% 100%, 43.13% 100%, 43.13% 88.75%, 27.46% 82.26%, 19.51% 90.22%, 9.78% 80.49%, 17.74% 72.54%, 11.25% 56.88%, 0% 56.88%, 0% 43.13%, 11.25% 43.13%, 17.74% 27.46%, 9.78% 19.51%, 19.51% 9.78%, 27.46% 17.74%); }
.small .face { clip-path: polygon(39.62% 16.98%, 39.62% 0%, 60.38% 0%, 60.38% 16.98%, 78.2% 29.93%, 94.35% 24.68%, 100.76% 44.42%, 84.61% 49.67%, 77.8% 70.61%, 87.78% 84.35%, 70.99% 96.55%, 61.01% 82.81%, 38.99% 82.81%, 29.01% 96.55%, 12.22% 84.35%, 22.2% 70.61%, 15.39% 49.67%, -0.76% 44.42%, 5.65% 24.68%, 21.8% 29.93%); }
.front { transform: translateZ(calc(8 * var(--u))); }
.back { transform: translateZ(calc(-8 * var(--u))); }
/* the top face carries a painted hub ring, so the eye has a centre to see the turn against */
.big .front { background: radial-gradient(circle, #c9bcff 0 7%, #4a2fb8 7.5% 12%, #a390ff 12.5%, #8b6cff 55%, #7657f0); }
.big .back, .big .rim, .big .tip { background: #5f42d6; }
.big .fl { background: #4e33be; }
.small .front { background: radial-gradient(circle, #d6fffb 0 9%, #12857a 9.5% 15%, #7cf2e8 15.5%, #2ee6d6 55%, #22ccbd); }
.small .back, .small .rim, .small .tip { background: #159c90; }
.small .fl { background: #117f75; }
/* every standing piece starts centred on the gear, then is turned to its tooth, moved out and
stood up. rotateX(90deg) stands a piece on a tangent (its height becomes depth, centred on the
faces); rotateY(90deg) stands one on a radius */
.gear i {
position: absolute;
top: 50%;
left: 50%;
}
/* a tooth's tip: 22 wide, the thickness tall, at the tip radius */
.tip {
width: calc(22 * var(--u));
height: calc(16 * var(--u));
margin: calc(-8 * var(--u)) 0 0 calc(-11 * var(--u));
transform: rotateZ(calc(var(--i) * var(--step))) translateY(calc(-1 * var(--rt) * var(--u))) rotateX(90deg);
}
/* a flank: the thickness wide, the tooth height (18) tall, 11 either side of the tooth's centre
line, its middle half way out the tooth */
.fl {
width: calc(16 * var(--u));
height: calc(18 * var(--u));
margin: calc(-9 * var(--u)) 0 0 calc(-8 * var(--u));
}
.fl.l { transform: rotateZ(calc(var(--i) * var(--step))) translate(calc(-11 * var(--u)), calc(-1 * var(--rm) * var(--u))) rotateY(90deg); }
.fl.r { transform: rotateZ(calc(var(--i) * var(--step))) translate(calc(11 * var(--u)), calc(-1 * var(--rm) * var(--u))) rotateY(90deg); }
/* the rim in the gap after tooth i: a chord at the half step, sized to meet both flanks' feet */
.rim {
width: calc(var(--rw) * var(--u));
height: calc(16 * var(--u));
margin: calc(-8 * var(--u)) 0 0 calc(-0.5 * var(--rw) * var(--u));
transform: rotateZ(calc((var(--i) + 0.5) * var(--step))) translateY(calc(-1 * var(--rd) * var(--u))) rotateX(90deg);
}
@keyframes gear-cw {
from { transform: rotateZ(0deg); }
to { transform: rotateZ(360deg); }
}
/* starts at 45deg so a gap faces the big gear's tooth; -576deg is 8/5 of a turn, eight of its
five teeth, so it ends on the pose it started in */
@keyframes gear-ccw {
from { transform: rotateZ(45deg); }
to { transform: rotateZ(-531deg); }
}