David Bastian — Design Director

Why it feels right

These studies aren't products — they're questions I'm asking myself through code and interaction. What makes a surface feel sticky instead of adhesive? Why does one motion read as rubber and another as paper? Each piece starts from a feeling — surprise, calm, the itch to reach out and touch — and works backwards to the algorithm that produces it.

The only way they work is if you feel them, not if I explain them. But once you've felt them, the explanation is the good part — so this page is the thinking made explicit: why each question was worth asking, the math and easing underneath, and the one decision that turns motion into emotion.

I work at Feels Like, designing and building the parts of an interface people actually touch — motion, materials, and the physics underneath them. Find me on LinkedIn, or by email.

Fish Skylight

Light as architecture, not decoration.

Why

Most WebGL water demos look down at the surface. I wanted to stand under it — a gray room where the only light source is a square skylight of water, the way a cenote or a James Turrell room treats light as the actual material of the space.

The fish are there to give the light something to touch. A school of koi drifting through the beam makes the volume readable: you see the shaft because something is inside it.

How

The beam is a real raymarched volume — sixteen jittered steps through an AABB, with transmittance accumulated as 1 − e^(−density·intensity) and density built from a square-frustum profile times domain-warped fbm noise curtains. My first version faked it with nested translucent shells and it looked exactly as cheap as it was: fragments only exist on a surface, so a radial fade evaluated there is always at the edge — always zero.

The school is a classic Reynolds boid flock — separation, alignment, cohesion — plus a seek force toward the cursor (raycast onto a vertical plane) and a soft pull into the shaft. Each fish's orientation slerps toward its velocity with an exponential rate, 1 - Math.exp(-8 * dt), so turning speed is framerate-independent. The floor caustics are an additive iterated-warp shader, and every fish feeds its position into a uniform array to cast a colour-tinted shadow that swims along underneath it.

The caustic fix — even amplitude everywhere

// the classic formula divides by p itself, so brightness
// decays away from the origin — a hot spot at the pool centre.
// a fixed-radius direction keeps the web, evens the light:
vec2 q = p / max(length(p), 1e-4) * 8.0;
c += 1.0 / length(vec2(q.x / (sin(i.x + tt) / inten),
                       q.y / (cos(i.y + tt) / inten)));

One detail

The reference photo showed perfectly even caustics, but mine were mysteriously hotter in the middle. The cause was the caustic formula itself: the classic Shadertoy version divides by the sample position, so its amplitude decays away from the origin — which sat at the pool centre. Normalising that term to a fixed-radius direction keeps the exact filament structure with uniform brightness. The bug was in the math everyone copies.

Try Fish Skylight

Sticky Jelly

A character with zero rig — personality is physics.

Why

Sticky-hand toys have a very specific behaviour: they don't just stick and release, they peel. I wanted a creature whose whole personality comes from simulation — no skeleton, no animation clips, nothing keyframed. If the physics is right, character is free.

The googly eyes are doing more work than anything else. Eyes that track the cursor with springy inertia turn a black blob into someone — and the mood changes (teeth, a cyclops, sleepy lids, a surprised O) cost almost nothing because they're just different arrangements of the same eye and mouth pool.

How

The body is a CPU Verlet integration — position and previous-position per point, so momentum survives constraint projection for free. The blob is a pressurised ring (distance constraints around the perimeter, an internal pressure force pushing outward); the worm is a thick rope of the same constraints. The outline is re-triangulated into a fresh BufferGeometry every frame and rendered with an orthographic camera where one world unit equals one pixel — the sim needs no coordinate conversion at all.

Sticking is per-point: each outline point carries its own anchor, its own break threshold, and a re-stick cooldown after it lets go. The eyes follow the cursor through a spring-damper, which is where the googly wobble comes from. Web Audio reacts to the physics — splats on stick, pops on peel, a rubber creak whose gain tracks how far the body is stretched.

Per-point peeling (simplified)

// every outline point decides alone — that's the peel
if (p.stuck && dist(p, anchor) > breakDist) {
  p.stuck = false;
  p.cd = RESTICK_COOLDOWN;  // frames before it may grab again
  pop();                    // Web Audio: one peel sound per point
}

One detail

One break threshold for the whole body felt like tape — it either held or it didn't. Peeling point by point, with a cooldown so freed points don't instantly re-grab, is the entire difference between "sticky" and "adhesive". The toy is in the sequence, not the force.

Try Sticky Jelly

Daylight

A liquid glass switch that drags the sun.

Why

A toggle is the most solved control in UI, which makes it the perfect place to push a material instead of a mechanic. This started as me playing with how far the liquid-glass look could go in WebGL: not a blur filter pretending to be glass, but a knob that genuinely refracts what's behind it — and glass is only convincing when there's something real back there to bend. So the switch drags the sun, and the world moves with it through golden hour, blue hour, and into stars.

The drag is where it has to come alive, so the knob deforms as you pull it: stretch is driven by velocity through its own spring, and it grows a real capsule mid-section along the travel axis rather than scaling into an ellipsoid — so it reads as a pill being pulled, not a ball squashed. Overdrag past either end pushes the glass with the same gum-like give as the track. The material keeps moving even when you stop: the refraction is perturbed by a slowly drifting noise field, so the surface is never a clean, dead lens.

How

Both the track and the knob are real glass, not a frosted overlay: the frame is rendered in layers into half-float render targets — sky alone, then sky plus the glass track — so each piece samples a texture of what is genuinely behind it. From there the fragment shader bends that sample. Refraction offsets along the surface normal at the rim and pulls radially inward across the interior, because on a flat front face the normal points straight at the camera and normal-based effects vanish — the radial term is what gives the body its slab-lens magnification instead of only an edge trick.

The liquid comes from domain warping. A 3-octave fbm field, drifting on two slightly different time rates, is sampled either side of each fragment to get a gradient, and that gradient perturbs the refraction — so the surface swirls like an uneven pour rather than sitting there as a clean lens. A second, higher-frequency noise threshold scatters droplets, each lensing through its own local gradient with a lit glint, a cool shaded side and a dark meniscus ring, so they read as beads rather than dots. Chromatic aberration then samples the scene three times, once per channel, split along a direction that blends the rim normal with the radial vector, so the fringe carries across the whole body and not just the silhouette. Thickness is faked by Beer–Lambert-style absorption — colour deepens toward the rim where the eye path through the material is longest — over a 13-tap disc blur, plus a Fresnel rim, an edge highlight and a tight Blinn–Phong specular.

The deformation is vertex-side. Overdrag displaces vertices with a gaussian weight around the press point, and the same weight drives a volume-conserving pinch: as the glass stretches along the drag axis it thins in the other two, the way a real blob would. Surface detail is sampled in undeformed object space, so the noise and droplets stay welded to the material while it moves instead of swimming across it.

What makes it read as glass

// Liquid: an fbm gradient perturbs the refraction, drifting with
// uTime — so the surface swirls instead of sitting there as a lens
vec2 np = vPos.xy * uWobbleScale + vec2(uTime * 0.15, uTime * 0.11);
vec2 grad = vec2(fbm(np + vec2(E, 0.0)) - fbm(np - vec2(E, 0.0)),
                 fbm(np + vec2(0.0, E)) - fbm(np - vec2(0.0, E)));
refr += grad * uWobble * 0.12;

// Chromatic aberration: sample the scene behind three times, one per
// channel, split along the rim normal. The fringe is the whole trick —
// a single sample reads as blur, three read as glass.
float ca = uAberration * (0.005 + 0.02 * bend + 0.012 * interior);
vec2 caDir = normalize(N.xy + radial * 1.5);
col.r = sampleBlur(uv + refr + caDir * ca, b).r;
col.g = sampleBlur(uv + refr,              b).g;
col.b = sampleBlur(uv + refr - caDir * ca, b).b;

One detail

ResizeObserver delivers its first measurement asynchronously, which means one frame can render at the wrong canvas size before layout is known. One frame is enough to read as a flash. Sizing the renderer synchronously before the first frame is invisible when it works — which is the point.

Try Daylight

Overflight

The interface is the residue, not the object.

Why

An exercise in restraint: a plane crossing a cobalt sky, banking with your mouse. The plane is almost incidental — what you actually interact with is the contrail it leaves behind. You end up drawing with the sky.

