— /playground
Small interactive things.
A rotating set of browser experiments — the kind of thing that doesn't fit in a client project but keeps the muscles loose. Everything here runs on plain canvas or CSS; no 3D libraries, no dependencies beyond what the site already ships.
Flow field
~220 particles · canvas 2d
Each particle reads a 2D vector field built from layered sine waves, moves one step along it, and draws a short segment behind itself. The background fades at low opacity every frame — no trail buffer, no extra memory, just the canvas forgetting slowly. Move the cursor to push particles away.
paused · press play
How it works
- Vector field — angle at each point is
sin(x·k + t) + cos(y·k + t)·0.8 + sin((x+y)·k + t)·0.6, multiplied by π. Cheap to evaluate per-particle and flows smoothly across the canvas. - Trails — every frame fills the canvas with
rgba(10,10,11,0.06)before drawing the new segments. Old strokes dim gradually instead of being cleared. - Cursor — within a 120 px radius, each particle is pushed away with a linear falloff. No pointer listeners means no redraw cost on mobile.
- Pause as ref — the rAF loop reads
playingRef.currentand skips physics when paused, so toggling Play/Pause doesn't reinitialise the particles.
Evade
homing enemies · last as long as you can
Your cursor is the white dot. Red enemies spawn from the edges and steer toward you with a homing acceleration. The spawn interval shrinks and the speed cap grows with every wave. Click Play to start — it doesn't run until you ask.
— Evade
Stay alive as long as you can.
Your cursor is the white dot. Red enemies spawn from the edges and chase you. They get faster. Don't let them touch.
Needs a mouse or trackpad.
Design notes
- Homing, not tracking — each enemy accumulates a small velocity vector toward the player (capped by a max speed). That's why fast swerves throw them off before they correct, instead of the enemy teleporting to follow.
- Difficulty as two knobs — spawn interval decays by 1.5% per spawn (floor 14 frames); enemy max speed grows by 0.02 px/frame per spawn (ceiling 2.8). No levels, no timers — the curve does the work.
- Score in the DOM, not React — the timer text is written directly via
ref.current.textContentinside the rAF loop, so the game doesn't re-render 60 times a second just to update a number. - Cursor-off pause — when the pointer leaves the canvas, enemies freeze and the timer stops. No cheap deaths from the cursor wandering into the sidebar.