An animation at the point of contact, six of them, one for each way a press can happen

Featurekamo-internal
Shipped
September 7, 2026 at 6:37 AM UTC
Author
Kamo
Commit
807a35c

Left down, left up, middle down, middle up, touch down and touch up each get their own animation around the point of contact. They are one family rather than six ideas: a press pulls energy IN and a release lets it OUT, so an ordinary click reads as a single gesture — the ring that collapses onto alone, because a press without a release (a drag) and a release whose press was never seen both happen. Right-click stays silent: it opens a menu that is about to cover the animation anyway. left down Converge a hairline ring snaps inward onto a core dot left up Bloom the core launches: two rings and six sparks, seeded per click middle down Aperture close four arc segments rotate and close, mechanical rather than organic middle up Aperture burst the segments counter-spin outward, narrowing into dashes touch down Pool a soft edgeless halo, because a finger covers the point itself touch up Ripple two concentric rings decelerating outward, wider than the mouse Colour is mixed from --color-primary, so a white-label organisation recolours the whole system without a line of code, and #696969 stands in when a tenant has never set one. This runs in a tool people keep open all day, so idle cost was the design constraint. The rAF loop is started by the first impulse and returns the moment the last one dies. The backing store — tens of megabytes of GPU memory at DPR 2, 33 MB on a 4K display — is allocated with the loop and released with it, so an idle shell pays for two listeners and nothing else. Each frame clears only the union of live bounds rather than the framebuffer, which is why every kind declares the reach it paints within and why that reach is asserted at ten points across its life: a ring that outgrew it would leave an arc on screen that the next clear is too small to erase, and nothing else would notice. Listeners are pointer events rather than mouse plus touch, which is a correctness point and not a preference: a touch device also synthesises mousedown/mouseup after a tap, so the two-family version paints every tap twice. They are in the capture phase because the shell calls stopPropagation in a great many places and a bubbling listener on window would never hear those clicks. Under prefers-reduced-motion all six collapse to one halo that fades without moving at all. The engine is pure and holds no DOM, so the whole drawing layer is tested in node against a recording context — 28 tests, including that no kind paints outside its reach, that each of the six is genuinely a different animation rather than one reused, and that the sparks vary with their seed. Two defects were found by rendering it rather than by reasoning about it. The sparks were mixed toward white, which on this forced-light-mode shell is the direction of the background, and the bloom's one flourish was all but invisible; they are mixed toward black now. And allocation was inferred from `canvas.width === 0`, which is never true for a canvas that has not been sized — it reports the intrinsic 300x150 — so the first impulse of every page load skipped allocation and smeared into a 300x150 store stretched across the viewport. It is an explicit flag now.

All changes

Like what you see shipping?

Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.

Start Free ForeverView Pricing