Screensavers taught a whole generation that ambient motion has value — something worth leaving on screen that asks nothing of you. Most interactive work demands attention; this one is deliberately happy to be ignored, and rewards you quietly when you don't.

How

Canvas 2D throughout. The plane silhouettes are hand-built bézier path drawers, nose along −Y, so one banking transform serves any aircraft — jet, prop, or paper plane, swapped from the control panel. Mouse position eases the bank angle rather than snapping it, because a plane can't change its mind instantly, and the ease is what makes steering feel like influence rather than control.

The trail is a chain of timestamped points; each point's stroke width and alpha are pure functions of its age, so the line widens and thins out the way condensation spreads before it dissolves. Nothing is simulated here — the spreading is just math on age — which keeps thousands of trail points cheap.

One detail

The trail lives for 22 seconds — deliberately long. It means your last few passes coexist on screen, so the sky accumulates a drawing of where you've been instead of resetting to zero every pass.

Try Overflight

Paper Accordion

Expansion that folds instead of resizes.

Why

Click-to-expand panels almost always animate height, and height animation reads as a spreadsheet row growing. Paper doesn't grow — it unfolds. I wanted the material behaviour, not the layout behaviour.

The panels are also a small colour lesson — cinnabar, gamboge, verdigris, lazurite, magnetite — each strip opening to reveal its pigment's name and history. The interaction is the packaging; the content is why you'd open more than one.

How

Each panel carries one spring-damper — stiffness 100, damping 26, which is just past critical damping, so folds land firmly with no bounce — on a single fold-progress scalar from 0 to 1. Everything else derives from that number: the fold geometry, the perspective foreshortening on the creases (a 0.28 perspective factor on the projected quads), and the shadow each panel casts on the stack beneath.

It's all Canvas 2D — no 3D engine, just quads projected with a fake perspective term. The fold shading is computed from the same progress scalar as the geometry, so a half-open panel is exactly half-shaded, and the stacked panels below shift down as the spring settles, giving the whole accordion a coupled, physical rhythm.

One spring, everything derived

// k=100, c=26 — just past critical damping: lands, no bounce
const acc = (target - s.t) * cfg.spring - s.v * cfg.damping;
s.v += acc * dt;
s.t += s.v * dt;
// fold geometry, crease shading and stack shadow all read s.t

One detail

Springing the fold progress instead of the pixel height means the shading and the shape can never desynchronise — they're the same number. When an animation has two sources of truth, it eventually shows a seam.

Try Paper Accordion

Springy Bar

A progress bar you can pluck.

Why

Progress bars are the deadest pixels in any player UI. Treating one as a guitar string — drag to bend it, release to let it ring — turns a readout into something you fidget with while the video plays.

There's a deeper argument underneath: chrome doesn't have to be inert. The parts of an interface that only display state are exactly the parts with room for play, because nothing breaks if they're also fun.

How

The bar is a damped harmonic oscillator: dragging displaces the curve toward your pointer, and release lets it ring as a decaying sinusoid — amplitude falling exponentially, the way a real string loses energy. The spring constants are exposed in the control panel, so you can tune it from bass string to rubber band.

The bend is drawn as a smooth curve through the displacement each frame, and the playhead keeps scrubbing normally through all of it — seek position and string physics are independent systems that happen to share a line.

One detail

The pluck is layered on top of a control that still works — bending the bar never breaks seeking. Playfulness that costs function is just breakage with charm.

Try Springy Bar

Paper Crumple

What if you could crumple a file before binning it?

Why

Crumpling is one of the first things our hands ever learn. The drawing that didn't work, the homework we gave up on, the receipt in a pocket — we've balled paper up thousands of times since we were children, and it's a whole-hand action with a sound, a resistance, and a result you can't undo. Then the desktop borrowed the folder, the desk and the wastebasket from that world, and left the gesture behind: deleting something now feels like nothing at all. So — what if you could crumple a file before dropping it in the bin?

It works on a screen because crumpling is the one thing digital surfaces never do. They scale and they fade, but they never suffer. It's also a study in communicating pressure: a press-and-hold needs visible escalation to read as holding rather than clicking slowly, and material damage is the most legible escalation there is — you know exactly how committed you are by how wrecked the thing looks.

How

Each poster is triangulated with Bowyer–Watson Delaunay so the crease lines are irregular — a regular grid folds like graph paper, and graph paper never reads as paper. The mesh deforms from the centre under two independent systems: a fold field that creases along the triangulation edges, and a ball pass that pulls vertices into a wad.

The press is raycast onto the poster so the crumple grows from where you actually grabbed it, and release runs the springs in reverse with exponential recovery — the poster relaxes, but keeps a memory of its worst creases. Every card carries its own state, so half-crumpled posters coexist in the grid.

One detail

Creases use a lock-in curve: fold amount stays near zero until a threshold, then transitions rapidly to one. Paper doesn't bend gradually — it resists, then gives. That nonlinearity is what makes it read as paper instead of cloth.

Try Paper Crumple

Luminous Text

Reading by flashlight, highlighting by touch.

Why

A page of dim text and a blob of light that follows your cursor: you read by moving. The interesting part is what you keep — click and the passage stays lit, so the page slowly becomes a record of what mattered to you.

It's an argument about attention. Search boxes and tables of contents assume you know what you're looking for; a flashlight assumes you don't, and makes the looking itself pleasant.

How

The light is a soft blob whose control points are spring-damped toward the cursor, so it stretches into a teardrop when you move fast and relaxes round when you stop — the liquid feel is entirely spring lag, not a shape animation. Move slowly and it pools; flick and it streaks.

Text brightness is masked by the blob's field in real time, with a soft falloff so words at the edge of the light are half-legible — which is what pulls your eye onward. Clicked passages get a permanent mark that survives after the light moves on, drawn in a different warmth so kept text reads differently from lit text.

One detail

The split between ephemeral light and permanent marks is the whole design: the hover state costs nothing and the click means something. Interfaces get this backwards all the time.

Try Luminous Text

Dither Effect

Ten algorithms you can scrub on live video.

Why

Dithering is usually explained with static image crops. Running Floyd–Steinberg against a Bayer matrix on your own webcam, live, shows each algorithm's character — error diffusion crawls and shimmers as the noise re-decides every frame, ordered matrices sit still like fabric.

The 1-bit aesthetic is having a revival, and most tools apply it as a dead filter. Making every parameter scrubbing-speed adjustable — algorithm, pixel scale, palette, quantization depth — turns a look into a space you can explore.

How

Real-time dithering over webcam, image, and video sources. The error-diffusion family (Floyd–Steinberg and its relatives) is defined entirely as data — [dx, dy, numerator, denominator] kernel tables describing where each pixel's quantization error flows — and the ordered family as Bayer threshold matrices at several sizes.

Everything runs per-frame on the CPU against a downscaled buffer, which is what the pixel-size slider really controls: the working resolution. Palette tinting maps the two output levels to arbitrary colours, so the same algorithms produce Game Boy green, paper white, or amber terminal from one switch.

One detail

The kernels live as data tables, not code — adding an algorithm is adding rows. When the variants are data, comparison becomes the feature.

Try Dither Effect

Face Warp

Your face, pressed against the glass.

Why

The question underneath: how far can you deform a face before it stops being you? Not funhouse-mirror distortion — plausible change, like catching a glimpse of yourself a few kilos heavier. The warp had to add weight without surrendering the proportions that make the face yours, staying natural enough to be uncanny rather than cartoonish.

Making the detected face the entire canvas gives the study its scale. Interfaces keep faces small and polite; blowing yours up to fill every pixel makes the change readable at a glance — and uncomfortable in exactly the way that makes people call someone else over to try it.

How

MediaPipe finds the face-plus-hair bounding box, and that box becomes the render plane: a fragment shader maps every canvas pixel back into face space by linear UV, so the face isn't drawn onto the canvas — the canvas is reinterpreted as the face.

The deformation is a power-function radial warp — magnification strongest at the centre, falling off to zero at the edges — and that falloff is the realism control. The centre fills out (cheeks, jaw) while the silhouette and hairline hold their line, so the proportions stay yours and the change reads as weight, not warping. The strength animates smoothly rather than switching, so the face swells and relaxes like something alive.

