2026 03 18 Vitto Avatar Improvements Design

Documentation for Plans

Vitto Avatar Improvements — Design Spec

Session: S237 Date: 2026-03-18 Approach: Vertical Slices (testable end-to-end after each slice)

Overview

Comprehensive enhancement of the Vitto avatar system across 6 vertical slices:

  1. CharacterConfig + Palette System — decouple appearance from animation
  2. Disney Animation Core — anticipation, follow-through, overlapping action, secondary action, exaggeration, staging
  3. 8 New Emotions — thinking, surprised, frustrated, grateful, mischievous, determined, listening, excited (total: 22 moods)
  4. Accessory System — wearables + held props with anchor points and spring physics
  5. Environmental Effects — weather, stage/spotlight, floating thought symbols, ground effects
  6. Backend + Refinement — API for character configs, accessory CRUD, enhanced inference

All changes must be testable on:


Slice 1: CharacterConfig + Palette System

Goal

Extract all hardcoded colors and geometry constants into a configurable CharacterConfig object so Vitto's appearance can be swapped without touching animation code.

CharacterConfig Interface

interface CharacterConfig {
  // ─── Palette ─────────────────────────────────────
  bodyColor: string;          // '#1a1a1a' (default black cat)
  bodyHighlight: string;      // '#2d2d2d'
  eyeScleraColor: string;     // '#ffffff'
  pupilColor: string;         // '#111111'
  noseColor: string;          // '#d4a060'
  noseHighlight: string;      // 'rgba(255,220,180,.35)'
  tongueColor: string;        // 'rgba(230,100,120,1)'
  earInnerColor: string;      // 'rgba(200,140,160,.35)'
  blushColor: string;         // 'rgba(255,130,170,.35)'
  whiskerColor: string;       // 'rgba(90,80,70,.65)'
  muzzleColor: string;        // 'rgba(50,42,35,.65)'
  browColor: string;          // 'rgba(60,55,50,.9)'
  teethColor: string;         // '#ffffff'
  mouthInterior: string;      // '#3a1520'

  // ─── Geometry Ratios (1.0 = current default) ─────
  headScale: number;          // multiplier on HEAD.radiusX/Y
  bodyScale: number;          // multiplier on BODY.radiusX/Y
  earSize: number;            // multiplier on ear triangle dimensions
  earPointiness: number;      // controls ear tip height (1.0 = current, 1.5 = pointed)
  eyeSpacing: number;         // multiplier on EYE.spacingX
  eyeSize: number;            // multiplier on EYE.baseWidth/baseHeight
  tailLength: number;         // multiplier on tail bezier control points
  tailThickness: number;      // multiplier on TAIL.strokeMain/Secondary
  bodyRoundness: number;      // interpolates body radiusX vs radiusY ratio
  whiskerLength: number;      // multiplier on WHISKER.length
  feetSize: number;           // multiplier on FEET.radiusX/Y
  noseSize: number;           // multiplier on NOSE.width/height
}

Default + Presets

const DEFAULT_CHARACTER: CharacterConfig = {
  // All current hardcoded values → 1.0 ratios
};

const CHARACTER_PRESETS: Record<string, CharacterConfig> = {
  classic: DEFAULT_CHARACTER,                    // Current black cat
  mochi:   { ...DEFAULT, bodyColor: '#f0ede8', earInnerColor: 'rgba(255,180,200,.4)', ... },
  marmalade: { ...DEFAULT, bodyColor: '#c86a28', noseColor: '#8b4513', ... },
  smokey:  { ...DEFAULT, bodyColor: '#5a5a6a', ... },
  midnight: { ...DEFAULT, bodyColor: '#1a1a3a', bodyHighlight: '#2a2a4a', ... },
};

Integration Points

Layer Change
engine/config/character.ts NEW — CharacterConfig type, DEFAULT, presets
engine/types.ts Add character: CharacterConfig to DrawContext
engine/renderer.ts Add setCharacterConfig() method, pass to DrawContext
engine/drawing/body.ts Replace #1a1a1a → dc.character.bodyColor, apply geometry ratios
engine/drawing/head.ts Replace hardcoded colors, apply headScale, earSize
engine/drawing/face.ts Replace all hardcoded colors with config reads
engine/drawing/effects.ts No color changes (particles are mood-colored, not character-colored)
vitto-avatar.component.ts New input: characterConfig: Partial<CharacterConfig>
vitto-mood.service.ts New signal: characterConfig()
services/character-config-api.service.ts NEW — CharacterConfigApiService for CRUD to /character-configs
Demo page Preset dropdown + color pickers + geometry sliders

