Build notes · Generative Assets
A live bait-ball of two thousand sardines, simulated in canvas 2D. Each fish runs three rules and flees your cursor; when the shoal banks, the light runs across its silver flanks as a single moving wave. This page is how it was built.
SARDINE RUN is a fictional Pelagic Cam — a drifting camera rig on the annual sardine run off South Africa's Wild Coast. Its one job: make you feel the shoal as a single animal that folds, splits, and flashes away from a predator. You are the predator. The brief was to distinguish it sharply from the collection's other ocean piece, lumen — that one is a dark vertical descent through an institute; this is bright open water, horizontal, and driven entirely by your hand.
Argued from the water itself: sunlit blue up top, cold dark below, and the one non-negotiable colour of the run — the silver flash of a banking flank.
Per the collection's contrast rule, body and label text use a dedicated pale ink (#DCE8EE / #9FBBCB) that clears 4.5:1 on the deep field; the bright #8FD0E0 is kept for rules and marks, not small type.
A grotesque with real muscle at heavy weights. Set enormous and tight for the wordmark, with "RUN" hollowed to an outline so the shoal can swim through it — the one moment of typographic bravery.
Quiet, humanist, and legible at small sizes over the animated water. Carries all reading copy, field-log entries and the live HUD readouts.
No libraries, no images — a single <canvas> 2D context. The shoal is a real boids simulation: every one of the 2,000 fish holds position and velocity in flat Float32Arrays, and each frame reads only the neighbours inside a small radius via a spatial hash grid (rebuilt each frame) so the cost stays roughly linear instead of the 4,000,000 comparisons a naive pass would need.
The signature — bait ball + flash-on-turn. Three classic rules give the body its mind: separation shoves fish apart below sixteen pixels, alignment steers each toward the mean heading of its neighbours, cohesion pulls it toward their centroid. A distance-proportional spring to a slowly wandering point contracts the whole flock into a defined ball that breathes rather than filling the frame. Your cursor is a predator: every fish inside a 150px radius accelerates directly away, force rising with the square of closeness, so the ball opens a hole and folds shut behind. The flash is the payoff — each fish measures the angle between its old and new heading; a hard turn spikes a per-fish flash value that then decays. Because neighbours turn together, the spike propagates outward as a wave. Fish are drawn as short round-capped line segments, sorted into seven brightness buckets from blue-grey to silver and stroked one beginPath per bucket, with the brightest two overdrawn additively for a shimmer. The flash wave you see folding around the cursor is emergent, not scripted.
Robustness: devicePixelRatio capped at 2; the rAF loop pauses on document.hidden and clamps its delta on resume; resize rebuilds the gradient and grid; a failed 2D context drops to a CSS gradient scene. Reduced-motion settles one frame and rim-lights the ball, then stops. On touch or after a few idle seconds, an autonomous predator resumes slow passes so the shoal is never still.
0.0) and a non-ASCII homoglyph in a variable name (coх) that risked a silent reference bug.scrollWidth stayed 375.