One detail

Mirroring is where this piece lives or dies. We've rehearsed our own reflection every day of our lives, so when a camera feed isn't flipped — when tilting your head left moves the face the wrong way — everyone feels the wrongness before they can name it. In the shader, that lifetime of instinct comes down to a single negated coordinate. Some of the smallest code in a project guards the deepest expectations.

Try Face Warp

Quad Gallery

A gallery you fall into.

Why

A grid of thumbnails is finite. A recursively subdividing grid isn't — scroll to zoom and every square splits into four more, each holding a live thumbnail, forever.

It's the browsing model flipped: instead of paging sideways through a collection, you descend into it. Depth as navigation gives the archive a sense of endlessness that a paginated grid can never fake.

How

Scroll input feeds a zoom level that's lerp-smoothed each frame rather than applied raw, so the dive has glide instead of steps. Cells subdivide into four children when they cross a screen-size threshold, and merge back when you retreat — the quadtree only ever materialises what's visible.

The critical move is re-normalisation: every time the zoom crosses a subdivision boundary, coordinates and zoom fold back into range, so no number ever grows unbounded. You're always rendering roughly one screen's worth of squares at what the math considers zoom level one-ish, whatever depth you've reached.

One detail

Infinite zoom is really periodic zoom with re-normalisation. The trick to endlessness is making sure nothing accumulates.

Try Quad Gallery

Hex Grid

A honeycomb with no edges.

Why

Square grids read as spreadsheets; hexagons read as a surface. An infinite pannable honeycomb of images, where each cell pops toward you on hover, feels like running a hand over tiles.

Hexagons also solve a real layout problem: every cell has six equidistant neighbours, so the eye travels the field without the row-by-row scanning rhythm a square grid imposes. The geometry itself changes how people browse.

How

The grid is generated in axial hex coordinates around the current pan offset — only visible cells exist, computed fresh from the offset each frame, and dragging pans the coordinate space rather than moving thousands of elements.

Hover pops ease each cell's scale up and back down with a soft envelope rather than a binary state, and the pop renders above its neighbours so the lifted tile reads as physically raised off the honeycomb. Images are assigned by hashing cell coordinates, so the same cell always shows the same image no matter how far you wander and return.

One detail

Panning the coordinate system instead of the cells means the working set stays constant no matter how far you scroll. Infinity is a windowing problem.

Try Hex Grid

Drag Sort

Reordering with weight.

Why

List reordering is usually a teleport: rows swap instantly and your eye loses the item. Giving each row inertia and gravity — it lifts, it settles, the others shuffle out of the way — keeps the object continuous in your mind.

Object permanence is the actual feature. When a row physically travels to its new slot, you never have to re-find it after the drop — your eye rode along the whole way.

How

Each row runs its own spring-damper — explicit stiffness and damping constants integrated per frame, not CSS transitions — toward its current slot, so displaced rows overshoot slightly and settle. Underdamped on purpose: the single small bounce is what gives the rows apparent mass.

The dragged item follows the pointer directly while everything else springs around it, and Web Audio marks the gesture with synthesised oscillator chirps — gain envelopes ramp linearly up and decay exponentially, pitch gliding upward on lift and landing lower on drop.

A chirp is two ramps

// linear attack, exponential decay — the shape of a small
// physical event; pitch glides up on lift, lands lower on drop
g.gain.linearRampToValueAtTime(peak, t + 0.05);
g.gain.exponentialRampToValueAtTime(0.0001, t + dur);

One detail

The lift chirp rises and the drop lands lower — pitch encodes direction. Sound that maps to the physics reads as feel, not as notification.

Try Drag Sort

Soft Press

Tension needs a payoff.

Why

Two questions started this. What if you could press and hold anywhere in the area instead of hunting for a button? And what if you held it for too long? Buttons are tiny targets we've trained ourselves to aim at; making the whole surface pressable turns a control into a place. And in the physical world pressing anything displaces it — a membrane bulges, a canvas gives — while on screens we press all day and nothing pushes back.

So the surface answers: hold anywhere and it swells under your finger, strains, and if you keep going, bursts. That failure state is the point. A progress ring tells you how long you've held; tension tells you what holding is doing to the thing, and a membrane visibly approaching its limit communicates the threshold better than any percentage — because your body already knows what happens next.

How

The membrane is a ring of spring-connected points — per-edge stiffness and damping — inflated by an internal pressure term that rises while you hold. The press point is raycast so the bulge grows toward your cursor, and the ring's strain is visible because the springs genuinely stretch as pressure climbs.

Past a strain threshold the constraints release, and the burst plays out through the same spring system — no canned animation, just the sim continuing without its skin. Fragments inherit the velocity and tension they had at the moment of failure.

One detail

Because the fragments inherit the membrane's tension at the exact moment of failure, every burst is slightly different — which is why it stays satisfying. Canned explosions are satisfying exactly once.

Try Soft Press

Elastic Card

Rubber, with the picture attached.

Why

Cards in UI are rigid rectangles. Grab this one and it stretches like rubber, snaps back with a wobble, and the image on it stretches too — the picture is part of the material, not a sticker on top.

Elasticity is the most forgiving interaction language there is: nothing you do can break a rubber sheet, so people pull harder, and the card's refusal to tear becomes its personality.

How

The card's corners are spring-damper bodies with explicit stiffness and damping constants; dragging displaces them toward the pointer with distance-weighted influence, and release lets the system settle underdamped — the snap-back overshoots once before it lands, and that single overshoot is the rubber.

The image is warped through an affine mapping of the deformed corner positions, so the picture shears and stretches exactly with the surface. The mapping runs on the raw corner positions every frame — there's no separate image animation to fall out of sync with the geometry.

One detail

Warping the image affinely with the geometry is the difference between "a card that stretches" and "a stretchy card". If the content doesn't deform, the material illusion dies instantly.

Try Elastic Card

Blur Focus

Attention as depth of field.

Why

A page where every word is blurred except the ones near your cursor turns reading into focusing — you pull sentences into sharpness the way a lens racks focus, and everything else stays atmosphere.

It's a small essay on how little an interaction needs. There's no click, no scroll hijack, no layout change — just proximity and clarity — and it still creates the strongest possible pull to move your hand across every line.

How

Each word carries a blur radius targeted by a distance-falloff function from the cursor and lerped toward that target every frame — exponential easing, no keyframes — so focus blooms and dissolves rather than switching. The lerp constant is tuned slower on the way out than in, so sharpness lingers just after you leave a word.

The words themselves are individually measured spans so each has its own blur state, and the falloff radius covers a few words in every direction — enough to read a phrase, not a sentence, which is what keeps you moving.

Exponential ease — no keyframes anywhere

// each word chases its target radius a fixed fraction per
// frame: fast when far, slow when close — a lens racking focus
word.blur += (targetFor(distance) - word.blur) * 0.12;
el.style.filter = 'blur(' + word.blur + 'px)';

One detail

Nothing translates a single pixel in this piece. Constraining the entire interaction to one CSS property — filter: blur() — is what keeps it feeling optical instead of animated.

Try Blur Focus

Pill Flood

Abundance as an aesthetic.

Why

It started with a designer's indecision: staring at a button, unable to commit to a colour, a weight, a radius. So I asked the opposite question — what happens if an algorithm shows me every way a button could look, endlessly? An infinite scroll of pill-shaped tags in every colour, weight, and size: part inspiration tool, part toy, no choosing required.

Endless generic content is usually a dark pattern; endless decorative content is a generator. Scroll until a combination catches your eye and steal it — or don't, and just enjoy running your thumb along the bead curtain. Indecision turned into a machine beats indecision turned into a stalled mockup.

How

Pills are generated procedurally from word, colour, and font-weight pools as you scroll — every pill is a fresh draw of word × palette colour × weight × size, so combinations never visibly repeat even though every ingredient is curated.

Off-screen pills are recycled back into the pool rather than accumulated, so the DOM stays at a constant node budget however far you go. Infinite content, fixed memory — the flood is a window, not a list.

One detail

The variety is pool-based, not random-random: curated palettes and weights combined freely. Uncurated randomness converges on mud; curated pools converge on style.

