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.
Every tread is the same flat slab pivoting on the pole. One index turns it 30° further and lifts it one step higher: that is the whole spiral.
Your edited version
rotateX(90deg).transform-origin: -5px 50%, a point 5px to the left of the slab, which is where the pole's axis is. Every rotation now pivots around the pole.rotateY(i × 30deg) turns the tread around the pole, translateY(i × -8px) lifts it one step. Turn + lift = spiral.::before hinged on the tread's edge and folded straight down. The pole is two crossed planes, which read as a round post from any angle.transform-origin on the pole axistranslateY + rotateY from one --iriser as a folded ::beforecrossed planes as a round pole<div class="scene">
<div class="stairs">
<b></b>
<b></b>
<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>
</div>
</div>
.scene {
perspective: 800px;
}
.stairs {
position: relative;
width: 130px;
height: 136px;
transform-style: preserve-3d;
animation: spin 16s linear infinite;
}
.stairs i {
--hue: calc(253 - var(--i) * 6); /* violet at the bottom, teal at the top */
position: absolute;
left: calc(50% + 5px); /* start just outside the pole */
top: 98px;
width: 58px;
height: 28px;
border: 1px solid hsl(var(--hue) 90% 82%);
border-radius: 0 6px 6px 0;
background: hsl(var(--hue) 85% 64% / 0.62);
transform-origin: -5px 50%; /* on the pole's axis */
transform-style: preserve-3d;
transform:
translateY(calc(var(--i) * -8px)) /* one step up ... */
rotateY(calc(var(--i) * 30deg)) /* ... and 30deg further round */
rotateX(90deg); /* lie flat */
}
/* riser: hinged on the tread's edge, folded straight down by one step */
.stairs i::before {
content: '';
position: absolute;
left: 0;
top: 100%;
width: 100%;
height: 8px;
background: hsl(var(--hue) 45% 28%);
transform-origin: top;
transform: rotateX(-90deg);
}
/* pole: two crossed planes */
.stairs b {
position: absolute;
left: calc(50% - 4px);
top: 0;
width: 8px;
height: 100%;
border-radius: 4px;
background: linear-gradient(90deg, #3a3f63, #eceefb, #3a3f63);
opacity: 0.85;
}
.stairs b + b {
transform: rotateY(90deg);
}
@keyframes spin {
from { transform: rotateX(-20deg) rotateY(0deg); }
to { transform: rotateX(-20deg) rotateY(360deg); }
}
@use 'sass:math';
$n: 14;
$turn: 30deg; // each step is turned this much more than the one below
$rise: 8px; // and lifted this much
$len: 58px; // tread length, from the pole outwards
$depth: 28px;
$gap: 5px; // treads start just outside the pole so the planes never intersect
$height: 136px;
.d-stairs {
position: relative;
width: 130px;
height: $height;
transform-style: preserve-3d;
animation: d-stairs-spin 16s linear infinite;
// A tread is a rectangle laid flat (rotateX 90deg) whose transform-origin sits on the pole's
// axis. One index drives both the turn and the lift: that is the whole spiral.
i {
--c: color-mix(in srgb, var(--accent-2) var(--mix), var(--accent));
position: absolute;
left: calc(50% + #{$gap});
top: $height - $depth - 10px;
width: $len;
height: $depth;
border: 1px solid color-mix(in srgb, var(--c) 70%, white);
border-radius: 0 6px 6px 0;
background: color-mix(in srgb, var(--c) 62%, transparent);
transform-origin: -$gap 50%;
transform-style: preserve-3d;
transform: translateY(calc(var(--i) * #{-$rise})) rotateY(calc(var(--i) * #{$turn})) rotateX(90deg);
// riser: a strip hinged on the tread's near edge, folded straight down
&::before {
content: '';
position: absolute;
left: 0;
top: 100%;
width: 100%;
height: $rise;
background: color-mix(in srgb, var(--c) 40%, var(--bg));
transform-origin: top;
transform: rotateX(-90deg);
}
}
@for $i from 0 to $n {
i:nth-of-type(#{$i + 1}) {
--i: #{$i};
--mix: #{math.round(math.div($i, $n - 1) * 100) * 1%};
}
}
// pole: two crossed planes read as a round post from every angle
b {
position: absolute;
left: calc(50% - 4px);
top: 0;
width: 8px;
height: 100%;
border-radius: 4px;
background: linear-gradient(90deg, var(--border-strong), var(--text), var(--border-strong));
opacity: 0.85;
& + b {
transform: rotateY(90deg);
}
}
}
@keyframes d-stairs-spin {
from {
transform: rotateX(-20deg) rotateY(0deg);
}
to {
transform: rotateX(-20deg) rotateY(360deg);
}
}