ASCII Donut Math Animation — 30 Days Web Challenge Day 2
Source: Dev.to
Overview
The classic donut.c program renders a spinning 3D torus using ASCII characters. For Day 2 of the 30 Days Web Challenge, the animation was ported to the browser with interactive glitch effects and sound. The donut appears as the “0” in “30 Days” on the landing page and hides easter‑egg animations.
Features
- Mathematical torus rendering – classic sin/cos projection with a z‑buffer and luminance mapping.
- Smooth rotation – two angles (
AandB) increment each frame for continuous spinning. - Glitch mode – clicking the donut adds random rotation offsets and scrambles luminance values.
- Neon glow styling – cyan text with a text‑shadow for a retro terminal feel; glitch switches to a chaotic rainbow glow.
- Interactive easter egg – after five clicks the donut explodes into 30 colored particles, accompanied by synthesized sound.
Core Rendering Code
const render = () => {
const b = new Int8Array(width * height).fill(-1); // output buffer
const z = new Float32Array(width * height); // z‑buffer
const sA = Math.sin(A), cA = Math.cos(A);
const sB = Math.sin(B), cB = Math.cos(B);
for (let j = 0; j 0 && y 0 && x z[o]) {
z[o] = D;
b[o] = N > 0 ? N : -1;
}
}
}
// Render to element using luminance characters
if (preRef.current) {
let s = "";
for (let k = 0; k 0 && k % width === 0) s += "\n";
s += b[k] >= 0 ? lum[b[k]] : " ";
}
preRef.current.textContent = s;
}
A += 0.015;
B += 0.008;
frameId = requestAnimationFrame(render);
};
The luminance string ".,-~:;=!*#$@" maps brightness values to ASCII characters, from dim (.) to bright (@).
Glitch Effect
// Glitch: add random rotation offset
const glitchA = glitching ? (Math.random() - 0.5) * 0.5 : 0;
const glitchB = glitching ? (Math.random() - 0.5) * 0.3 : 0;
// During glitch, randomly scramble luminance
if (glitching && Math.random() 0 ? N : -1;
}
Visual Styling
style={{
color: glitching ? `hsl(${Math.random() * 360}, 100%, 70%)` : "#00AFFF",
textShadow: glitching
? "0 0 8px #ff0066, 0 0 20px #ff6600"
: "0 0 6px #00AFFF, 0 0 20px #0077ff",
}}
Interaction Logic
const handleDonutClick = useCallback(() => {
if (isGlitching || isExploding || showDonutPopup) return;
const newCount = donutClicks + 1;
if (newCount >= 5) {
// BOOM!
setDonutClicks(0);
setIsExploding(true);
playExplosionSound();
const colors = ["#00AFFF", "#00E676", "#ff0066", "#ff6600", "#6C5CE7", "#FFD700"];
const particles = Array.from({ length: 30 }, (_, i) => ({
id: explosionId.current++,
x: 50, y: 40,
angle: (i / 30) * Math.PI * 2 + Math.random() * 0.5,
speed: 5 + Math.random() * 15,
color: colors[i % colors.length],
}));
setExplosionParticles(particles);
} else {
// Glitch for 1 second
setDonutClicks(newCount);
setIsGlitching(true);
playGlitchSound();
}
}, [donutClicks, isGlitching, isExploding, showDonutPopup]);
Technology Stack
| Technology | Role |
|---|---|
| Next.js | React framework |
| TypeScript | Type‑safe math operations |
Canvas / <pre> element | ASCII character rendering |
| Web Audio API | Glitch and explosion sound synthesis |
| Framer Motion | Particle animations for the explosion |
Live Demo & Source
- Live Demo:
- Source Code: (key file:
DonutAnimation.tsx) - Original
donut.c:
Originally published at