Try Pill Flood

Face Doodle

A censor bar with a personality.

Why

Privacy blur is dull. A physics noodle that scribbles over your eyes and follows your face around — draggable, pinchable, always slightly too late — makes the censorship the entertainment.

The piece plays with obedience: the noodle is doing its job, but sloppily, and the gap between intent and execution is where the character lives. Perfectly tracked censorship would be creepy; laggy censorship is funny.

How

FaceMesh tracks the face and a hand model reads pinch gestures, so you can grab the noodle and drag it off your eyes — it fights its way back. The noodle is a chain of segments where each link is damped toward the one ahead of it: a follow-the-leader integrator whose damping constant sets exactly how far the tail lags the head.

The target switches between the eye line and the whole face, and the chain re-forms to cover either without any special-case animation — the same follower dynamics just chase a different set of anchors.

One detail

The legacy MediaPipe builds share one global WASM module that can only initialise once per page — try a second time and the whole thing dies. So FaceMesh and Hands are created exactly once and reused for the life of the app. A limitation in someone else's code became the cleanest part of the architecture: constraints don't just restrict a design, they often hand you one.

Try Face Doodle

Word Graph

A sentence as a constellation.

Why

Poetry is read one word at a time anyway — this makes that literal. Words appear across the canvas one by one, each tethered to the next by an animated line, so the sentence assembles as a graph you watch grow.

Scattering the words also breaks the tyranny of the line: when placement is spatial instead of sequential, the connecting stroke becomes the reading order, and following it feels like tracing a constellation rather than scanning text.

How

Each word is placed and revealed on a timer, faded in where it lands, and the connecting line draws itself between anchors with ease-in-out timing — slow to leave a word, slow to arrive at the next — so the stroke reads as travel rather than a wipe.

The pacing constants — how long a word holds before the line departs, how long the line takes to arrive — are the real design surface, tuned so you finish reading a word just as the stroke leaves it.

One detail

The pacing is the typography here: reveal timing does the work that font choice usually does. Slow enough to read, fast enough to anticipate.

Try Word Graph

Card Carousel

One animated number, everything derived.

Why

A vertical card stack with perspective depth — scroll and cards rise toward you, pass, and recede. The classic cover-flow idea, rebuilt to find out how little needs to be animated to sell the depth.

The answer turned out to be: one number. Everything the eye reads as 3D — approach, scale, layering — can be a pure function of a single scroll-driven value, and the piece is the proof.

How

Only each card's y position is animated; its scale is computed from y in the render — a pure function of position, so a card's size always states exactly where it is in the stack. Depth sorting falls out of the same value.

Scroll input eases the stack's master offset rather than jumping it, and because every card property derives from that one eased value, the entire carousel shares a single motion signature — nothing can lag or lead anything else.

One source of truth

// only y animates — scale is a pure function of it,
// so overshoot can never put a big card in the wrong place
const scale = 1 - Math.abs(y - centerY) / depthRange;
card.style.transform =
  'translateY(' + y + 'px) scale(' + scale + ')';

One detail

Deriving scale from y means overshoot can never desynchronise size from position — a card physically cannot be large in the wrong place. Correlated properties should share one source of truth.

Try Card Carousel

Rotating Gallery

An orbit you can feed anything.

Why

A carousel of squares orbiting in 3D, switchable between flat palette colours and photographs — half toy, half test rig for how motion reads differently on solid colour versus imagery.

The comparison is the point: identical motion feels calmer on flat colour and busier on photos, and having one switch to flip between them makes that observable instead of theoretical. I use it to sanity-check motion choices for real projects.

How

The squares orbit on a shared ring whose rotation is lerp-eased toward its target each frame, so scrubbing feels damped rather than geared — release mid-spin and the ring glides to rest instead of stopping dead.

Hover picking is a raycast into the 3D scene so the pointer interacts with the actual tilted quads, not their screen rectangles, and the content mode swaps between palette fills and images loaded at runtime.

One detail

Images load via fetch instead of image tags — a CORS-poisoned browser cache was serving dead responses, and fetching fresh bypasses it. The fix you need is rarely the fix you expected to write.

Try Rotating Gallery

Webcam Mosaic

A portrait made of its own eyes.

Why

Photo mosaics are usually built from someone else's image library. This one builds you out of you: the tiles are crops of your own eyes, sampled live, assembling your face from its own parts.

There's a feedback loop hiding in it — the picture is made of pieces of itself, one step removed — and that recursion is what makes people lean closer, realise what the tiles are, and laugh.

How

MediaPipe's face mesh locates the eyes each frame; crops around them are bucketed by mean luminance into a tile library that continuously refreshes while you move, blink, and change the lighting.

The camera image is then downsampled to the mosaic grid, and each cell is matched to the nearest-luminance bucket — a live nearest-neighbour mosaic where both the target image and the tile supply are the same feed, a frame apart.

One detail

The tile library updates live, so blinking changes the palette the mosaic is built from. The source and the target being the same feed is the whole piece.

Try Webcam Mosaic

Pixel Studio

A love letter to 1-bit MacPaint.

Why

Constraint-driven tools produce a specific joy: one bit per pixel, dither patterns instead of grays, fat pixels you place one at a time. I wanted the whole editor — animation frames, pattern fills, lasso selection, undo — not just the look.

Rebuilding a classic tool is also the best way to study it. You discover that MacPaint's feel wasn't the rendering — it was the immediacy: every tool responds on the first pixel of the first click, no mode dance.

How

A full sprite editor: bitmap layers drawn on canvas, classic 8×8 pattern fills, marching-ants lasso selection with float-and-drop, and frame-based animation with onion-skinning so you can see the previous frame ghosted under the one you're drawing.

Undo is a stack of full-state snapshots — at 1-bit depth, whole-canvas snapshots are cheap enough that command-pattern bookkeeping would be over-engineering. The constraint of the medium simplified the architecture.

One detail

Undo was built first, not last. A drawing tool without undo isn't a tool yet — it's a demo you're afraid to touch.

Try Pixel Studio

Floating Words

Vocabulary drills disguised as arcade games.

Why

Flashcards fail because repetition is boring. Wrapping the same word-recall loop in four different arcade skins — Bucket, Meteor, Snake, Fruit Ninja — keeps the drill identical while the dopamine changes costume.

The design bet is that novelty belongs in the shell, not the mechanic. Learners need the recall loop to stay stable so it becomes automatic; they need everything around it to keep changing so they come back.

How

Word bubbles share one physics core — velocity integration with friction, plus collision — and each game mode reskins the win condition on top of it: catch the right word, shoot it, eat it, slice it. The words and their translations are the only payload that changes.

All the game sounds are synthesised with Web Audio oscillators and gain envelopes rather than sampled files, so hits, misses, and combos can pitch-shift with streaks — feedback that scales with performance instead of repeating one wav.

One detail

Four games, one loop: the game modes are interchangeable shells around a single learning mechanic. The pedagogy is the engine; the game is the paint.

Try Floating Words

Corner Repel

Cards that flinch.

Why

Hover states usually happen to a whole element. Making just the corners flee the cursor gives a rectangle something like reflexes — the card keeps its place but visibly reacts, like it's shy about being touched.

Partial reaction is more alive than total reaction. A card that moves away entirely is a chase; a card that only deforms is an organism holding its ground — and the difference is entirely in which vertices respond.

How

Each corner is displaced along the away-from-cursor vector by a distance-falloff function, and springs back once the cursor leaves — so the flinch has recoil, not just an on/off state. The falloff means near corners duck hard while far corners barely notice.

The rounded-rect path is rebuilt through the displaced corners every frame, with a blurred image masked inside the deformed shape — the mask follows the geometry so the card reads as one soft object, not an outline over a picture.

One detail

The image inside stays put while the boundary deforms — the mask moves, the content doesn't. That's what makes it read as a flinch rather than a wobble.

Try Corner Repel

Alpha Checker

The transparency grid as a subject.

Why

Every designer has stared at Photoshop's gray checkerboard. Cutting a live person out of their background and showing the checker through the hole makes the software convention the star of the piece.

It's a one-liner, and it should be — the entire concept lands in the first second. The craft budget goes into making the joke technically flawless rather than adding a second idea that would dilute it.

