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 3×3×3 wireframe: three layers of bars and glowing nodes, and nine posts through them. The outer layers move apart while the posts stretch by the same amount, so no joint ever opens.
Your edited version
::after turned rotateX(90deg) around the bar's own length.rotateY(90deg), standing up with rotateZ(90deg). --x, --y, --z (−1, 0 or 1) times the spacing place it.translateY(y × 1.3 × u): the middle one has y = 0, so the same keyframes leave it in place.scaleY(1.3) on the same beat, so the posts stretch by exactly what the layers move and no joint ever opens.rotateY(-360deg), same timing), so they always face you.bars as two crossed planeslayers: translateY(y × distance)posts: scaleY in step with the layersbillboard nodes<div class="scene">
<div class="lattice">
<div class="layer" style="--y:-1">
<i style="--z:-1"></i>
<i style="--z:0"></i>
<i style="--z:1"></i>
<i class="z" style="--x:-1"></i>
<i class="z" style="--x:0"></i>
<i class="z" style="--x:1"></i>
<b style="--x:-1; --z:-1"></b>
<b style="--x:0; --z:-1"></b>
<b style="--x:1; --z:-1"></b>
<b style="--x:-1; --z:0"></b>
<b style="--x:0; --z:0"></b>
<b style="--x:1; --z:0"></b>
<b style="--x:-1; --z:1"></b>
<b style="--x:0; --z:1"></b>
<b style="--x:1; --z:1"></b>
</div>
<div class="layer mid" style="--y:0">
<i style="--z:-1"></i>
<i style="--z:0"></i>
<i style="--z:1"></i>
<i class="z" style="--x:-1"></i>
<i class="z" style="--x:0"></i>
<i class="z" style="--x:1"></i>
<b style="--x:-1; --z:-1"></b>
<b style="--x:0; --z:-1"></b>
<b style="--x:1; --z:-1"></b>
<b style="--x:-1; --z:0"></b>
<b style="--x:0; --z:0"></b>
<b style="--x:1; --z:0"></b>
<b style="--x:-1; --z:1"></b>
<b style="--x:0; --z:1"></b>
<b style="--x:1; --z:1"></b>
</div>
<div class="layer" style="--y:1">
<i style="--z:-1"></i>
<i style="--z:0"></i>
<i style="--z:1"></i>
<i class="z" style="--x:-1"></i>
<i class="z" style="--x:0"></i>
<i class="z" style="--x:1"></i>
<b style="--x:-1; --z:-1"></b>
<b style="--x:0; --z:-1"></b>
<b style="--x:1; --z:-1"></b>
<b style="--x:-1; --z:0"></b>
<b style="--x:0; --z:0"></b>
<b style="--x:1; --z:0"></b>
<b style="--x:-1; --z:1"></b>
<b style="--x:0; --z:1"></b>
<b style="--x:1; --z:1"></b>
</div>
<div class="posts">
<i style="--x:-1; --z:-1"></i>
<i style="--x:0; --z:-1"></i>
<i style="--x:1; --z:-1"></i>
<i style="--x:-1; --z:0"></i>
<i style="--x:0; --z:0"></i>
<i style="--x:1; --z:0"></i>
<i style="--x:-1; --z:1"></i>
<i style="--x:0; --z:1"></i>
<i style="--x:1; --z:1"></i>
</div>
</div>
</div>
.scene {
perspective: 800px;
}
.lattice {
--u: 50px; /* spacing */
position: relative;
width: 0;
height: 0;
transform-style: preserve-3d;
animation: spin 18s linear infinite;
}
.layer,
.posts {
position: absolute;
transform-style: preserve-3d;
}
.layer {
transform: translateY(calc(var(--y) * var(--u)));
animation: breathe 2.4s ease-in-out infinite alternate;
}
.posts {
animation: stretch 2.4s ease-in-out infinite alternate;
}
/* a bar along X, two crossed planes */
.lattice i {
position: absolute;
top: -1px;
left: calc(var(--u) * -1);
width: calc(var(--u) * 2);
height: 2px;
transform-style: preserve-3d;
background: linear-gradient(90deg, #2ee6d6, rgb(139 108 255 / 0.85) 30% 70%, #2ee6d6);
transform: translateZ(calc(var(--z) * var(--u)));
}
.lattice i::after {
content: '';
position: absolute;
inset: 0;
background: inherit;
transform: rotateX(90deg);
}
/* the same bar along Z */
.lattice i.z {
transform: translateX(calc(var(--x) * var(--u))) rotateY(90deg);
}
/* and standing up */
.posts i {
transform: translateX(calc(var(--x) * var(--u))) translateZ(calc(var(--z) * var(--u))) rotateZ(90deg);
}
.lattice b {
--glow: 46 230 214;
position: absolute;
top: -6px;
left: -6px;
width: 12px;
height: 12px;
border-radius: 50%;
background: radial-gradient(circle, color-mix(in srgb, rgb(var(--glow)) 25%, #fff) 0 22%, rgb(var(--glow)) 42%, rgb(var(--glow) / 0) 72%);
animation: face-you 18s linear infinite;
}
.mid b { --glow: 255 77 157; }
.mid b:nth-of-type(5) { --glow: 255 181 71; } /* the very centre */
@keyframes spin {
from { transform: rotateZ(-8deg) rotateX(-22deg) rotateY(0deg); }
to { transform: rotateZ(-8deg) rotateX(-22deg) rotateY(360deg); }
}
/* y = 0 for the middle layer, so it stays put */
@keyframes breathe {
to { transform: translateY(calc(var(--y) * var(--u) * 1.3)); }
}
/* (u + 0.3u) / u: exactly what the outer layers moved */
@keyframes stretch {
to { transform: scaleY(1.3); }
}
@keyframes face-you {
from { transform: translate3d(calc(var(--x) * var(--u)), 0, calc(var(--z) * var(--u))) rotateY(0deg); }
to { transform: translate3d(calc(var(--x) * var(--u)), 0, calc(var(--z) * var(--u))) rotateY(-360deg); }
}
$u: 50px; // lattice spacing
$p: 0.3; // how far the outer layers breathe out, as a share of $u
$spin: 18s;
$beat: 2.4s;
// A 3 × 3 × 3 lattice as three horizontal layers (each: 6 bars and 9 nodes) plus 9 posts running
// through all three. The outer layers move out along Y and the posts stretch with them (scaleY),
// so every joint stays joined.
.d-lattice {
position: relative;
width: 0;
height: 0;
transform-style: preserve-3d;
animation: d-lattice-spin $spin linear infinite;
// Everything is placed from the lattice's centre. --x / --y / --z are -1, 0 or 1.
&__layer,
&__posts {
position: absolute;
transform-style: preserve-3d;
}
&__layer {
transform: translateY(calc(var(--y) * #{$u}));
animation: d-lattice-out $beat ease-in-out infinite alternate;
}
// the posts stretch by exactly what the layers move: (u + p·u) / u
&__posts {
animation: d-lattice-stretch $beat ease-in-out infinite alternate;
}
// A bar is two crossed planes (the element and its ::after): a single plane would vanish
// whenever it is seen edge-on.
i {
position: absolute;
top: -1px;
left: -$u;
width: $u * 2;
height: 2px;
transform-style: preserve-3d;
background: linear-gradient(
90deg,
var(--accent-2),
color-mix(in srgb, var(--accent) 85%, transparent) 30% 70%,
var(--accent-2)
);
transform: translateZ(calc(var(--z) * #{$u})); // a bar along X, at depth z
&::after {
content: '';
position: absolute;
inset: 0;
background: inherit;
transform: rotateX(90deg);
}
// a bar along Z: the same bar turned a quarter, at x
&.is-z {
transform: translateX(calc(var(--x) * #{$u})) rotateY(90deg);
}
}
// posts: the same bar standing up (rotateZ), at x and z
&__posts i {
transform: translateX(calc(var(--x) * #{$u})) translateZ(calc(var(--z) * #{$u})) rotateZ(90deg);
}
// nodes: glowing dots that turn back against the spin, so they always face you and stay round
b {
--glow: var(--accent-2);
position: absolute;
top: -6px;
left: -6px;
width: 12px;
height: 12px;
border-radius: 50%;
background: radial-gradient(circle, color-mix(in srgb, var(--glow) 25%, #fff) 0 22%, var(--glow) 42%, color-mix(in srgb, var(--glow) 0%, transparent) 72%);
animation: d-lattice-face $spin linear infinite;
}
// the middle layer glows pink, its very centre amber
.is-mid b {
--glow: var(--hot);
&:nth-of-type(5) {
--glow: var(--warm);
}
}
}
@keyframes d-lattice-spin {
from {
transform: rotateZ(-8deg) rotateX(-22deg) rotateY(0deg);
}
to {
transform: rotateZ(-8deg) rotateX(-22deg) rotateY(360deg);
}
}
// --y is 0 for the middle layer, so it stays put while the outer two move out
@keyframes d-lattice-out {
to {
transform: translateY(calc(var(--y) * #{$u * (1 + $p)}));
}
}
@keyframes d-lattice-stretch {
to {
transform: scaleY(1 + $p);
}
}
@keyframes d-lattice-face {
from {
transform: translate3d(calc(var(--x) * #{$u}), 0, calc(var(--z) * #{$u})) rotateY(0deg);
}
to {
transform: translate3d(calc(var(--x) * #{$u}), 0, calc(var(--z) * #{$u})) rotateY(-360deg);
}
}