Backend

New schema CharacterConfigSchema in backend-vitto-api/src/app/mind/schemas/:

New controller CharacterConfigController at /character-configs:

Seed 5 built-in presets on startup.


Slice 2: Disney Animation Core

Goal

Implement 6 Disney animation principles as behavior modules that enhance ALL moods — existing and new.

2a: Anticipation System

What: Before transitioning to a high-energy mood, Vitto briefly winds up in the opposite direction.

// engine/behaviors/anticipation.ts

interface AnticipationConfig {
  /** Moods that trigger anticipation on entry */
  triggerMoods: Set<string>;  // celebratory, excited, surprised, inspired, urgent
  /** Duration of wind-up phase (seconds) */
  windUpDuration: number;     // 0.15
  /** How much to overshoot properties in opposite direction */
  overshootFactor: number;    // 0.3
}

How it works:

New property added to MoodProperties:

Default values for ALL 14 existing moods (added as part of Slice 2):

2b: Follow-Through System

What: After a mood transition completes, body parts overshoot their target and settle back.

// engine/behaviors/follow-through.ts

interface FollowThroughState {
  earOvershoot: number;       // decaying oscillation
  headOvershoot: number;      // head position overshoot
  tailOvershoot: number;      // tail speed overshoot
  bodyOvershoot: number;      // body position overshoot
  browOvershoot: number;      // brow position overshoot
}

How it works:

2c: Overlapping Action

What: Different body parts move at different rates during transitions, not all in lockstep.

// New property groups with per-group transition speeds
const OVERLAP_GROUPS = {
  eyes:  { speed: 3.5, delay: 0 },      // Eyes react first (fastest)
  mouth: { speed: 3.0, delay: 0.03 },   // Mouth follows eyes closely
  brows: { speed: 2.5, delay: 0.05 },   // Brows slightly behind
  ears:  { speed: 2.0, delay: 0.08 },   // Ears are slower (heavier)
  body:  { speed: 1.8, delay: 0.1 },    // Body is heaviest, moves last
  tail:  { speed: 2.2, delay: 0.12 },   // Tail reacts last with spring
};

How it works (single-tT with per-group speed multipliers — preserves existing API):

2d: Secondary Action

What: Small supporting gestures that happen alongside the primary mood expression.

// engine/behaviors/secondary-action.ts

interface SecondaryAction {
  trigger: string;            // mood name or transition event
  getOverrides: (t: number) => Partial<IdleOffsets>;  // returns offsets the renderer blends additively
  duration: number;           // how long the action lasts (seconds)
  probability: number;        // 0-1, chance of firing per trigger
  cooldown: number;           // minimum seconds between fires
}
// NOTE: Actions return Partial<IdleOffsets> instead of mutating state via closures.
// The renderer blends active secondary action offsets additively into the idle offsets.
// This keeps the engine functional and debuggable.

const SECONDARY_ACTIONS: SecondaryAction[] = [
  // Ear flick when transitioning to cheerful
  { trigger: 'cheerful', probability: 0.8, cooldown: 2, action: earFlick },
  // Tail curl when thinking
  { trigger: 'thinking', probability: 0.6, cooldown: 3, action: tailCurl },
  // Quick nose scrunch when confused
  { trigger: 'confused', probability: 0.7, cooldown: 1.5, action: noseScrunch },
  // Chest puff when proud
  { trigger: 'proud', probability: 0.9, cooldown: 3, action: chestPuff },
  // Double-blink when surprised
  { trigger: 'surprised', probability: 1.0, cooldown: 0, action: doubleBlink },
  // Whisker bristle when determined
  { trigger: 'determined', probability: 0.8, cooldown: 2, action: whiskerBristle },
  // Yawn cycle when sleepy (periodic)
  { trigger: 'sleepy', probability: 0.3, cooldown: 8, action: yawnCycle },
];

How it works:

2e: Exaggeration

What: Stronger expressions for dramatic moods. The 27 properties already support this — we enhance the ranges.

Implementation:

2f: Staging