How

A segmentation model masks the person in the webcam feed; a GLSL fragment shader composites the live silhouette over a checker generated in the shader itself — a step/mod pattern computed per fragment, not a texture — so the "deleted background" updates in real time at any resolution.

The mask edge gets a small amount of feathering in the shader, because a hard segmentation boundary flickers frame to frame and the flicker breaks the illusion that the background is genuinely gone.

One detail

Generating the checker in the shader keeps its edges crisp at every canvas size. If the joke is the checkerboard, the checkerboard has to be perfect.

Try Alpha Checker

Depth Blur

Portrait mode, opened up.

Why

Phone cameras blur backgrounds with depth data you never get to play with. This exposes the machinery: live segmentation-driven blur where you choose what's sharp — and how the falloff behaves.

Handing over the parameters is the point. Portrait mode is a single opinion about focus; a focal plane you can slide through your own scene is an understanding of it.

How

Multi-pass blur driven by depth segmentation, with two modes in the shader: mask-based — blur the background, or invert it and blur the person — and true depth-of-field, where blur radius grows with each pixel's distance from a focal plane you position.

The passes stack smaller blurs into large radii cheaply, which is the standard trick for real-time big blurs: several cheap passes approximate one expensive Gaussian at a fraction of the cost, and the pass count is exposed so you can watch quality trade against speed.

One detail

The two modes exist because they answer different questions — mask blur is a graphics effect, focal-plane blur is a camera. Keeping both made the difference legible.

Try Depth Blur

Pixel Repeat

Debug views as a feature.

Why

A tiling effect that repeats pixels along depth layers — the person stays coherent while the background shatters into pattern. Built as much to understand depth-layer masking as to look good.

Effects built on ML segmentation live or die by data you can't see. Making the invisible layer inspectable — literally a slider away — changed how fast I could tune everything else.

How

A GLSL shader tiles source pixels with modulo UV arithmetic, masked by segmentation depth layers so the repeat respects the person's silhouette — the subject samples from itself while the background samples from a repeating cell.

The GUI ships the debug views as a first-class mode switch: mode 0 is the effect, 1 is a depth heat-map, 2 is a red mask overlay. Tuning the tile size against the heat-map is how the final look was actually found.

One detail

The debug masks stayed in the shipped GUI on purpose. Showing your working is a feature in a study — the heat-map view teaches what the effect view hides.

Try Pixel Repeat

Glass Ripple

Sound you can see through.

Why

Audio-reactive visuals usually mean bars and blobs. Rippling a glass-distortion shader across a person's silhouette in time with live audio makes the sound feel like it's physically hitting the surface you're looking through.

Confining the effect to the silhouette matters: when the whole screen ripples it's a filter, but when only you ripple it's an event happening to a body — the segmentation turns a shader into a subject.

How

Person segmentation masks the refraction shader to the silhouette, and ripple amplitude follows the detected pitch and level of live audio input — sing a note and watch yourself distort in sympathy.

Pitch detection is built like a guitar tuner: a harmonic product spectrum over the FFT, summing energy at the fundamental and its harmonic bins in the log domain — far more stable on voices and instruments than picking the loudest bin, which octave-jumps constantly.

One detail

A silence gate skips detection below −50 dB. Without it, room noise keeps the glass trembling and the correspondence between sound and ripple — the entire point — dissolves.

Try Glass Ripple

Depth Dots

A person, resolved into particles by category.

Why

Segmentation models don't just find "a person" — they know hair from face from clothes. Rendering each category as its own layer of dots turns the model's understanding of you into the picture itself.

It's a portrait mode for the model rather than the subject: what you see is literally the classifier's opinion, dot by dot, and moving around in frame shows you where that opinion gets confident and where it frays.

How

Six segmentation categories — background, hair, body skin, face skin, clothes, accessories — each render as a dot-particle layer with an independent weight, so you can dial hair up and clothes away and watch the model's map of you shift in real time.

Dot size and density derive from the category confidence at each sample point, so uncertain boundary regions naturally thin out — the model's doubt renders as sparseness, which reads as a drawing decision even though it's diagnostic truth.

One detail

The per-category weight sliders go to 3×, not 1× — overdriving a single layer is how you discover what the model actually sees. Tools should let you exaggerate.

Try Depth Dots

Face Synth

An instrument you play by expression.

Why

We have dozens of little muscles that move just to show how we feel — as I put it in my OFFF 2026 keynote, a wink or a smile isn't just skin and bone moving; it's the face taking a hidden emotion from inside the mind and turning it into a physical bridge someone else can see and feel. This study asks: what if that bridge could also be heard? Your face as the performer, expression as the instrument.

The hard part isn't the tracking, it's the musicality: raw landmark coordinates make noise, not music. All the design lives in the mapping layer between those dozens of little muscles and the sound they conduct.

How

MediaPipe landmarks drive synthesis parameters, but pitch is quantised to musical scales — semitone-offset tables built across three octaves up from A1 at 55 Hz — and melodic movement follows patterns keyed to the active drum pattern, so the face steers within a groove rather than wandering chromatically.

Note envelopes attack linearly and decay exponentially, which is what keeps rapid expression changes from clicking, and the drum patterns run on a fixed scheduler the melody quantises against — the face plays in time because time is not the face's job.

One detail

Quantising to a scale means your face cannot play a wrong note. Free-pitch mapping sounds like a theremin accident; constrained mapping sounds like music. The constraint is the instrument.

Try Face Synth

Camera Cut

Editing grammar, applied to a mirror.

Why

A live camera feed that cuts, glitches, and jump-cuts like a music video — applying the grammar of editing to the one footage source that's never edited: the mirror you're looking into right now.

Editing is a language everyone reads and nobody notices. Pointing it at the viewer's own present tense makes the grammar suddenly visible — you feel the cut because you're on both sides of it.

How

The feed runs through sequenced cut and glitch transitions on editable timing — cut length and rhythm are parameters, so the piece can pace like a trailer or a fever dream from the same machinery.

Glitch displacement spikes on each cut and decays exponentially back to clean, so every edit lands as an impact with a tail rather than a toggle. The decay constant is the difference between punchy and broken.

One detail

Because the footage is live, every cut is a jump-cut of now — the discontinuity is felt, not observed. The same effect on recorded video would just look like an effect.

Try Camera Cut

Text Form

Words as a temporary arrangement of particles.

Why

Text on screens pretends to be solid. Letting characters dissolve into a particle field and re-form as new text treats words as what they are on a screen — a momentary agreement between pixels.

The transition is where the meaning is: between words there's a readable moment of pure potential where the particles belong to nothing, and the new word emerging from it feels earned rather than swapped.

How

Characters are sampled into per-particle targets in WebGL, and each particle is a spring-damper steered at its target — transitions simply re-assign targets and let the springs carry everyone across. There's no timeline; the motion is entirely emergent from the spring constants.

Damping is tuned so particles arrive with a slight overshoot and settle, which reads as condensation rather than a crossfade, and per-particle variation in the spring parameters means the crowd never moves in lockstep.

One detail

Particles travel individually, not as a group morph — stragglers arrive late and the word sharpens as they land. A word forming out of latecomers is what makes it feel physical.

Try Text Form

Hand Vision

Your loudest "Yes!", answered.

Why

The clap is our loudest "Yes!" — as I put it in my OFFF 2026 keynote: you feel the sting in your hands and hear the pop in the air. It's a physical exclamation point, the sound of our energy hitting the world. This study gives the screen a way to answer it: clap, and the air between your hands bursts.

Hand-tracking demos usually stop at proving the skeleton works. Pointing the tracking at a gesture this loaded turns a tech demo into call-and-response — the body's oldest applause, celebrated back with confetti.

How

MediaPipe's HandLandmarker tracks both hands with the skeleton drawn as landmark-pair bones — the tracking stays visible, so the causality is never in doubt. Clap detection is a distance check between the hands: close within a threshold and a burst of emoji particles erupts from the exact midpoint between them, the point where the clap physically happened.

The sound is synthesised in Web Audio as two layered noise bursts — a sharp attack and a softer body — which is what a real clap is, acoustically. No sample: the pop is manufactured the same way your hands manufacture it, from a broadband impulse shaped by an envelope.

One detail

