a small case study

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.

idle reaction 650–900ms rare idle yawn · 1.9s static pose reduced motion tap done 20s untouched done prefers-reduced-motion one state at a time. no exceptions.

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.

anticipation stretch squash follow-through

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

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.