What: Vitto subtly shifts position and rotation to "present" the emotion to the viewer.

// New MoodProperties additions:
stageX: number;     // horizontal shift (-1 to 1, normalized)
stageY: number;     // vertical shift (-1 to 1, normalized)
stageRotate: number; // subtle body rotation (-0.1 to 0.1 radians)

Default values for ALL 14 existing moods (added as part of Slice 2):

Backend sync: The backend MoodProperties interface in mood-definition.schema.ts and BUILTIN_MOODS in mood-definition.service.ts must BOTH be updated with stageX, stageY, stageRotate, and anticipation fields (default 0 in schema, non-zero values for moods listed above).


Slice 3: 8 New Emotions

New MoodProperties Additions

Before defining moods, add new animatable properties:

// Added to MoodProperties in types.ts
stageX: number;        // staging horizontal shift
stageY: number;        // staging vertical shift
stageRotate: number;   // staging body rotation
anticipation: number;  // wind-up intensity (0-1)

New Mood Definitions (complete — ALL 31 properties specified)

CRITICAL: Every mood MUST define ALL properties including whiskerAngle, stageX, stageY, stageRotate, anticipation. Missing properties cause NaN in interpolation.

// engine/moods.ts — 8 new entries (indices 14-21)

thinking: {
  label: 'Thinking', sub: 'processing · working', accent: '#fbbf24',
  bg: [235, 175, 55],
  eyeH: .85, eyeW: .95, pupilR: .7, eyeSquish: .15, happyEye: 0, starEye: 0,
  smile: 0, mouthOpen: 0, mouthW: .7, teeth: 0, tongue: 0,
  browY: .15, browAngle: .2, browAsym: .3,
  earAngle: .1, headTilt: -.08, whiskerAngle: 0,
  blush: 0,
  sparkle: 0, sweat: 0, exclaim: 0, hearts: 0, question: 0, zzz: 0,
  bodyBounce: 0, tailSpeed: .4, bodySquash: .02,
  stageX: 0, stageY: -.05, stageRotate: .02, anticipation: 0,
},
surprised: {
  label: 'Surprised', sub: 'wow · startled', accent: '#facc15',
  bg: [255, 210, 75],
  eyeH: 1.4, eyeW: 1.2, pupilR: .4, eyeSquish: 0, happyEye: 0, starEye: 0,
  smile: 0, mouthOpen: .4, mouthW: .7, teeth: 0, tongue: 0,
  browY: -.5, browAngle: 0, browAsym: .1,
  earAngle: .5, headTilt: 0, whiskerAngle: .3,
  blush: .15,
  sparkle: 0, sweat: 0, exclaim: .6, hearts: 0, question: 0, zzz: 0,
  bodyBounce: .12, tailSpeed: 2.0, bodySquash: 0,
  stageX: 0, stageY: .1, stageRotate: 0, anticipation: .8,
},
frustrated: {
  label: 'Frustrated', sub: 'struggling · stuck', accent: '#f97316',
  bg: [240, 145, 40],
  eyeH: .6, eyeW: .95, pupilR: .5, eyeSquish: .4, happyEye: 0, starEye: 0,
  smile: -.7, mouthOpen: .05, mouthW: .6, teeth: .15, tongue: 0,
  browY: .6, browAngle: .6, browAsym: .15,
  earAngle: -.3, headTilt: 0, whiskerAngle: -.15,
  blush: .2,
  sparkle: 0, sweat: .5, exclaim: .3, hearts: 0, question: 0, zzz: 0,
  bodyBounce: .02, tailSpeed: 2.5, bodySquash: .08,
  stageX: 0, stageY: .03, stageRotate: 0, anticipation: .3,
},
grateful: {
  label: 'Grateful', sub: 'thankful · warm', accent: '#a78bfa',
  bg: [248, 195, 65],
  eyeH: 1.0, eyeW: 1.05, pupilR: 1.2, eyeSquish: 0, happyEye: .6, starEye: 0,
  smile: .6, mouthOpen: .1, mouthW: .9, teeth: 0, tongue: 0,
  browY: -.1, browAngle: -.2, browAsym: 0,
  earAngle: .15, headTilt: .06, whiskerAngle: .08,
  blush: .6,
  sparkle: .2, sweat: 0, exclaim: 0, hearts: .4, question: 0, zzz: 0,
  bodyBounce: 0, tailSpeed: .8, bodySquash: 0,
  stageX: 0, stageY: -.08, stageRotate: -.03, anticipation: 0,
},
mischievous: {
  label: 'Mischievous', sub: 'sly · sneaky', accent: '#34d399',
  bg: [245, 200, 60],
  eyeH: .8, eyeW: 1.0, pupilR: .85, eyeSquish: .2, happyEye: 0, starEye: 0,
  smile: .65, mouthOpen: .08, mouthW: 1.0, teeth: .1, tongue: .3,
  browY: -.05, browAngle: .3, browAsym: .8,
  earAngle: .2, headTilt: .1, whiskerAngle: .2,
  blush: .15,
  sparkle: .15, sweat: 0, exclaim: 0, hearts: 0, question: 0, zzz: 0,
  bodyBounce: .04, tailSpeed: 1.8, bodySquash: .02,
  stageX: .03, stageY: 0, stageRotate: .02, anticipation: 0,
},
determined: {
  label: 'Determined', sub: 'resolute · locked', accent: '#f59e0b',
  bg: [242, 165, 35],
  eyeH: .75, eyeW: 1.0, pupilR: .6, eyeSquish: .35, happyEye: 0, starEye: 0,
  smile: -.1, mouthOpen: 0, mouthW: .7, teeth: 0, tongue: 0,
  browY: .3, browAngle: .5, browAsym: 0,
  earAngle: .4, headTilt: 0, whiskerAngle: .12,
  blush: 0,
  sparkle: 0, sweat: 0, exclaim: 0, hearts: 0, question: 0, zzz: 0,
  bodyBounce: .01, tailSpeed: .5, bodySquash: .03,
  stageX: 0, stageY: -.03, stageRotate: -.02, anticipation: .2,
},
listening: {
  label: 'Listening', sub: 'attentive · present', accent: '#60a5fa',
  bg: [242, 185, 48],
  eyeH: 1.1, eyeW: 1.05, pupilR: 1.15, eyeSquish: 0, happyEye: 0, starEye: 0,
  smile: .08, mouthOpen: 0, mouthW: .75, teeth: 0, tongue: 0,
  browY: -.05, browAngle: 0, browAsym: .1,
  earAngle: .45, headTilt: .05, whiskerAngle: .05,
  blush: 0,
  sparkle: 0, sweat: 0, exclaim: 0, hearts: 0, question: 0, zzz: 0,
  bodyBounce: 0, tailSpeed: .6, bodySquash: 0,
  stageX: 0, stageY: 0, stageRotate: 0, anticipation: 0,
},
excited: {
  label: 'Excited', sub: 'hyped · electric', accent: '#f472b6',
  bg: [255, 210, 65],
  eyeH: 1.1, eyeW: 1.1, pupilR: .3, eyeSquish: 0, happyEye: .7, starEye: 0,
  smile: 1, mouthOpen: .35, mouthW: 1.3, teeth: .2, tongue: .4,
  browY: -.25, browAngle: 0, browAsym: 0,
  earAngle: .35, headTilt: .06, whiskerAngle: .25,
  blush: .7,
  sparkle: .7, sweat: 0, exclaim: 0, hearts: .3, question: 0, zzz: 0,
  bodyBounce: .18, tailSpeed: 3.2, bodySquash: 0,
  stageX: 0, stageY: .05, stageRotate: 0, anticipation: .9,
},