A clap is an event, not a state — so a latch fires the burst once when the hands meet, and re-arms only after they separate well past the trigger distance. That gap is hysteresis, the same trick in every thermostat: without it, hands trembling at the threshold would machine-gun celebrations, and the piece would stop understanding what a clap is.

Try Hand Vision

Cursor Figure

A walking figure made from the tool you point with.

Why

The cursor is the one graphic every computer user has stared at for thousands of hours without seeing. Building a walking human figure out of three hundred of them makes the most familiar pixel on the screen suddenly visible again.

It's also a study in swarm-as-surface: no single cursor matters, but three hundred of them tracking a body's vertices produce an unmistakable person — presence emerging from population.

How

A rigged GLTF character (DRACO-compressed) walks via AnimationMixer; each frame, 300 cursor sprites are pinned to the SkinnedMesh's animated vertex positions — sampled in local space with getVertexPosition, then transformed to world by the mesh matrix after the mixer ticks and world matrices update.

A swarm of 55 stragglers orbits the figure on spring-damped steering, like flies that can't quite land — they seek moving points near the body and their damping keeps them permanently late. The cursor art itself is a macOS arrow drawn to a canvas texture: white border first, then drop shadow, then the black fill, in that order so the outline stays clean.

Pinning sprites to animated skin

// order matters: mixer first, then world matrices, then read
mixer.update(dt);
charScene.updateWorldMatrix(true, true);
mesh.getVertexPosition(idx, tmp);   // LOCAL space
tmp.applyMatrix4(mesh.matrixWorld); // → world
cursorSprite.position.copy(tmp);

One detail

Reading animated SkinnedMesh vertices means updating world matrices after the mixer update and doing the local-to-world transform yourself — get the order wrong and the figure walks while its cursors stay frozen in T-pose.

Try Cursor Figure

Viscous Sentence

Typography with surface tension.

Why

Text is usually the most rigid thing on a screen. I wanted sentences that behave like a viscous material — disturb them with the cursor and they ripple, hold together, and slowly find their way home while staying readable the whole time.

Legibility under motion is the actual constraint. Anyone can make letters fly; the design problem is letting them move as much as possible while the sentence never stops being a sentence.

How

Every letter is a physics body integrated with damping and friction, connected to its neighbours by springs, plus a much weaker "spine" spring pulling it toward its rest position in the line. The mouse applies force inside a falloff radius with a tunable falloff exponent.

The damping constant — around 0.82 to 0.86 depending on preset — is what makes the whole thing syrupy instead of jittery, and the presets (threshold, global, single) are really different force-application policies over the same physics: whole rows, everything at once, or one letter at a time.

The viscosity is a ratio

// neighbours hold the sentence together as a body;
// the spine is ~80x weaker — recovery without rigidity
neighborK: 0.25,
spineK:    0.003,
damping:   0.825,

One detail

The spine spring is roughly two orders of magnitude weaker than the neighbour springs (0.003 vs 0.25). That ratio is the whole design: strong neighbours make sentences move as a body, and a faint spine means they always recover legibility — but lazily, like something thick.

Try Viscous Sentence

Spray Paint

Hand tracking that becomes a tool, not a demo.

Why

When we spray, we're extending our reach — as I said in my OFFF 2026 keynote, it's like a superpower: you aren't just touching a wall, you're throwing colour across it, taking a mess and turning it into your own mark. Also — it's a little bit illegal, which is why it's fun. This study puts that superpower in front of a webcam: pinch to press the nozzle, shake the can and the mixing ball rattles.

Most hand-tracking studies stop at the skeleton overlay — look, it sees my hand. The interesting part starts after that: putting something in the hand. And a tool needs consequences, so the paint behaves like paint — spray too long in one spot and it over-saturates and drips. The failure mode is what makes the skill mode exist.

How

A canvas sim you spray-paint with your hand through the camera: pinch to spray, shake the can for the rattle, hold up two fingers to wipe. MediaPipe's HandLandmarker runs in VIDEO mode on the GPU; pinch distance maps continuously to nozzle pressure — not a binary trigger — and hand depth drives how hard the paint lands. Paint is a density-based particle scatter inside the spray radius, with palette colours, and drips that spawn where it accumulates, each carrying its own descent speed and run length.

All the sound — hiss, ball rattle, wipe — is synthesised from filtered noise with Web Audio: the hiss is a noise source whose filter and gain open with pinch pressure, the rattle triggers off shake acceleration, and the wipe gets a slow 2 Hz amplitude wobble so it reads as a wet cloth rather than an eraser. No samples anywhere. MediaPipe, Web Audio and Canvas 2D, nothing else.

One detail

Synthesised audio matters because the hiss can open continuously with pinch pressure, exactly in step with the gesture. A sample would loop and gate — on, off. A synth tracks — more, less. Continuous input deserves continuous feedback.

Try Spray Paint

Code Trail

The cursor as a comet of source code.

Why

Cursor trails are the oldest trick on the web, which makes them worth revisiting: instead of sparkles, this one streams scrolling code characters in the cursor's wake — the machine leaking its own text as you move.

The pleasure is in the density gradient: move fast and the code stretches thin and fragmentary; pause and it pools into readable lines. Your gesture is typesetting.

How

Trail segments carry columns of characters that scroll and decay over their lifetime, with an fbm noise wobble keeping the stream from reading as a straight pipe — the drift is what makes it smoke instead of ticker tape.

High-frequency cursor state lives in refs so the render loop never touches React's render cycle — the trail runs at canvas speed while the component tree stays inert. Mixing React state into a per-frame path is how trails stutter.

One detail

The characters scroll within the trail while the trail itself fades — two independent motions layered on one gesture. A static-character trail read as confetti; the inner scroll makes it read as code.

Try Code Trail

Character Walk

A walk cycle with nowhere flat to walk.

Why

Walk cycles are usually canned clips on flat ground. Generating the walk procedurally and pushing dynamic terrain under it forces the character to actually negotiate the world — which is where walking gets interesting.

Watching a procedural gait recover from terrain it's never seen tells you whether the system understands walking or just replays it. Canned animation can't be surprised; this can.

How

The gait is generated rather than keyframed, adapting to terrain height as it changes underfoot, and steering comes from a raycast of the mouse onto the ground plane with lerp-smoothed heading changes — the character turns like something with momentum, not a joystick avatar.

The figure renders as skinned points through a custom shader — circular sprites tracking the skinned skeleton — so the study stays about motion, with no surface detail to hide behind.

One detail

Rendering the body as points was a deliberate subtraction — with no mesh to admire, the only thing left to evaluate is whether the walk is convincing. Removing surface is a way of testing motion.

Try Character Walk

Matching Characters

A sentence that walks.

Why

Kinetic typography usually means letters on motion paths. I wanted letters with anatomy: type any word or sentence and it walks past in a loop — one walking figure per character, each glyph riding the skeleton of its own body. The text adapts live; the sentence is the parade.

The design surface is the joint level. Which bones carry the glyph, at what scale, with what offset — control the joints and the typography inherits the gait, so the motion is earned from the rig instead of keyframed onto the letters. And the same skeleton can swap costumes: clean sprite glyphs, or the letter dissolved into a cloud of particles, without touching the walk.

How

Each letter of the input spawns a SkeletonUtils clone of one rigged character, marching in a spaced queue with a screen-space wrap so the parade loops forever. Walk and run clips play simultaneously on every mixer, cross-blended by weight and synced across all clones, so the whole sentence strides — or breaks into a run — in step.

The glyphs are rasterised to canvas textures and attached directly to skeleton bones, with controls for which bones carry letters (every Nth, up to a cap), their scale, and their offset from the joint — each character literally inherits the skeleton's swing. A particle mode samples the letter's bitmap into a point cloud instead, turning the word into a walking constellation, and toon shading with outlines keeps the figures graphic enough to read as type.

One detail

Typing drives the crowd: the clone count follows the text length, and letters are assigned back-to-front through the queue so the sentence reads left-to-right as it walks past. Change the word and the cast hires and fires itself — the text isn't applied to the animation, the text is the cast list.

Try Matching Characters

Antz Text

Letters with a population.

Why

