ASCII Fluid · Stable-Fluids

A fog that flows like liquid ink

A field of ASCII characters driven by a real Stable-Fluids simulation — born, blown and dissolved beneath your cursor. This header is running it right now.

Move · Click
Fluid ASCII A Stable-Fluids Field · Technical Note

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.

01
The mechanism

How the fog thinks


i

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.

ii

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 >).

iii

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.

iv

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.

luma = texture × min(density, 1) ○ > _  space
Fig. 1 — the render equation · density drives presence, texture drives shape
02
Reuse

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

Markupindex.html
<!-- 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

Stylestyle.css
#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

Scriptindex.html
<script src="fluid-simulation.js"></script>
That's all. The script sizes the canvas to #hero, follows the pointer, and anchors to the page as it scrolls — hiding once the first screen is gone. The header of this page is that same file, wired exactly this way.
03
Calibration

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:

Table 1 — the dials worth turning
ParameterNowEffect
TEX_SHAPE1.8○-to-> ratio (lower → more ○)
TEX_FLOOR0.42floor that keeps the dense core hole-free
DEN_DISSIP1.2how long the fog lingers (lower → longer)
HOVER_RADIUS13brush size
SPLASH_RANGE170click shockwave reach
data-fog-colorattrglyph colour — set on the <canvas>
data-fog-clearattrselector the fog fades over (readability)