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.
Sixteen thin triangles on a circle, each leaning in by atan(radius / height) so all the tips meet on the axis, over a flat disc base.
Your edited version
rotateY(i × 22.5deg) translateZ(r). Each is 2r × tan(180° / 16) wide, so their bottom edges close the ring.atan(r / h). That is exactly the angle at which every tip lands on the axis, h above the base.√(h² + r²), not h, because leaning shortens it.--u, so the cone is the same share of a gallery card, the editor and a recording canvas. The numbers above are those units.translateY that answers it, the same in both poses.triangle width = 2r × tan(180° / n)lean = atan(r / h)rocking wrapper + spinning childrotateY(i × 22.5deg) per face<div class="scene">
<div class="cone">
<div class="cone-body">
<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>
<i style="--i:6"></i>
<i style="--i:7"></i>
<i style="--i:8"></i>
<i style="--i:9"></i>
<i style="--i:10"></i>
<i style="--i:11"></i>
<i style="--i:12"></i>
<i style="--i:13"></i>
<i style="--i:14"></i>
<i style="--i:15"></i>
<b></b>
</div>
</div>
</div>
.scene {
/* one base unit: every length below is a multiple of it, so the cone is the same share of a
card, the editor, a full screen and a recording canvas */
--u: 0.46vmin;
perspective: calc(800 * var(--u));
}
/* wrapper: rocks back and forth */
.cone {
transform-style: preserve-3d;
animation: rock 7s ease-in-out infinite alternate;
}
/* child: spins */
.cone-body {
position: relative;
width: calc(19.89 * var(--u)); /* triangle base = 2 × 50 × tan(180deg / 16) */
height: calc(110 * var(--u)); /* cone height */
transform-style: preserve-3d;
animation: spin 10s linear infinite;
}
.cone-body i {
--hue: calc(293 + 40 * cos(var(--i) * 22.5deg)); /* pink ... violet ... pink */
--edge: hsl(var(--hue) 90% 68% / 0.8);
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: calc(120.83 * var(--u)); /* slant = √(110² + 50²) */
transform-origin: 50% 100%;
/* stand on the base circle, then lean in by atan(50 / 110) */
transform: rotateY(calc(var(--i) * 22.5deg)) translateZ(calc(50 * var(--u))) rotateX(24.44deg);
clip-path: polygon(50% 0, 0 100%, 100% 100%);
/* the edge lines fade in over 1.5 units instead of starting hard: a hard line on a long,
thin, slanted triangle is sampled unevenly and breaks into dashes */
background:
linear-gradient(to top left, transparent calc(50% - 2.5 * var(--u)), var(--edge) calc(50% - 1 * var(--u)) 50%, transparent 50%) left / 50% 100% no-repeat,
linear-gradient(to top right, transparent calc(50% - 2.5 * var(--u)), var(--edge) calc(50% - 1 * var(--u)) 50%, transparent 50%) right / 50% 100% no-repeat,
linear-gradient(to top, hsl(var(--hue) 90% 68% / 0.45), hsl(var(--hue) 90% 68% / 0.14));
}
/* base disc through the triangles' corners: 2 × 50 / cos(180deg / 16) */
.cone-body b {
position: absolute;
left: calc(50% - 50.98 * var(--u));
top: calc(100% - 50.98 * var(--u));
/* its border is inside that diameter, so the disc stays centred on the cone's axis */
box-sizing: border-box;
width: calc(101.96 * var(--u));
height: calc(101.96 * var(--u));
border-radius: 50%;
background: rgb(46 230 214 / 0.26);
border: calc(1 * var(--u)) solid rgb(46 230 214 / 0.75);
box-shadow: inset 0 0 calc(24 * var(--u)) rgb(46 230 214 / 0.3);
transform: rotateX(90deg);
}
/* The cone is centred on what it draws, not on its layout box: the base disc is a circle lying
flat at the bottom, so tipping it back swings half the disc BELOW the base, while the tip only
comes down. The lift answers that, and it is the same in both poses so the rock stays a rock. */
@keyframes rock {
from { transform: translateY(calc(-12 * var(--u))) rotateX(-30deg) rotateZ(-6deg); }
to { transform: translateY(calc(-12 * var(--u))) rotateX(24deg) rotateZ(6deg); }
}
@keyframes spin {
to { transform: rotateY(360deg); }
}