cynarAI 5ae8f15931 feat(web): Ingo UX — Proaktivität, Oo-Gesicht, dezentes Audio (#76)
* feat(web): Ingo UX — proactivity signals, Oo face richness, soft audio

Priority S: Heute Brief buckets (Needs you ≤3 / Doing / Done), roster doing-verbs
and Braucht-dich chips, sidebar badge on Heute, need-you ping on waiting states.

Priority A/B: Web Audio cues (app-start shimmer, interact tick, success-resolve,
need-you ouh), reaction vocals with cooldown, optional eOo cold-open voice mark.
Oo eyes gain iris/specular detail and asymmetric blink; hands reduced to rest
except punctuation poses; speaking mouth animates.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): biome format and unused import in HeuteBrief

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): a11y and import order for Heute badge

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* docs+motion: Motion God craft — pose stagger, audio bus, ducking

Add MOTION-EVOO-CHARACTER.md (Brand Soul brief): eyes lead, mouth ~80%,
hands punctuation, 40–80 ms eye→mouth lag, 220–320 ms springs, SFX LUFS
and duck-under-TTS/PTT, nod mh/Mhh, reduced-motion rules.

Implement motion-tokens, staggered mouthPose in EvooMascot, shared
oo-audio-bus with voice-lane ducking, mint→gold success, hm nod vocal.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): align conversation roster with bot-only sidebar groups after rebase

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 14:39:40 +02:00

140 lines
7.2 KiB
Markdown

# evOo Character — Poses & Runtime Mapping
Motion timing, SFX levels, and expressor hierarchy: **[MOTION-EVOO-CHARACTER.md](./MOTION-EVOO-CHARACTER.md)** (Brand Soul / Motion God).
## 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 |