From dc9538187009586c8f1e566574735ad7cc5352ca Mon Sep 17 00:00:00 2001 From: Vassiliy Yegorov Date: Sun, 14 Jun 2026 09:39:07 +0700 Subject: [PATCH] feat(app): CSS-variable theming with dark/light palettes and accents Co-Authored-By: Claude Sonnet 4.6 --- app/src/theme.ts | 115 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 92 insertions(+), 23 deletions(-) diff --git a/app/src/theme.ts b/app/src/theme.ts index e1421ce..879df39 100644 --- a/app/src/theme.ts +++ b/app/src/theme.ts @@ -2,36 +2,105 @@ import type { SurfaceState } from "./layoutTypes"; /** Design tokens — mirror of DOCS/space-sh.pen variables. Single source for the UI. */ export const COLORS = { - accent: "#4C8DFF", - bgApp: "#0E1116", - bgElevated: "#1A2029", - bgHover: "#222A35", - bgPanel: "#0A0D12", - bgSidebar: "#13171F", - borderStrong: "#323C49", - borderSubtle: "#232A33", - textPrimary: "#E6EDF3", - textSecondary: "#8B97A6", - textMuted: "#5A6573", - stWork: "#4C8DFF", - stWait: "#F2B84B", - stDone: "#3FB950", - stError: "#F4544E", - stIdle: "#5A6573", - searchMatch: "#5A4A1F", + accent: "var(--c-accent)", + bgApp: "var(--c-bg-app)", + bgElevated: "var(--c-bg-elevated)", + bgHover: "var(--c-bg-hover)", + bgPanel: "var(--c-bg-panel)", + bgSidebar: "var(--c-bg-sidebar)", + borderStrong: "var(--c-border-strong)", + borderSubtle: "var(--c-border-subtle)", + textPrimary: "var(--c-text-primary)", + textSecondary: "var(--c-text-secondary)", + textMuted: "var(--c-text-muted)", + stWork: "var(--c-st-work)", + stWait: "var(--c-st-wait)", + stDone: "var(--c-st-done)", + stError: "var(--c-st-error)", + stIdle: "var(--c-st-idle)", + searchMatch: "var(--c-search-match)", } as const; export const FONT = { - ui: "Inter, system-ui, sans-serif", + ui: "Inter, system-ui, sans-serif", mono: "'JetBrains Mono Variable', 'JetBrains Mono', monospace", } as const; /** Status color by surface state, plus the stopped pseudo-state. */ export const STATE_COLOR: Record = { - work: COLORS.stWork, - wait: COLORS.stWait, - done: COLORS.stDone, - error: COLORS.stError, - idle: COLORS.stIdle, + work: COLORS.stWork, + wait: COLORS.stWait, + done: COLORS.stDone, + error: COLORS.stError, + idle: COLORS.stIdle, stopped: COLORS.stIdle, }; + +// --------------------------------------------------------------------------- +// Palettes +// --------------------------------------------------------------------------- + +type Palette = Record; + +/** Dark palette — hex values identical to what COLORS contained before. */ +const DARK: Palette = { + "bg-app": "#0E1116", + "bg-elevated": "#1A2029", + "bg-hover": "#222A35", + "bg-panel": "#0A0D12", + "bg-sidebar": "#13171F", + "border-strong": "#323C49", + "border-subtle": "#232A33", + "text-primary": "#E6EDF3", + "text-secondary": "#8B97A6", + "text-muted": "#5A6573", + "st-work": "#4C8DFF", + "st-wait": "#F2B84B", + "st-done": "#3FB950", + "st-error": "#F4544E", + "st-idle": "#5A6573", + "search-match": "#5A4A1F", +}; + +/** Light palette — a readable counterpart for every token. */ +const LIGHT: Palette = { + "bg-app": "#F5F7FA", + "bg-elevated": "#FFFFFF", + "bg-hover": "#E8EDF3", + "bg-panel": "#EBEEF3", + "bg-sidebar": "#DDE3EC", + "border-strong": "#B0BAC7", + "border-subtle": "#CDD4DE", + "text-primary": "#0D1117", + "text-secondary": "#4A5568", + "text-muted": "#8898AA", + "st-work": "#2266CC", + "st-wait": "#C07800", + "st-done": "#1A7A30", + "st-error": "#C4231F", + "st-idle": "#8898AA", + "search-match": "#FDE68A", +}; + +export const ACCENTS: Record = { + blue: "#4C8DFF", + teal: "#34D3C2", + purple: "#9B7BFF", + green: "#3FB950", + orange: "#F2934B", +}; + +export type ThemeName = "dark" | "light"; + +/** Real color values for consumers that can't use var() (xterm). Keys are the kebab tokens plus "accent". */ +export function resolvePalette(theme: ThemeName, accent: string): Record { + const base = theme === "light" ? LIGHT : DARK; + return { ...base, accent: ACCENTS[accent] ?? ACCENTS.blue }; +} + +/** Write the active palette to :root as --c-* custom properties. */ +export function applyTheme(theme: ThemeName, accent: string): void { + const p = resolvePalette(theme, accent); + const root = document.documentElement.style; + for (const [k, v] of Object.entries(p)) root.setProperty(`--c-${k}`, v); +}