Fluid
Move the pointer over it to stir the fluid; tune the field and the reader in the fluid workbench.
@glyphcss/fluid is a living braille fluid as a glyphcss surface. A shallow-water simulation runs on a cell grid; DOM text, images and meshes become targets the ink flows toward and condenses into, then melts back out of. Each frame is one textContent write to one <pre>, drawn as 8-dot braille with per-cell colour through glyphcss’s colour-font atlas. The simulation runs in a Web Worker.
A first fluid
Section titled “A first fluid”import { createGlyphFluid, GlyphFluidTargets } from "@glyphcss/fluid";
const host = document.querySelector<HTMLElement>("#fluid")!;const fluid = createGlyphFluid(host);await fluid.ready;
const h1 = document.querySelector<HTMLElement>("h1")!;h1.style.color = "transparent"; // the fluid draws it; the element keeps layout and accessibilityconst title = fluid.addTarget(GlyphFluidTargets.text(h1, { color: "#3ccf96" }));
title.melt(); // flows out; title.condense() flows back in// On unmount: fluid.destroy();Install with npm install @glyphcss/fluid glyphcss. There are no React/Vue bindings: create the fluid in your framework’s mount hook and destroy() it on unmount.
Layers
Section titled “Layers”Each layer works alone; only the last touches the DOM.
GlyphFluidSim → computeGlyphFluidCells → GlyphFluidEncoder → createGlyphFluid(the fluid sim, (braille mask + colour (glyphcss atlas text (targets, worker, bit-identical) per cell) + pooled palette) one <pre>)GlyphFluidSimsteps bit-identically to a reference implementation of the step.computeGlyphFluidCellsis the renderer’s per-cell decision as data: which of the 8 dots light and in what colour.GlyphFluidEncoderturns cells into atlas text against a pooled palette, so the palette CSS changes only when it repools.createGlyphFluidcomposes targets and zones, runs the pipeline in a worker and writes the frame.@glyphcss/fluid/readeradds a speed reader on top.
Handle reference
Section titled “Handle reference”| Task | Methods |
|---|---|
| Content | addTarget(spec) → { enabled, condense, melt, remove }; addZone({ el, kind }) → { remove } |
| Motion | impulse(clientX, clientY, { strength, radius }), push(clientX, clientY, dx, dy) |
| Clock | step(dt) with clock: "manual"; relayout() after layout changes the observers miss |
| Lifecycle | ready, stats(), destroy() |
stats() reports fps, pipeline ("worker" or "main"), encoding, cols, rows, frames, stalls, stepMs and lit cells.
Where next
Section titled “Where next”- Targets & zones: text, image and mesh targets and the zones that shape the field.
- Runtime & performance: worker, frame rate, reduced motion and measured costs.
- Reader: one word at a time out of the fluid.
- Troubleshooting.