VITTO AVATAR DOCUMENTATION
Documentation for Developers
Vitto Animated Avatar — Technical Documentation
Overview
Vitto is an animated cat avatar built as a self-contained TypeScript/Canvas 2D application. It renders a black silhouette cat character with expressive emotions, mouse-following eyes, idle blinking, and smooth mood transitions — all with advanced idle behavior systems, enhanced eyes, and ambient mood glow effects.
The visual design is inspired by the Rive marketplace "Cat following the mouse" by pedroalpera (#3920): a clean black cat silhouette on a golden background with big white eyes, whiskers, a curvy tail, and pink inner ears.
Tech Stack
| Layer | Technology |
|---|---|
| Rendering | HTML5 Canvas 2D (CanvasRenderingContext2D) |
| Animation | requestAnimationFrame at 60fps with delta-time |
| Language | Pure TypeScript (NX library structure) |
| Styling | Inline CSS |
| Dependencies | None — fully self-contained |
| Bundle Size | ~21KB (minified IIFE) |
Why Canvas 2D?
We evaluated three approaches before settling on Canvas 2D:
Rive Runtime — High quality but created integration complexity. Chrome's Private Network Access policy blocked localhost testing from HTTPS pages, and the Rive file format made it difficult to programmatically control individual facial features per-emotion.
SVG/CSS Animation — Good for simple transitions but becomes unwieldy when animating 30+ properties simultaneously with custom easing. SVG DOM manipulation at 60fps can also cause layout thrashing.
Canvas 2D (chosen) — Full programmatic control over every pixel, no DOM overhead, trivial to interpolate any numeric property between states, and the imperative drawing API maps naturally to "draw a cat face" logic.
Trade-offs
| Advantage | Limitation |
|---|---|
| Zero dependencies, works offline | No vector export (rasterized) |
| Embeddable anywhere (iframe, React wrapper, Electron) | Requires JS for rendering (no static fallback) |
| Full programmatic control over every visual element | Drawing code is manual (no visual editor) |
| Smooth interpolation of unlimited properties | Accessibility requires additional work (ARIA labels) |
| Tiny file size (~21KB) | Resolution-dependent (mitigated by 2x DPI) |
Architecture
Rendering Pipeline
requestAnimationFrame(render)
→ Compute idle behaviors (8-channel noise-driven system)
→ Update transition state (interpolate all properties)
→ Update blink timer
→ drawBg() // Golden gradient background + mood glow + dynamic shadow
→ drawShadow() // Ground shadow ellipse (scales with mood energy)
→ drawTail() // Animated bezier curve tail + idle tailVariation
→ drawBody() // Body ellipse + feet + idle bodyShiftX
→ drawHead() // Head group (translated by mouse look + bounce)
→ drawEars() // Triangular ears with pink inner + idle earTwitchL/R
→ drawEyes() // 3-tier dynamic highlights + mood-tinted iris + eyeWideVariation
→ drawNose() // Golden triangle nose with highlight
→ drawMouth() // Muzzle, W-shaped mouth, open mouth, tongue, teeth
→ drawWhiskers() // 6 whiskers (3 per side)
→ drawBlush() // Pink cheek circles with manga lines
→ drawBrows() // Expressive eyebrow arcs
→ drawParticles() // Sparkles, hearts, question marks, sweat, exclamation marks, zzz
Canvas Setup
- Internal resolution: 840×840 pixels
- Display size: 420×420 CSS pixels
- This gives 2× DPI for crisp rendering on standard displays and native sharpness on Retina/HiDPI
Coordinate System
- Origin: top-left corner (0, 0)
- Center reference:
CX = 420, CY = 400(slightly above center for visual balance) - Head center:
CY - 55(offset upward from body center) - All facial features are drawn relative to the head center using
ctx.translate()
Mood System
The 14 Moods
Each mood is defined as an object with ~30 numeric properties that control every visual aspect of the cat's expression:
| Mood | Key Visual Features | Accent Color | Energy |
|---|---|---|---|
| Neutral | Calm round eyes, relaxed posture | #c4b5fd |
0.5 |
| Focused | Dramatic squint, tiny pupils, furrowed brows | #818cf8 |
0.6 |
| Cheerful | Happy arc eyes, blush + manga lines, tongue, floating hearts | #f472b6 |
0.9 |
| Empathetic | Big soft eyes, worried brows, gentle head tilt, hearts | #a78bfa |
0.5 |
| Curious | Wide eyes, one raised brow, head tilt, blue question mark | #34d399 |
0.7 |
| Analytical | Slightly squinted, asymmetric brows, question mark | #60a5fa |
0.6 |
| Celebratory | Happy arcs, open mouth + fangs + tongue, golden sparkles, bouncing | #fbbf24 |
1.0 |
| Apologetic | Sad frown, worried brows, sweat drops, drooped ears, shrunk body | #c4b5fd |
0.3 |
| Inspired | Star-shaped eyes with purple glow, sparkles, open smile | #c084fc |
0.85 |
| Urgent | Huge eyes, tiny pupils, red exclamation marks, orange background | #f87171 |
1.0 |
| Playful | Wide bright eyes, open smile, bouncy posture, colorful sparkles | #ec4899 |
0.95 |
| Confused | Asymmetric eyes, tilted head, multiple question marks | #f59e0b |
0.5 |
| Proud | Steady eyes, raised chin, sparkle particles, golden aura | #fbbf24 |
0.85 |
| Sleepy | Half-closed eyes, relaxed expression, zzz particles, slow breathing | #6b7280 |
0.2 |
Animatable Properties
These are the numeric properties that smoothly interpolate between moods:
Eyes:
eyeH— eye height multiplier (0.3 = squint, 1.3 = wide)eyeW— eye width multiplierpupilR— pupil radius (0 = no pupil / special eye mode, 1.3 = large)eyeSquish— squint deformation (0–1, reshapes eye via bezier)happyEye— happy arc eyes (>0.5 triggers arc rendering)starEye— star-shaped eyes (>0.5 triggers star rendering)eyeWideVariation— idle-driven eye size flutter (computed by idle system)
Mouth:
smile— smile amount (-0.55 = frown, 1 = big smile)mouthOpen— mouth opening size (0–0.5)mouthW— mouth width multiplierteeth— fang visibility (0–0.4)tongue— tongue visibility (0–0.8)
Brows:
browY— vertical offset (-0.35 = raised, 0.55 = lowered/worried)browAngle— tilt angle (-0.7 = inner-up/sad, 0.4 = outer-up/angry)browAsym— asymmetry between left/right brow (0–0.7)
Head & Body:
earAngle— ear rotation (-0.4 = drooped, 0.45 = perked)headTilt— head rotation with oscillation (-0.06 to 0.12 radians)whiskerAngle— whisker spread angleblush— cheek blush intensity (0–0.9)bodyBounce— vertical bounce amplitude (0–0.15)bodySquash— body squash/stretch deformationtailSpeed— tail wag speed multiplier (0.2 = slow, 3.5 = fast)
Idle Behavior (Computed):
earTwitchL— left ear noise-driven twitchearTwitchR— right ear noise-driven twitchheadTiltIdle— noise-driven head tilt variationheadBobY— noise-driven vertical head bobbingwhiskerFlutter— noise-driven whisker angle variationtailVariation— noise-driven tail amplitude variationbodyShiftX— noise-driven horizontal body swayeyeWideVariation— noise-driven eye height variation
Particle Effects:
sparkle— orbiting star particles (0–1)hearts— floating heart particles (0–0.5)question— question mark icon (0–0.8)sweat— sweat drop(s) (0–1)exclaim— exclamation marks (0–1)zzz— sleep particles (implemented — shows during sleepy mood)
Background:
bg— RGB array[r, g, b]for background color (e.g.,[250,130,50]for urgent orange)
Mood Transition System
When setMood(key) is called:
- All current property values are snapshot into
S.from - The transition timer
S.tTresets to0 - On each frame,
S.tTincrements bydt * 2.8(completing in ~360ms) - A cubic ease function
t < 0.5 ? 4t³ : 1 - (-2t+2)³/2is applied - Every numeric property is lerped:
current = lerp(from, target, ease(tT)) - Background RGB values are also lerped independently
This means any property change between any two moods is always smooth — there are no hard cuts or jarring transitions.
Idle Behavior System
Overview
The idle behavior system adds organic, subtle micro-animations that keep Vitto feeling alive and reactive during normal interaction. Rather than static poses, the avatar continuously exhibits small movements driven by noise functions when idle intensity is non-zero.
8-Channel Noise-Driven Architecture
The idle system runs 8 independent noise-driven animation channels:
| Channel | Target | Behavior | Range |
|---|---|---|---|
earTwitchL |
Left ear angle offset | Subtle ear flick | ±0.02 rad |
earTwitchR |
Right ear angle offset | Subtle ear flick | ±0.02 rad |
headTiltIdle |
Head rotation variation | Gentle side-to-side tilt | ±0.03 rad |
headBobY |
Head vertical position variation | Soft up-down bobbing | ±8 pixels |
whiskerFlutter |
Whisker angle modulation | Whisker wiggle | ±0.04 rad |
tailVariation |
Tail wag amplitude variation | Tail throb/pulse | ±0.15 multiplier |
bodyShiftX |
Body horizontal sway | Gentle weight shift | ±6 pixels |
eyeWideVariation |
Eye height variation | Eye flutter/blink-like motion | ±0.08 multiplier |
Noise Generation
- Simplex-inspired noise function (
noise1D) produces smooth, continuous values over time - Fractional Brownian Motion (FBM) combines multiple noise octaves for richer, multi-scale motion
- Each channel samples its own noise at different frequencies for asynchronous, natural-feeling variety
Idle Intensity Calculation
The idle intensity is inversely proportional to mood energy:
moodEnergy = mood.energy // 0.2 (sleepy) to 1.0 (celebratory)
idleIntensity = 1.0 - moodEnergy
During sleepy mood (energy = 0.2), idle intensity peaks at 0.8, creating prominent micro-animations. During celebratory mood (energy = 1.0), idle intensity is 0, freezing all micro-animations.
Smoothing & Blending
Each computed idle value is smoothed using exponential blending:
smoothedValue = lerp(smoothedValue, noiseValue, 0.15)
A blend factor of 0.15 creates natural lag — rapid enough to feel organic, slow enough to avoid jitter.
Configuration
Idle behaviors are controlled via RendererConfig:
enableIdleBehaviors: boolean // Default: true
idleBlendFactor: number // Default: 0.15
idleAmplitude: number // Default: 1.0 (scale factor for all channels)
Disable idling for performance-critical contexts or to freeze the avatar in a specific state.
Enhanced Eyes System
3-Tier Dynamic Highlights
The eye rendering system now includes three layers of highlights for realistic depth:
- Primary Gaze-Following Highlight — Main glossy spot that follows the mouse cursor, positioned at the pupil center offset
- Secondary Opposite Corner Highlight — Smaller accent highlight in the opposite corner, creating an occlusion/shadow effect for depth
- Tertiary Pulsing Sparkle — Micro-highlight that gently pulses based on
sparkleintensity, adding shimmer and life
Mood-Tinted Iris
The iris color is dynamically derived from the current background RGB values, creating a subtle chromatic harmony:
irisColor = interpolateFromBG(mood.bg) // Harmonizes eye color with mood background
This creates visual cohesion where the cat's eyes "glow" with the same chromatic tone as the surrounding aura.
Idle Eye Variation
The eyeWideVariation channel from the idle system creates gentle eye flutter even during neutral expressions, mimicking natural biological eye movement and blinking patterns.
Ambient Mood Glow & Dynamic Shadow
Ambient Mood Glow Aura
Behind the cat, a pulsing colored aura reacts to emotional intensity:
Driven by:
sparkleintensity (golden pulse during inspired/celebratory moods)heartsintensity (pink/warm pulse during empathetic/cheerful moods)exclaimintensity (red pulse during urgent mood)blushintensity (soft pink undertone)
Rendering: Semi-transparent ellipse behind the body, growing and shrinking with the aggregate emotional particle intensity
Dynamic Shadow Scaling
The ground shadow beneath the cat scales with mood energy:
- High energy (celebratory, urgent, playful): Shadow expands and darkens, suggesting a "grounded" powerful presence
- Low energy (sleepy, apologetic): Shadow shrinks and fades, suggesting a withdrawn, small demeanor
This creates an intuitive visual metaphor where emotional intensity physically weighs the avatar down or lifts it up.
Interactive Features
Mouse-Following Pupils
- The canvas wrapper element tracks
mousemoveevents - Mouse position is mapped from CSS coordinates to canvas coordinates
- A smoothed position (
smx,smy) follows the actual mouse vialerp(smooth, actual, 0.08) - This creates natural eye-tracking lag
- Pupil offset is clamped to stay within the eye boundary
- On
mouseleave, pupils smoothly return to center
Idle Blinking
- A random timer triggers blinks every 2–5.5 seconds
- Blink animation runs as a sine wave over ~200ms (5× speed)
- Blink factor (0→1→0) scales eye height down by 90%
- Blinking works across all eye types (normal, squint, happy arc, star)
UI Controls
- Mood buttons at the bottom allow manual mood switching (14 moods total)
- "Auto" button cycles through all 14 moods every 2.8 seconds
- Active mood button is highlighted with a white border
- Label and subtitle text update with each mood change, using the mood's accent color
API Integration
Basic Usage
To change Vitto's mood programmatically from your application:
// Direct call
setMood('cheerful');
// From an external iframe
document.getElementById('vitto-iframe').contentWindow.setMood('playful');
// Available mood keys (14 moods)
const moods = [
'neutral', 'focused', 'cheerful', 'empathetic', 'curious',
'analytical', 'celebratory', 'apologetic', 'inspired', 'urgent',
'playful', 'confused', 'proud', 'sleepy'
];
Connecting to Vitto's Emotion API
// Example: poll emotion endpoint and update avatar
async function syncVittoMood() {
const response = await fetch('https://your-api.com/vitto/emotion');
const { mood } = await response.json();
setMood(mood);
}
// Poll every 2 seconds
setInterval(syncVittoMood, 2000);
// Or use WebSocket for real-time updates (SSE stream integration planned)
const ws = new WebSocket('wss://your-api.com/vitto/ws');
ws.onmessage = (event) => {
const { mood } = JSON.parse(event.data);
setMood(mood);
};
Embedding Options
Iframe:
<iframe src="vitto-avatar.html" width="420" height="500" frameborder="0"></iframe>
React Wrapper:
function VittoAvatar({ mood }) {
const iframeRef = useRef(null);
useEffect(() => {
if (iframeRef.current?.contentWindow?.setMood) {
iframeRef.current.contentWindow.setMood(mood);
}
}, [mood]);
return <iframe ref={iframeRef} src="/vitto-avatar.html" width={420} height={420} />;
}
File Structure
libs/libs-anim/vitto-avatar/src/lib/engine/
├── index.ts # Main entry point
├── renderer.ts # Canvas rendering orchestration
├── moods.ts # Mood definitions (14 moods)
├── types.ts # TypeScript type definitions
├── math.ts # Interpolation, easing, transforms
├── noise.ts # Simplex noise & FBM (NEW)
├── drawing/
│ ├── background.ts # Mood glow + dynamic shadow (enhanced)
│ ├── body.ts # Idle bodyShiftX + tailVariation
│ ├── head.ts # Idle headBobY + headTiltIdle + earTwitchL/R
│ ├── face.ts # Enhanced eyes (3-tier highlights + mood iris)
│ ├── effects.ts # Particle effects including zzz
│ └── shapes.ts # Shape drawing primitives
└── behaviors/
├── breathing.ts # Chest rise/fall animation
├── saccades.ts # Eye movement patterns
└── idle.ts # 8-channel idle behavior system (NEW)
Future Enhancement Roadmap
Phase 1: COMPLETED ✓
- ✓ Idle Micro-Animations (8-channel noise-driven system)
- ✓ Enhanced eyes (3-tier highlights + mood iris color)
- ✓ Ambient mood glow & dynamic shadow
- ✓ Sleep particles (
zzz) implementation - ✓ 4 new moods (playful, confused, proud, sleepy)
Phase 2: Expanded Properties
- Pupil Dilation — Eyes respond to mood intensity (wide pupils = excited/surprised, pinned pupils = focused)
- Ear Droop — Ears flatten and droop during sad/apologetic moods for additional expressiveness
- Chest Rise/Fall — Breathing animation that accelerates with mood energy
- Paw Curl — Front paws curl/relax based on emotional state
- Fur Ruffle — Localized fur bristle animation during tense moods
- Eye Moisture — Subtle glisten or tear effects during emotional moods
Phase 3: Live Emotional Expression
- Wire Vitto to the
vitto-chatSSE stream for real-time emotion updates - Stream mood changes directly from Claude's response generation
- Add micro-expressions during response generation (thinking face, uncertainty flickers, etc.)
Future Enhancements
- Transition Reactions — Brief exaggerated "squash and stretch" when switching between very different moods
- Speech/Typing Indicator — Animated mouth movement or bouncing dots when processing
- Sound Reactivity — Mouth or ear movements synced to audio output
- Seasonal/Themed Variants — Holiday hats, accessories, or background themes
- WebGL Upgrade — Migrate renderer for higher performance if particle count increases
- Lottie Export — Convert animation parameters to Lottie format for mobile
- Accessibility — ARIA labels, reduced-motion mode, static expressions
- Size Variants — Compact (64px), medium (200px), full (420px)
Design Decisions Log
| Decision | Rationale |
|---|---|
| Canvas 2D over Rive | Full programmatic control needed for 30+ interpolated properties per mood; Rive's state machine couldn't express the granularity we needed |
| Canvas 2D over SVG | Better performance for 60fps with many animated properties; no DOM thrashing |
| NX library structure | Modular, testable, reusable components across the platform; enables proper TypeScript tooling and code splitting |
| 840×840 internal resolution | 2× DPI at 420px display for crisp rendering on all screens |
| Cubic easing for transitions | Feels natural — slow start, fast middle, soft landing. More organic than linear or quadratic |
| Property-based mood system | Adding a new mood is just adding an object with numeric values — no animation code changes needed |
| Threshold-based eye rendering | happyEye > 0.5 and starEye > 0.5 allow smooth transitions where special eyes "pop in" at the midpoint |
| Noise-driven idle behaviors | Simplex + FBM creates organic, unpredictable micro-motions without hardcoded animation sequences |
| Inverse mood energy for idle intensity | Low-energy moods benefit most from micro-animation (sleepy = prominent twitches), while high-energy moods are already visually dynamic |
| Mood-tinted iris from background | Creates chromatic harmony — the cat's eyes feel like they belong in the emotional environment |
| Dynamic shadow scaling | Visual metaphor for emotional weight; grounded/heavy when energetic, light/withdrawn when sad |