# Munder Difflin — Design System > The aesthetic is **Animal Crossing × Earthbound × SNES menu UI**. Pixel-snapped, chunky, friendly. Every UI element should feel like it could appear in a Nintendo game from 1995–2005. This document is canonical: any new component must derive from these tokens. --- ## 1. Principles 1. **Pixel-snapped everything.** No half-pixels. No CSS blur. No floaty `border-radius`. The grid is real. 2. **Chunky over slick.** Borders are visible, panels have weight, buttons feel pressable. If a component could exist on iOS 17, it's wrong. 3. **Limited palette.** Each screen uses ≤ 8 colors. Each sprite uses ≤ 5. Restraint creates the look. 4. **Information through motion.** An avatar walking *is* the status. Don't add a progress bar if the walk already communicates. 5. **Friendly, never cute-for-its-own-sake.** Copy is short and human. Avoid baby talk. Think Tom Nook's signage, not Saturday morning cartoons. 6. **Read like a 90s game manual.** Heavy use of named panels, framed groups, status windows. Information has a *home*. ### What we are NOT - Not glassmorphism. Not Material. Not iOS. Not "modern web." Not "retro filter on a normal app." - Not pixel art for its own sake — every pixel choice serves a UX function. --- ## 2. References (study these) | Reference | What we steal | |---|---| | **Animal Crossing: New Leaf / NH** | Villager characters, soft palette, friendly copy, dialog boxes | | **Earthbound / Mother 3** | Status windows, multi-layer panel borders, vibrant flat colors | | **Stardew Valley** | Font choice, tile floors, sprite proportions | | **Pokémon B/W & X/Y** | Clean info panels, summary screens | | **Mario Kart 8 (HUD only)** | Coin/timer chips, vibrant accents on neutral backgrounds | | **Undertale** | Terminal-style typography for system feedback | | **Stardew Concerned Ape sprites** | Walk cycles, station design | | **SNES Final Fantasy VI menus** | Three-layer panel borders, cornered headers | --- ## 3. Color system All colors specified in `#RRGGBB`. Token names use a `--cth--` pattern (CSS variables) and a parallel TypeScript `tokens.colors..` (objects). ### 3.1 Base (panels, floors, surfaces) | Token | Hex | Use | |---|---|---| | `cream-50` | `#FFFDF5` | Lightest highlight, dialog box innermost | | `cream-100` | `#FFF8E7` | Default panel fill | | `cream-200` | `#F4E9C7` | Inset / alt row | | `cream-300` | `#E8D9A0` | Disabled fill | | `paper-100` | `#FCFAF0` | Terminal background | | `paper-200` | `#F0EAD2` | Subtle panel variant | ### 3.2 Ink (text, outlines) | Token | Hex | Use | |---|---|---| | `ink-900` | `#1A1320` | Body text, outer borders. **Never use `#000`.** | | `ink-700` | `#3D2E4A` | Secondary text, middle border layer | | `ink-500` | `#6B5878` | Tertiary text, disabled borders | | `ink-300` | `#A899B5` | Placeholder, hairline dividers | | `ink-100` | `#D9CFE0` | Subtle separators | ### 3.3 Agent accents (vibrant, character colors) Saturated and warm. Each avatar gets one — the strip badge, the agent's chat selection highlight, their nameplate. | Token | Hex | Mnemonic | |---|---|---| | `coral` | `#FF6B6B` | Mario red | | `coral-light` | `#FFB4B4` | | | `mint` | `#6BCF7F` | 1UP green | | `mint-light` | `#B4E5BD` | | | `sky` | `#4ECDC4` | Wind Waker ocean | | `sky-light` | `#A8E6E0` | | | `lemon` | `#FFD93D` | Pikachu | | `lemon-light` | `#FFEC99` | | | `lilac` | `#B197FC` | Psychic-type | | `lilac-light` | `#D6C5FF` | | | `peach` | `#FFA07A` | Princess Peach | | `peach-light` | `#FFD0B5` | | ### 3.4 Status (system semantics) | Token | Hex | Means | |---|---|---| | `status-idle` | `#A899B5` | Agent at desk, awaiting | | `status-thinking` | `#4ECDC4` | Reasoning + en route to a station | | `status-working` | `#FFD93D` | At a station, using a tool | | `status-waiting` | `#6C8EF5` | Worker stalled on god or another agent | | `status-blocked` | `#FF6B6B` | Notification fired, needs user | | `status-success` | `#6BCF7F` | Just finished | | `status-ghost` | `#D9CFE0` | Pane closed, fading out | | `status-compacting` | `#9B7EDE` | Boxing up context (PreCompact/PostCompact) | | `status-looping` | `#FF9F43` | Circuit breaker armed — runaway | | `status-typing` | `#E8A33D` | **Not an agent state.** *You* have unsent text on that agent's prompt, which is holding its message queue | ### 3.5 World (the floor itself) | Token | Hex | Use | |---|---|---| | `grass-light` | `#D4EAB0` | Light tile | | `grass-dark` | `#B5D589` | Dark tile (checkerboard) | | `wood-light` | `#E5C896` | Room floor light tile | | `wood-dark` | `#C9A66B` | Room floor dark tile | | `path` | `#E8D8B0` | Pathways between rooms | | `wall` | `#8B6F47` | Room walls (3px stroke) | ### 3.6 Gradient bans No gradients except: vertical 2-stop on title bars (`cream-100` → `cream-200`). That's it. Every other surface is flat. --- ## 4. Typography Three fonts, all loaded from Google Fonts. **Every text element must declare a font from this set.** No system fonts. | Role | Family | Why | |---|---|---| | **Display** | `Press Start 2P` | NES-iconic, headings only, 8/12/16 px | | **UI** | `Pixelify Sans` | Modern readable pixel font, body/labels | | **Mono / terminal** | `VT323` | CRT terminal feel, large x-height | ### 4.1 Type scale (all px integers) | Token | Size | Line height | Use | |---|---|---|---| | `display-lg` | 16 / `Press Start 2P` | 24 | App title, screen titles | | `display-md` | 12 / `Press Start 2P` | 20 | Section headers, modal titles | | `display-sm` | 8 / `Press Start 2P` | 12 | Badges, chip labels | | `body-lg` | 18 / `Pixelify Sans` | 24 | Primary body | | `body-md` | 16 / `Pixelify Sans` | 20 | Default UI text | | `body-sm` | 14 / `Pixelify Sans` | 18 | Secondary, captions | | `mono-md` | 16 / `VT323` | 20 | Terminal stream | | `mono-sm` | 14 / `VT323` | 18 | Inline log lines, paths | ### 4.2 Weight All fonts ship in a single weight. **Never bold.** For emphasis: use color (`ink-900` vs `ink-500`) or a chip/badge. ### 4.3 Case - Display fonts: **TITLE CASE**, never ALL CAPS (Press Start 2P is already loud). - UI fonts: Sentence case. - Status badges: lowercase ("working", "thinking", "blocked"). ### 4.4 Letter spacing - Press Start 2P: `0` (already wide enough). - Pixelify Sans: `0`. - VT323: `0`. Never add letter-spacing — it breaks the pixel grid. --- ## 5. Spacing & grid Base unit: **4 px**. Every margin, padding, gap, position must be a multiple of 4. No exceptions outside sprite-internal art. | Token | px | |---|---| | `space-0` | 0 | | `space-1` | 4 | | `space-2` | 8 | | `space-3` | 12 | | `space-4` | 16 | | `space-5` | 24 | | `space-6` | 32 | | `space-7` | 48 | | `space-8` | 64 | ### Layout - Main window minimum: 1280 × 800. - Standard gutter: 16 px (`space-4`). - Panel internal padding: 12 px (`space-3`). - Floor canvas: dynamically sized, but tile grid is 32 × 32 px (one game tile). ### Pixel snapping - All `transform: translate(...)` values must be integers. - `imageRendering: pixelated` on every `` and any rendered sprite ``. - Zoom levels are integer scales (1×, 2×, 3×) — never 1.5×. --- ## 6. Borders & panels The SNES three-layer border is foundational. Every panel uses it. ### 6.1 Anatomy ``` ┌────────────────────────────────┐ ← outer: ink-900, 2px │┌──────────────────────────────┐│ ← middle: cream-200, 2px ││┌────────────────────────────┐││ ← inner: ink-700, 1px │││ │││ │││ panel content │││ ← fill: cream-100 │││ │││ ││└────────────────────────────┘││ │└──────────────────────────────┘│ └────────────────────────────────┘ ``` CSS implementation: nested `box-shadow inset` rather than nested DOM. No `border-radius`. Total border weight: 5 px on each side. ### 6.2 Panel variants | Variant | Outer | Middle | Inner | Fill | Use | |---|---|---|---|---|---| | `panel/default` | `ink-900` | `cream-200` | `ink-700` | `cream-100` | Standard | | `panel/inset` | `ink-700` | `cream-100` | `ink-500` | `cream-200` | Recessed area | | `panel/active` | `ink-900` | accent | `ink-700` | `cream-100` | Selected agent, focused input | | `panel/terminal` | `ink-900` | `ink-700` | `ink-500` | `paper-100` | Terminal background | | `panel/dialog` | `ink-900` | `cream-200` | `ink-700` | `cream-50` | Modals, notifications | ### 6.3 Corner cuts Optional. Adds an 8-bit "rounded corner" feel by clipping 2 px squares from each corner. Implementation: SVG `clip-path` or four absolute-positioned 2 × 2 squares matching the parent background. Reserved for: dialogs, the main app frame. ### 6.4 Drop shadow The only shadow allowed is a **hard offset**: 4 px right, 4 px down, `ink-900` at 25% opacity. No blur. Used on: modals, toasts, dragging avatars. ```css filter: drop-shadow(4px 4px 0 rgba(26, 19, 32, 0.25)); ``` Or as a sibling block element absolutely positioned 4 px offset. --- ## 7. Components Every component is spec'd by its anatomy, states, props, and example. ### 7.1 `` Foundational container. ``` Props: variant 'default' | 'inset' | 'active' | 'terminal' | 'dialog' title? string — renders titlebar accent? AccentColor — applies to title bar + middle border if active children ReactNode States: default — as drawn hover — no change (panels don't hover; only buttons do) focused — middle border becomes accent, 1px wider ``` ### 7.2 `` 3D pressable. Defaults to chunky. ``` Props: variant 'primary' | 'secondary' | 'ghost' | 'destructive' size 'sm' (24h) | 'md' (32h) | 'lg' (40h) icon? IconName children ReactNode States: default — top edge bright, bottom edge dark hover — fill becomes light variant of variant color active — translate(0, 2px), bottom edge disappears (pressed) disabled — fill = cream-300, ink-500 text, no press affordance focus — 2px ink-900 outline at +2px offset Primary: fill = ink-900, text = cream-50 Secondary: fill = cream-100, text = ink-900, border = ink-900 Ghost: no fill, border = ink-500, text = ink-700 Destructive: fill = coral, text = cream-50 ``` ### 7.3 `` (status chip) ``` Props: status 'idle' | 'thinking' | 'working' | 'waiting' | 'blocked' | 'success' | 'ghost' | 'compacting' | 'looping' | 'typing' label string icon? IconName Anatomy: 8 px tall pixel dot + space-1 + lowercase Pixelify Sans 14 px. Color: status palette. Background: status-color at 20% opacity over cream-100. ``` Labels are not the token names — they read from the *user's* side. `blocked` shows "needs you" (reserved for the god agent waiting on you), `waiting` stays "waiting" (honest about the worker being stalled on another agent), and `typing` shows **"your draft"** — it is your text on the prompt, not the agent's, and it is why that agent's message queue is not draining. See [`docs/message-queue.md`](./docs/message-queue.md). ### 7.4 `` (bottom strip) ``` Width: 200 px. Height: 80 px. Panel variant: default. Top row: sprite portrait (32 × 32) + name (body-md) + status badge. Mid row: current project name (body-sm, ink-500), current tool/action. Bottom row: 8-segment progress dots (filled = work units in current step). Selected state: panel/active variant with agent's accent color. ``` ### 7.5 `` ``` Anatomy: PixelPanel inset variant. Contains: - prompt prefix "> " (mono-md, agent's accent) - text input (mono-md, no border) - send button (primary, size-md, icon: arrow) - mode tabs above: [Free] [/skill] [Quick] States: - typing — caret = 2px wide block, blinks 500ms - busy — input border tints lemon (agent is working) - blocked — input border tints coral with helper text ``` ### 7.6 `` ``` PixelPanel terminal variant. xterm.js with theme: background = paper-100 foreground = ink-900 cursor = coral selection = lemon-light ansi colors: see §11 Font: VT323 16 px. Top edge: 2px dashed ink-300 line, label "live · pipe-pane" in mono-sm. ``` ### 7.7 `` (notification) ``` PixelPanel dialog variant, 320 px wide. Top: 12 px stripe of agent's accent color. Body: avatar portrait (24 × 24) + message (body-md, max 3 lines). Actions row: two buttons max. Drop shadow (hard 4/4). Slide in from top-right, snap (no easing past first frame). Auto-dismiss only for non-blocking. Blocking notifications wait for user. ``` ### 7.8 `` (signpost over each project room) ``` Signpost: 8 px wood post + plank. Plank: cream-200 fill, ink-900 outline, display-sm text. Reads "project: ". Positioned at top-left of each room. Functionally a pixel sprite, not HTML. ``` ### 7.9 `` ``` Slides in from right (240 ms snap, no easing). Width: 480 px. Title bar: display-md + close button. Sections (collapsible): Identity, Goal, Runtime, Skills, MCP, Hooks. Each section header: ink-900 + 2px underline + accent dot. ``` ### 7.10 `` ``` PixelPanel dialog variant. Backdrop: ink-900 @ 60% opacity. NO blur. Position: centered. Snap-in (200 ms ease-out scale 0.92 → 1.0). Always has close button top-right and at least one action button bottom-right. ``` --- ## 8. Avatar sprites The whole product hinges on these. Spec is exact. ### 8.1 Grid - **24 × 24 px** sprite cell. - Walk cycle: **4 frames** (idle, step-A, idle, step-B). Each frame 24 × 24. - Animation: 8 fps (125 ms per frame). - Directions: **4 cardinal** (down, up, left, right). Diagonals are computed at runtime by selecting the dominant axis. ### 8.2 Anatomy ``` 0123456789012345678901234 (x) ▓▓▓▓▓▓▓▓ row 4-5: hair ▓░░░░░░░░░▓ row 6-9: head, skin ▓░██░░░██░▓ row 8: eyes ▓░░░░░░░░░▓ row 10: mouth/cheeks ▓▓▓▓▓▓▓▓ row 11: jaw ▓░░░░░░░▓ row 12-17: torso, outfit ▓░██░██░▓ outfit detail ▓░██░██░▓ ▓░░░░░░░▓ ▓░░░░░▓ row 18-22: legs ▓░░ ░░▓ walk: alternating ▓▓ ▓▓ feet ``` ### 8.3 Per-avatar palette (4 colors max) Each avatar uses **exactly 4 sprite colors** (plus `ink-900` outline = 5 total slots): | Slot | Role | |---|---| | `skin` | face, hands | | `hair` | top of head | | `primary` | main outfit color | | `accent` | outfit detail (collar, belt) | The agent's **accent palette token** (from §3.3) drives `primary`. ### 8.4 Starter character archetypes Built-in sprite presets. Each has its own outfit pattern. | Archetype | Vibe | Outfit notes | |---|---|---| | `scientist` | Lab researcher | White coat panel down center, square glasses (2px black on row 8) | | `wizard` | Magic mode | Pointed hat (rows 2-5 above head), star on chest (row 14) | | `astronaut` | Explorer | Helmet (3px ring around head), antenna pixel on top | | `cat-villager` | Animal Crossing | Triangle ears (rows 3-4), tail visible behind torso | | `hacker` | Hoodie | Hood drape down sides of head, headphones (2px black on rows 6-7 sides) | | `ninja` | Stealth | Mask covering lower face, ninja headband | ### 8.5 Walk cycle Frame 0 (idle): feet aligned, slight droop (y+0) Frame 1 (step-A): left foot raised 1 px (y-1), right foot planted (y+0) Frame 2 (idle): same as frame 0 Frame 3 (step-B): right foot raised 1 px, left foot planted Walking adds a sin-wave bob to the whole sprite: ±1 px on y, sampled at 8 fps phased with the foot cycle. This is the Stardew Valley walk feel. ### 8.6 Status overlays Drawn above sprite, 8 × 8 px: | State | Overlay | |---|---| | `thinking` | 3 dots cycling (`...`) at +2 above head | | `blocked` | Pulsing `!` mark (coral), 2-frame blink | | `success` | Sparkle (4-frame star burst) | | `attention` | Wave hand (drawn into right-arm slot, 2-frame loop) | | `ghost` | Sprite opacity 50%, no overlay | ### 8.7 Movement - Speed: 80 px / sec when walking. - Pathing: A* on a 32 × 32 px tile grid. For MVP: simple lerp toward target tile center. - Bob: `y += sin(t * 8π) * 1` while walking; `0` while standing. ### 8.8 Carrying artifacts When walking back from a station after a tool result, the avatar carries a **token** above its hands: | Tool | Token | |---|---| | `Read` / `Edit` / `Write` | 6 × 8 px folded paper (cream-50 + ink-700 outline) | | `Bash` | 6 × 6 px terminal `>_` (ink-900 fill) | | `WebFetch` / `WebSearch` | 6 × 6 px globe (sky + mint) | | `Grep` / `Glob` | 6 × 6 px magnifier (ink-900 + cream-50) | | MCP tool | 6 × 6 px diamond in MCP server's color | | `TodoWrite` | 6 × 8 px checklist sprite | Token is dropped onto desk on arrival (3-frame fade). --- ## 9. Stations (the workshop) Stations are 64 × 64 px structures placed inside each room. ### 9.1 Catalog | Station | Purpose | Visual | |---|---|---| | **Desk** | Per-avatar home | 32 × 32 wooden desk with mini laptop, chair | | **File shelf** | Read/Edit/Write | 64 × 48 bookshelf, 3 rows of 4 books each in random palette | | **Terminal station** | Bash | 32 × 48 CRT monitor on a table, blinking caret | | **Web portal** | WebFetch/Search | 48 × 48 archway, lilac swirl gradient (animated) | | **MCP corner** | Any `mcp__*` | 48 × 48 modular shelf; mini-icon per MCP server placed on it | | **Task board** | TodoWrite | 32 × 48 corkboard with sticky notes (3-color rotation) | | **Mailbox** | Notification | 16 × 24 pole mailbox; flag UP when notification pending | ### 9.2 Station states Each station has 3 states: 1. **Idle** — static sprite 2. **In use** — 2-frame animation, +sparkle particles around it 3. **Highlighted** — when hovered or when its avatar is approaching (1 px white outline added) ### 9.3 Placement Within a room (a project), stations are arranged in a fixed pattern: ``` ┌───── project: ──────────────┐ │ [shelf] [terminal] [web] │ │ │ │ · · · · │ ← pathways (path color tiles) │ │ │ [desks of agents in this project] │ │ │ │ [board] [mailbox] [mcp] │ └────────────────────────────────────┘ ``` Room min size: 480 × 320 px. Room grows to fit number of agents (extra desk row every 4 agents). --- ## 10. Iconography 16 × 16 px pixel icons. 2 colors max (ink + accent). All icons hand-crafted. ### 10.1 Required icon set | Name | Use | Colors | |---|---|---| | `gear` | Configure | ink-900 + ink-300 | | `plus` | Add | ink-900 + mint | | `x` | Close / cancel | ink-900 + coral | | `check` | Confirm | ink-900 + mint | | `arrow-right` | Send / next | ink-900 + sky | | `pause` | Stop / pause | ink-900 + lemon | | `play` | Resume | ink-900 + mint | | `bell` | Notification | ink-900 + peach | | `folder` | Project | ink-900 + lemon | | `terminal` | Terminal | ink-900 + mint | | `code` | File / code | ink-900 + sky | | `web` | Web tool | ink-900 + lilac | | `mcp` | MCP server | ink-900 + lilac | | `sparkle` | Success | ink-900 + lemon | ### 10.2 Implementation Icons live as inline SVG `` components, all paths drawn at integer coordinates. `image-rendering: pixelated`. Scale via `transform: scale(N)` integer only. --- ## 11. Terminal (xterm.js) theme ```ts { background: '#FCFAF0', foreground: '#1A1320', cursor: '#FF6B6B', cursorAccent: '#FCFAF0', selectionBackground: '#FFEC99', selectionForeground: '#1A1320', black: '#1A1320', red: '#FF6B6B', green: '#6BCF7F', yellow: '#FFD93D', blue: '#4ECDC4', // we use sky as our blue magenta: '#B197FC', cyan: '#4ECDC4', white: '#FFF8E7', brightBlack: '#6B5878', brightRed: '#FFB4B4', brightGreen: '#B4E5BD', brightYellow: '#FFEC99', brightBlue: '#A8E6E0', brightMagenta:'#D6C5FF', brightCyan: '#A8E6E0', brightWhite: '#FFFDF5', } ``` Font: `VT323`, 16 px, line-height 1. --- ## 12. Motion ### 12.1 Durations | Type | ms | Easing | |---|---|---| | UI snap-in (modal, drawer) | 200 | cubic-bezier(.2, .8, .2, 1) | | Hover state | 0 | none — instant | | Button press | 0 | none — instant translate | | Toast slide | 200 | cubic-bezier(.2, .8, .2, 1) | | Sprite walk | continuous | sin-wave bob @ 8 fps | | Sprite frame | 125 ms each | step (no easing) | | Avatar teleport (room change) | 400 | step — fade-out, move, fade-in | ### 12.2 Forbidden motion - No spring physics on UI. - No bouncing. - No parallax. - No ambient idle animations on static UI panels. Animation belongs to the **game layer** (avatars, stations, particles). The UI layer is largely still. ### 12.3 Particles Used sparingly: - **Sparkle** on task complete: 4 pixel stars burst out from desk, 250 ms total - **Dust** when an avatar lands at a station: 3 pixel dots arc out, gravity-influenced, 300 ms - **Pulse** on mailbox flag: every 800 ms, 1-frame `+1 px scale` on the flag --- ## 13. Sound (deferred — spec only) 8-bit SFX in this order of priority: 1. `agent-arrives.wav` — bloop on station arrival 2. `task-complete.wav` — 3-note major-third jingle 3. `notification.wav` — single chime 4. `button-press.wav` — soft click 5. `error.wav` — descending buzz 6. `mailbox-flag.wav` — flag-up clack All sounds capped at 200 ms, mono, 22 kHz. Off by default; user can enable in preferences. --- ## 14. Voice & copy ### Tone Friendly, brief, factual. Imagine an Animal Crossing villager who happens to be technically literate. ### Examples (do / don't) | Don't | Do | |---|---| | "Agent is currently performing a Read operation on SPEC.md" | "Ada is reading SPEC.md" | | "An error has occurred" | "Ada hit a snag" | | "The agent has completed the task" | "Ada is done!" | | "Permission denied" | "Ada needs your permission" | | "Confirm operation" | "Sure?" | | "Loading..." | "One sec..." | ### Always - Use the avatar's name. Never "the agent." - Keep system feedback under 12 words. - Use second person to the user ("Ada needs you to take a look"). ### Never - Emojis in copy. We have icons. - Exclamation marks except for completions and notifications. - Apostrophe-free contractions ("dont"). Use proper punctuation. --- ## 15. Layout templates ### 15.1 Main view ``` ┌─────────────────────── App title bar (display-md) ──────────────────────┐ ├──────────────────────────────────────────┬─────────────────────────────┤ │ │ │ │ Floor canvas (Pixi) │ Selected agent panel │ │ — fills remaining width │ — 360 px wide │ │ │ - portrait + name │ │ │ - terminal view │ │ │ - command bar │ │ │ - status badge │ │ │ │ ├──────────────────────────────────────────┴─────────────────────────────┤ │ Agent strip — horizontal scroll of s, 80 px tall │ └─────────────────────────────────────────────────────────────────────────┘ ``` Min window: 1280 × 800. Right panel collapses below 1024 to bottom drawer. ### 15.2 Z-index layers | Layer | z | Contents | |---|---|---| | 0 | floor canvas | | 1 | UI chrome (panels, strip) | | 2 | drawer / sidebar | | 3 | toasts | | 4 | modals | | 5 | tooltips | --- ## 16. Token files All tokens live in two synced files: - `src/renderer/design/tokens.css` — CSS custom properties for use in any styled element. - `src/renderer/design/tokens.ts` — TypeScript objects for use in Pixi.js and inline styles. Both files import from a single source-of-truth `tokens.json` at build time (future work). For MVP, hand-keep them in sync. --- ## 17. Accessibility notes - Pixel fonts are inherently harder to read at small sizes — never go below 14 px for any user-facing text. - Color contrast: every text/background pair in this doc passes WCAG AA (4.5:1) — verify when adding new pairs. - Status is communicated via **color + icon + position** (avatar location). Never color alone. - Keyboard navigation: every interactive UI element reachable via Tab; focus state is the 2 px outline (§7.2). - Reduced motion: when `prefers-reduced-motion: reduce`, sprite bob disabled and walks become instant teleports. Particles disabled. --- ## 18. Open design decisions (revisit) 1. Whether to commission custom sprite art vs continuing programmatic sprites long-term. 2. Dark mode: not in v1 — pixel art with cream backgrounds is the brand. Revisit if requested. 3. Resizable rooms vs fixed grid — currently spec'd fixed; may want to drag-resize rooms. 4. Whether to add ambient floor decorations (flowers, rugs) — yes, low-priority polish. 5. Custom mouse cursor (pixel-style hand) — defer.