New Particle Types

Particle Used By Description
gears thinking 2-3 interlocking gear outlines rotating slowly near head-top
steam frustrated small puffs rising from top of head, expanding and fading
lightbulb inspired (enhanced) brief lightbulb flash on eureka transitions (one-shot, not continuous)

Backend Seeding

Add 8 new entries to BUILTIN_MOODS in mood-definition.service.ts. Update seedBuiltInMoods() to handle incremental seeding (existing moods untouched, new ones added).

Frontend Registrations


Slice 4: Accessory System

Architecture

engine/
  accessories/
    types.ts          — AccessoryDef, AnchorPoint, AccessoryState
    registry.ts       — ACCESSORY_DEFINITIONS map
    manager.ts        — AccessoryManager (spring physics, lifecycle)
    drawing/
      wearables.ts    — drawGlasses, drawHat, drawScarf, drawCape, drawBowTie, drawCrown, drawBadge
      props.ts        — drawCoffee, drawBook, drawWand, drawPhone, drawToolbox, drawUmbrella

Anchor Points

// NOTE: Vitto has no arms/hands. "body-side-left" and "body-side-right" are positioned
// at the sides of the body ellipse (CX ± BODY.radiusX, CY + BODY.offsetY).
// Props "hover" beside the body — no visible limb needed. This matches the
// cartoon aesthetic where items appear held without explicit arms.
type AnchorPoint = 'head-top' | 'head-left' | 'head-right' | 'nose-bridge' | 'body-front' | 'body-side-left' | 'body-side-right' | 'body-back';