Text assembled by an ant colony inverts typography: instead of shapes being drawn, they're inhabited. Thousands of agents discover the letterforms, and the word emerges as a census.

It borrows the oldest trick in swarm graphics — meaning from density — and points it at the most meaning-dense target there is: written language. The tension between chaotic agents and rigid glyphs is the whole show.

How

Three regimes, switchable live. First, steering behaviours in the Reynolds tradition — turn-rate-limited seek toward glyph targets plus a random wander term for life. Then true ant colony optimisation: ants deposit pheromone into a grid and follow its trails, with evaporation keeping paths honest — there's a pheromone visualiser in the panel so you can watch the trails form. A third route mode biases agents along shared corridors.

The letterforms are rasterised into a field whose gradient every agent reads: near a glyph edge, ants steer along the edge's tangent and lock to the outline, so they trace the letters rather than just pooling inside them. The staging is choreographed too — agents swarm in on pure steering, and only once the whole population has arrived does the piece hand over to the pheromone colony.

One detail

Legibility here is a density threshold — the word appears when enough ants agree. There's a moment mid-swarm where the text is almost readable, and that moment is the piece.

Try Antz Text

Organic Particles

Emergence, with film grain on top.

Why

Particle systems become organic when behaviour is local — no leader, no path, just neighbours reacting to neighbours. This is the sketchbook version of that idea: fluid particles plus analogue texture.

The grain layer is doing real aesthetic work: pure vector particles read as software, but the same particles under animated grain read as footage — the noise lends the simulation a camera it never had.

How

A particle field with emergent flocking-style motion, integrated per frame with local neighbour influence, overlaid with generated film-grain noise rendered to its own canvas.

The grain's refresh rate is mapped from a speed slider to a frame-skip interval — at 1.0 the grain regenerates every frame, at 0 it freezes — so grain "speed" is really update frequency, which is how film actually behaves.

One detail

Mapping grain speed to a frame-skip interval instead of opacity keeps the grain crisp at every setting — slow grain is still sharp grain, like slowing film rather than fading it.

Try Organic Particles

Polygon Rays

Geometry as a light source.

Why

Flat geometric compositions come alive the moment shapes cast light instead of just sitting in it. Rays sweeping from polygon edges give a static arrangement weather.

It's a poster that breathes: the composition is fixed and the light is not, which is exactly how architecture works — the building holds still and the day moves across it.

How

Polygonal forms emit rays computed from their edges and vertices, swept over time with slow phase offsets per shape so the composition's lighting continuously redraws itself without ever synchronising into a loop you can spot.

Ray brightness falls off along length and overlapping rays sum, so intersections glow brighter — the geometry's relationships become literally visible where their light overlaps.

One detail

The rays move slowly on purpose — at sweep speeds closer to a lighthouse than a strobe, the eye reads atmosphere instead of animation.

Try Polygon Rays

Spaghetti Untangle

A puzzle your hands understand instantly.

Why

Everyone has untangled headphone cables; nobody needs a tutorial. Physics-simulated strands you pull apart with the cursor borrow twenty years of muscle memory as the entire onboarding.

The puzzle difficulty is emergent, not authored — the knot is whatever the physics settled into, so every round is fair in the way real tangles are fair: annoying, but never impossible.

How

Each strand is a chain of physics segments with distance constraints and strand-to-strand collision; dragging applies force to the grabbed point and tension propagates through the knot, so pulling one strand visibly tightens another.

The strands render as Catmull–Rom splines through the simulation points, so the chunky physics chain draws as smooth pasta — the sim runs at one resolution and the picture at another.

One detail

The puzzle works because tension travels: a pull on one end visibly moves through the knot. Without strand-to-strand collision it's just ribbons crossing; with it, it's a knot.

Try Spaghetti Untangle

Pixelated Brush

A brush with a sampling rate.

Why

Digital brushes chase smoothness. Going the other way — a brush that quantises every stroke into fat pixels — makes the digitalness the medium, closer to placing tiles than dragging ink.

Quantisation also flatters the hand: a shaky line snapped to a grid looks deliberate. The tool's constraint quietly edits your gesture into confidence.

How

Pointer samples are lerp-interpolated between events before quantising into the coarse grid — pointer events arrive slower than fast hands move, and without interpolation quick strokes turn into dotted lines.

An animated painting effect fills cells in the stroke's wake with a slight delay rather than instantaneously, so colour appears to flow into the squares behind your hand.

One detail

The cells fill just behind the cursor — the stroke assembles rather than appears. That lag is what makes chunky pixels feel painted instead of stamped.

Try Pixelated Brush

Blobby Clocks

Time told by things that refuse to hold still.

Why

Digital clocks are the most rigid typography there is. Rendering the digits as metaballs — blobs that merge, wobble, and reluctantly re-form each minute — makes time feel liquid while staying readable.

A clock is the perfect host for this because it animates itself: the content changes once a minute whether you interact or not, so the display gets a guaranteed moment of theatre sixty times an hour.

How

A metaball field — summed radial falloffs thresholded into a surface — is steered toward digit-shaped target layouts, with blob positions eased by exponential smoothing so arrivals decelerate naturally.

When the time changes, the blobs migrate to the new digit's targets, merging and splitting wherever their fields overlap along the way — the surface tension effects are free, they're just what thresholded fields do when sources pass each other.

One detail

The transition is the clock's best moment, so it's allowed to take real time — the blobs travel, they don't crossfade. A clock that's briefly illegible once a minute is a fair trade for one that's alive.

Try Blobby Clocks

Rope Physics

The hello-world of believable physics.

Why

A hanging rope is the simplest object that either feels real or doesn't — droop, swing, and settle expose every flaw in an integrator. This is the piece the other physics studies stand on.

Ropes are also brutally honest about tuning: too little damping and it's a live wire, too much and it's a wet sock. Finding the band where it reads as rope calibrated my instincts for every spring system that came after.

How

Verlet integration over a chain of points — each point stores position and previous position, velocity is implicit in the difference — with distance constraints relaxed iteratively per frame to keep segment lengths honest.

Gravity, friction, and damping are tuned so the rope drapes into a natural catenary and swings without stretching or jittering; the constraint iteration count trades stiffness against cost, and watching that trade-off live is half the education.

Verlet in four lines

// velocity is implicit in (x - px) — no velocity variable,
// so constraints can shove points and momentum stays honest
const vx = (p.x - p.px) * friction;
const vy = (p.y - p.py) * friction;
p.px = p.x;  p.py = p.y;
p.x += vx;   p.y += vy + gravity;

One detail

Verlet's trick is storing previous position instead of velocity — constraints can shove points around and momentum stays consistent for free. The right representation deletes the hard code.

Try Rope Physics

Chaos Grid

Order and turbulence in the same frame.

Why

In my OFFF 2026 keynote I put it this way: imagine a messy crowd reaching the white lines of the street — suddenly everyone walks in a perfect line, like a wild wave becoming a flat line on the sand. It feels good to move together as one. Chaos Grid is that feeling built as an instrument: a perfect grid you can stir into turbulence, and the deeper pleasure of watching it find its way back.

A perfect grid is the most ordered thing on a screen, which makes it the best possible stage. Attractor fields swirl the cells into a storm, but the storm isn't the point — the return is. The crowd finding its lines again reads so strongly precisely because you watched what it came back from.

How

Grid cells lerp each frame toward targets displaced by attractor fields — force falling off with distance, with a swirl component that turns straight pull into orbit, which is what produces the vortex look instead of a pinch.

Cells near an attractor spiral into turbulence while distant ones hold formation, and when the field moves on, each cell's target snaps back to its grid position and the same lerp walks it home — the recovery needs no extra code, only patience. The lerp constant sets how reluctantly order gives way and how calmly it returns: snappy reads as magnetism, slow reads as viscous flow.

One detail

The untouched cells matter as much as the swirling ones — chaos is only legible against the intact grid around it, the way the crowd's wildness only reads against the white lines of the street. The piece is really about the boundary, and the walk back across it.

Try Chaos Grid

Rain Wiper

Weather on the inside of the screen.

Why

Raindrops streaking down glass put the screen surface itself into the scene — suddenly there's an inside and an outside. The wiper is the punchline: a UI element borrowed from a windshield.

