how i built her
there's an illustrated version of me standing on the homepage. she breathes, blinks, gets bored after twenty seconds, and reacts if you poke her. this is the short write-up of how she works — the rig, the state machine, and the boring decisions that keep her smooth.
the rig
she's a single hand-drawn svg in a fashion-sketch style — gestural ink lines over loose watercolor washes. the drawing is split into fifteen named groups, and every group pivots from where a body would actually pivot: the head from the base of the neck, the hair from its roots, the arms from the shoulders, the sunglasses from the bridge of her nose, the plush from the point where it hangs off the strap.
the detail that matters most: the hair isn't one shape. it's three drawn masses — the crown, the wave framing her face, and the sheet falling down her back — each swinging from its own root, offset by a beat. hair never agrees with itself perfectly — the disagreement is the whole effect.
the hair, isolated — three masses, staggered by a beat
the state machine
she's always in exactly one of four states. idle is the default loop — a 3s breath (torso scales to 1.02, head and arms ride along so nothing detaches), the two hair masses following a beat behind on offset delays, and the plush swinging ±3° on its strap.
tapping her plays one of four reactions: a hop with full squash-and-stretch, a hair flip with a little head tilt, the plush lifting to wave a paw, or the good one — she tips her sunglasses down and winks, and one small heart floats off. after twenty seconds untouched she plays the rare idle — a yawn and a stretch. i tried giving her a tiny phone to scroll, but conjuring props reads wrong in a sketch this loose; the yawn stays in character.
and if you've set prefers-reduced-motion, she skips all of it and stands politely still. no timers, no listeners, nothing.
the whole machine — four states, five arrows
the hop
the hop is the only reaction with real choreography: an anticipation dip (squash to 0.93), the jump (stretch to 1.06 while the boots kick out a few degrees), a landing squash, and then one beat of follow-through where the hair settles a frame after she does. the curve is the whole trick — the drawing just goes along with it.
scaleY over time — the red line is now
interruption-safe queueing
people tap fast. if every tap started a reaction, three taps would leave her mid-hop, mid-flip, and mid-wink at once — limbs everywhere. so reactions are exclusive, and rapid taps queue at most one:
tap → playing ? queued = true
: play(random ≠ last)
done → playing = false
queued && play() // at most one waits
everything returns through idle. the rare-idle timer resets on any touch, so she never yawns at someone actively playing with her. there's also a 120ms scale-down on pointer-down, so touch feels acknowledged before the reaction picks a direction.
performance
- every animation is
transformoropacity— nothing that triggers layout or paint, so the whole loop runs on the compositor. will-changeexactly once, on the root group. the browser doesn't need twelve hints.- the pupil tracking (desktop only) reads a cached bounding rect and updates inside a single
requestAnimationFrame— zero layout reads per mouse event. - blinks and the rare idle check
document.visibilityState, so she doesn't perform to an empty room. - reduced motion is a hard gate: static pose, no listeners, no timers.
the whole thing is one file, no dependencies: a custom element you drop into any page with a script tag. 27.9 kb on disk, 7.5 kb gzipped — roughly one third of a decent favicon.png, for a person who waves back.