The water on this site is a 2D canvas, not WebGL
2026-07-09 · Vorluno
The first instinct for an animated ASCII background is a WebGL pipeline: React Three Fiber, a shader, a post-processing pass. We prototyped in that direction and threw it away. The water you see on this site's hero is a plain 2D canvas — no Three.js, no shaders, about two hundred lines of TypeScript.
The simulation is two layers of trigonometry. A base field of overlapping sine waves gives the surface its constant, slow breathing; on top of that, pointer events spawn ripples that expand outward and decay exponentially with distance and time. Each cell of a coarse grid samples the combined field, and the sampled height picks a glyph from a density ramp — " .:-=+*#%@" — so tone comes from character density, not from color. The center of the canvas renders glyphs; toward the edges, cells degrade into displaced dots, which reads as the water dissolving into paper.
The budget is enforced, not hoped for: the loop is capped at 30fps (a water surface does not need 60), the device pixel ratio is clamped at 2, and the grid cell size — 14px on the hero, 9px on the small patches behind cards — keeps the per-frame work proportional to what the effect actually needs. On prefers-reduced-motion the simulation renders exactly one frame and stops: the texture stays, the motion goes.
Accessibility is structural, not an afterthought. The canvas is aria-hidden and sits behind real, server-rendered content — the H1 a crawler or screen reader sees is text in the HTML, never pixels. The water is decoration in the strictest sense: remove it and the page still says everything it needs to say.
The honest lesson: WebGL earns its complexity when you need depth, lighting or thousands of independent particles. For a field of characters on a grid, a 2D context is simpler to reason about, cheaper to ship, and — because the whole thing is one requestAnimationFrame loop you own — trivially easy to budget. The fanciest-looking thing on this site is also one of the most boring pieces of code in it. That's the point.