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.
The classic fold loader laid on a floor in real perspective: each quadrant swings up over one edge, rests, then folds away over the next, a quarter-beat after its neighbour.
Your edited version
rotateZ(calc(var(--i) * 90deg)) — one keyframe animation drives all four.transform-origin: calc(100% + 2px) calc(100% + 2px), just outside its own corner.animation-delay (calc(var(--i) * 0.3s - 2.4s)) starts each flap already part-way through the cycle instead of waiting its turn, which is what makes the four look like they are chasing each other.opacity: 0, so the fold-in and fold-out happen off-screen — the loop has no visible seam.rotateX(58deg) rotateZ(45deg) turns a normally flat spinner into flaps that visibly stand up off a floor.one quadrant rotated 4× with --itransform-origin on the shared cornernegative animation-delay chaseinvisible at both ends = seamless<div class="scene">
<div class="cubeloader" role="img" aria-label="Loading">
<i style="--i:0;--c:#8b6cff"></i>
<i style="--i:1;--c:#ff4d9d"></i>
<i style="--i:2;--c:#2ee6d6"></i>
<i style="--i:3;--c:#ffb547"></i>
</div>
</div>
.scene {
perspective: 800px;
}
/* the classic "folding cube" spinner, but laid on a floor in real perspective so the flaps
genuinely stand up out of the plane while they fold */
.cubeloader {
--s: 92px;
position: relative;
width: var(--s);
height: var(--s);
transform-style: preserve-3d;
transform: rotateX(58deg) rotateZ(45deg);
}
/* the floor plate */
.cubeloader::before {
content: '';
position: absolute;
inset: -9px;
border: 1px dashed rgb(140 150 220 / 0.34);
border-radius: 12px;
background: color-mix(in srgb, #8b6cff 9%, transparent);
transform: translateZ(-1px);
}
/* all four quadrants are the SAME top-left square, turned around the loader's centre in
90° steps (--i = 0…3), so one keyframe animation serves all four */
.cubeloader i {
position: absolute;
top: 0;
left: 0;
width: 50%;
height: 50%;
transform-origin: 100% 100%;
transform: rotateZ(calc(var(--i) * 90deg));
transform-style: preserve-3d;
}
/* the flap, hinged on the corner that touches the loader's centre */
.cubeloader i::before {
content: '';
position: absolute;
inset: 2px;
border-radius: 5px;
transform-origin: calc(100% + 2px) calc(100% + 2px);
/* a negative delay starts each flap part-way through: no waiting, and the four run in a chase */
animation: cubeloader-fold 2.4s ease-in-out calc(var(--i) * 0.3s - 2.4s) infinite;
background: color-mix(in srgb, var(--c) 45%, transparent);
border: 1px solid color-mix(in srgb, var(--c) 75%, transparent);
box-shadow: inset 0 0 24px color-mix(in srgb, var(--c) 30%, transparent);
}
/* fold in over the bottom edge, rest, fold out over the right edge. Both ends are invisible,
so the loop has no seam. Both functions appear in every keyframe so the browser interpolates
the two angles separately instead of blending matrices */
@keyframes cubeloader-fold {
0%,
10% {
opacity: 0;
transform: rotateX(-180deg) rotateY(0deg);
}
25%,
75% {
opacity: 1;
transform: rotateX(0deg) rotateY(0deg);
}
90%,
100% {
opacity: 0;
transform: rotateX(0deg) rotateY(180deg);
}
}
@use '../mixins' as *;
$time: 2.4s;
// The classic "folding cube" spinner, but laid on a floor in real perspective so the flaps
// genuinely stand up out of the plane while they fold.
.d-cubeloader {
--s: 92px;
position: relative;
width: var(--s);
height: var(--s);
transform-style: preserve-3d;
transform: rotateX(58deg) rotateZ(45deg);
// the floor plate
&::before {
content: '';
position: absolute;
inset: -9px;
border: 1px dashed var(--border-strong);
border-radius: 12px;
background: color-mix(in srgb, var(--accent) 9%, transparent);
transform: translateZ(-1px);
}
// All four quadrants are the SAME top-left square, turned around the centre of the loader in
// 90° steps (--i = 0…3). So one keyframe animation serves all four.
i {
position: absolute;
top: 0;
left: 0;
width: 50%;
height: 50%;
transform-origin: 100% 100%;
transform: rotateZ(calc(var(--i) * 90deg));
transform-style: preserve-3d;
// the flap, hinged on the corner that touches the loader's centre
&::before {
content: '';
position: absolute;
inset: 2px;
border-radius: 5px;
transform-origin: calc(100% + 2px) calc(100% + 2px);
// a negative delay starts each flap part-way through: no waiting, and the four run in a chase
animation: d-cubeloader-fold $time ease-in-out calc(var(--i) * #{$time * 0.125} - #{$time}) infinite;
@include face(var(--c), 45%);
}
}
}
// Fold in over the bottom edge, rest, fold out over the right edge. Both ends are invisible,
// so the loop has no seam. Both functions appear in every keyframe so the browser interpolates
// the two angles separately instead of blending matrices.
@keyframes d-cubeloader-fold {
0%,
10% {
opacity: 0;
transform: rotateX(-180deg) rotateY(0deg);
}
25%,
75% {
opacity: 1;
transform: rotateX(0deg) rotateY(0deg);
}
90%,
100% {
opacity: 0;
transform: rotateX(0deg) rotateY(180deg);
}
}