interface AccessoryAnchor {
  point: AnchorPoint;
  offsetX: number;
  offsetY: number;
  followRotation: boolean;  // rotate with head tilt
  lagFactor: number;        // 0=locked, 1=full follow-through lag
}

Accessory Definition

interface AccessoryDef {
  id: string;
  name: string;
  category: 'wearable' | 'prop';
  anchor: AccessoryAnchor;
  triggerMoods?: string[];    // auto-show on these moods
  manualOnly?: boolean;       // never auto-triggered
  draw: (ctx: CanvasRenderingContext2D, state: AccessoryState) => void;
  springConfig?: { stiffness: number; damping: number };
}

Priority Accessories (Phase 1)

Priority Accessory Category Anchor Trigger Moods
1 Reading glasses wearable nose-bridge analytical, thinking
2 Party hat wearable head-top celebratory, excited
3 Coffee cup prop body-side-right thinking
4 Crown wearable head-top proud
5 Magic wand prop body-side-right inspired
6 Nightcap wearable head-top sleepy
7 Toolbox/wrench prop body-side-left determined, focused
8 Sunglasses wearable nose-bridge proud, mischievous
9 Bow tie wearable body-front focused
10 Umbrella prop body-side-right apologetic

Physics

Each accessory has its own SpringState for follow-through:

Renderer Integration

// In renderer.ts render() method, after drawHead():
if (this.accessoryManager) {
  this.accessoryManager.update(dt, this.state, headPosition, headRotation);
  this.accessoryManager.draw(this.ctx);
}

Public API

class VittoRenderer {
  setAccessory(id: string, visible: boolean): void;
  getActiveAccessories(): string[];
  setAccessoryAutoTrigger(enabled: boolean): void;
}

Backend

New schema AccessoryConfigSchema:

Endpoint: GET /character-configs/:name/accessories — returns enabled accessories for a character config.


Slice 5: Environmental Effects

Architecture

engine/
  environment/
    types.ts         — EnvironmentLayer, WeatherType, etc.
    manager.ts       — EnvironmentManager (particle pools, layer ordering)
    weather.ts       — sun rays, rain, snow, lightning, clouds
    stage.ts         — spotlight cone, dim ambiance, dramatic lighting
    floating.ts      — thought symbols: gears, lightbulbs, music notes, etc.
    ground.ts        — flowers, cracks, sparkle trails, grass

Layer Ordering (draw order)

  1. Background (existing)
  2. Weather background layer (rain, snow, clouds — behind Vitto)
  3. Ground effects (flowers, cracks — at feet level)
  4. Shadow (existing)
  5. Tail (existing)
  6. Body (existing)
  7. Accessories — body layer (bow tie, badges)
  8. Head + face (existing)
  9. Accessories — head layer (glasses, hats)
  10. Particles (existing, enhanced)
  11. Floating thought symbols (above head)
  12. Weather foreground layer (rain drops in front, lightning flash)
  13. Stage effects (spotlight cone overlay)

Mood-to-Environment Mapping

Mood Weather Stage Floating Ground
cheerful sunshine rays warm glow — flowers bloom
celebratory confetti spotlight — sparkle burst
apologetic light rain dim — —
sleepy gentle snow dim, warm — —
urgent lightning flash red pulse — cracks
inspired — spotlight pulse lightbulbs sparkle trail
thinking — — gears —
analytical — — gears (slow) —
frustrated storm clouds red tint — cracks
playful — — music notes flowers
excited sunshine + confetti bright spotlight — sparkle burst
determined — focused beam — —
grateful warm rays golden glow hearts (soft) flowers (gentle)

