# evOo Character — Poses & Runtime Mapping ## Brand lock (non-negotiable) > **Oo trademark = asymmetric Kulleraugen + size difference** All Oo's — `EvooMascot`, `BotAvatar`, Wordmark, every pose — must stay instantly recognizable through: 1. **Consistent size asymmetry** between the two eyes (big O vs small o). Never symmetrize. Never equal radii. 2. **Kulleraugen** — big, round, glossy googly/cute sclera with dark pupils and catchlights. Personality (mouth, hands, accessories, poses) builds **on top** of this signature. Never replace with Grok-bot-style emoji faces. | Constant | Value | Role | |----------|-------|------| | `EVOO_EYE_BIG_R` | 30 | Capital O sclera | | `EVOO_EYE_SMALL_R` | 18.54 | Lowercase o sclera (~φ⁻¹ of big) | | `EVOO_EYE_ASYMMETRY_MIN_RATIO` | 1.45 | CI guard — catches accidental symmetrization | Enforced in `evoo-character/brand-lock.ts` + `brand-lock.test.ts`. Poses may adjust `eyeOpen` (squint), `gaze`, brows — **not** eye size parity. --- The evOo mascot is **not** a generic emoji face. Identity comes from the **Oo geometry** (large O + small o Kulleraugen, capsule-glass hands) shared by `EvooMascot` and `BotAvatar`. ## Architecture ``` Runtime signal (status / voice phase / explicit pose) → resolveEvooPose / mapVoicePhaseToPose (evoo-character/pose-map.ts) → EvooMascot → EvooCharacterSvg (eyes + mouth + hands + accessory) BotAvatar = persona shape silhouette + EvooMascot overlay (same soul) Wordmark = calm EvooMascot + text, trackPointer="none" ``` Legacy pose names (`typing`, `wave`, `peek`, `profit`) normalize to canonical states via `normalizeEvooPose`. ## Pose library | Pose | Eyes / mouth / hands | Accessory | Typical trigger | |------|----------------------|-----------|-----------------| | **idle** | soft smile, rest hands | — | Default; sidebar Wordmark | | **listening** | tiny-o mouth, attentive gaze | headset | Voice phase `listening`; status `waiting_input` | | **speaking** | speaking mouth, conductor hands | — | Voice phase `speaking`; streaming text | | **thinking** | flat mouth, upward gaze | thought bubble | Voice phase `thinking` | | **working** | flat mouth, typing hands | — | Status `running` / `queued`; live message bubble | | **reading** | tiny-o, clipboard hold | clipboard | Status `booting`; onboarding („liest sich ein“); curator persona idle | | **analyzing** | narrowed eyes, magnifier | magnifier | CTO / quant persona idle | | **skeptical** | raised brow, side gaze | — | Manual / future tool-review moments | | **curious** | wide eyes, peek hands | — | Builder persona idle; legacy `peek` | | **delighted** | grin, sparkle pupil | spark | Delight / achievement beats | | **concerned** | worried brow | — | Life-inbox persona idle | | **waiting** | half-lidded | — | Work-inbox persona idle | | **success** | grin + sparkle, nod hands | — | Click delight; legacy `profit` | | **greeting** | grin, wave hand | — | Empty thread hero; legacy `wave` | | **asleep** | closed lids | zzz | Mascot idle timeout (pointer tracking modes) | | **error** | worried mouth | spark | Status `error` / `failed` | | **collaborating** | soft smile, dual hands | tool | Voice awareness (subagent / handoff) | ## Runtime maps ### `mapVoicePhaseToPose(phase, collaborating?)` Used by `VoiceStage` → `BotAvatar.pose` via `personaMascotPose`. | Input | Pose | |-------|------| | `listening` | `listening` | | `speaking` | `speaking` | | `thinking` | `thinking` (or `collaborating` when awareness event active) | ### `mapBotStatusToPose(status, streaming?)` Used by `BotAvatar` when no explicit `pose` prop. | Status | Pose | |--------|------| | `running`, `queued`, `leased` | `working` | | `waiting_input` | `listening` | | `error`, `failed` | `error` | | `booting` | `reading` | | `idle` / unknown | `idle` | | streaming flag | `working` | ### `resolveEvooPose({ pose, status, streaming, voicePhase, collaborating })` Priority: **voicePhase → explicit pose → streaming → status → idle**. ### Shell / chrome call sites | Surface | Pose source | |---------|-------------| | **Oo Stage (1:1 text hero)** | `personaTextPose(persona, resolveTextStagePhase(...), collaborating)` | | **Oo Group Stage (multi-Oo)** | Active speaker → full hero; ensemble row for the rest (working pulse only — same chrome as 1:1) | | Empty thread (legacy / non-encounter) | `greeting` (interactive, hover tracking) | | Encounter transcript trail | Caption labels only — identity lives on Oo Stage | | Live streaming message header | `working` (hidden in encounter mode) | | Sidebar Wordmark | `idle` (click cycles greeting → success) | | Bot picker / command palette | `mapBotStatusToPose(status)` | | Voice Stage hero | `personaMascotPose(persona, phase, collaborating)` | ### Gesamterfahrung Stage + transcript-as-trace + sound language form one coherent encounter. Poses on the stage reflect runtime status (`working`, `reading`, `listening`, …); the transcript is the audit trail, not the hero. ### No Wunderwuzzi Every Oo is a **specialist**. Persona copy and poses assume a defined lane; unmatched bots use the `specialist` archetype (not a generic companion). Crew collaboration happens in **Gruppenunterhaltungen**, not one omnibus agent. **The ensemble should feel stronger than any single Oo** — emergent capability from differences (`docs/PRODUCT.md`). Group chats need **fun factor** — friction and banter when useful, never sterile committee tone (`docs/GROUP-CONVERSATIONS.md`). ### Personalization (persona selection) Lifestyle-agnostic: the same archetype system serves any user. Onboarding infers **help & vibe** (geek-peer vs quiet-doer) and **suggested lanes** — spawn/select Oos to match, never a single default personality. Voice copy for `specialist` models both modes: depth when wanted, execution without lecturing. Details: `docs/PERSONA-SELECTION.md`. **Accessibility of ambition:** skill **floor** = grandma-simple entry; skill **ceiling** = expert-grade per specialty (not mediocre-at-everything Wunderwuzzi). Shell and onboarding stay plain; specialists carry domain depth. ### Persona idle layer `mapPersonaArchetypeToIdlePose` sets the fallback when voice is not in an active phase — e.g. engineer → `working`, curator → `reading`. ## Design constraints - **Brand lock** — `EVOO_OO_TRADEMARK`; asymmetric Kulleraugen always; see `brand-lock.ts`. - **No Grok-style emoji ripoff** — ownable Oo eyes, glass capsule hands, soft motion (`evoo-breathe-awake`, `evoo-wave-harmonic`). - **Wordmark** stays pointer-calm (`trackPointer="none"`) for chrome. - **Design tokens** from the shared ui-web palette; persona tint only on Voice bloom, not eye geometry. ## Files | Path | Role | |------|------| | `evoo-character/brand-lock.ts` | Trademark constants + asymmetry guard | | `evoo-character/types.ts` | `EvooPose`, accessories, mouth styles | | `evoo-character/poses.ts` | `POSE_DEFINITIONS` per pose | | `evoo-character/EvooCharacterSvg.tsx` | SVG layers (eyes, mouth, hands, accessories) | | `evoo-character/pose-map.ts` | Runtime mappers + `resolveEvooPose` | | `evoo-brand.tsx` | `EvooMascot`, `Wordmark`, blink/saccade life | | `bot-avatar.tsx` | Shape + mascot composite |