3D bar chart
Pure CSSEach bar is a real cuboid — front, side and top face — growing on a staggered delay.
Real time needs JS. Each pair that changes re-triggers a CSS rotateX flip.
Your edited version
rotateX(-90deg) → 0.void el.offsetWidth), add the class back.setInterval for the timeanimation restart via reflowrotateX flip keyframes<div class="scene">
<div class="clock"><span></span><em>:</em><span></span><em>:</em><span></span></div>
</div>
.scene {
perspective: 700px;
}
.clock {
display: flex;
align-items: center;
gap: 8px;
font: 800 3.5rem ui-monospace, monospace;
font-variant-numeric: tabular-nums;
transform: rotateY(-14deg) rotateX(6deg);
transform-style: preserve-3d;
}
.clock span {
min-width: 2.2ch;
padding: 10px 14px;
border-radius: 12px;
text-align: center;
color: #fff;
background: linear-gradient(#2a2f52 49%, #0c0e1d 49% 52%, #1d2140 52%);
box-shadow: 0 14px 22px -12px #000;
}
.clock em { color: #ffb547; font-style: normal; }
.clock .is-flipping {
animation: flip 0.55s cubic-bezier(0.3, 1.5, 0.5, 1);
}
@keyframes flip {
from { transform: rotateX(-90deg); }
}
const parts = [...document.querySelectorAll('.clock span')];
function tick() {
const d = new Date();
[d.getHours(), d.getMinutes(), d.getSeconds()].forEach((n, i) => {
const text = String(n).padStart(2, '0');
const el = parts[i];
if (el.textContent === text) return;
el.textContent = text;
el.classList.remove('is-flipping');
void el.offsetWidth; // force reflow so the animation restarts
el.classList.add('is-flipping');
});
}
tick();
setInterval(tick, 250);
.d-clock {
display: flex;
align-items: center;
gap: 6px;
font-family: var(--mono);
font-size: 40px;
font-weight: 800;
font-variant-numeric: tabular-nums;
transform-style: preserve-3d;
transform: rotateY(-14deg) rotateX(6deg);
span {
position: relative;
min-width: 2.2ch;
padding: 8px 10px;
border-radius: 10px;
background: linear-gradient(#2a2f52 49%, #0c0e1d 49% 52%, #1d2140 52%);
color: #fff;
text-align: center;
box-shadow: 0 14px 22px -12px #000;
transform-origin: center;
// JS re-adds this class whenever the value changes.
&.is-flipping {
animation: d-clock-flip 0.55s cubic-bezier(0.3, 1.5, 0.5, 1);
// With animations paused the first frame would leave the digits edge-on and unreadable.
:root[data-paused] & {
animation: none;
}
}
}
em {
color: var(--warm);
font-style: normal;
}
}
@keyframes d-clock-flip {
from {
transform: rotateX(-90deg);
}
}