Particle Pool

Environmental effects use a shared particle pool (max 100 particles) to avoid GC pressure:

class ParticlePool {
  private pool: Particle[] = [];
  acquire(): Particle;
  release(p: Particle): void;
}

Each particle: { x, y, vx, vy, life, maxLife, size, rotation, type, color }.

Coexistence with existing effects.ts: The existing particle effects (sparkles, hearts, question marks, sweat, exclamation, zzz) are drawn procedurally each frame with no lifecycle state — they stay as-is. The new ParticlePool is ONLY for environmental effects (weather, ground). Two rendering paradigms coexist intentionally: existing effects are mood-property-driven (immediate), environmental particles are lifecycle-driven (spawn, live, die).

Renderer Integration

// In renderer.ts render():
this.environmentManager.updateWeatherBg(dt, state);  // before drawShadow
this.environmentManager.drawWeatherBg(ctx);
// ... existing draw calls ...
this.environmentManager.updateWeatherFg(dt, state);  // after drawHead
this.environmentManager.drawFloating(ctx);
this.environmentManager.drawWeatherFg(ctx);
this.environmentManager.drawStage(ctx);

Slice 6: Backend + Refinement

Enhanced Mood Inference

Update ALL locations in MoodInferenceService (APIs/backend-vitto-api/src/app/mind/mood-inference.service.ts):

Add weighted scoring for new moods:

Character Config API

Full CRUD for CharacterConfig:

Accessory API

MoodDefinition Schema Updates

Add to existing schema:

@Prop({ type: Object, default: {} })
environmentConfig?: {
  weather?: string;
  stage?: string;
  floating?: string[];
  ground?: string;
};

@Prop({ type: [String], default: [] })
accessories?: string[];  // accessory IDs triggered by this mood

@Prop({ type: Number, default: 0 })
anticipation?: number;  // 0-1

@Prop({ type: Object, default: {} })
staging?: {
  x?: number;
  y?: number;
  rotate?: number;
};

Demo Page Updates

Demo page component: apps/gograb-dev/src/app/pages/vitto-avatar-dev/vitto-avatar-dev.component.ts Route: http://localhost:6300/demos/vitto-avatar

Each slice adds its controls to the demo page:

Slice Demo Addition
1 Preset dropdown, color pickers (9 colors), geometry sliders (12 values)
2 "Expressiveness" slider, checkboxes per Disney principle, transition speed control
3 8 new mood cards in the grid (auto from MOOD_DEFINITIONS)
4 Accessory toggle panel (checkboxes per accessory, auto-trigger on/off toggle)
5 Environment toggles per layer (weather, stage, floating, ground)
6 N/A (backend-only)

vitto-chat Integration

Slice Chat Change
1 VittoAvatarComponent accepts [characterConfig] input. Load from backend at init.
2 Automatic — Disney behaviors enhance all existing mood transitions
3 VittoEmotionService updated with new heuristics + PHASE_MOOD_MAP additions
4 Accessories auto-triggered by mood. No chat code change needed beyond renderer.
5 Environment auto-triggered by mood. Same as above.
6 Inference returns new moods. MoodInferenceService updated.

Testing Strategy

Each slice is self-validating on the demo page:

  1. Slice 1: Switch presets → character colors/proportions change, animations unchanged
  2. Slice 2: Trigger mood transitions → observe anticipation wind-up, ear/head overshoot, staggered movement
  3. Slice 3: Click new mood cards → see 8 new expressions with correct particles
  4. Slice 4: Toggle accessories → see glasses/hat/coffee appear, bounce with head movement
  5. Slice 5: Trigger moods → see rain, sunshine, floating gears, ground flowers
  6. Slice 6: Send chat messages → backend infers new moods correctly

Risk Mitigations

Risk Mitigation
Overlapping action breaks transition smoothness Per-group tT still uses same easing; if jerky, fall back to single tT with per-group speed multiplier
Accessories clip with head/body Each accessory has z-index (draw order) and clip-safe bounds; demo page shows bounds overlay for debugging
Too many particles = frame drops Particle pool capped at 100; profile on demo page; environment effects have per-frame budget
New moods confuse LLM inference Inference prompt has explicit examples per mood; heuristic fallback always works
CharacterConfig bloat Config is flat object (~26 fields), not nested. Serializes to <1KB JSON