Synthwave floor
Pure CSSA gradient-drawn grid laid flat with rotateX, scrolling toward you forever.
Click anywhere. JS spawns particles with random 3D vectors; one CSS animation flies them all.
Your edited version
--x, --y, --z, --spin, --hue.--z is what makes it 3D: particles coming toward the camera grow, the others shrink away.animationend, so the DOM never fills up.random --x / --y / --z / --spin per particletranslate3d + rotate3d keyframesremove on animationend<div class="party">click anywhere</div>
.party {
position: fixed;
inset: 0;
display: grid;
place-items: center;
overflow: hidden;
perspective: 500px;
color: #949bc0;
font-family: system-ui;
cursor: pointer;
user-select: none;
}
.party i {
position: absolute;
width: 10px;
height: 14px;
margin: -7px 0 0 -5px;
border-radius: 2px;
background: hsl(var(--hue) 90% 62%);
pointer-events: none;
animation: fly 1.3s cubic-bezier(0.1, 0.7, 0.3, 1) forwards;
}
@keyframes fly {
to {
opacity: 0;
/* + 160px on Y is the "gravity" */
transform:
translate3d(var(--x), calc(var(--y) + 160px), var(--z))
rotate3d(1, 1, 0.4, var(--spin));
}
}
const party = document.querySelector('.party');
const rand = (min, max) => min + Math.random() * (max - min);
party.addEventListener('pointerdown', (e) => {
for (let i = 0; i < 36; i++) {
const p = document.createElement('i');
p.style.left = e.clientX + 'px';
p.style.top = e.clientY + 'px';
p.style.setProperty('--x', rand(-220, 220) + 'px');
p.style.setProperty('--y', rand(-240, 80) + 'px');
p.style.setProperty('--z', rand(-200, 300) + 'px');
p.style.setProperty('--spin', rand(360, 1080) + 'deg');
p.style.setProperty('--hue', rand(0, 360));
p.addEventListener('animationend', () => p.remove(), { once: true });
party.append(p);
}
});
.d-confetti {
position: absolute;
inset: 0;
display: grid;
place-items: center;
overflow: hidden;
perspective: 500px;
cursor: pointer;
user-select: none;
span {
color: var(--muted);
font-size: 13px;
}
// Every particle gets --x --y --z --spin --hue from JS and runs the same animation.
i {
position: absolute;
width: 8px;
height: 12px;
margin: -6px 0 0 -4px;
border-radius: 2px;
background: hsl(var(--hue) 90% 62%);
pointer-events: none;
animation: d-confetti-fly 1.3s cubic-bezier(0.1, 0.7, 0.3, 1) forwards;
}
}
@keyframes d-confetti-fly {
to {
opacity: 0;
// + 120px on Y is the "gravity"
transform: translate3d(var(--x), calc(var(--y) + 120px), var(--z)) rotate3d(1, 1, 0.4, var(--spin));
}
}