Anatomy
A field, a fluid, a character
Nothing here is a particle system, and nothing is stamped. Every frame evaluates a scalar field, advances a fluid simulation that the pointer has pushed forces into, and turns the sum in each cell into a character. The grid never moves — only what is printed in it changes, which is why the whole thing reads as type rather than as graphics.
The canvas is fixed to the viewport and the page floats above it on a perforated veil, so the field never stops below the hero — it keeps running under the text, and the small openings punched through the veil are live windows onto it. The pointer is tracked page-wide, so the field answers wherever you move, including here.
Pass 1 — Field
A continuous value
Two sine waves warp a third. Each cell asks the field for a number between −1 and 1 at its own coordinates and the current time.
Pass 2 — Fluid
Velocity carries density
The pointer injects velocity over a small radius. Advection carries the density along it, a pressure solve keeps the flow divergence free, and dissipation drains both.
Pass 3 — Glyph
Quantised to a character
The field is sampled through the flow — the velocity drags the sampling point — then the density is added and the sum is mapped onto the glyph ramp.
Parameters
What each dial actually changes
Every dial remembers what it shipped with: a control that is off its default turns blue in the head, and a double-click on it — or Esc while it has focus — puts it back. The deck writes straight into the render state — no rebuild, no re-layout except when the cell size changes, which resizes the fluid grid with it. The fixed constants live in the source: velocity dissipation 1.25/s, diffusion 0.16, six Jacobi iterations, force scale 0.5.
| Control | Variable | Range | Effect |
|---|---|---|---|
| Cell size | fontSize | 8 – 26 px | Sets the type size; the column width is then measured from the font, so the grid stays exactly monospaced. |
| Force radius | radius | 12 – 90 px | How wide a patch of velocity each splat writes. Small is the point: the spread you see is the fluid's, not the brush's. Falloff is smoothstepped to zero, so the injection has no rim. |
| Wake life | life | 0.2 – 5 s | Dissipation time for the density, applied as 1 / (1 + dt / life) so it is the same wake at 30fps and at 120. |
| Drift | flow | 0 – 1.5 | Time multiplier for the field. Zero freezes the background and leaves only the pointer alive. |
| Response | mode | 3 modes | Wake pushes along the stroke; Repel pushes radially outward from it; Reveal darkens the field so only what the flow carries prints. |
| Glyph set | ramp | 4 sets | The ordered string of characters. Index 0 is a space, so low values leave the cell empty. |
Source
Forces in, glyphs out
The pointer never touches a cell directly. It writes velocity into a grid; advection, a pressure solve and dissipation do the rest, and the glyph pass reads the picture back through that flow. Rows are printed one string per glyph-and-colour bucket rather than one fillText per cell — dozens of calls a frame instead of thousands.