The piece works because rain-on-glass has extremely specific physics everyone has watched for hours from cars — drops that hesitate, wander, merge and suddenly run. Get those behaviours right and the screen becomes a window; miss them and it's a particle demo.

How

Drops spawn with varied mass and integrate downward with friction against the glass — heavier drops break loose and streak, light ones stick and bead, and a drop's path wanders slightly because real glass is never clean.

Colliding drops merge their mass and speed up — the merge-then-run behaviour is the signature of real rain — and the wiper sweeps its arc clearing everything under the blade, which then re-beads gradually.

One detail

The wipe isn't a reset — the glass re-beads gradually, the way real glass mists back over. The loop of clearing and re-accumulating is what makes it weather instead of an effect.

Try Rain Wiper

Text Beam

Reading with a searchlight.

Why

A beam of light tracing through a field of text particles reveals words the way a lighthouse reveals coastline — in passes, partially, with darkness doing half the composition.

Withholding is the design move: at any moment most of the text is invisible, so reading becomes anticipation. You don't scan the page — you wait for the light to come back around.

How

Characters exist as particles in the dark; the beam's position is eased along its path with exponential smoothing — lerped toward its target each frame — so the light glides with momentum instead of tracking raw input.

Particle brightness falls off with distance from the beam's cone and decays after the light passes, so legibility literally travels with the beam and trails behind it like phosphor afterglow.

One detail

The beam moves along paths rather than following the cursor directly — light that moves with intention reads as revealing something, light that jitters with your hand reads as a flashlight.

Try Text Beam

Word Split

The moment a word stops being one.

Why

Think of pulling an accordion apart to hear a note, or opening a book to find a story — as I put it in my OFFF 2026 keynote, when we separate two sides, a gift appears in the centre. It feels like magic because "nothing" suddenly becomes "something". Splitting a word is that gesture applied to language: pull it apart and the letters — the parts you stopped seeing — are the gift inside.

A word is a single object in your mind until it breaks — then it's suddenly letters, and the switch runs in both directions. The two directions deserve different physics too, because they mean different things: breaking is something that happens to the word, reassembly is something the word does.

How

Each letter is an independent body. The scatter runs on spring-dampers with explicit stiffness and damping constants so debris has weight and the break feels like consequence.

The reassembly tweens each letter home on an easeInOutCubic curve with per-letter stagger — choreography rather than physics — so the word returns with intent, letters arriving in a readable wave rather than a simultaneous snap.

The reassembly curve

const easeInOutCubic = (t) =>
  t < 0.5 ? 4 * t * t * t
          : 1 - Math.pow(-2 * t + 2, 3) / 2;
// scatter = springs (consequence), return = easing (intent)

One detail

Breaking apart uses physics and coming back uses easeInOutCubic — on purpose. Destruction should look like consequence; reassembly should look like intent.

Try Word Split

Fractal Tree

Recursion that doesn't look like math.

Why

Textbook fractal trees look like antennas — perfect, dead. The entire problem is variation: how much randomness turns recursion into growth without collapsing the structure.

It's a compact lesson in naturalism: nature is not random, it's varied within constraints. The same insight applies to UI — jitter a stagger delay slightly and a list stops feeling machine-stamped.

How

Recursive branching where every angle, length, and taper carries bounded random variation per branch — each parameter deviates within a band around its ideal, seeded so a given tree is reproducible.

The crown is drawn across auxiliary canvases so the dense upper branches don't redraw from scratch each frame — recursion is cheap to compute and expensive to paint, so the painting is cached in layers.

One detail

The variation is bounded, not free. Too little reads as circuit board, too much as scribble; the band between them is where "tree" lives.

Try Fractal Tree

Scribble Mosaic

A photo redrawn in handwriting.

Why

Photo mosaics from photo tiles are a solved trick. Rebuilding an image from hand-drawn scribbles instead gives the machine reproduction a human tremor — the picture emerges from marks that couldn't care less about it.

The pleasure is in the double reading: stand back and it's a photograph, lean in and it's nervous pen marks. The image only exists at viewing distance, which is true of print halftones too — this just makes the dots handmade.

How

The source image is sampled into cells by average tone, and each cell renders scribble strokes whose density matches the sampled darkness — darker areas get denser scrawl, lighter areas a few lazy loops.

Stroke placement and direction are jittered per cell so no two cells repeat, and the scribbles draw in over time rather than appearing complete — the portrait sketches itself.

One detail

Tone is carried entirely by scribble density, never by opacity. The strokes stay full-strength ink, like real pen — greyness is an emergent property of how much the pen scribbled.

Try Scribble Mosaic

Infinite Peel

A surface that's all reveal, no bottom.

Why

We have loved this since we were babies: peel a sticker, peel an orange, peel the plastic off a new phone. That slow pull, the clean little sound when it comes off. It is hidden, then it is not — that's half of magic. The other half is surprise.

This study keeps the magic and removes the ending: every layer peels to reveal another layer already mid-peel. A reveal implies a final surface, and denying that forever turns the most satisfying gesture there is into something hypnotic — closer to an M. C. Escher drawing, motion that promises resolution and structurally can't deliver it.

How

The peel is a continuously animated curl — the fold line advances while the curled-over region renders the layer's backside with its own shading — and the revealed content is the next layer's image, cycled through a pool so the recursion never terminates.

The curl geometry and its shadow are computed from the fold-line position each frame, so the lift, the highlight along the curl, and the shadow it casts all move as one — three cues, one driver.

One detail

Each new layer arrives already slightly peeling. If layers arrived flat, the loop would have a visible reset; starting mid-gesture is what welds the loop shut.

Try Infinite Peel

Progressive Blur

Blur as a gradient, not a state.

Why

UI blur is binary — a surface is frosted or it isn't. Ramping blur progressively across the canvas treats focus as a spatial gradient, the way a tilted lens does, and the falloff curve becomes the design surface.

Progressive blur has since become a fashionable UI treatment, which makes the mechanics worth understanding rather than importing: where the falloff starts, how fast it ramps, and what that does to reading order.

How

The image is blurred in graduated bands with radius increasing along the axis — a stack of cheap box blurs standing in for one expensive variable-radius blur, which real-time canvases can't afford.

The band boundaries are blended into each other so the quantised levels read as one continuous falloff; the blend widths are where all the effort went, because that's where the eye hunts for seams.

One detail

The eye is ruthless at finding seams: one visible boundary between blur levels and the lens illusion collapses into a stack of filters. So all the craft went into the hand-offs — each band dissolving into the next before you can catch it. The best compliment this piece can get is that nobody notices it's built from steps at all.

Try Progressive Blur

Extruded Text

Type you can walk around.

Why

Extruding type is the fastest way to make it monumental — a word becomes an object with weight and shadows. The exercise is keeping typographic quality once the letterform has a third dimension.

Most 3D text reads as a 90s screensaver because it treats the glyph as a cookie cutter. Treating it as an object with edges that catch light — that's the difference between rendered type and built type.

How

Glyphs become bevelled ExtrudeGeometry in Three.js — the bevel exists specifically to catch highlights along every stem, because a hard 90° extrusion edge reflects nothing and reads as cardboard.

The layout flows along a CatmullRomCurve3, so the word can bend through space as one ribbon, and a fit tween reframes the camera whenever the text changes — new copy always arrives composed.

One detail

The bevel does the typography in 3D — sharp extrusions read as cardboard cutouts. A small bevel catching a highlight along every stem is what makes letters read as cast objects.

Try Extruded Text

Wave Plane

Typography riding terrain.

Why

Setting animated type on an undulating plane makes the text a passenger on the surface — the words hold still in their own frame while the world underneath them breathes.

It's the calmest kind of motion design: nothing moves toward you or away, nothing demands a response. The scene just swells, which makes it a good stress test for how much ambient motion text can carry before reading suffers.

How

A subdivided plane displaces its vertices with layered fbm noise on top of travelling waves — the noise breaks up the wave's mechanical regularity — lightly damped so the swell stays slow.

The typography is mapped onto the surface and inherits the displacement, so letters tilt and rise with the terrain under them rather than floating above it.

One detail

The wave components run at non-integer frequency ratios, so the surface never visibly repeats — looping motion you can't catch looping is what keeps a simple idea watchable.

Try Wave Plane