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.
Rungs spin on negative delays; dots counter-rotate so they always face the camera.
Your edited version
rotateY animation.animation-delay per rung starts each one part-way through its turn — that offset is the twist.reverse. The two rotations cancel and the dot always faces the camera (a "billboard").preserve-3d for that cancellation to happen in 3D space.negative animation-delaybillboard counter-rotationSass @for<div class="scene">
<div class="helix">
<div style="--i:1"><i></i><i></i></div>
<div style="--i:2"><i></i><i></i></div>
<div style="--i:3"><i></i><i></i></div>
<div style="--i:4"><i></i><i></i></div>
<div style="--i:5"><i></i><i></i></div>
<div style="--i:6"><i></i><i></i></div>
<div style="--i:7"><i></i><i></i></div>
<div style="--i:8"><i></i><i></i></div>
<div style="--i:9"><i></i><i></i></div>
<div style="--i:10"><i></i><i></i></div>
<div style="--i:11"><i></i><i></i></div>
<div style="--i:12"><i></i><i></i></div>
</div>
</div>
.scene {
perspective: 800px;
}
.helix {
display: grid;
gap: 12px;
transform-style: preserve-3d;
transform: rotateZ(-14deg);
}
.helix > div {
--delay: calc(var(--i) * -0.22s);
position: relative;
width: 120px;
height: 3px;
background: linear-gradient(90deg, #2ee6d6, transparent 35% 65%, #ff4d9d);
transform-style: preserve-3d;
animation: helix-spin 4s linear infinite;
animation-delay: var(--delay);
}
.helix i {
position: absolute;
top: -6px;
width: 15px;
height: 15px;
border-radius: 50%;
/* same animation, reversed → the dot always faces the camera */
animation: helix-spin 4s linear infinite reverse;
animation-delay: var(--delay);
}
.helix i:first-child { left: -7px; background: #2ee6d6; box-shadow: 0 0 10px #2ee6d6; }
.helix i:last-child { right: -7px; background: #ff4d9d; box-shadow: 0 0 10px #ff4d9d; }
@keyframes helix-spin {
to { transform: rotateY(360deg); }
}
$rungs: 14;
$period: 4s;
.d-helix {
display: grid;
gap: 9px;
transform-style: preserve-3d;
transform: rotateZ(-14deg);
&__rung {
position: relative;
width: 96px;
height: 3px;
border-radius: 3px;
background: linear-gradient(90deg, var(--accent-2), transparent 35% 65%, var(--hot));
transform-style: preserve-3d;
animation: d-helix-spin $period linear infinite;
animation-delay: var(--delay);
// Negative delays start every rung part-way through the turn → the twist.
@for $i from 1 through $rungs {
&:nth-child(#{$i}) {
--delay: #{$i * -0.22s};
}
}
}
i {
position: absolute;
top: -5px;
width: 13px;
height: 13px;
border-radius: 50%;
background: var(--accent-2);
box-shadow: 0 0 10px currentColor;
color: var(--accent-2);
// Billboard: spin the opposite way so the dot always faces the camera.
animation: d-helix-spin $period linear infinite reverse;
animation-delay: var(--delay);
&:first-child {
left: -6px;
}
&:last-child {
right: -6px;
background: var(--hot);
color: var(--hot);
}
}
}
@keyframes d-helix-spin {
to {
transform: rotateY(360deg);
}
}