3D equalizer
Pure CSSSeven real cuboids bouncing like an audio meter. Walls are squashed with scaleY and each lid rides down by the same amount, so nothing but transforms ever changes.
Eight glowing dots with comet trails circle a core on three tilted orbits, while the whole atom slowly turns. Every dot counter-rotates so it always faces you, yet still passes behind the core.
Your edited version
rotateX(72deg) and turned with rotateZ(--phi); the dots live inside it, so they share its tilt and pass in front of and behind the core in real depth.rotateZ(θ) translateX(r) rotateZ(−θ): step out to the orbit, go round, and turn back by the same angle, so it travels without spinning. Then rotateX(-72deg) rotateZ(-phi) undoes the orbit's tilt so it faces the viewer.--lag), each smaller and fainter. Every delay has one full lap subtracted, so all of them are negative and no dot waits at the centre when the page loads.linear, so the loop has no seam; three lap times (1.6s, 2.4s, 3.3s) and one reversed ring keep it from ever looking in sync.--u, tied to the canvas: the orbits are 76 units in radius and a dot 12 across, so the atom is the same share of a gallery card, the editor and a recording canvas.rotateZ(θ) translateX(r) rotateZ(−θ): orbit without turningundo the plane’s tilt = billboardtrail copies = same animation, a little latervar() inside @keyframes<div class="scene">
<div class="atom" role="img" aria-label="Loading">
<b></b>
<div class="orbit" style="--c:#2ee6d6; --dur:1.6s; --dir:normal; --phi:0deg">
<i style="--p:0.000; --lag:0"></i>
<i style="--p:0.000; --lag:1"></i>
<i style="--p:0.000; --lag:2"></i>
<i style="--p:0.333; --lag:0"></i>
<i style="--p:0.333; --lag:1"></i>
<i style="--p:0.333; --lag:2"></i>
<i style="--p:0.667; --lag:0"></i>
<i style="--p:0.667; --lag:1"></i>
<i style="--p:0.667; --lag:2"></i>
</div>
<div class="orbit" style="--c:#ff4d9d; --dur:2.4s; --dir:reverse; --phi:60deg">
<i style="--p:0.000; --lag:0"></i>
<i style="--p:0.000; --lag:1"></i>
<i style="--p:0.000; --lag:2"></i>
<i style="--p:0.333; --lag:0"></i>
<i style="--p:0.333; --lag:1"></i>
<i style="--p:0.333; --lag:2"></i>
<i style="--p:0.667; --lag:0"></i>
<i style="--p:0.667; --lag:1"></i>
<i style="--p:0.667; --lag:2"></i>
</div>
<div class="orbit" style="--c:#ffb547; --dur:3.3s; --dir:normal; --phi:120deg">
<i style="--p:0.000; --lag:0"></i>
<i style="--p:0.000; --lag:1"></i>
<i style="--p:0.000; --lag:2"></i>
<i style="--p:0.500; --lag:0"></i>
<i style="--p:0.500; --lag:1"></i>
<i style="--p:0.500; --lag:2"></i>
</div>
</div>
</div>
.scene {
/* one base unit: every length below is a multiple of it, so the atom is the same share of a
gallery card, the editor, a full screen and a recording canvas */
--u: 0.39vmin;
/* where the loop starts: every animation is set 36s into its run, one shared moment, so the
timing between them is kept. There the dots are spread round the core, and a paused atom
(which shows this first frame) is centred; at 0s they bunch on one side */
--t0: -36s;
perspective: calc(800 * var(--u));
}
/* the whole atom turns slowly; everything flat inside turns back by the same amount */
.atom {
position: relative;
width: calc(152 * var(--u));
height: calc(152 * var(--u));
transform-style: preserve-3d;
animation: atom-turn 16s linear var(--t0) infinite;
}
/* the core */
.atom b {
position: absolute;
inset: calc(50% - 20 * var(--u));
border-radius: 50%;
background: radial-gradient(circle at 40% 36%, #fff 0 7%, #c5b6ff 20%, #8b6cff 46%, #463680 70%, transparent 72%);
animation: atom-face 16s linear var(--t0) infinite;
}
/* a soft halo that breathes (a gradient, no blur) */
.atom b::before {
content: '';
position: absolute;
inset: calc(-16 * var(--u));
border-radius: 50%;
background: radial-gradient(closest-side, rgb(139 108 255 / 0.45), transparent);
animation: atom-breathe 1.6s ease-in-out var(--t0) infinite alternate;
}
/* an orbit: a circle laid over by 72°, its axis turned by --phi */
.orbit {
position: absolute;
inset: 0;
border-radius: 50%;
transform-style: preserve-3d;
transform: rotateZ(var(--phi)) rotateX(72deg);
}
/* the dashed track */
.orbit::before {
content: '';
position: absolute;
inset: 0;
border: calc(1 * var(--u)) dashed color-mix(in srgb, var(--c) 45%, transparent);
border-radius: 50%;
}
/* a dot: an invisible carrier going round; --p spaces the dots, --lag makes the trail
(same animation, a little later). One whole lap is subtracted so every delay is negative. */
.orbit i {
position: absolute;
top: calc(50% - 6 * var(--u));
left: calc(50% - 6 * var(--u));
width: calc(12 * var(--u));
height: calc(12 * var(--u));
transform-style: preserve-3d;
animation: dot-orbit var(--dur) linear infinite var(--dir);
animation-delay: calc(var(--t0) + var(--lag) * var(--dur) * 0.017 - var(--p) * var(--dur) - var(--dur));
}
/* the glow; trail copies are smaller and fainter */
.orbit i::before {
content: '';
position: absolute;
inset: 0;
border-radius: 50%;
background: radial-gradient(circle,
color-mix(in srgb, var(--c) 30%, #fff) 0 7%,
var(--c) 24% 38%,
color-mix(in srgb, var(--c) 30%, transparent) 56%,
transparent 70%);
opacity: calc(1 - var(--lag) * 0.3);
scale: calc(1.25 - var(--lag) * 0.25);
animation: atom-face 16s linear var(--t0) infinite;
}
@keyframes atom-turn {
from { transform: rotateX(-16deg) rotateY(0deg); }
to { transform: rotateX(-16deg) rotateY(360deg); }
}
/* exactly undoes atom-turn: same duration, reversed order and signs */
@keyframes atom-face {
from { transform: rotateY(0deg) rotateX(16deg); }
to { transform: rotateY(-360deg) rotateX(16deg); }
}
/* read right to left: undo the orbit's tilt, cancel the lap, step out, go round */
@keyframes dot-orbit {
from { transform: rotateZ(0deg) translateX(calc(76 * var(--u))) rotateZ(0deg) rotateX(-72deg) rotateZ(calc(var(--phi) * -1)); }
to { transform: rotateZ(360deg) translateX(calc(76 * var(--u))) rotateZ(-360deg) rotateX(-72deg) rotateZ(calc(var(--phi) * -1)); }
}
@keyframes atom-breathe {
from { opacity: 0.55; transform: scale(0.85); }
to { opacity: 1; transform: scale(1.1); }
}