Try it live at 30days.abduarrahman.com — and the source code is on GitHub.
The Origin
Every programmer has seen donut.c — the legendary C code that renders a spinning 3D torus using ASCII characters. It's a rite of passage.
For Day 2 of the 30 Days Web Challenge, I wanted to bring this classic to the browser — not just as a static render, but as an interactive element with glitch effects and sound. The donut sits on the landing page as the "0" in "30 Days", and clicking it reveals hidden easter eggs.
What I Built
A real-time ASCII torus renderer with:
- Mathematical torus rendering — the classic sin/cos projection with z-buffer depth sorting and luminance mapping
- Smooth rotation — two rotation angles (A and B) increment each frame for continuous spinning
- Glitch mode — clicking the donut adds random rotation offsets and luminance scrambling
-
Neon glow styling — cyan text with
textShadowfor that retro terminal feel - Interactive easter egg — click the donut 5 times on the landing page to trigger an explosion with particles
How It Works
The Torus Rendering Algorithm
The core is the classic donut math — project a 3D torus onto a 2D character grid using two rotation angles:
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 < 6.283185; j += 0.07) { // theta: around the tube
const st = Math.sin(j), ct = Math.cos(j);
for (let i = 0; i < 6.283185; i += 0.02) { // phi: around the ring
const sp = Math.sin(i), cp = Math.cos(i);
const h = ct + 2;
const D = 1 / (sp * h * sA + st * cA + 5); // perspective
const t = sp * h * cA - st * sA;
const x = ~~(cx + kx * D * (cp * h * cB - t * sB));
const y = ~~(cy + ky * D * (cp * h * sB + t * cB));
const o = x + width * y;
const N = ~~(8 * ((st * sA - sp * ct * cA) * cB
- sp * ct * sA - st * cA - cp * ct * sB));
if (y > 0 && y < height && x > 0 && x < width && D > z[o]) {
z[o] = D;
b[o] = N > 0 ? N : -1;
}
}
}
// Render to <pre> element using luminance characters
if (preRef.current) {
let s = "";
for (let k = 0; k < width * height; k++) {
if (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 (dot) to bright (@).
Glitch Mode
When the donut is clicked on the landing page, random offsets are injected into the rotation calculations, and 15% of luminance values get randomly scrambled:
// 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.15) {
b[o] = ~~(Math.random() * 12);
} else {
b[o] = N > 0 ? N : -1;
}
The visual styling switches from calm cyan to chaotic rainbow with red/orange glow:
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",
}}
The 5-Click Easter Egg
The donut is interactive — each click triggers a 1-second glitch with a synthesized sound. After 5 clicks, the donut explodes into 30 colored particles:
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]);
Tech Stack
| Technology | Purpose |
|---|---|
| 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 explosion |
Links
- Live Demo: 30days.abduarrahman.com
- Source Code: github.com/ab2rahman/30days-web-challenge
-
Key File:
DonutAnimation.tsx - Original donut.c: a1k0n.net/2011/07/20/donut-math.html
Follow the challenge:
- Instagram: @abduarrahman
- YouTube: @abduarrahmanscode
- TikTok: @anduarrahmans
Support the challenge:
- Ko-fi: ko-fi.com/abduarrahman
Originally published at abduarrahman.com
Top comments (0)