v2 keeps v1's locked identity: the cyan gradient body, two white eyes and no mouth. It adds a solid gumdrop body, eyes that move across it, a status light, reactions and a seasonal wardrobe. The product's state machine drives all of it.
v1's mascot sheet stays canonical until this draft is approved. v2 changes the shape, the depth and the behaviour. Everything that makes it recognisable stays the same.
#22d3ee → #0891b2The body is defined by one equation, so the 3D shader, the SVG and any future app icon draw exactly the same outline. All measurements are in R, the body's unit radius.
A soft candy shell lit by a key light from the top-left at 35°. The layers below are the shading model. The 3D shader computes them per pixel; the SVG version approximates them with gradients.
#e2e8f0 → #64748b): Mira's magnifier, Nila's wrench and the headphone band. Soren's orbit ring turns rose (#ffe4e6 → #e11d48).0.42 + 0.75·d²There are five eye shapes: pill, crescent, line, round and droop. Gaze, eyelid, eye size, body motion and the status light turn them into eleven states. Every tile below is live; tap one to send that state to the hero.
Short expressions that play on top of any state: bot.emote("love"). They're built from eight more eye shapes (heart, ><, star, smug, dot, ring, wink, angry) and flat manga marks (blush hatching, sweat, anger mark, tears, gloom lines, sparkles, hearts, surprise lines). There is still no mouth.
Open the Emote Playground → full-screen stage, tap any emote, auto-play reel
The product sets the state, and the mascot never changes state on its own. People trigger reactions: short one-shots that play on top of the current state and then hand control back. Idle is the hub. Tap a node to preview it.
| Product status | State | Eyes | Body | Light |
|---|
| Person does | Reaction | What it looks like |
|---|---|---|
| Moves the pointer nearby | look | Eyes follow with a soft limit, tanh(dx/260), so far-away pointers never pin the eyes to the edge |
| Hovers over it | notice | One blink; eyes grow ×1.08 |
| Holds still on it for 1.9 s | curious | Leans toward the pointer, lifts slightly, eyes ×1.18 |
| Taps it | giggle | Squish, crescent eyes for 1.1 s, a quick wobble. Wakes it if it's asleep. |
| Taps 3× within 1.7 s | spin | A full 360° turn; the face goes round the back and comes back (an easter egg) |
| Product accepts a task | nod | Two quick nods: "got it". The state doesn't change. |
The same named curves as the namabots title reel. Scripted moves are keyframe tweens that briefly "lock" a property; springs take over again when they finish. Nothing fades in.
| Token | Value | Used for |
|---|---|---|
| blink | 90 ms close · 30 hold · 140 ms open | Every 2.2–5.5 s at random; 22% are double blinks. Also used to swap expressions. |
| saccade | spring k 320 · c 30 | Gaze jumps: idle wandering every 0.8–3.2 s, and the working "reading" scan every 320 ms |
| squash | spring k 420 · c 14 | Breathing, landings and anticipation. Volume is preserved: x and z scale by 1/√s. Range 0.84–1.12. |
| breathe | ±1.4% · 3.2 s | All awake states (sleeping uses ±3% over 4.6 s) |
| hop | crouch 140 · rise 250 · fall 240 · land 90 ms | Approved. Lift 0.42 R; stretches to 1.12 on the way up, squashes to 0.86 on landing, then overshoots back. |
| shake | 5 keys · 480 ms | Error: tilt and turn in opposite directions, then it settles |
| wave | 5 keys · 1.3 s | Greeting: a side-to-side wobble with a small hop |
| light swap | 90 ms in-out → 280 ms back | The status light shrinks, changes colour, then pops back |
| battery | 10 fps while asleep · 0 fps off screen | Sleeping only needs a slow breath, so it renders less often |
| reduced motion | prefers-reduced-motion | A static pose for each state, with no wandering, blinks, entrances or reactions. The hero renders single frames. |
Every agent uses the same body. Only the gradient hue and one role prop change. Outfits are seasonal and use the same rule: outside the body, never over the eyes, one at a time. The hero and the "o" in the wordmark stay brand cyan.
The same component simplifies itself as it gets smaller. Below 30 px it drops the rim, highlight, props, glow and shadow, and keeps the eyes at least 1.6 px wide so the face never disappears.
Requirements: it must look 3D, many mascots must fit on one screen (rosters, status chips), it must stay crisp from 16 px to the hero, run smoothly on mid-range phones, and stay light. Sizes below were measured on 2026-10-04 from the jsDelivr CDN, gzipped.
| Option | Download (gz) | Real 3D | Dozens on screen | Verdict |
|---|---|---|---|---|
| three.js r180 | ~179 KB | Yes | No: one WebGL context each | Overkill for one rounded shape with eyes. Worth it only if we later need real models, physics or a full scene. |
| Rive (canvas-lite) | ~475 KB (JS + WASM) | No (2D) | Yes | Has a strong built-in state machine, but every expression must be hand-built in the Rive editor and the runtime is the heaviest here. Revisit if a motion designer joins. |
| anime.js v4 | ~41 KB (full) | No (2D/DOM) | Yes | Good tweening, but a 60-line keyframe engine plus springs covers everything this mascot needs. |
| Spline runtime | ~37 KB + scene files | Yes | No | Scenes are authored in Spline's hosted tool, loaded as extra files, and can't be driven state by state as easily. |
| Canvas 2D (Coucou's approach) | 0 KB | Faked | Yes | Proven and light, but each shape is hand-drawn per frame and the hats need their own projection maths. Our SVG tier plays the same role with crisper small sizes. |
| Custom: SDF shader + SVG (this page) | ~17.5 KB SVG 10.9 + 3D 6.6 | Yes (ray-marched) | Yes (SVG tier) | Chosen. The shader draws the real lit 3D body, hats and props for the hero. The SVG component uses the same lighting recipe and the same state machine for everything else. No dependencies. |
<nama-bot>. All instances share one animation loop, pause when off screen, and drop to 10 fps while asleep. Detail drops automatically by size.Bot brain (springs, keyframe tweens with locks, blinks, reactions) outputs one pose that both renderers draw.<!-- anywhere in the app --> <nama-bot state="working" agent="mira" size="48"></nama-bot> <nama-bot state="idle" outfit="diya" interactive follow></nama-bot> // product state → mascot state el.setAttribute("state", "waiting"); // blinks, light swaps el.bot.react("nod"); // task accepted // hero (marketing / empty states only) const hero = new NamaHero(canvas, { agent: "brand" }); hero.bot.set("approved"); // hop + crescents
We studied Coucou by Louis Raillé, a notch companion whose mascot, Mochi, shows coding-agent status. Its code is MIT-licensed. Its name, the Mochi character, artwork and sounds are all rights reserved (see its LICENSE-ASSETS.md). So we borrowed engineering ideas and deliberately kept the character our own.
tanh-limited gaze, and 22% double blinks.| Mochi (Coucou) | namabot v2 | |
|---|---|---|
| Body | White/cream, wide 1.30 : 1, n 2.7 | Cyan gradient gumdrop, 1.11 : 1, n 2.4, tapered |
| Eyes | Dark ink, many shapes (hearts, stars, spirals) | Self-lit white, five shapes, no symbols |
| Face extras | Blush cheeks; mouth for some moves | None (the v1 rule) |
| Status | Tints the body; pill badge with •••, !, ? | Body keeps its agent colour; a glowing orb plus a text label |
| Signature moves | Roll on finish, dizzy spiral, swallowing files | Hop on approve, slump on reject, a nod, a full 3D spin |
| Renderer | Canvas 2D (Swift, plus a TypeScript port) | SDF shader (real 3D) + SVG |
Additions to the v1 rules. The v1 rules all still apply.