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 crown of trapezoid facets leaning 45° above the girdle and a deep pavilion of triangles below, with a glint that walks from facet to facet.
Your edited version
rotateY(i × 45deg) translateZ(60px).clip-path: 20% to 80%.transform-origin: top) and lean in by atan(60 / 72) ≈ 39.8°, so a 72px-deep point forms where the tips meet.animation-delay grows with --i, so the sparkle walks around the stone. Only opacity animates.trapezoid + triangle clip-pathshinge on the girdle: transform-origin top / bottomlean = atan(radius / depth)staggered opacity glint<div class="scene">
<div class="gem">
<i class="crown" style="--i:0"></i>
<i class="crown" style="--i:1"></i>
<i class="crown" style="--i:2"></i>
<i class="crown" style="--i:3"></i>
<i class="crown" style="--i:4"></i>
<i class="crown" style="--i:5"></i>
<i class="crown" style="--i:6"></i>
<i class="crown" style="--i:7"></i>
<i class="pav" style="--i:0"></i>
<i class="pav" style="--i:1"></i>
<i class="pav" style="--i:2"></i>
<i class="pav" style="--i:3"></i>
<i class="pav" style="--i:4"></i>
<i class="pav" style="--i:5"></i>
<i class="pav" style="--i:6"></i>
<i class="pav" style="--i:7"></i>
<b></b>
</div>
</div>
.scene {
perspective: 800px;
}
.gem {
position: relative;
width: 49.71px; /* one girdle edge = 2 × 60px × tan(180deg / 8) */
height: 96px; /* crown 24px + pavilion 72px */
transform-style: preserve-3d;
animation: spin 14s linear infinite;
}
.gem i {
--alpha: 0.2;
position: absolute;
left: 0;
width: 100%;
}
.gem i:nth-child(odd) {
--alpha: 0.4;
}
/* the glint: fades in and out, one facet after another */
.gem i::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(115deg, transparent 30%, rgb(255 255 255 / 0.8) 50%, transparent 70%);
opacity: 0;
animation: glint 3.2s ease-in-out infinite;
animation-delay: calc(var(--i) * -0.4s + var(--late, 0s));
}
/* crown: trapezoids standing on the girdle, leaning 45deg inward */
.gem .crown {
bottom: 72px;
height: 33.94px; /* slant = √2 × 24px */
transform-origin: 50% 100%;
transform: rotateY(calc(var(--i) * 45deg)) translateZ(60px) rotateX(45deg);
clip-path: polygon(20% 0, 80% 0, 100% 100%, 0 100%); /* top edge = 36 / 60 of the bottom */
background:
linear-gradient(rgb(46 230 214 / 0.8), rgb(46 230 214 / 0.8)) top / 100% 1.5px no-repeat,
linear-gradient(rgb(46 230 214 / 0.8), rgb(46 230 214 / 0.8)) bottom / 100% 1.5px no-repeat,
linear-gradient(to top, rgb(46 230 214 / var(--alpha)), rgb(46 230 214 / 0.12));
}
/* pavilion: triangles hanging from the girdle, leaning in by atan(60 / 72) */
.gem .pav {
--late: -1.7s;
top: 24px;
height: 93.72px; /* slant = √(72² + 60²) */
transform-origin: 50% 0;
transform: rotateY(calc(var(--i) * 45deg)) translateZ(60px) rotateX(-39.81deg);
clip-path: polygon(0 0, 100% 0, 50% 100%);
background:
linear-gradient(to top right, transparent 50%, rgb(139 108 255 / 0.8) 50% calc(50% + 1.2px), transparent calc(50% + 1.2px)) left / 50% 100% no-repeat,
linear-gradient(to top left, transparent 50%, rgb(139 108 255 / 0.8) 50% calc(50% + 1.2px), transparent calc(50% + 1.2px)) right / 50% 100% no-repeat,
linear-gradient(rgb(139 108 255 / var(--alpha)), rgb(255 77 157 / 0.14));
}
/* table: a flat octagon on top, 36px from the axis */
.gem b {
position: absolute;
left: calc(50% - 36px);
top: -36px;
width: 72px;
height: 72px;
clip-path: polygon(29.29% 0, 70.71% 0, 100% 29.29%, 100% 70.71%, 70.71% 100%, 29.29% 100%, 0 70.71%, 0 29.29%);
background: linear-gradient(135deg, rgb(46 230 214 / 0.55), rgb(255 255 255 / 0.35), rgb(46 230 214 / 0.3));
transform: rotateX(90deg);
}
@keyframes spin {
from { transform: rotateX(-18deg) rotateY(0deg); }
to { transform: rotateX(-18deg) rotateY(360deg); }
}
@keyframes glint {
0%, 55%, 100% { opacity: 0; }
78% { opacity: 0.9; }
}
@use 'sass:math';
$n: 8;
$G: 60px; // girdle (widest ring) apothem
$T: 36px; // table (flat top) apothem
$hc: $G - $T; // crown height, equal to the step inwards, so crown facets lean exactly 45deg
$hp: 72px; // pavilion depth
$wg: 2 * $G * math.tan(math.div(180deg, $n)); // width of one girdle edge
$Lc: math.sqrt(2) * $hc; // slant length of a crown facet
$Lp: math.hypot($hp, $G); // slant length of a pavilion facet
$lean-p: math.atan(math.div($G, $hp)); // pavilion lean from vertical
$edge: color-mix(in srgb, var(--c) 80%, transparent);
.d-diamond {
position: relative;
width: $wg;
height: $hc + $hp;
transform-style: preserve-3d;
animation: d-diamond-spin 14s linear infinite;
i {
--a: 20%;
position: absolute;
left: 0;
width: $wg;
&:nth-child(odd) {
--a: 40%;
}
// the glint: a white streak on every facet, fading in and out on a delay that walks round the gem
&::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(115deg, transparent 30%, rgb(255 255 255 / 0.8) 50%, transparent 70%);
opacity: 0;
animation: d-diamond-glint 3.2s ease-in-out infinite;
animation-delay: calc(var(--i) * -0.4s + var(--late, 0s));
}
}
// crown: trapezoids standing on the girdle, hinged at their bottom edge, leaning 45deg inwards
&__c {
--c: var(--accent-2);
bottom: $hp;
height: $Lc;
transform-origin: 50% 100%;
transform: rotateY(calc(var(--i) * #{math.div(360deg, $n)})) translateZ($G) rotateX(45deg);
// the top edge shrinks to T/G of the bottom edge
clip-path: polygon(
#{math.div(1 - math.div($T, $G), 2) * 100%} 0,
#{math.div(1 + math.div($T, $G), 2) * 100%} 0,
100% 100%,
0 100%
);
background:
linear-gradient($edge, $edge) top / 100% 1.5px 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) 12%, transparent));
}
// pavilion: long triangles hanging from the girdle, hinged at their top edge, meeting in a point
&__p {
--c: var(--accent);
--late: -1.7s;
top: $hc;
height: $Lp;
transform-origin: 50% 0;
transform: rotateY(calc(var(--i) * #{math.div(360deg, $n)})) translateZ($G) rotateX(-$lean-p);
clip-path: polygon(0 0, 100% 0, 50% 100%);
// slanted edges are drawn with "to corner" gradients: their 50% line is the box diagonal
background:
linear-gradient(to top right, transparent 50%, $edge 50%, $edge calc(50% + 1.2px), transparent calc(50% + 1.2px)) left /
50% 100% no-repeat,
linear-gradient(to top left, transparent 50%, $edge 50%, $edge calc(50% + 1.2px), transparent calc(50% + 1.2px)) right /
50% 100% no-repeat,
linear-gradient(to bottom, color-mix(in srgb, var(--c) var(--a), transparent), color-mix(in srgb, var(--hot) 14%, transparent));
}
// table: an octagon laid flat on top
b {
position: absolute;
left: calc(50% - #{$T});
top: -$T;
width: $T * 2;
height: $T * 2;
clip-path: polygon(29.29% 0, 70.71% 0, 100% 29.29%, 100% 70.71%, 70.71% 100%, 29.29% 100%, 0 70.71%, 0 29.29%);
background: linear-gradient(
135deg,
color-mix(in srgb, var(--accent-2) 55%, transparent),
rgb(255 255 255 / 0.35) 50%,
color-mix(in srgb, var(--accent-2) 30%, transparent)
);
transform: rotateX(90deg);
}
}
@keyframes d-diamond-spin {
from {
transform: rotateX(-18deg) rotateY(0deg);
}
to {
transform: rotateX(-18deg) rotateY(360deg);
}
}
@keyframes d-diamond-glint {
0%,
55%,
100% {
opacity: 0;
}
78% {
opacity: 0.9;
}
}