PointerSmoke

Smoke off the cursor, spreading and thinning as it drifts.

move the cursor across it

Paste into Claude Code, Cursor or any agent. Pinned to a tag, so it will not change under you.

Options

paper #fbfaf4

css colour

The paper the smoke drifts over.

smoke #b4afa4

css colour

The body of it, once it has spread. Keep it close to the paper: smoke is the absence of a clear view, not a dark shape, and a strong colour here reads as paint.

accent #bfae9e

css colour

What warmth the freshest smoke carries, at the head of the trail. Only a little of the trail is ever this, so a saturated colour shows up as a bead following the cursor.

marks 28

2 to 28

How many puffs the trail holds. The shader has room for 28; asking for more silently gives you 28, because the array is a fixed size and growing it means editing the shader as well.

size 0.026

0.005 to 0.2

Radius of a fresh puff, as a share of the short side, so it keeps its proportion when the element changes shape.

spread 2.4

0 to 4

How much wider a puff gets by the end of its life. Nothing holds smoke together, so this is the main thing that makes it read as smoke rather than as a cursor with a tail. At 0 you get a row of identical discs.

fade 2.4

0.2 to 6

How quickly a puff gives up. Higher is a shorter trail with a cleaner end; lower leaves a tail that hangs about.

edge 0.95

0 to 1

How much of a puff is its soft shoulder rather than its body. Smoke has no edge at all, so this wants to be near 1; at 0 you get plates of grey.

life 0.45

0.1 to 3

Seconds a puff lasts, which is the length of the trail. This is measured in time rather than in frames, so the same gesture leaves the same trail on a 60Hz display and a 144Hz one. Past about a second the smoke starts to read as something heavy being dragged behind the cursor.

grain 0.4

0 to 1

Paper tooth under the smoke.

What it needs

Category
Pointer, an effect
Dependencies
None at all
Browser
WEBGL2
Frame budget
~1.2 ms · 1 context
Pinned to
v0.12.0
  • An older puff is wider and fainter than a new one, because nothing holds smoke together. That is the opposite of a particle trail, where older means smaller
  • The puffs multiply rather than compositing, so two that overlap are denser than either
  • Every puff carries its own age in seconds rather than taking it from its place in the list. The list version worked out to one puff per frame, which left a third of a second of smoke on a 60Hz display and a sixth on a 120Hz one
  • A trail needs history, which is not a function of t. When pointerPath is set the whole track is known in advance and the trail is read backwards off it instead of accumulated, so a recorded take is identical however the frames are asked for. A live pointer keeps its own stamps
  • One WebGL2 context, one full-screen triangle, no buffers and no attributes
  • One distance and one smoothstep per puff per pixel, and the loop stops at the number of puffs actually held
  • The puff positions and ages are two array uniforms, uploaded once per frame
  • Cost scales with the puff count rather than with how far the cursor has moved