# Phase 3 — Two Orca-derived named themes and notification sounds _One PR, after phase 2. Small._ ## Goal 1. Offer Orca's neutral palette as two **optional** named themes — "Orca Dark" and "Orca Light" — in the existing Appearance picker. Defaults do not change; nothing else about the theme system changes. 2. Play a short sound with a browser notification, per category, user-chosen. ## 1. Themes `client/src/hooks/useTheme.ts` — extend the registry only: ```ts export const THEMES = { dark: ["default", "darcula", "apple-dark", "orca-dark"], light: ["default", "apple-light", "intellij-light", "canvas", "orca-light"], } as const … ``` `client/src/index.css` — two override blocks shaped exactly like `.dark[data-theme="darcula"]` (every token the other named themes set, no more, no fewer; comment each with the hex). Values mapped from Orca's `src/renderer/src/assets/main.css` (`.dark` / `:root`) onto AgentHub's roles: | AgentHub token | Orca Dark | Orca Light | | --- | --- | --- | | `--surface-0` (page) | `#0a0a0a` | `#ffffff` | | `--surface-1` | `#111111` | `#fafafa` | | `--surface-2` | `#171717` (card) | `#f5f5f5` (muted) | | `--surface-3` (`.card`) | `#1c1c1c` | `#ffffff` | | `--surface-4` | `#262626` (secondary/muted) | `#ececec` | | `--surface-5` | `#404040` (accent/hover) | `#e5e5e5` | | `--border` | `#1f1f1f` (≈ white 7 % on #0a0a0a) | `#e5e5e5` | | `--border-light` | `#2e2e2e` (≈ white 15 %) | `#d4d4d4` | | `--accent` | `#e5e5e5` (Orca `primary`) — **check contrast of accent-on-surface text; if the UI uses `text-accent` on `surface-3`, use `#3b82f6` instead and note it** | `#171717` (same caveat) | | `--accent-hover` | `#fafafa` | `#262626` | | `--fg-primary` | `#fafafa` | `#0a0a0a` | | `--fg-secondary` | `#d4d4d4` | `#404040` | | `--fg-muted` | `#a1a1a1` | `#737373` | | `--status-success` | `#86efac` | `#15803d` | | `--status-warning` | `#eab308` | `#ca8a04` | | `--status-danger` | `#ff6568` | `#e40014` | | `--syn-*` | copy from `apple-dark` | copy from `apple-light` | The light theme's surfaces must still *rise in lightness with their index* as `index.css` documents; the table above honours that except `surface-3` = `#ffffff` on `surface-0` = `#ffffff` — set `--surface-0` to `#f5f5f5` and `--surface-1` to `#fafafa` so the card lifts. State the final values in the PR. Verify with the existing contrast rule (`fg-muted` ≥ 4.5:1 on `surface-3`, status labels ≥ 4.5:1 on their densest chip) — the Appearance swatch test (`Sidebar.theme.test.tsx` / `AppearanceSettings` tests) covers the picker; add the two names to whatever list those tests assert. `terminalTheme.ts` already derives the xterm palette from the tokens; confirm it reads the new blocks live when switching. i18n: `settings.json` `appearance.themes.orca-dark` / `orca-light` (en: "Orca Dark"/"Orca Light", vi same). ## 2. Notification sounds ### Prefs `client/src/lib/notifPrefs.ts`: ```ts export type NotifSound = "none" | "chime" | "pop" | "alert"; export interface NotifPrefs { …existing… sound: NotifSound; // global default soundByType?: Partial, NotifSound>>; } defaultNotifPrefs.sound = "none"; ``` `loadNotifPrefs` already spreads defaults over stored JSON, so existing users get `"none"`. ### Assets `client/public/sounds/{chime,pop,alert}.wav`, each ≤ 20 KB, generated — not downloaded — by `scripts/gen-notification-sounds.js` (Node, PCM 16-bit 22.05 kHz mono, two or three sine partials with a short decay; commit the WAVs *and* the script; the script carries the file header). No licence question, no network. ### Playback `client/src/lib/notify.ts`: `playSound(kind)` — a module-level `Map` created lazily, `audio.currentTime = 0; audio.play().catch(() => {})` (autoplay may be refused before a user gesture; that is fine). `useNotifications.ts`: in `notify()`, after deciding to show (native, push or local fallback), resolve `soundByType[type] ?? sound` and play it when not `"none"`. Play whenever a notification is shown, visible tab or not; the per-type setting is the user's volume control. No focus heuristics. ### Settings UI Phase-2 `notifications` section: a `SettingsRow` "Sound" with a `