Every website needs a landing page. Most people write “Welcome” and call it a day. I decided mine needed 512 particles morphing through eight mathematical forms while displaying LaTeX equations. You know, the usual.
It started innocently enough. “What if the particles formed a tensor?” Then a graph. Then a galaxy spiral. Before I knew it, I was knee-deep in quaternion rotations and wondering why my wormhole looked more like a donut.
The Architecture of Madness
The whole thing runs on a state machine that cycles through shapes: Tensor → Graph → DiskGalaxy → Wormhole → TorusKnot → MobiusRibbon → DoubleHelix → Hypercube. Each shape gets 2 seconds to shine, then 3 seconds to morph into the next one.
const animationState = new AnimationStateMachine(
['Tensor', 'Graph', 'DiskGalaxy', ...],
startIndex: 0,
pauseMs: 2000, // Look at me, I'm beautiful
morphMs: 3000 // Now watch me transform
);
Each particle needs to know where it’s going. The trick? Don’t map particle 1 to position 1 every time. Shuffle the mappings so particles actually travel during morphs instead of just sliding to their neighbors:
// Generate random mapping for interesting transitions
const mapping = Array.from({ length: 512 }, (_, i) => i);
for (let i = mapping.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[mapping[i], mapping[j]] = [mapping[j], mapping[i]];
}
Making Math Look Good
Each shape has its mathematical equation displayed below. The tensor shows p = T[i,j,k]
, the galaxy shows z = r·e^(iθ)
. I used KaTeX because MathJax made my browser cry with 60fps animations running.
The equations fade during transitions. At the 50% morph point, we swap from the old equation to the new one. Smooth operator.
if (morphProgress < 0.5) {
displayEquation = currentState;
opacity = 0.9 * (1 - morphProgress * 2);
} else {
displayEquation = nextState;
opacity = 0.9 * ((morphProgress - 0.5) * 2);
}
The Brownian Jitter
Static particles look dead. Add some Brownian motion and suddenly they’re alive. Each particle jiggles based on its size - smaller particles move more, just like in real physics:
const jitter = baseAmplitude * (1 / Math.sqrt(particleSize));
offset.x += (Math.random() - 0.5) * jitter;
offset.y += (Math.random() - 0.5) * jitter;
This tiny detail makes everything feel organic. The tensor breathes. The galaxy pulses. The wormhole… worms?
Performance Tricks
Drawing 512 particles plus hundreds of connecting lines 60 times per second isn’t free. Some lessons learned the hard way:
Batch everything. Instead of 512 individual circle draws, batch them:
ctx.beginPath();
for (let i = 0; i < 512; i++) {
ctx.moveTo(x + radius, y);
ctx.arc(x, y, radius, 0, Math.PI * 2);
}
ctx.fill(); // One fill call for all particles
Viewport culling. Don’t draw what you can’t see:
if (x < -margin || x > width + margin) continue;
if (y < -margin || y > height + margin) continue;
Cache colors. Theme changes are rare. Color calculations are expensive. Cache them and check every 60 frames.
The Fun Parts
The TorusKnot was satisfying to get right. It’s not just a donut - it’s a (p,q)-knot where the path winds p times around the major radius and q times around the minor radius. The (2,3) trefoil knot looks like it’s tying itself.
The MobiusRibbon took three attempts. First try: flat strip. Second try: twisted but wrong. Third try: finally got the half-twist that makes it a true Möbius surface. You can trace a path along it and end up on the “other” side without crossing an edge.
The Hypercube projection still breaks my brain. We’re taking a 4D cube and projecting it into 3D (then onto your 2D screen). It’s cubes within cubes, all connected in ways that shouldn’t make sense but somehow do.
What I Learned
Canvas performance is all about batching. Every beginPath()
costs you. Every state change hurts. Batch, batch, batch.
Mathematical beauty translates well to code. These shapes aren’t random - they’re based on real mathematical forms. The galaxy follows a logarithmic spiral. The wormhole uses an embedding function. There’s elegance in implementing math correctly.
React and Canvas can be friends. The React component manages state and lifecycle, Canvas does the heavy lifting. Let each tool do what it’s good at.
Try It Yourself
The whole animation is available as a standalone demo. One HTML file with everything inline. No build process, no dependencies to install. Just math, particles, and a dream.
Load it up, watch the shapes morph, and remember: sometimes your landing page doesn’t need to make complete sense. Sometimes it just needs 512 particles doing their thing.
If you enjoy visualizing abstract concepts, see Emergence for the ideas behind complexity from simple rules.