Originally the hero fog of the Vector Terminal site, rebuilt line by line from the ASCII overlay on OpenAI's Codex page. Now it is a single, dependency-free module — one file, a few lines of markup, and any header gains a layer of breathing typographic fluid.
How the fog thinks
The fluid field Stable Fluids
Jos Stam's stable-fluids solver advances a velocity field and a density field on a low-resolution grid: diffuse → project (make divergence-free) → semi-Lagrangian advect → project, after which the density is advected and decays as exp(−k·dt). Moving the cursor injects its velocity as a force — the source of the wind and the inertia; reflective walls let the fog swirl and gather instead of blowing straight off-screen.
Luma → glyph The pipeline
Each cell's character comes from a single line. The luminance is multiplied first, then run through Codex's contrast → gamma → invert → quantize pipeline into the set ○ > _ space. The trick is the division of labour: density decides whether there is fog at all — multiplying by zero means blank, so glyphs are born and die with the fluid; the texture decides only which glyph appears (○ vs >).
The texture floor No holes
A pure multiply has a trap: dark blobs in the flowing texture fall to zero even inside dense fog — punching holes through the middle of a burst. A floor beneath the texture (0.42) keeps the dense core always above >, so it stays filled; blanks are reserved for the genuinely low-density edges — a natural feather.
Restraint & readability Hero-only
It lives on the first screen alone — anchored to the page as it scrolls, hidden once you move past it. The moving brush is small and capped, dissolving softly within a couple of seconds; a click sends an outward-expanding shockwave ring. And where the fog crosses a headline or caption, it quietly steps back so the words stay legible.
Drop it in
The whole effect is a single fluid-simulation.js — the Stam solver, the glyph rendering and the pointer interaction, with zero dependencies. Wiring it up takes three moves.
01 Markup — two elements at the very top of the document
<!-- canvas + first-screen container; #hero sits at the very top -->
<section id="hero">
<canvas id="fog"
data-fog-color="#2a2822" data-fog-alpha="0.62"
data-fog-clear=".hero-title, .hero-sub"></canvas>
<!-- …your headline / buttons… -->
</section>
data-fog-color / data-fog-alpha are optional — omit them for the default green-on-dark. data-fog-clear takes a CSS selector: the fog dims as it passes over those elements, so the text underneath stays readable.
02 Style — a handful of lines
#hero { position: relative; min-height: 100vh; overflow: hidden;
background: #fff; /* match the glyph colour to it */ }
#fog { position: absolute; top: 0; left: 0;
z-index: 0; pointer-events: none; }
/* keep hero content at position:relative; z-index:1 — above the fog */
03 Script — loaded after the elements
<script src="fluid-simulation.js"></script>
Tune it
Every knob is a constant at the top of fluid-simulation.js — colour and text-clearing read straight off the <canvas>. The ones you'll reach for first:
| Parameter | Now | Effect |
|---|---|---|
| TEX_SHAPE | 1.8 | ○-to-> ratio (lower → more ○) |
| TEX_FLOOR | 0.42 | floor that keeps the dense core hole-free |
| DEN_DISSIP | 1.2 | how long the fog lingers (lower → longer) |
| HOVER_RADIUS | 13 | brush size |
| SPLASH_RANGE | 170 | click shockwave reach |
| data-fog-color | attr | glyph colour — set on the <canvas> |
| data-fog-clear | attr | selector the fog fades over (readability) |