From b0767ed5e1dc2a4cf0f4cb494dc6adc839f7cbcd Mon Sep 17 00:00:00 2001 From: Ame Date: Tue, 16 Jun 2026 21:02:47 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(ui):=20light=20/=20dark=20/=20auto=20t?= =?UTF-8?q?heme=20=E2=80=94=20Daybreak=20+=20Midnight=20palettes=20+=20tog?= =?UTF-8?q?gle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shell was dark-only (the #353 Linear palette). Add a real adaptive color mode so the all-black structure isn't the only option. Two palettes, one identity — alice-blue (her dress, from the character card) is the brand accent in BOTH modes: - Daybreak (light): cream paper + engraved-navy ink + alice-blue + gold. - Midnight (dark): TradingView-ward trading terminal — charcoal canvas, layered panels, brightened alice-blue, teal up / coral down (the #353 contributor's teal is reused as the up/positive color). How it works: - index.css `@theme` holds Daybreak (light) as the DEFAULT; a `:root[data-theme="dark"]` block + a `@media (prefers-color-scheme:dark)` block flip the same `--color-*` token names, so every bg-bg / text-accent / border-border utility follows the active theme with zero component changes. `auto` follows the OS. - theme store (ui/src/theme, mirrors the locale store): light|dark|auto, persisted; side-effect module applies ``. An inline script in index.html mirrors the persisted value pre-paint (no flash). - ThemeToggle in the ActivityBar footer cycles auto → light → dark. De-hardcoded the chrome that assumed a dark bg: ActivityBar / TabStrip hover + active overlays and the ChatLanding backdrop now use two theme-aware overlay tokens (--color-overlay / -strong); the avatar / empty-editor / form-focus accent glows now use --color-accent-dim instead of stale GitHub-blue rgba literals. Deferred (ANG-110): the Workspaces surface keeps its own local dark palette (its ~40 semantic chips would wash out illegibly on cream without a dedicated pass), and a few data-viz colors (chart, react-flow, session status). A dark terminal-ish surface in a light app is acceptable interim. Living color card kept as a dev artifact: ui/design/theme-study.html. Test plan: - cd ui && npx tsc -b clean; vite build clean (overlay utilities + data-theme/prefers-color-scheme blocks emitted) - ui vitest project green (42) - Verified live (demo): light applies cream/navy/alice-blue, dark applies the TradingView set, toggle cycles auto→light→dark and persists 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 (1M context) --- ui/design/theme-study.html | 504 ++++++++++++++++++++++++++++++ ui/index.html | 19 +- ui/src/components/ActivityBar.tsx | 11 +- ui/src/components/EmptyEditor.tsx | 2 +- ui/src/components/TabStrip.tsx | 4 +- ui/src/components/ThemeToggle.tsx | 50 +++ ui/src/components/form.tsx | 2 +- ui/src/i18n/locales/en.ts | 4 + ui/src/i18n/locales/ja.ts | 4 + ui/src/i18n/locales/zh-Hant.ts | 4 + ui/src/i18n/locales/zh.ts | 4 + ui/src/index.css | 113 +++++-- ui/src/main.tsx | 1 + ui/src/pages/ChatLandingPage.tsx | 6 +- ui/src/theme/index.ts | 26 ++ ui/src/theme/store.ts | 53 ++++ 16 files changed, 774 insertions(+), 33 deletions(-) create mode 100644 ui/design/theme-study.html create mode 100644 ui/src/components/ThemeToggle.tsx create mode 100644 ui/src/theme/index.ts create mode 100644 ui/src/theme/store.ts diff --git a/ui/design/theme-study.html b/ui/design/theme-study.html new file mode 100644 index 000000000..f1087d3cc --- /dev/null +++ b/ui/design/theme-study.html @@ -0,0 +1,504 @@ + + + + + +OpenAlice — Theme Study · Daybreak / Midnight + + + + + + +
+ + +
+
+
+ +

OpenAlice Theme Study

+
+

One Alice · Two Lights — Daybreak & Midnight

+
+
+
+ + + +
+
+
+
+ + +
+
+ 01 +

Two Palettes, One Identity

+

Alice-blue — the dress — is the brand accent in both modes. Day borrows the reference card's cream & ink; night goes trading-terminal (TradingView-ward) — layered charcoal panels, the same blue brightened, teal up / coral down.

+
+ +
+ +
+
Light
+
Daybreak
+
Cream paper · engraved navy · alice-blue · a touch of gold
+
+
+ + +
+
Dark
+
Midnight
+
Trading-terminal charcoal · layered panels · brightened alice-blue · teal up / coral down
+
+
+
+
+ + +
+
+ 02 +

In Context

+

The shell below adopts the active theme — flip the toggle up top and watch it re-skin live.

+
+ +
+
+ +
+
+ + OpenAlice +
+ + +
Build
+ + +
+ +
+ + +
+
+ Ask Alice + +
+
+
Today
+
CWhat's moving in semis today?
+
X解释美债收益率曲线倒挂
+
OBacktest a momentum sweep
+
Yesterday
+
πThesis on NVDA earnings
+
+
+ + +
+
+
Ask Alice
+
chat-jun16
+
+
+
+

What are we trading today?

+

Type a message — Alice opens a fresh session already on it.

+
+
Ask anything, or describe a task…
+
+ Chat + Claude ▾ + +
+
+
+
+
+
+
+
+ + +
+
+ 03 +

Variable Map

+

Drops straight into ui/src/index.css — one set for :root (day), one for the dark override.

+
+ + + +
TokenDaybreak (light)Midnight (dark)Drawn from
+
+ +
+ Observant Mind. Dry Wit. Elegant Efficiency. + theme-study · scratch +
+ +
+ + + + diff --git a/ui/index.html b/ui/index.html index ca5aa4105..48328db12 100644 --- a/ui/index.html +++ b/ui/index.html @@ -1,11 +1,26 @@ - + OpenAlice - + +
diff --git a/ui/src/components/ActivityBar.tsx b/ui/src/components/ActivityBar.tsx index eba472513..3e5fbb241 100644 --- a/ui/src/components/ActivityBar.tsx +++ b/ui/src/components/ActivityBar.tsx @@ -8,6 +8,7 @@ import { useUnreadInboxCount } from '../live/inbox-read' import { usePendingPushCount } from '../live/trading-push' import { useActivityBarCollapse } from '../live/activity-bar-collapse' import { useTranslation } from 'react-i18next' +import { ThemeToggle } from './ThemeToggle' /** * Map ActivityBar page enum (visual layout grouping) to the ActivitySection @@ -197,7 +198,7 @@ export function ActivityBar({ open, onClose, onItemActivated }: ActivityBarProps Alice

OpenAlice

@@ -274,8 +275,8 @@ export function ActivityBar({ open, onClose, onItemActivated }: ActivityBarProps title={t(item.labelKey)} className={`relative flex items-center gap-3 px-3 py-1.5 rounded-md text-[13px] transition-colors text-left ${ isActive - ? 'bg-bg-tertiary text-text shadow-[inset_0_0_0_1px_rgba(255,255,255,0.045)]' - : 'text-text-muted hover:text-text hover:bg-white/[0.035]' + ? 'bg-bg-tertiary text-text shadow-[inset_0_0_0_1px_var(--color-overlay)]' + : 'text-text-muted hover:text-text hover:bg-overlay' }`} > {/* Active indicator — left vertical bar */} @@ -315,6 +316,10 @@ export function ActivityBar({ open, onClose, onItemActivated }: ActivityBarProps })} + {/* Footer — global toggles pinned to the bottom of the rail. */} +
+ +
) diff --git a/ui/src/components/EmptyEditor.tsx b/ui/src/components/EmptyEditor.tsx index e1fdba9be..c014799fa 100644 --- a/ui/src/components/EmptyEditor.tsx +++ b/ui/src/components/EmptyEditor.tsx @@ -10,7 +10,7 @@ export function EmptyEditor() { OpenAlice
diff --git a/ui/src/components/TabStrip.tsx b/ui/src/components/TabStrip.tsx index b97c9026b..b39df302a 100644 --- a/ui/src/components/TabStrip.tsx +++ b/ui/src/components/TabStrip.tsx @@ -153,7 +153,7 @@ function TabButton({ title, active, onSelect, onClose, onContextMenu }: TabButto className={`group flex items-center gap-2 pl-3 pr-2 h-full text-[13px] cursor-pointer border-r border-border/80 transition-colors ${ active ? 'bg-bg-tertiary text-text' - : 'text-text-muted hover:text-text hover:bg-white/[0.035]' + : 'text-text-muted hover:text-text hover:bg-overlay' }`} > {title} @@ -163,7 +163,7 @@ function TabButton({ title, active, onSelect, onClose, onContextMenu }: TabButto e.stopPropagation() onClose() }} - className="w-4 h-4 rounded flex items-center justify-center text-text-muted/60 hover:text-text hover:bg-white/[0.06]" + className="w-4 h-4 rounded flex items-center justify-center text-text-muted/60 hover:text-text hover:bg-overlay-strong" aria-label={`Close ${title}`} > diff --git a/ui/src/components/ThemeToggle.tsx b/ui/src/components/ThemeToggle.tsx new file mode 100644 index 000000000..92e4d9591 --- /dev/null +++ b/ui/src/components/ThemeToggle.tsx @@ -0,0 +1,50 @@ +/** + * The color-theme toggle that lives in the ActivityBar footer. One button + * that cycles auto → light → dark → auto; the icon + label reflect the + * CURRENT mode, the tooltip names the NEXT one. Styled to match the nav rows + * above it (same height, padding, hover) so the rail reads as one column. + * + * State is the theme store (ui/src/theme/store); the side-effect module + * applies ``, CSS does the rest. No prop drilling. + */ + +import { Monitor, Moon, Sun } from 'lucide-react' +import type { LucideIcon } from 'lucide-react' +import { useTranslation } from 'react-i18next' + +import { useThemeStore, type AppTheme } from '../theme/store' + +const ICON: Record = { + auto: Monitor, + light: Sun, + dark: Moon, +} + +/** What the NEXT click switches to (auto → light → dark → auto). */ +const NEXT: Record = { + auto: 'light', + light: 'dark', + dark: 'auto', +} + +export function ThemeToggle() { + const { t } = useTranslation() + const theme = useThemeStore((s) => s.theme) + const cycle = useThemeStore((s) => s.cycleTheme) + const Icon = ICON[theme] + + return ( + + ) +} diff --git a/ui/src/components/form.tsx b/ui/src/components/form.tsx index f18cb8c78..f18948c32 100644 --- a/ui/src/components/form.tsx +++ b/ui/src/components/form.tsx @@ -3,7 +3,7 @@ import type { ReactNode } from 'react' // ==================== Shared class constants ==================== export const inputClass = - 'w-full px-3 py-2 bg-bg text-text border border-border rounded-lg font-sans text-sm outline-none transition-all duration-200 focus:border-accent/60 focus:shadow-[0_0_0_1px_rgba(88,166,255,0.1)]' + 'w-full px-3 py-2 bg-bg text-text border border-border rounded-lg font-sans text-sm outline-none transition-all duration-200 focus:border-accent/60 focus:shadow-[0_0_0_1px_var(--color-accent-dim)]' // ==================== Card ==================== diff --git a/ui/src/i18n/locales/en.ts b/ui/src/i18n/locales/en.ts index 7006b9c83..66665f485 100644 --- a/ui/src/i18n/locales/en.ts +++ b/ui/src/i18n/locales/en.ts @@ -137,6 +137,10 @@ export const en = { ex2: 'Build a thesis on NVDA', ex3: 'Scan the EV supply chain', }, + theme: { + mode: { auto: 'Auto', light: 'Light', dark: 'Dark' }, + switchTo: 'Switch to {{mode}}', + }, dev: { snapshots: 'Snapshots', }, diff --git a/ui/src/i18n/locales/ja.ts b/ui/src/i18n/locales/ja.ts index 146c42411..8b12fd1ef 100644 --- a/ui/src/i18n/locales/ja.ts +++ b/ui/src/i18n/locales/ja.ts @@ -126,6 +126,10 @@ export const ja: Resources = { ex2: 'NVDA の強気シナリオを作って', ex3: 'EV サプライチェーンを整理して', }, + theme: { + mode: { auto: '自動', light: 'ライト', dark: 'ダーク' }, + switchTo: '{{mode}}に切り替え', + }, dev: { snapshots: 'スナップショット', }, diff --git a/ui/src/i18n/locales/zh-Hant.ts b/ui/src/i18n/locales/zh-Hant.ts index bc636468c..e9c9cfd4c 100644 --- a/ui/src/i18n/locales/zh-Hant.ts +++ b/ui/src/i18n/locales/zh-Hant.ts @@ -134,6 +134,10 @@ export const zhHant: Resources = { ex2: '幫我做一個輝達的多頭邏輯', ex3: '梳理一下電動車產業鏈', }, + theme: { + mode: { auto: '自動', light: '淺色', dark: '深色' }, + switchTo: '切換到{{mode}}', + }, dev: { snapshots: '快照', }, diff --git a/ui/src/i18n/locales/zh.ts b/ui/src/i18n/locales/zh.ts index 8af5f52d1..545b7cfa4 100644 --- a/ui/src/i18n/locales/zh.ts +++ b/ui/src/i18n/locales/zh.ts @@ -126,6 +126,10 @@ export const zh: Resources = { ex2: '给我做一个英伟达的多头逻辑', ex3: '梳理一下电动车产业链', }, + theme: { + mode: { auto: '自动', light: '浅色', dark: '深色' }, + switchTo: '切换到{{mode}}', + }, dev: { snapshots: '快照', }, diff --git a/ui/src/index.css b/ui/src/index.css index 1bb1c1790..1af8ba657 100644 --- a/ui/src/index.css +++ b/ui/src/index.css @@ -1,23 +1,39 @@ @import "tailwindcss"; -/* Linear dark Alice shell palette */ +/* ============================================================ + Theme tokens — two palettes, one identity. Alice-blue (her dress) + is the brand accent in BOTH modes. @theme below is Daybreak (light) + and the DEFAULT; the [data-theme="dark"] block + the prefers-color- + scheme block flip the same token names, so every `bg-bg`/`text-accent`/ + `border-border` utility follows the active theme with zero component + changes. Living color card: ui/design/theme-study.html. The toggle lives in the + ActivityBar footer (light / dark / auto), persisted by the theme store + (ui/src/theme) and mirrored pre-paint by the inline script in index.html. + ============================================================ */ @theme { - --color-bg: #0b0c0e; - --color-bg-secondary: #0e0f12; - --color-bg-tertiary: #1a1b21; - --color-border: #24262c; - --color-text: #dfe1e6; - --color-text-muted: #8f929b; - --color-accent: #23b99a; - --color-accent-dim: rgba(35, 185, 154, 0.16); - --color-user-bubble: #1f2026; - --color-assistant-bubble: #141519; - --color-notification-bg: #1d1610; - --color-notification-border: #8a5b2f; - --color-green: #23b99a; - --color-red: #e5484d; - --color-purple: #8f72ff; - --color-purple-dim: rgba(143, 114, 255, 0.16); + /* Daybreak — light (default). Cream paper · engraved navy · alice-blue. */ + --color-bg: #f7f4ec; + --color-bg-secondary: #f0ebdd; + --color-bg-tertiary: #e5decb; + --color-border: #d8d0bc; + --color-text: #1c2a41; + --color-text-muted: #5e6573; + --color-accent: #2f62b0; + --color-accent-dim: rgba(47, 98, 176, 0.14); + --color-user-bubble: #2f62b0; + --color-assistant-bubble: #fbf8f0; + --color-notification-bg: #fbf1d6; + --color-notification-border: #c99a2e; + --color-green: #2e8b6f; + --color-red: #be4138; + --color-purple: #6b5bc2; + --color-purple-dim: rgba(107, 91, 194, 0.14); + + /* Elevation overlays — theme-aware "subtle wash on hover / active". + Light darkens with navy ink; dark lightens with white. Components use + `bg-overlay` / `bg-overlay-strong`; raw CSS uses `var(--color-overlay)`. */ + --color-overlay: rgba(28, 42, 65, 0.05); + --color-overlay-strong: rgba(28, 42, 65, 0.085); --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", "Hiragino Sans GB", "Noto Sans CJK SC", sans-serif; @@ -25,6 +41,62 @@ monospace; } +/* Base resolution (no attribute / explicit light). */ +:root { + color-scheme: light; + --app-bg-wash: radial-gradient(circle at 50% 24%, rgba(47, 98, 176, 0.045), transparent 38rem); +} + +/* ---- Midnight — dark (TradingView-ward terminal) ---- + Forced via data-theme="dark", AND the resolution for data-theme="auto" + when the OS is in dark mode (media block below). KEEP THE TWO IN SYNC. */ +:root[data-theme="dark"] { + --color-bg: #131722; + --color-bg-secondary: #1c2331; + --color-bg-tertiary: #2a3142; + --color-border: #2a2e39; + --color-text: #d1d4dc; + --color-text-muted: #787b86; + --color-accent: #3b82f6; + --color-accent-dim: rgba(59, 130, 246, 0.16); + --color-user-bubble: #2f62b0; + --color-assistant-bubble: #1c2331; + --color-notification-bg: #241d0e; + --color-notification-border: #d6a23a; + --color-green: #26a69a; + --color-red: #ef5350; + --color-purple: #8b7bff; + --color-purple-dim: rgba(139, 123, 255, 0.18); + --color-overlay: rgba(255, 255, 255, 0.04); + --color-overlay-strong: rgba(255, 255, 255, 0.07); + color-scheme: dark; + --app-bg-wash: radial-gradient(circle at 50% 20%, rgba(59, 130, 246, 0.05), transparent 42rem); +} +@media (prefers-color-scheme: dark) { + :root[data-theme="auto"] { + --color-bg: #131722; + --color-bg-secondary: #1c2331; + --color-bg-tertiary: #2a3142; + --color-border: #2a2e39; + --color-text: #d1d4dc; + --color-text-muted: #787b86; + --color-accent: #3b82f6; + --color-accent-dim: rgba(59, 130, 246, 0.16); + --color-user-bubble: #2f62b0; + --color-assistant-bubble: #1c2331; + --color-notification-bg: #241d0e; + --color-notification-border: #d6a23a; + --color-green: #26a69a; + --color-red: #ef5350; + --color-purple: #8b7bff; + --color-purple-dim: rgba(139, 123, 255, 0.18); + --color-overlay: rgba(255, 255, 255, 0.04); + --color-overlay-strong: rgba(255, 255, 255, 0.07); + color-scheme: dark; + --app-bg-wash: radial-gradient(circle at 50% 20%, rgba(59, 130, 246, 0.05), transparent 42rem); + } +} + /* Japanese gets a JP-first sans stack so shared Han characters render in Japanese glyph forms, not the Simplified-Chinese forms the default stack would pick (Han unification). Scoped via , which the locale @@ -52,14 +124,13 @@ body, } body { - background: - radial-gradient(circle at 50% 28%, rgba(255, 255, 255, 0.022), transparent 34rem), - linear-gradient(180deg, #0e0f12 0%, #0b0c0e 46%, #090a0c 100%); + background: var(--app-bg-wash), var(--color-bg); color: var(--color-text); + transition: background-color 0.4s ease, color 0.4s ease; } ::selection { - background: rgba(35, 185, 154, 0.28); + background: color-mix(in srgb, var(--color-accent) 28%, transparent); color: var(--color-text); } diff --git a/ui/src/main.tsx b/ui/src/main.tsx index f204d7a65..b9be27a56 100644 --- a/ui/src/main.tsx +++ b/ui/src/main.tsx @@ -6,6 +6,7 @@ import { ToastProvider } from './components/Toast' import { AuthProvider } from './auth/AuthContext' import { AuthGate } from './auth/AuthGate' import './index.css' +import './theme' // side-effect: apply persisted color theme to import './i18n' // side-effect: init react-i18next + seed locale before first render if (import.meta.env.VITE_DEMO_MODE) { diff --git a/ui/src/pages/ChatLandingPage.tsx b/ui/src/pages/ChatLandingPage.tsx index 2dbc9cc4f..225d44b35 100644 --- a/ui/src/pages/ChatLandingPage.tsx +++ b/ui/src/pages/ChatLandingPage.tsx @@ -106,9 +106,9 @@ export function ChatLandingPage() { dropped: they drift on portrait and read as pixel-placed art, not a responsive surface. pointer-events-none so it never intercepts clicks. */}
-
-
-
+
+
+
diff --git a/ui/src/theme/index.ts b/ui/src/theme/index.ts new file mode 100644 index 000000000..3158cdbcf --- /dev/null +++ b/ui/src/theme/index.ts @@ -0,0 +1,26 @@ +/** + * Theme bootstrap — side-effect module. `import './theme'` once in main.tsx, + * BEFORE first render, so `` matches the persisted choice. + * + * Wiring is one-directional, mirroring i18n/index.ts: the theme store is the + * source of truth; here we (a) apply the persisted value at boot and (b) + * subscribe so every later switch re-applies. The CSS in index.css resolves + * `data-theme` → the active palette (and `auto` follows prefers-color-scheme + * via a media query), so this module never touches CSS variables itself. + * + * A near-identical apply already ran from index.html's inline script to avoid + * a first-paint flash; re-applying here is cheap and self-heals any drift + * (e.g. the persisted key changing shape across a version bump). + */ + +import { useThemeStore, readInitialTheme, type AppTheme } from './store' + +function applyTheme(theme: AppTheme): void { + document.documentElement.dataset.theme = theme +} + +applyTheme(readInitialTheme()) + +useThemeStore.subscribe((state, prev) => { + if (state.theme !== prev.theme) applyTheme(state.theme) +}) diff --git a/ui/src/theme/store.ts b/ui/src/theme/store.ts new file mode 100644 index 000000000..a23b880e4 --- /dev/null +++ b/ui/src/theme/store.ts @@ -0,0 +1,53 @@ +import { create } from 'zustand' +import { persist } from 'zustand/middleware' + +/** + * Color-theme preference store — single source of truth for light / dark. + * + * `'auto'` follows the OS (`prefers-color-scheme`); `'light'` / `'dark'` pin + * it. Default is `'auto'` — unlike the locale store (which deliberately does + * NOT auto-detect), a color theme SHOULD honor the user's system setting out + * of the box; that's the whole point of the mode. + * + * Persistence mirrors the locale store's loud-fail contract (i18n/store.ts): + * a `version` bump clears stored state, NO migrate function. + * + * Stays pure (no DOM imports) so the wiring is one-directional: ui/src/theme + * subscribes here and applies ``. The inline script in + * index.html reads the SAME persisted key to avoid a first-paint flash. + */ + +export type AppTheme = 'light' | 'dark' | 'auto' + +/** Cycle order for the single toggle button: auto → light → dark → auto. */ +const CYCLE: readonly AppTheme[] = ['auto', 'light', 'dark'] + +interface ThemeStore { + theme: AppTheme + setTheme: (theme: AppTheme) => void + /** Advance to the next mode (drives the ActivityBar toggle). */ + cycleTheme: () => void +} + +export const useThemeStore = create()( + persist( + (set, get) => ({ + theme: 'auto', + setTheme: (theme) => set({ theme }), + cycleTheme: () => { + const i = CYCLE.indexOf(get().theme) + set({ theme: CYCLE[(i + 1) % CYCLE.length]! }) + }, + }), + { + // Keep this key in sync with the no-flash script in index.html. + name: 'openalice.theme.v1', + version: 1, + }, + ), +) + +/** Persisted theme at boot (zustand persist rehydrates localStorage sync). */ +export function readInitialTheme(): AppTheme { + return useThemeStore.getState().theme +} From 138c3e8ba5a2f0ea434c514419feabf240929326 Mon Sep 17 00:00:00 2001 From: Ame Date: Tue, 16 Jun 2026 21:09:31 +0800 Subject: [PATCH 2/2] fix(ui): revert Midnight to neutral charcoal-black (drop the navy tint) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TradingView-ward dark (#131722 navy-charcoal panels) read as "too blue" once the full shell — activity rail + sidebar + tabs — was on screen: a large navy-tinted surface is fatiguing. Revert the dark backgrounds / panels / border / text to the #353 neutral-black palette (#0b0c0e / #0e0f12 / #1a1b21 / #24262c / #dfe1e6 / #8f929b), which stays calm at shell scale. Alice-blue survives ONLY as small brand highlights — the accent (active tab / nav indicator / buttons) and the user's own message bubble — so the large-area blue tint is gone while the brand stays consistent with Daybreak. Green/red/purple semantics also revert to the #353 values. Daybreak (light) is unchanged. Color card (ui/design/theme-study.html) synced to match. Verified: vite build emits --color-bg:#0b0c0e under [data-theme=dark], --color-accent stays #3b82f6. --- ui/design/theme-study.html | 74 +++++++++++++++++++------------------- ui/src/index.css | 63 +++++++++++++++++--------------- 2 files changed, 71 insertions(+), 66 deletions(-) diff --git a/ui/design/theme-study.html b/ui/design/theme-study.html index f1087d3cc..23910bbc1 100644 --- a/ui/design/theme-study.html +++ b/ui/design/theme-study.html @@ -41,31 +41,31 @@ color-scheme: light; } :root[data-theme="dark"] { - --bg: #131722; /* trading-terminal canvas (TradingView-ward) */ - --bg-2: #1c2331; /* lifted panel — the secondary-sidebar layer */ - --bg-3: #2a3142; /* active / hover well */ - --border: #2a2e39; /* cool charcoal hairline */ - --border-soft:#222632; - --text: #d1d4dc; - --muted: #787b86; - --accent: #3b82f6; /* alice-blue brightened for the terminal */ - --accent-2: #d6a23a; /* gold (hair) */ + --bg: #0b0c0e; /* neutral charcoal-black canvas (#353 palette) */ + --bg-2: #0e0f12; /* chrome panels — rail / sidebar / tabs */ + --bg-3: #1a1b21; /* active / hover well */ + --border: #24262c; /* neutral hairline */ + --border-soft:#1a1b21; + --text: #dfe1e6; + --muted: #8f929b; + --accent: #3b82f6; /* alice-blue — small brand highlights only */ + --accent-2: #c9a23a; /* gold (hair) */ --accent-dim:rgba(59,130,246,0.16); - --green: #26a69a; /* up / positive — terminal teal */ - --red: #ef5350; /* down / negative */ - --purple: #8b7bff; + --green: #23b99a; /* success / up */ + --red: #e5484d; /* danger / down */ + --purple: #8f72ff; --paper: none; - --vignette: radial-gradient(circle at 50% 20%, rgba(59,130,246,0.05), transparent 42rem); + --vignette: radial-gradient(circle at 50% 24%, rgba(255,255,255,0.03), transparent 38rem); --shadow: 0 1px 2px rgba(0,0,0,0.45), 0 16px 44px rgba(0,0,0,0.5); color-scheme: dark; } @media (prefers-color-scheme: dark) { :root[data-theme="auto"] { - --bg:#131722; --bg-2:#1c2331; --bg-3:#2a3142; --border:#2a2e39; --border-soft:#222632; - --text:#d1d4dc; --muted:#787b86; --accent:#3b82f6; --accent-2:#d6a23a; - --accent-dim:rgba(59,130,246,0.16); --green:#26a69a; --red:#ef5350; --purple:#8b7bff; + --bg:#0b0c0e; --bg-2:#0e0f12; --bg-3:#1a1b21; --border:#24262c; --border-soft:#1a1b21; + --text:#dfe1e6; --muted:#8f929b; --accent:#3b82f6; --accent-2:#c9a23a; + --accent-dim:rgba(59,130,246,0.16); --green:#23b99a; --red:#e5484d; --purple:#8f72ff; --paper:none; - --vignette: radial-gradient(circle at 50% 20%, rgba(59,130,246,0.05), transparent 42rem); + --vignette: radial-gradient(circle at 50% 24%, rgba(255,255,255,0.03), transparent 38rem); --shadow: 0 1px 2px rgba(0,0,0,0.45), 0 16px 44px rgba(0,0,0,0.5); color-scheme: dark; } @@ -134,8 +134,8 @@ .card { border-radius: 16px; padding: 26px 24px 22px; position: relative; overflow: hidden; border: 1px solid; box-shadow: var(--shadow); } .card.day { background: #f7f4ec; border-color: #ddd5c2; color: #1c2a41; } -.card.night{ background: #131722; border-color: #2a2e39; color: #d1d4dc; - background-image: radial-gradient(circle at 50% 0%, rgba(59,130,246,0.08), transparent 22rem); } +.card.night{ background: #0b0c0e; border-color: #24262c; color: #dfe1e6; + background-image: radial-gradient(circle at 50% 0%, rgba(59,130,246,0.06), transparent 22rem); } .card .label { font-size: 12px; letter-spacing: .16em; text-transform: uppercase; opacity: .7; } .card .cardname { font-family:"Cormorant Garamond",serif; font-weight:600; font-size: 30px; margin-top: 2px; } .card .cardname em { font-style: italic; } @@ -155,7 +155,7 @@ .shex { font-family:"JetBrains Mono",monospace; font-size: 12px; opacity: .8; } .rule { height: 1px; margin: 14px 0 4px; } .card.day .rule { background: #e3dcc9; } -.card.night .rule { background: #222632; } +.card.night .rule { background: #1a1b21; } /* ============ Live mini-shell preview (adopts active theme) ============ */ .previewframe { border: 1px solid var(--border); border-radius: 16px; overflow: hidden; @@ -287,7 +287,7 @@

OpenAlice
01

Two Palettes, One Identity

-

Alice-blue — the dress — is the brand accent in both modes. Day borrows the reference card's cream & ink; night goes trading-terminal (TradingView-ward) — layered charcoal panels, the same blue brightened, teal up / coral down.

+

Alice-blue — the dress — is the brand accent in both modes. Day borrows the reference card's cream & ink; night stays a calm neutral charcoal-black (the #353 palette) so the full shell doesn't read as blue — alice-blue survives only as small highlights.

@@ -303,7 +303,7 @@

Two Palettes, One Identity

Dark
Midnight
-
Trading-terminal charcoal · layered panels · brightened alice-blue · teal up / coral down
+
Neutral charcoal-black (#353) · calm shell · alice-blue only as small brand highlights
@@ -423,25 +423,25 @@

Variable Map

'purple-dim':'rgba(107,91,194,0.14)' }; const NIGHT = { - bg:'#131722', 'bg-secondary':'#1c2331', 'bg-tertiary':'#2a3142', border:'#2a2e39', - text:'#d1d4dc', 'text-muted':'#787b86', accent:'#3b82f6', 'accent-dim':'rgba(59,130,246,0.16)', - 'user-bubble':'#2f62b0', 'assistant-bubble':'#1c2331', 'notification-bg':'#241d0e', - 'notification-border':'#d6a23a', green:'#26a69a', red:'#ef5350', purple:'#8b7bff', - 'purple-dim':'rgba(139,123,255,0.18)' + bg:'#0b0c0e', 'bg-secondary':'#0e0f12', 'bg-tertiary':'#1a1b21', border:'#24262c', + text:'#dfe1e6', 'text-muted':'#8f929b', accent:'#3b82f6', 'accent-dim':'rgba(59,130,246,0.16)', + 'user-bubble':'#2f62b0', 'assistant-bubble':'#141519', 'notification-bg':'#1d1610', + 'notification-border':'#8a5b2f', green:'#23b99a', red:'#e5484d', purple:'#8f72ff', + 'purple-dim':'rgba(143,114,255,0.16)' }; /* swatch rows shown on the two specimen cards (curated subset + source notes) */ const SPEC = [ - ['bg','Background','Cream paper — her reference card','Terminal canvas — TradingView-ward'], - ['bg-secondary','Chrome panels','Warmer paper for the rails','Lifted panel — the sidebar layer'], + ['bg','Background','Cream paper — her reference card','Neutral charcoal-black (#353 palette)'], + ['bg-secondary','Chrome panels','Warmer paper for the rails','Rail / sidebar / tabs — calm neutral'], ['bg-tertiary','Hover / active well','Toned paper','Active well'], - ['border','Borders','Soft tan rule','Cool charcoal hairline'], - ['text','Ink','Engraved navy — the card titling','Terminal off-white'], - ['text-muted','Muted','Desaturated navy','Cool slate'], - ['accent','Accent · alice-blue','The dress','The dress, brightened for the terminal'], - ['notification-border','Gold','Her golden-blonde hair','Her hair, glowing'], - ['green','Up / positive','Botanical teal-green','Terminal teal — the up tick'], - ['red','Down / negative','Warm brick','The down tick'], - ['purple','Purple','Muted iris','Twilight violet'], + ['border','Borders','Soft tan rule','Neutral hairline'], + ['text','Ink','Engraved navy — the card titling','Soft off-white'], + ['text-muted','Muted','Desaturated navy','Cool grey'], + ['accent','Accent · alice-blue','The dress','The dress — small brand highlights only'], + ['notification-border','Gold','Her golden-blonde hair','Muted gold'], + ['green','Success','Botanical teal-green','Teal-green'], + ['red','Danger','Warm brick','Clean red'], + ['purple','Purple','Muted iris','Iris violet'], ]; /* render specimen swatches */ diff --git a/ui/src/index.css b/ui/src/index.css index 1af8ba657..20d6e1137 100644 --- a/ui/src/index.css +++ b/ui/src/index.css @@ -47,53 +47,58 @@ --app-bg-wash: radial-gradient(circle at 50% 24%, rgba(47, 98, 176, 0.045), transparent 38rem); } -/* ---- Midnight — dark (TradingView-ward terminal) ---- +/* ---- Midnight — dark (neutral charcoal-black) ---- + Reverted to the #353 neutral-black palette: a navy-tinted dark read as + "too blue" once the full shell (rail + sidebar + tabs) was on screen — a + large neutral surface is calmer. Alice-blue survives ONLY as small brand + highlights (accent + the user's own bubble), so the large-area blue tint + is gone while the brand stays consistent with Daybreak. Forced via data-theme="dark", AND the resolution for data-theme="auto" when the OS is in dark mode (media block below). KEEP THE TWO IN SYNC. */ :root[data-theme="dark"] { - --color-bg: #131722; - --color-bg-secondary: #1c2331; - --color-bg-tertiary: #2a3142; - --color-border: #2a2e39; - --color-text: #d1d4dc; - --color-text-muted: #787b86; + --color-bg: #0b0c0e; + --color-bg-secondary: #0e0f12; + --color-bg-tertiary: #1a1b21; + --color-border: #24262c; + --color-text: #dfe1e6; + --color-text-muted: #8f929b; --color-accent: #3b82f6; --color-accent-dim: rgba(59, 130, 246, 0.16); --color-user-bubble: #2f62b0; - --color-assistant-bubble: #1c2331; - --color-notification-bg: #241d0e; - --color-notification-border: #d6a23a; - --color-green: #26a69a; - --color-red: #ef5350; - --color-purple: #8b7bff; - --color-purple-dim: rgba(139, 123, 255, 0.18); + --color-assistant-bubble: #141519; + --color-notification-bg: #1d1610; + --color-notification-border: #8a5b2f; + --color-green: #23b99a; + --color-red: #e5484d; + --color-purple: #8f72ff; + --color-purple-dim: rgba(143, 114, 255, 0.16); --color-overlay: rgba(255, 255, 255, 0.04); --color-overlay-strong: rgba(255, 255, 255, 0.07); color-scheme: dark; - --app-bg-wash: radial-gradient(circle at 50% 20%, rgba(59, 130, 246, 0.05), transparent 42rem); + --app-bg-wash: radial-gradient(circle at 50% 28%, rgba(255, 255, 255, 0.02), transparent 34rem); } @media (prefers-color-scheme: dark) { :root[data-theme="auto"] { - --color-bg: #131722; - --color-bg-secondary: #1c2331; - --color-bg-tertiary: #2a3142; - --color-border: #2a2e39; - --color-text: #d1d4dc; - --color-text-muted: #787b86; + --color-bg: #0b0c0e; + --color-bg-secondary: #0e0f12; + --color-bg-tertiary: #1a1b21; + --color-border: #24262c; + --color-text: #dfe1e6; + --color-text-muted: #8f929b; --color-accent: #3b82f6; --color-accent-dim: rgba(59, 130, 246, 0.16); --color-user-bubble: #2f62b0; - --color-assistant-bubble: #1c2331; - --color-notification-bg: #241d0e; - --color-notification-border: #d6a23a; - --color-green: #26a69a; - --color-red: #ef5350; - --color-purple: #8b7bff; - --color-purple-dim: rgba(139, 123, 255, 0.18); + --color-assistant-bubble: #141519; + --color-notification-bg: #1d1610; + --color-notification-border: #8a5b2f; + --color-green: #23b99a; + --color-red: #e5484d; + --color-purple: #8f72ff; + --color-purple-dim: rgba(143, 114, 255, 0.16); --color-overlay: rgba(255, 255, 255, 0.04); --color-overlay-strong: rgba(255, 255, 255, 0.07); color-scheme: dark; - --app-bg-wash: radial-gradient(circle at 50% 20%, rgba(59, 130, 246, 0.05), transparent 42rem); + --app-bg-wash: radial-gradient(circle at 50% 28%, rgba(255, 255, 255, 0.02), transparent 34rem); } }