refactor(themes): web dashboard presets derive from the desktop palette table
The desktop and the web dashboard each carried a private copy of the cyberpunk / ember / midnight / mono palettes and they had drifted: the dashboard's cyberpunk canvas was #040608 with a mint #9bffcf accent while the desktop's was #000a00 with #00ff41, ember and midnight disagreed on both canvas and accent, mono agreed only by luck. Move the raw palette table for every built-in preset into @hermes/shared (`THEME_PRESET_PALETTES`, apps/shared/src/theme-presets.ts) and make it the single source of truth: - apps/desktop/src/themes/presets.ts spreads its `colors` / `darkColors` from the shared table; the OKLCH synthesis, terminal palettes and typography stay in the desktop. Serialised BUILTIN_THEMES are byte-identical to before, so the existing `--dt-primary-solid` parity pins stay green untouched. - web/src/themes/presets.ts projects each shared preset onto its 3-slot model through one pure function, `webPresetFromShared` (background <- background, midground <- primary, warmGlow <- the midground/ring accent), so cyberpunk / ember / midnight / mono now render the desktop's palette. Web-only presets (default, default-large, nous-blue, rose) are untouched. - Invariant test (web): for every preset shared by both surfaces the dashboard canvas equals the shared background and the projected text colour keeps >= 3:1 contrast against it. Red on the previous hexes, green now. Why: one edit in one place should recolour a preset on every surface; two hand-maintained tables guarantee the drift the audit found.
This commit is contained in:
@@ -18,6 +18,8 @@
|
||||
* re-derived. `nous-alt` is first-party — do not re-derive it from GitHub.
|
||||
*/
|
||||
|
||||
import { THEME_PRESET_PALETTES } from '@hermes/shared'
|
||||
|
||||
import type { DesktopTheme, DesktopThemeTypography } from './types'
|
||||
|
||||
// Color-emoji fonts to append to every stack as a last resort. None of the UI
|
||||
@@ -57,62 +59,7 @@ export const githubTheme: DesktopTheme = {
|
||||
name: 'github',
|
||||
label: 'GitHub',
|
||||
description: 'GitHub Light Default and Dark Default',
|
||||
colors: {
|
||||
background: '#ffffff',
|
||||
foreground: '#1f2328',
|
||||
card: '#f6f8fa',
|
||||
cardForeground: '#1f2328',
|
||||
muted: '#f6f6f6',
|
||||
mutedForeground: '#656d76',
|
||||
popover: '#ffffff',
|
||||
popoverForeground: '#1f2328',
|
||||
primary: '#196d31',
|
||||
primaryForeground: '#ffffff',
|
||||
secondary: '#dfebe2',
|
||||
secondaryForeground: '#1f2328',
|
||||
accent: '#e3ede6',
|
||||
accentForeground: '#1f2328',
|
||||
border: '#d0d7de',
|
||||
input: '#ffffff',
|
||||
ring: '#196d31',
|
||||
midground: '#196d31',
|
||||
midgroundForeground: '#ffffff',
|
||||
composerRing: '#196d31',
|
||||
destructive: '#cf222e',
|
||||
destructiveForeground: '#ffffff',
|
||||
sidebarBackground: '#f6f8fa',
|
||||
sidebarBorder: '#d0d7de',
|
||||
userBubble: '#dbe7e2',
|
||||
userBubbleBorder: '#d0d7de'
|
||||
},
|
||||
darkColors: {
|
||||
background: '#0d1117',
|
||||
foreground: '#e6edf3',
|
||||
card: '#010409',
|
||||
cardForeground: '#e6edf3',
|
||||
muted: '#1a1e24',
|
||||
mutedForeground: '#7d8590',
|
||||
popover: '#161b22',
|
||||
popoverForeground: '#e6edf3',
|
||||
primary: '#4f9e5e',
|
||||
primaryForeground: '#ffffff',
|
||||
secondary: '#1f382b',
|
||||
secondaryForeground: '#e6edf3',
|
||||
accent: '#192a24',
|
||||
accentForeground: '#e6edf3',
|
||||
border: '#30363d',
|
||||
input: '#0d1117',
|
||||
ring: '#4f9e5e',
|
||||
midground: '#4f9e5e',
|
||||
midgroundForeground: '#ffffff',
|
||||
composerRing: '#4f9e5e',
|
||||
destructive: '#f85149',
|
||||
destructiveForeground: '#ffffff',
|
||||
sidebarBackground: '#010409',
|
||||
sidebarBorder: '#30363d',
|
||||
userBubble: '#0f2018',
|
||||
userBubbleBorder: '#30363d'
|
||||
},
|
||||
...THEME_PRESET_PALETTES.github,
|
||||
typography: {
|
||||
fontSans: SYSTEM_SANS,
|
||||
fontMono: SYSTEM_MONO,
|
||||
@@ -175,62 +122,7 @@ export const nousTheme: DesktopTheme = {
|
||||
name: 'nous',
|
||||
label: 'Nous',
|
||||
description: 'GitHub chrome, Nous blue accent',
|
||||
colors: {
|
||||
background: '#ffffff',
|
||||
foreground: '#1f2328',
|
||||
card: '#f6f8fa',
|
||||
cardForeground: '#1f2328',
|
||||
muted: '#f6f6f6',
|
||||
mutedForeground: '#656d76',
|
||||
popover: '#ffffff',
|
||||
popoverForeground: '#1f2328',
|
||||
primary: '#0053fd',
|
||||
primaryForeground: '#ffffff',
|
||||
secondary: '#deeaff',
|
||||
secondaryForeground: '#1f2328',
|
||||
accent: '#e3edff',
|
||||
accentForeground: '#1f2328',
|
||||
border: '#d0d7de',
|
||||
input: '#ffffff',
|
||||
ring: '#0053fd',
|
||||
midground: '#0053fd',
|
||||
midgroundForeground: '#ffffff',
|
||||
composerRing: '#0053fd',
|
||||
destructive: '#cf222e',
|
||||
destructiveForeground: '#ffffff',
|
||||
sidebarBackground: '#f6f8fa',
|
||||
sidebarBorder: '#d0d7de',
|
||||
userBubble: '#dae7fd',
|
||||
userBubbleBorder: '#d0d7de'
|
||||
},
|
||||
darkColors: {
|
||||
background: '#0d1117',
|
||||
foreground: '#e6edf3',
|
||||
card: '#010409',
|
||||
cardForeground: '#e6edf3',
|
||||
muted: '#1a1e24',
|
||||
mutedForeground: '#7d8590',
|
||||
popover: '#161b22',
|
||||
popoverForeground: '#e6edf3',
|
||||
primary: '#4a84fe',
|
||||
primaryForeground: '#161616',
|
||||
secondary: '#1d2e4f',
|
||||
secondaryForeground: '#e6edf3',
|
||||
accent: '#17243a',
|
||||
accentForeground: '#e6edf3',
|
||||
border: '#30363d',
|
||||
input: '#0d1117',
|
||||
ring: '#4a84fe',
|
||||
midground: '#4a84fe',
|
||||
midgroundForeground: '#161616',
|
||||
composerRing: '#4a84fe',
|
||||
destructive: '#f85149',
|
||||
destructiveForeground: '#ffffff',
|
||||
sidebarBackground: '#010409',
|
||||
sidebarBorder: '#30363d',
|
||||
userBubble: '#07162c',
|
||||
userBubbleBorder: '#30363d'
|
||||
},
|
||||
...THEME_PRESET_PALETTES.nous,
|
||||
typography: {
|
||||
fontSans: SYSTEM_SANS,
|
||||
fontMono: SYSTEM_MONO,
|
||||
@@ -281,62 +173,7 @@ export const catppuccinTheme: DesktopTheme = {
|
||||
name: 'catppuccin',
|
||||
label: 'Catppuccin',
|
||||
description: 'Soothing pastels — Latte and Mocha',
|
||||
colors: {
|
||||
background: '#eff1f5',
|
||||
foreground: '#4c4f69',
|
||||
card: '#e6e9ef',
|
||||
cardForeground: '#4c4f69',
|
||||
muted: '#e8ebef',
|
||||
mutedForeground: '#4c4f69',
|
||||
popover: '#e6e9ef',
|
||||
popoverForeground: '#4c4f69',
|
||||
primary: '#6d2ebf',
|
||||
primaryForeground: '#ffffff',
|
||||
secondary: '#ddd6ed',
|
||||
secondaryForeground: '#4c4f69',
|
||||
accent: '#dfdaef',
|
||||
accentForeground: '#4c4f69',
|
||||
border: '#acb0be',
|
||||
input: '#ccd0da',
|
||||
ring: '#6d2ebf',
|
||||
midground: '#6d2ebf',
|
||||
midgroundForeground: '#ffffff',
|
||||
composerRing: '#6d2ebf',
|
||||
destructive: '#d20f39',
|
||||
destructiveForeground: '#ffffff',
|
||||
sidebarBackground: '#e6e9ef',
|
||||
sidebarBorder: '#acb0be',
|
||||
userBubble: '#d7d3e9',
|
||||
userBubbleBorder: '#acb0be'
|
||||
},
|
||||
darkColors: {
|
||||
background: '#1e1e2e',
|
||||
foreground: '#cdd6f4',
|
||||
card: '#181825',
|
||||
cardForeground: '#cdd6f4',
|
||||
muted: '#29293a',
|
||||
mutedForeground: '#cdd6f4',
|
||||
popover: '#181825',
|
||||
popoverForeground: '#cdd6f4',
|
||||
primary: '#cba6f7',
|
||||
primaryForeground: '#ffffff',
|
||||
secondary: '#4e4466',
|
||||
secondaryForeground: '#cdd6f4',
|
||||
accent: '#3d3652',
|
||||
accentForeground: '#cdd6f4',
|
||||
border: '#585b70',
|
||||
input: '#313244',
|
||||
ring: '#cba6f7',
|
||||
midground: '#cba6f7',
|
||||
midgroundForeground: '#ffffff',
|
||||
composerRing: '#cba6f7',
|
||||
destructive: '#f38ba8',
|
||||
destructiveForeground: '#ffffff',
|
||||
sidebarBackground: '#181825',
|
||||
sidebarBorder: '#585b70',
|
||||
userBubble: '#38324b',
|
||||
userBubbleBorder: '#585b70'
|
||||
},
|
||||
...THEME_PRESET_PALETTES.catppuccin,
|
||||
terminal: {
|
||||
foreground: '#4c4f69',
|
||||
cursor: '#dc8a78',
|
||||
@@ -386,62 +223,7 @@ export const everforestTheme: DesktopTheme = {
|
||||
name: 'everforest',
|
||||
label: 'Everforest',
|
||||
description: 'Warm, low-contrast forest greens',
|
||||
colors: {
|
||||
background: '#fdf6e3',
|
||||
foreground: '#5c6a72',
|
||||
card: '#fdf6e3',
|
||||
cardForeground: '#5c6a72',
|
||||
muted: '#f7f0de',
|
||||
mutedForeground: '#939f91',
|
||||
popover: '#fdf6e3',
|
||||
popoverForeground: '#5c6a72',
|
||||
primary: '#586b35',
|
||||
primaryForeground: '#ffffff',
|
||||
secondary: '#e6e3cb',
|
||||
secondaryForeground: '#5c6a72',
|
||||
accent: '#e9e5ce',
|
||||
accentForeground: '#5c6a72',
|
||||
border: '#fdf6e3',
|
||||
input: '#fdf6e3',
|
||||
ring: '#586b35',
|
||||
midground: '#586b35',
|
||||
midgroundForeground: '#ffffff',
|
||||
composerRing: '#586b35',
|
||||
destructive: '#f1706f',
|
||||
destructiveForeground: '#ffffff',
|
||||
sidebarBackground: '#fdf6e3',
|
||||
sidebarBorder: '#fdf6e3',
|
||||
userBubble: '#e9e5ce',
|
||||
userBubbleBorder: '#fdf6e3'
|
||||
},
|
||||
darkColors: {
|
||||
background: '#2d353b',
|
||||
foreground: '#d3c6aa',
|
||||
card: '#2d353b',
|
||||
cardForeground: '#d3c6aa',
|
||||
muted: '#373e42',
|
||||
mutedForeground: '#859289',
|
||||
popover: '#2d353b',
|
||||
popoverForeground: '#d3c6aa',
|
||||
primary: '#a7c080',
|
||||
primaryForeground: '#ffffff',
|
||||
secondary: '#4f5c4e',
|
||||
secondaryForeground: '#d3c6aa',
|
||||
accent: '#434e47',
|
||||
accentForeground: '#d3c6aa',
|
||||
border: '#2d353b',
|
||||
input: '#2d353b',
|
||||
ring: '#a7c080',
|
||||
midground: '#a7c080',
|
||||
midgroundForeground: '#ffffff',
|
||||
composerRing: '#a7c080',
|
||||
destructive: '#da6362',
|
||||
destructiveForeground: '#ffffff',
|
||||
sidebarBackground: '#2d353b',
|
||||
sidebarBorder: '#2d353b',
|
||||
userBubble: '#434e47',
|
||||
userBubbleBorder: '#2d353b'
|
||||
},
|
||||
...THEME_PRESET_PALETTES.everforest,
|
||||
terminal: {
|
||||
foreground: '#5c6a72',
|
||||
cursor: '#5c6a72',
|
||||
@@ -489,62 +271,7 @@ export const solarizedTheme: DesktopTheme = {
|
||||
name: 'solarized',
|
||||
label: 'Solarized',
|
||||
description: 'Fixed-contrast light and dark',
|
||||
colors: {
|
||||
background: '#fdf6e3',
|
||||
foreground: '#1f1f1f',
|
||||
card: '#d3cbb7',
|
||||
cardForeground: '#1f1f1f',
|
||||
muted: '#f4eddb',
|
||||
mutedForeground: '#9ca8a6',
|
||||
popover: '#eee8d5',
|
||||
popoverForeground: '#1f1f1f',
|
||||
primary: '#675e34',
|
||||
primaryForeground: '#ffffff',
|
||||
secondary: '#e8e1cb',
|
||||
secondaryForeground: '#1f1f1f',
|
||||
accent: '#ebe4ce',
|
||||
accentForeground: '#1f1f1f',
|
||||
border: '#ddd6c1',
|
||||
input: '#ddd6c1',
|
||||
ring: '#675e34',
|
||||
midground: '#675e34',
|
||||
midgroundForeground: '#ffffff',
|
||||
composerRing: '#675e34',
|
||||
destructive: '#e25563',
|
||||
destructiveForeground: '#ffffff',
|
||||
sidebarBackground: '#eee8d5',
|
||||
sidebarBorder: '#ddd6c1',
|
||||
userBubble: '#c6bea7',
|
||||
userBubbleBorder: '#ddd6c1'
|
||||
},
|
||||
darkColors: {
|
||||
background: '#002b36',
|
||||
foreground: '#839496',
|
||||
card: '#002b36',
|
||||
cardForeground: '#839496',
|
||||
muted: '#08313c',
|
||||
mutedForeground: '#586e75',
|
||||
popover: '#001f26',
|
||||
popoverForeground: '#839496',
|
||||
primary: '#6ea1c4',
|
||||
primaryForeground: '#ffffff',
|
||||
secondary: '#1f4c5e',
|
||||
secondaryForeground: '#839496',
|
||||
accent: '#144050',
|
||||
accentForeground: '#839496',
|
||||
border: '#234751',
|
||||
input: '#073642',
|
||||
ring: '#6ea1c4',
|
||||
midground: '#6ea1c4',
|
||||
midgroundForeground: '#ffffff',
|
||||
composerRing: '#6ea1c4',
|
||||
destructive: '#e35957',
|
||||
destructiveForeground: '#ffffff',
|
||||
sidebarBackground: '#001f26',
|
||||
sidebarBorder: '#234751',
|
||||
userBubble: '#144050',
|
||||
userBubbleBorder: '#234751'
|
||||
},
|
||||
...THEME_PRESET_PALETTES.solarized,
|
||||
terminal: {
|
||||
foreground: '#657b83',
|
||||
black: '#657b83',
|
||||
@@ -587,13 +314,6 @@ export const solarizedTheme: DesktopTheme = {
|
||||
}
|
||||
}
|
||||
|
||||
const NOUS_ALT_BLUE = '#0053FD'
|
||||
const NOUS_ALT_NAVY = '#1540B1'
|
||||
const NOUS_ALT_CREAM = '#FFE6CB'
|
||||
|
||||
const nousAltTint = (pct: number) => `color-mix(in srgb, ${NOUS_ALT_BLUE} ${pct}%, #FFFFFF)`
|
||||
const nousAltTintTransparent = (pct: number) => `color-mix(in srgb, ${NOUS_ALT_BLUE} ${pct}%, transparent)`
|
||||
|
||||
/**
|
||||
* Nous Alt — the hand-authored Nous from before the GitHub fork. Light is
|
||||
* glass neutrals with brand blue; dark is cream on mission-blue.
|
||||
@@ -602,60 +322,7 @@ export const nousAltTheme: DesktopTheme = {
|
||||
name: 'nous-alt',
|
||||
label: 'Nous Alt',
|
||||
description: 'Glass neutrals, cream on mission-blue',
|
||||
colors: {
|
||||
background: '#F8FAFF',
|
||||
foreground: '#17171A',
|
||||
card: '#FFFFFF',
|
||||
cardForeground: '#17171A',
|
||||
muted: nousAltTint(5),
|
||||
mutedForeground: '#666678',
|
||||
popover: '#FFFFFF',
|
||||
popoverForeground: '#17171A',
|
||||
primary: NOUS_ALT_BLUE,
|
||||
primaryForeground: '#FCFCFC',
|
||||
secondary: nousAltTint(7),
|
||||
secondaryForeground: '#242432',
|
||||
accent: nousAltTint(10),
|
||||
accentForeground: '#202030',
|
||||
border: nousAltTintTransparent(22),
|
||||
input: nousAltTintTransparent(30),
|
||||
ring: NOUS_ALT_BLUE,
|
||||
midground: NOUS_ALT_BLUE,
|
||||
composerRing: NOUS_ALT_BLUE,
|
||||
destructive: '#C72E4D',
|
||||
destructiveForeground: '#FFFFFF',
|
||||
sidebarBackground: '#F3F7FF',
|
||||
sidebarBorder: nousAltTintTransparent(18),
|
||||
userBubble: nousAltTint(6),
|
||||
userBubbleBorder: nousAltTintTransparent(24)
|
||||
},
|
||||
darkColors: {
|
||||
background: '#0D2F86',
|
||||
foreground: NOUS_ALT_CREAM,
|
||||
card: '#12378F',
|
||||
cardForeground: NOUS_ALT_CREAM,
|
||||
muted: '#183F9A',
|
||||
mutedForeground: '#B5C7F3',
|
||||
popover: '#123A96',
|
||||
popoverForeground: NOUS_ALT_CREAM,
|
||||
primary: NOUS_ALT_CREAM,
|
||||
primaryForeground: '#0D2F86',
|
||||
secondary: '#1B45A4',
|
||||
secondaryForeground: '#E0E8FF',
|
||||
accent: NOUS_ALT_NAVY,
|
||||
accentForeground: '#F0F4FF',
|
||||
border: '#3158AD',
|
||||
input: '#0B2566',
|
||||
ring: NOUS_ALT_CREAM,
|
||||
midground: NOUS_ALT_BLUE,
|
||||
composerRing: NOUS_ALT_CREAM,
|
||||
destructive: '#C0473A',
|
||||
destructiveForeground: '#FEF2F2',
|
||||
sidebarBackground: '#09286F',
|
||||
sidebarBorder: '#234A9C',
|
||||
userBubble: '#143B91',
|
||||
userBubbleBorder: '#3A63BD'
|
||||
},
|
||||
...THEME_PRESET_PALETTES['nous-alt'],
|
||||
typography: {
|
||||
fontSans: SYSTEM_SANS,
|
||||
fontMono: SYSTEM_MONO,
|
||||
@@ -671,32 +338,7 @@ export const midnightTheme: DesktopTheme = {
|
||||
name: 'midnight',
|
||||
label: 'Midnight',
|
||||
description: 'Deep blue-violet with cool accents',
|
||||
colors: {
|
||||
background: '#08081c',
|
||||
foreground: '#ddd6ff',
|
||||
card: '#0d0d28',
|
||||
cardForeground: '#ddd6ff',
|
||||
muted: '#13133a',
|
||||
mutedForeground: '#7c7ab0',
|
||||
popover: '#0f0f2e',
|
||||
popoverForeground: '#ddd6ff',
|
||||
primary: '#ddd6ff',
|
||||
primaryForeground: '#08081c',
|
||||
secondary: '#1a1a4a',
|
||||
secondaryForeground: '#c4bff0',
|
||||
accent: '#1a1a44',
|
||||
accentForeground: '#d0c8ff',
|
||||
border: '#1e1e52',
|
||||
input: '#1e1e52',
|
||||
ring: '#8b80e8',
|
||||
midground: '#8b80e8',
|
||||
destructive: '#b03060',
|
||||
destructiveForeground: '#fef2f2',
|
||||
sidebarBackground: '#06061a',
|
||||
sidebarBorder: '#12123a',
|
||||
userBubble: '#14143a',
|
||||
userBubbleBorder: '#242466'
|
||||
},
|
||||
...THEME_PRESET_PALETTES.midnight,
|
||||
typography: {
|
||||
fontMono: `"JetBrains Mono", ${SYSTEM_MONO}`,
|
||||
fontUrl: 'https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap'
|
||||
@@ -707,32 +349,7 @@ export const emberTheme: DesktopTheme = {
|
||||
name: 'ember',
|
||||
label: 'Ember',
|
||||
description: 'Warm crimson and bronze — forge vibes',
|
||||
colors: {
|
||||
background: '#160800',
|
||||
foreground: '#ffd8b0',
|
||||
card: '#1e0e04',
|
||||
cardForeground: '#ffd8b0',
|
||||
muted: '#2a1408',
|
||||
mutedForeground: '#aa7a56',
|
||||
popover: '#221008',
|
||||
popoverForeground: '#ffd8b0',
|
||||
primary: '#ffd8b0',
|
||||
primaryForeground: '#160800',
|
||||
secondary: '#341800',
|
||||
secondaryForeground: '#f0c090',
|
||||
accent: '#301600',
|
||||
accentForeground: '#e8c080',
|
||||
border: '#3a1c08',
|
||||
input: '#3a1c08',
|
||||
ring: '#d97316',
|
||||
midground: '#d97316',
|
||||
destructive: '#c43010',
|
||||
destructiveForeground: '#fef2f2',
|
||||
sidebarBackground: '#100600',
|
||||
sidebarBorder: '#2a1004',
|
||||
userBubble: '#2a1000',
|
||||
userBubbleBorder: '#4a2010'
|
||||
},
|
||||
...THEME_PRESET_PALETTES.ember,
|
||||
typography: {
|
||||
fontMono: `"IBM Plex Mono", ${SYSTEM_MONO}`,
|
||||
fontUrl: 'https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;700&display=swap'
|
||||
@@ -744,32 +361,7 @@ export const monoTheme: DesktopTheme = {
|
||||
name: 'mono',
|
||||
label: 'Mono',
|
||||
description: 'Clean grayscale — minimal and focused',
|
||||
colors: {
|
||||
background: '#0e0e0e',
|
||||
foreground: '#eaeaea',
|
||||
card: '#141414',
|
||||
cardForeground: '#eaeaea',
|
||||
muted: '#1e1e1e',
|
||||
mutedForeground: '#808080',
|
||||
popover: '#181818',
|
||||
popoverForeground: '#eaeaea',
|
||||
primary: '#eaeaea',
|
||||
primaryForeground: '#0e0e0e',
|
||||
secondary: '#262626',
|
||||
secondaryForeground: '#c8c8c8',
|
||||
accent: '#222222',
|
||||
accentForeground: '#d8d8d8',
|
||||
border: '#2a2a2a',
|
||||
input: '#2a2a2a',
|
||||
ring: '#9a9a9a',
|
||||
midground: '#9a9a9a',
|
||||
destructive: '#a84040',
|
||||
destructiveForeground: '#fef2f2',
|
||||
sidebarBackground: '#0a0a0a',
|
||||
sidebarBorder: '#202020',
|
||||
userBubble: '#1a1a1a',
|
||||
userBubbleBorder: '#363636'
|
||||
}
|
||||
...THEME_PRESET_PALETTES.mono
|
||||
}
|
||||
|
||||
/** Neon green on black. Matches the CLI cyberpunk skin and dashboard theme. */
|
||||
@@ -777,32 +369,7 @@ export const cyberpunkTheme: DesktopTheme = {
|
||||
name: 'cyberpunk',
|
||||
label: 'Cyberpunk',
|
||||
description: 'Neon green on black — matrix terminal',
|
||||
colors: {
|
||||
background: '#000a00',
|
||||
foreground: '#00ff41',
|
||||
card: '#001200',
|
||||
cardForeground: '#00ff41',
|
||||
muted: '#001a00',
|
||||
mutedForeground: '#1a8a30',
|
||||
popover: '#001000',
|
||||
popoverForeground: '#00ff41',
|
||||
primary: '#00ff41',
|
||||
primaryForeground: '#000a00',
|
||||
secondary: '#002800',
|
||||
secondaryForeground: '#00cc34',
|
||||
accent: '#002000',
|
||||
accentForeground: '#00e038',
|
||||
border: '#003000',
|
||||
input: '#003000',
|
||||
ring: '#00ff41',
|
||||
midground: '#00ff41',
|
||||
destructive: '#ff003c',
|
||||
destructiveForeground: '#000a00',
|
||||
sidebarBackground: '#000600',
|
||||
sidebarBorder: '#001800',
|
||||
userBubble: '#001400',
|
||||
userBubbleBorder: '#004800'
|
||||
},
|
||||
...THEME_PRESET_PALETTES.cyberpunk,
|
||||
typography: {
|
||||
fontMono: `"Courier New", Courier, monospace, ${EMOJI_FALLBACK}`,
|
||||
fontSans: `"Courier New", Courier, monospace, ${EMOJI_FALLBACK}`
|
||||
@@ -814,32 +381,7 @@ export const slateTheme: DesktopTheme = {
|
||||
name: 'slate',
|
||||
label: 'Slate',
|
||||
description: 'Cool slate blue — focused developer theme',
|
||||
colors: {
|
||||
background: '#0d1117',
|
||||
foreground: '#c9d1d9',
|
||||
card: '#161b22',
|
||||
cardForeground: '#c9d1d9',
|
||||
muted: '#21262d',
|
||||
mutedForeground: '#8b949e',
|
||||
popover: '#1c2128',
|
||||
popoverForeground: '#c9d1d9',
|
||||
primary: '#c9d1d9',
|
||||
primaryForeground: '#0d1117',
|
||||
secondary: '#2a3038',
|
||||
secondaryForeground: '#adb5bf',
|
||||
accent: '#1e2530',
|
||||
accentForeground: '#c0c8d0',
|
||||
border: '#30363d',
|
||||
input: '#30363d',
|
||||
ring: '#58a6ff',
|
||||
midground: '#58a6ff',
|
||||
destructive: '#cf4848',
|
||||
destructiveForeground: '#fef2f2',
|
||||
sidebarBackground: '#090d13',
|
||||
sidebarBorder: '#1c2228',
|
||||
userBubble: '#1e2a38',
|
||||
userBubbleBorder: '#2e4060'
|
||||
},
|
||||
...THEME_PRESET_PALETTES.slate,
|
||||
typography: {
|
||||
fontMono: `"JetBrains Mono", ${SYSTEM_MONO}`
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
"./reasoning-effort": "./src/reasoning-effort.ts",
|
||||
"./reconnect-backoff": "./src/reconnect-backoff.ts",
|
||||
"./skin": "./src/skin.ts",
|
||||
"./slash": "./src/slash.ts"
|
||||
"./slash": "./src/slash.ts",
|
||||
"./theme-presets": "./src/theme-presets.ts"
|
||||
},
|
||||
"types": "./src/index.ts",
|
||||
"scripts": {
|
||||
|
||||
@@ -167,6 +167,12 @@ export {
|
||||
type SkillCommandDispatchResponse,
|
||||
SLASH_COMMAND_RE
|
||||
} from './slash'
|
||||
export {
|
||||
THEME_PRESET_PALETTES,
|
||||
type ThemePresetColors,
|
||||
type ThemePresetName,
|
||||
type ThemePresetPalette
|
||||
} from './theme-presets'
|
||||
export {
|
||||
backgroundMaterialFor,
|
||||
clampIntensity,
|
||||
|
||||
@@ -0,0 +1,555 @@
|
||||
/**
|
||||
* Raw palette table for every built-in Hermes theme preset — the single source
|
||||
* of truth shared by the desktop app (which layers OKLCH synthesis, terminal
|
||||
* palettes and typography on top) and the web dashboard (which projects each
|
||||
* preset down to its 3-slot background/midground/foreground model via
|
||||
* `webPresetFromShared`). Edit a preset's colours HERE; both surfaces follow.
|
||||
*
|
||||
* The palette-bearing presets (nous, github, catppuccin, everforest, solarized)
|
||||
* are forks of their VS Code originals converted by the desktop's
|
||||
* `buildThemeFromMarketplace`; re-convert from the upstream extension rather
|
||||
* than hand-editing hexes. `nous-alt` is first-party — do not re-derive it.
|
||||
*/
|
||||
|
||||
/** Tailwind-style colour slots a preset carries (light palette, or the only palette). */
|
||||
export interface ThemePresetColors {
|
||||
background: string
|
||||
foreground: string
|
||||
card: string
|
||||
cardForeground: string
|
||||
muted: string
|
||||
mutedForeground: string
|
||||
popover: string
|
||||
popoverForeground: string
|
||||
primary: string
|
||||
primaryForeground: string
|
||||
secondary: string
|
||||
secondaryForeground: string
|
||||
accent: string
|
||||
accentForeground: string
|
||||
border: string
|
||||
input: string
|
||||
/** Generic focus ring — buttons, inputs, etc. */
|
||||
ring: string
|
||||
/**
|
||||
* Brand-accent stroke — focus rings, streaming cursors, active session
|
||||
* pills, branded scrollbars, text selection. Falls back to `ring`.
|
||||
* Aliased to the DS `--midground` token.
|
||||
*/
|
||||
midground?: string
|
||||
/** Auto-derived from `midground` luminance when omitted. */
|
||||
midgroundForeground?: string
|
||||
/** Composer outline / focus color. Falls back to `midground`. */
|
||||
composerRing?: string
|
||||
destructive: string
|
||||
destructiveForeground: string
|
||||
sidebarBackground?: string
|
||||
sidebarBorder?: string
|
||||
userBubble?: string
|
||||
userBubbleBorder?: string
|
||||
}
|
||||
|
||||
export interface ThemePresetPalette {
|
||||
/** Light palette (also reused for dark when `darkColors` is omitted). */
|
||||
colors: ThemePresetColors
|
||||
/** Hand-tuned dark palette. Skins like `nous` ship one. */
|
||||
darkColors?: ThemePresetColors
|
||||
}
|
||||
|
||||
const NOUS_ALT_BLUE = '#0053FD'
|
||||
const NOUS_ALT_NAVY = '#1540B1'
|
||||
const NOUS_ALT_CREAM = '#FFE6CB'
|
||||
|
||||
const nousAltTint = (pct: number) => `color-mix(in srgb, ${NOUS_ALT_BLUE} ${pct}%, #FFFFFF)`
|
||||
const nousAltTintTransparent = (pct: number) => `color-mix(in srgb, ${NOUS_ALT_BLUE} ${pct}%, transparent)`
|
||||
|
||||
export const THEME_PRESET_PALETTES = {
|
||||
github: {
|
||||
colors: {
|
||||
background: '#ffffff',
|
||||
foreground: '#1f2328',
|
||||
card: '#f6f8fa',
|
||||
cardForeground: '#1f2328',
|
||||
muted: '#f6f6f6',
|
||||
mutedForeground: '#656d76',
|
||||
popover: '#ffffff',
|
||||
popoverForeground: '#1f2328',
|
||||
primary: '#196d31',
|
||||
primaryForeground: '#ffffff',
|
||||
secondary: '#dfebe2',
|
||||
secondaryForeground: '#1f2328',
|
||||
accent: '#e3ede6',
|
||||
accentForeground: '#1f2328',
|
||||
border: '#d0d7de',
|
||||
input: '#ffffff',
|
||||
ring: '#196d31',
|
||||
midground: '#196d31',
|
||||
midgroundForeground: '#ffffff',
|
||||
composerRing: '#196d31',
|
||||
destructive: '#cf222e',
|
||||
destructiveForeground: '#ffffff',
|
||||
sidebarBackground: '#f6f8fa',
|
||||
sidebarBorder: '#d0d7de',
|
||||
userBubble: '#dbe7e2',
|
||||
userBubbleBorder: '#d0d7de'
|
||||
},
|
||||
darkColors: {
|
||||
background: '#0d1117',
|
||||
foreground: '#e6edf3',
|
||||
card: '#010409',
|
||||
cardForeground: '#e6edf3',
|
||||
muted: '#1a1e24',
|
||||
mutedForeground: '#7d8590',
|
||||
popover: '#161b22',
|
||||
popoverForeground: '#e6edf3',
|
||||
primary: '#4f9e5e',
|
||||
primaryForeground: '#ffffff',
|
||||
secondary: '#1f382b',
|
||||
secondaryForeground: '#e6edf3',
|
||||
accent: '#192a24',
|
||||
accentForeground: '#e6edf3',
|
||||
border: '#30363d',
|
||||
input: '#0d1117',
|
||||
ring: '#4f9e5e',
|
||||
midground: '#4f9e5e',
|
||||
midgroundForeground: '#ffffff',
|
||||
composerRing: '#4f9e5e',
|
||||
destructive: '#f85149',
|
||||
destructiveForeground: '#ffffff',
|
||||
sidebarBackground: '#010409',
|
||||
sidebarBorder: '#30363d',
|
||||
userBubble: '#0f2018',
|
||||
userBubbleBorder: '#30363d'
|
||||
}
|
||||
},
|
||||
nous: {
|
||||
colors: {
|
||||
background: '#ffffff',
|
||||
foreground: '#1f2328',
|
||||
card: '#f6f8fa',
|
||||
cardForeground: '#1f2328',
|
||||
muted: '#f6f6f6',
|
||||
mutedForeground: '#656d76',
|
||||
popover: '#ffffff',
|
||||
popoverForeground: '#1f2328',
|
||||
primary: '#0053fd',
|
||||
primaryForeground: '#ffffff',
|
||||
secondary: '#deeaff',
|
||||
secondaryForeground: '#1f2328',
|
||||
accent: '#e3edff',
|
||||
accentForeground: '#1f2328',
|
||||
border: '#d0d7de',
|
||||
input: '#ffffff',
|
||||
ring: '#0053fd',
|
||||
midground: '#0053fd',
|
||||
midgroundForeground: '#ffffff',
|
||||
composerRing: '#0053fd',
|
||||
destructive: '#cf222e',
|
||||
destructiveForeground: '#ffffff',
|
||||
sidebarBackground: '#f6f8fa',
|
||||
sidebarBorder: '#d0d7de',
|
||||
userBubble: '#dae7fd',
|
||||
userBubbleBorder: '#d0d7de'
|
||||
},
|
||||
darkColors: {
|
||||
background: '#0d1117',
|
||||
foreground: '#e6edf3',
|
||||
card: '#010409',
|
||||
cardForeground: '#e6edf3',
|
||||
muted: '#1a1e24',
|
||||
mutedForeground: '#7d8590',
|
||||
popover: '#161b22',
|
||||
popoverForeground: '#e6edf3',
|
||||
primary: '#4a84fe',
|
||||
primaryForeground: '#161616',
|
||||
secondary: '#1d2e4f',
|
||||
secondaryForeground: '#e6edf3',
|
||||
accent: '#17243a',
|
||||
accentForeground: '#e6edf3',
|
||||
border: '#30363d',
|
||||
input: '#0d1117',
|
||||
ring: '#4a84fe',
|
||||
midground: '#4a84fe',
|
||||
midgroundForeground: '#161616',
|
||||
composerRing: '#4a84fe',
|
||||
destructive: '#f85149',
|
||||
destructiveForeground: '#ffffff',
|
||||
sidebarBackground: '#010409',
|
||||
sidebarBorder: '#30363d',
|
||||
userBubble: '#07162c',
|
||||
userBubbleBorder: '#30363d'
|
||||
}
|
||||
},
|
||||
catppuccin: {
|
||||
colors: {
|
||||
background: '#eff1f5',
|
||||
foreground: '#4c4f69',
|
||||
card: '#e6e9ef',
|
||||
cardForeground: '#4c4f69',
|
||||
muted: '#e8ebef',
|
||||
mutedForeground: '#4c4f69',
|
||||
popover: '#e6e9ef',
|
||||
popoverForeground: '#4c4f69',
|
||||
primary: '#6d2ebf',
|
||||
primaryForeground: '#ffffff',
|
||||
secondary: '#ddd6ed',
|
||||
secondaryForeground: '#4c4f69',
|
||||
accent: '#dfdaef',
|
||||
accentForeground: '#4c4f69',
|
||||
border: '#acb0be',
|
||||
input: '#ccd0da',
|
||||
ring: '#6d2ebf',
|
||||
midground: '#6d2ebf',
|
||||
midgroundForeground: '#ffffff',
|
||||
composerRing: '#6d2ebf',
|
||||
destructive: '#d20f39',
|
||||
destructiveForeground: '#ffffff',
|
||||
sidebarBackground: '#e6e9ef',
|
||||
sidebarBorder: '#acb0be',
|
||||
userBubble: '#d7d3e9',
|
||||
userBubbleBorder: '#acb0be'
|
||||
},
|
||||
darkColors: {
|
||||
background: '#1e1e2e',
|
||||
foreground: '#cdd6f4',
|
||||
card: '#181825',
|
||||
cardForeground: '#cdd6f4',
|
||||
muted: '#29293a',
|
||||
mutedForeground: '#cdd6f4',
|
||||
popover: '#181825',
|
||||
popoverForeground: '#cdd6f4',
|
||||
primary: '#cba6f7',
|
||||
primaryForeground: '#ffffff',
|
||||
secondary: '#4e4466',
|
||||
secondaryForeground: '#cdd6f4',
|
||||
accent: '#3d3652',
|
||||
accentForeground: '#cdd6f4',
|
||||
border: '#585b70',
|
||||
input: '#313244',
|
||||
ring: '#cba6f7',
|
||||
midground: '#cba6f7',
|
||||
midgroundForeground: '#ffffff',
|
||||
composerRing: '#cba6f7',
|
||||
destructive: '#f38ba8',
|
||||
destructiveForeground: '#ffffff',
|
||||
sidebarBackground: '#181825',
|
||||
sidebarBorder: '#585b70',
|
||||
userBubble: '#38324b',
|
||||
userBubbleBorder: '#585b70'
|
||||
}
|
||||
},
|
||||
everforest: {
|
||||
colors: {
|
||||
background: '#fdf6e3',
|
||||
foreground: '#5c6a72',
|
||||
card: '#fdf6e3',
|
||||
cardForeground: '#5c6a72',
|
||||
muted: '#f7f0de',
|
||||
mutedForeground: '#939f91',
|
||||
popover: '#fdf6e3',
|
||||
popoverForeground: '#5c6a72',
|
||||
primary: '#586b35',
|
||||
primaryForeground: '#ffffff',
|
||||
secondary: '#e6e3cb',
|
||||
secondaryForeground: '#5c6a72',
|
||||
accent: '#e9e5ce',
|
||||
accentForeground: '#5c6a72',
|
||||
border: '#fdf6e3',
|
||||
input: '#fdf6e3',
|
||||
ring: '#586b35',
|
||||
midground: '#586b35',
|
||||
midgroundForeground: '#ffffff',
|
||||
composerRing: '#586b35',
|
||||
destructive: '#f1706f',
|
||||
destructiveForeground: '#ffffff',
|
||||
sidebarBackground: '#fdf6e3',
|
||||
sidebarBorder: '#fdf6e3',
|
||||
userBubble: '#e9e5ce',
|
||||
userBubbleBorder: '#fdf6e3'
|
||||
},
|
||||
darkColors: {
|
||||
background: '#2d353b',
|
||||
foreground: '#d3c6aa',
|
||||
card: '#2d353b',
|
||||
cardForeground: '#d3c6aa',
|
||||
muted: '#373e42',
|
||||
mutedForeground: '#859289',
|
||||
popover: '#2d353b',
|
||||
popoverForeground: '#d3c6aa',
|
||||
primary: '#a7c080',
|
||||
primaryForeground: '#ffffff',
|
||||
secondary: '#4f5c4e',
|
||||
secondaryForeground: '#d3c6aa',
|
||||
accent: '#434e47',
|
||||
accentForeground: '#d3c6aa',
|
||||
border: '#2d353b',
|
||||
input: '#2d353b',
|
||||
ring: '#a7c080',
|
||||
midground: '#a7c080',
|
||||
midgroundForeground: '#ffffff',
|
||||
composerRing: '#a7c080',
|
||||
destructive: '#da6362',
|
||||
destructiveForeground: '#ffffff',
|
||||
sidebarBackground: '#2d353b',
|
||||
sidebarBorder: '#2d353b',
|
||||
userBubble: '#434e47',
|
||||
userBubbleBorder: '#2d353b'
|
||||
}
|
||||
},
|
||||
solarized: {
|
||||
colors: {
|
||||
background: '#fdf6e3',
|
||||
foreground: '#1f1f1f',
|
||||
card: '#d3cbb7',
|
||||
cardForeground: '#1f1f1f',
|
||||
muted: '#f4eddb',
|
||||
mutedForeground: '#9ca8a6',
|
||||
popover: '#eee8d5',
|
||||
popoverForeground: '#1f1f1f',
|
||||
primary: '#675e34',
|
||||
primaryForeground: '#ffffff',
|
||||
secondary: '#e8e1cb',
|
||||
secondaryForeground: '#1f1f1f',
|
||||
accent: '#ebe4ce',
|
||||
accentForeground: '#1f1f1f',
|
||||
border: '#ddd6c1',
|
||||
input: '#ddd6c1',
|
||||
ring: '#675e34',
|
||||
midground: '#675e34',
|
||||
midgroundForeground: '#ffffff',
|
||||
composerRing: '#675e34',
|
||||
destructive: '#e25563',
|
||||
destructiveForeground: '#ffffff',
|
||||
sidebarBackground: '#eee8d5',
|
||||
sidebarBorder: '#ddd6c1',
|
||||
userBubble: '#c6bea7',
|
||||
userBubbleBorder: '#ddd6c1'
|
||||
},
|
||||
darkColors: {
|
||||
background: '#002b36',
|
||||
foreground: '#839496',
|
||||
card: '#002b36',
|
||||
cardForeground: '#839496',
|
||||
muted: '#08313c',
|
||||
mutedForeground: '#586e75',
|
||||
popover: '#001f26',
|
||||
popoverForeground: '#839496',
|
||||
primary: '#6ea1c4',
|
||||
primaryForeground: '#ffffff',
|
||||
secondary: '#1f4c5e',
|
||||
secondaryForeground: '#839496',
|
||||
accent: '#144050',
|
||||
accentForeground: '#839496',
|
||||
border: '#234751',
|
||||
input: '#073642',
|
||||
ring: '#6ea1c4',
|
||||
midground: '#6ea1c4',
|
||||
midgroundForeground: '#ffffff',
|
||||
composerRing: '#6ea1c4',
|
||||
destructive: '#e35957',
|
||||
destructiveForeground: '#ffffff',
|
||||
sidebarBackground: '#001f26',
|
||||
sidebarBorder: '#234751',
|
||||
userBubble: '#144050',
|
||||
userBubbleBorder: '#234751'
|
||||
}
|
||||
},
|
||||
'nous-alt': {
|
||||
colors: {
|
||||
background: '#F8FAFF',
|
||||
foreground: '#17171A',
|
||||
card: '#FFFFFF',
|
||||
cardForeground: '#17171A',
|
||||
muted: nousAltTint(5),
|
||||
mutedForeground: '#666678',
|
||||
popover: '#FFFFFF',
|
||||
popoverForeground: '#17171A',
|
||||
primary: NOUS_ALT_BLUE,
|
||||
primaryForeground: '#FCFCFC',
|
||||
secondary: nousAltTint(7),
|
||||
secondaryForeground: '#242432',
|
||||
accent: nousAltTint(10),
|
||||
accentForeground: '#202030',
|
||||
border: nousAltTintTransparent(22),
|
||||
input: nousAltTintTransparent(30),
|
||||
ring: NOUS_ALT_BLUE,
|
||||
midground: NOUS_ALT_BLUE,
|
||||
composerRing: NOUS_ALT_BLUE,
|
||||
destructive: '#C72E4D',
|
||||
destructiveForeground: '#FFFFFF',
|
||||
sidebarBackground: '#F3F7FF',
|
||||
sidebarBorder: nousAltTintTransparent(18),
|
||||
userBubble: nousAltTint(6),
|
||||
userBubbleBorder: nousAltTintTransparent(24)
|
||||
},
|
||||
darkColors: {
|
||||
background: '#0D2F86',
|
||||
foreground: NOUS_ALT_CREAM,
|
||||
card: '#12378F',
|
||||
cardForeground: NOUS_ALT_CREAM,
|
||||
muted: '#183F9A',
|
||||
mutedForeground: '#B5C7F3',
|
||||
popover: '#123A96',
|
||||
popoverForeground: NOUS_ALT_CREAM,
|
||||
primary: NOUS_ALT_CREAM,
|
||||
primaryForeground: '#0D2F86',
|
||||
secondary: '#1B45A4',
|
||||
secondaryForeground: '#E0E8FF',
|
||||
accent: NOUS_ALT_NAVY,
|
||||
accentForeground: '#F0F4FF',
|
||||
border: '#3158AD',
|
||||
input: '#0B2566',
|
||||
ring: NOUS_ALT_CREAM,
|
||||
midground: NOUS_ALT_BLUE,
|
||||
composerRing: NOUS_ALT_CREAM,
|
||||
destructive: '#C0473A',
|
||||
destructiveForeground: '#FEF2F2',
|
||||
sidebarBackground: '#09286F',
|
||||
sidebarBorder: '#234A9C',
|
||||
userBubble: '#143B91',
|
||||
userBubbleBorder: '#3A63BD'
|
||||
}
|
||||
},
|
||||
midnight: {
|
||||
colors: {
|
||||
background: '#08081c',
|
||||
foreground: '#ddd6ff',
|
||||
card: '#0d0d28',
|
||||
cardForeground: '#ddd6ff',
|
||||
muted: '#13133a',
|
||||
mutedForeground: '#7c7ab0',
|
||||
popover: '#0f0f2e',
|
||||
popoverForeground: '#ddd6ff',
|
||||
primary: '#ddd6ff',
|
||||
primaryForeground: '#08081c',
|
||||
secondary: '#1a1a4a',
|
||||
secondaryForeground: '#c4bff0',
|
||||
accent: '#1a1a44',
|
||||
accentForeground: '#d0c8ff',
|
||||
border: '#1e1e52',
|
||||
input: '#1e1e52',
|
||||
ring: '#8b80e8',
|
||||
midground: '#8b80e8',
|
||||
destructive: '#b03060',
|
||||
destructiveForeground: '#fef2f2',
|
||||
sidebarBackground: '#06061a',
|
||||
sidebarBorder: '#12123a',
|
||||
userBubble: '#14143a',
|
||||
userBubbleBorder: '#242466'
|
||||
}
|
||||
},
|
||||
ember: {
|
||||
colors: {
|
||||
background: '#160800',
|
||||
foreground: '#ffd8b0',
|
||||
card: '#1e0e04',
|
||||
cardForeground: '#ffd8b0',
|
||||
muted: '#2a1408',
|
||||
mutedForeground: '#aa7a56',
|
||||
popover: '#221008',
|
||||
popoverForeground: '#ffd8b0',
|
||||
primary: '#ffd8b0',
|
||||
primaryForeground: '#160800',
|
||||
secondary: '#341800',
|
||||
secondaryForeground: '#f0c090',
|
||||
accent: '#301600',
|
||||
accentForeground: '#e8c080',
|
||||
border: '#3a1c08',
|
||||
input: '#3a1c08',
|
||||
ring: '#d97316',
|
||||
midground: '#d97316',
|
||||
destructive: '#c43010',
|
||||
destructiveForeground: '#fef2f2',
|
||||
sidebarBackground: '#100600',
|
||||
sidebarBorder: '#2a1004',
|
||||
userBubble: '#2a1000',
|
||||
userBubbleBorder: '#4a2010'
|
||||
}
|
||||
},
|
||||
mono: {
|
||||
colors: {
|
||||
background: '#0e0e0e',
|
||||
foreground: '#eaeaea',
|
||||
card: '#141414',
|
||||
cardForeground: '#eaeaea',
|
||||
muted: '#1e1e1e',
|
||||
mutedForeground: '#808080',
|
||||
popover: '#181818',
|
||||
popoverForeground: '#eaeaea',
|
||||
primary: '#eaeaea',
|
||||
primaryForeground: '#0e0e0e',
|
||||
secondary: '#262626',
|
||||
secondaryForeground: '#c8c8c8',
|
||||
accent: '#222222',
|
||||
accentForeground: '#d8d8d8',
|
||||
border: '#2a2a2a',
|
||||
input: '#2a2a2a',
|
||||
ring: '#9a9a9a',
|
||||
midground: '#9a9a9a',
|
||||
destructive: '#a84040',
|
||||
destructiveForeground: '#fef2f2',
|
||||
sidebarBackground: '#0a0a0a',
|
||||
sidebarBorder: '#202020',
|
||||
userBubble: '#1a1a1a',
|
||||
userBubbleBorder: '#363636'
|
||||
}
|
||||
},
|
||||
cyberpunk: {
|
||||
colors: {
|
||||
background: '#000a00',
|
||||
foreground: '#00ff41',
|
||||
card: '#001200',
|
||||
cardForeground: '#00ff41',
|
||||
muted: '#001a00',
|
||||
mutedForeground: '#1a8a30',
|
||||
popover: '#001000',
|
||||
popoverForeground: '#00ff41',
|
||||
primary: '#00ff41',
|
||||
primaryForeground: '#000a00',
|
||||
secondary: '#002800',
|
||||
secondaryForeground: '#00cc34',
|
||||
accent: '#002000',
|
||||
accentForeground: '#00e038',
|
||||
border: '#003000',
|
||||
input: '#003000',
|
||||
ring: '#00ff41',
|
||||
midground: '#00ff41',
|
||||
destructive: '#ff003c',
|
||||
destructiveForeground: '#000a00',
|
||||
sidebarBackground: '#000600',
|
||||
sidebarBorder: '#001800',
|
||||
userBubble: '#001400',
|
||||
userBubbleBorder: '#004800'
|
||||
}
|
||||
},
|
||||
slate: {
|
||||
colors: {
|
||||
background: '#0d1117',
|
||||
foreground: '#c9d1d9',
|
||||
card: '#161b22',
|
||||
cardForeground: '#c9d1d9',
|
||||
muted: '#21262d',
|
||||
mutedForeground: '#8b949e',
|
||||
popover: '#1c2128',
|
||||
popoverForeground: '#c9d1d9',
|
||||
primary: '#c9d1d9',
|
||||
primaryForeground: '#0d1117',
|
||||
secondary: '#2a3038',
|
||||
secondaryForeground: '#adb5bf',
|
||||
accent: '#1e2530',
|
||||
accentForeground: '#c0c8d0',
|
||||
border: '#30363d',
|
||||
input: '#30363d',
|
||||
ring: '#58a6ff',
|
||||
midground: '#58a6ff',
|
||||
destructive: '#cf4848',
|
||||
destructiveForeground: '#fef2f2',
|
||||
sidebarBackground: '#090d13',
|
||||
sidebarBorder: '#1c2228',
|
||||
userBubble: '#1e2a38',
|
||||
userBubbleBorder: '#2e4060'
|
||||
}
|
||||
}
|
||||
} satisfies Record<string, ThemePresetPalette>
|
||||
|
||||
export type ThemePresetName = keyof typeof THEME_PRESET_PALETTES
|
||||
@@ -0,0 +1,29 @@
|
||||
import { contrastRatio, THEME_PRESET_PALETTES } from "@hermes/shared";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { BUILTIN_THEMES, webPresetFromShared } from "./presets";
|
||||
|
||||
// Every preset the dashboard shares with the desktop must render the shared
|
||||
// table's palette, not a private copy — that is the whole point of the table.
|
||||
// The second assertion keeps the projection honest: whatever slot the mapping
|
||||
// picks as the dashboard's text/primary colour has to stay legible on the
|
||||
// canvas it picks, so a future re-mapping cannot silently ship grey-on-grey.
|
||||
describe("dashboard presets derive from the shared palette table", () => {
|
||||
const shared = Object.keys(BUILTIN_THEMES).filter(
|
||||
(name): name is keyof typeof THEME_PRESET_PALETTES => name in THEME_PRESET_PALETTES,
|
||||
);
|
||||
|
||||
it("covers the presets both surfaces ship", () => {
|
||||
expect(shared).toEqual(expect.arrayContaining(["cyberpunk", "ember", "midnight", "mono"]));
|
||||
});
|
||||
|
||||
it.each(shared)("%s: canvas equals the shared background and the accent reads on it", (name) => {
|
||||
const preset = THEME_PRESET_PALETTES[name];
|
||||
const derived = webPresetFromShared(preset);
|
||||
const palette = BUILTIN_THEMES[name].palette;
|
||||
|
||||
expect(palette.background.hex).toBe((preset.darkColors ?? preset.colors).background);
|
||||
expect(palette.midground.hex).toBe(derived.midground.hex);
|
||||
expect(contrastRatio(palette.midground.hex, palette.background.hex)).toBeGreaterThanOrEqual(3);
|
||||
});
|
||||
});
|
||||
+32
-17
@@ -1,4 +1,5 @@
|
||||
import type { DashboardTheme, ThemeTypography, ThemeLayout } from "./types";
|
||||
import { parseColor, THEME_PRESET_PALETTES, type ThemePresetPalette } from "@hermes/shared";
|
||||
import type { DashboardTheme, ThemePalette, ThemeTypography, ThemeLayout } from "./types";
|
||||
|
||||
/**
|
||||
* Built-in dashboard themes.
|
||||
@@ -9,6 +10,10 @@ import type { DashboardTheme, ThemeTypography, ThemeLayout } from "./types";
|
||||
*
|
||||
* Theme names must stay in sync with the backend's
|
||||
* `_BUILTIN_DASHBOARD_THEMES` list in `hermes_cli/web_server.py`.
|
||||
*
|
||||
* Presets that also ship on the desktop (midnight, ember, mono, cyberpunk)
|
||||
* take their colours from `@hermes/shared` `THEME_PRESET_PALETTES` so both
|
||||
* surfaces render one palette; only typography/layout/overrides live here.
|
||||
*/
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -34,6 +39,28 @@ const DEFAULT_LAYOUT: ThemeLayout = {
|
||||
density: "comfortable",
|
||||
};
|
||||
|
||||
/**
|
||||
* Project a shared (desktop-shaped) preset palette onto the dashboard's
|
||||
* 3-slot model. The dashboard's `midground` is its text + primary-fill
|
||||
* colour, which is the desktop's `primary`; its `warmGlow` is the brand
|
||||
* accent stroke, which is the desktop's `midground` (falling back to `ring`).
|
||||
* `foreground` stays the dashboard's invisible white overlay. Dark palettes
|
||||
* are the dashboard's home turf, so a preset shipping `darkColors` is read
|
||||
* from that side.
|
||||
*/
|
||||
export function webPresetFromShared(
|
||||
preset: ThemePresetPalette,
|
||||
): Omit<ThemePalette, "noiseOpacity"> {
|
||||
const colors = preset.darkColors ?? preset.colors;
|
||||
const [r, g, b] = parseColor(colors.midground ?? colors.ring) ?? [255, 255, 255];
|
||||
return {
|
||||
background: { hex: colors.background, alpha: 1 },
|
||||
midground: { hex: colors.primary, alpha: 1 },
|
||||
foreground: { hex: "#ffffff", alpha: 0 },
|
||||
warmGlow: `rgba(${r}, ${g}, ${b}, 0.3)`,
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Themes
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -59,10 +86,7 @@ export const midnightTheme: DashboardTheme = {
|
||||
label: "Midnight",
|
||||
description: "Deep blue-violet with cool accents",
|
||||
palette: {
|
||||
background: { hex: "#0a0a1f", alpha: 1 },
|
||||
midground: { hex: "#d4c8ff", alpha: 1 },
|
||||
foreground: { hex: "#ffffff", alpha: 0 },
|
||||
warmGlow: "rgba(167, 139, 250, 0.32)",
|
||||
...webPresetFromShared(THEME_PRESET_PALETTES.midnight),
|
||||
noiseOpacity: 0.8,
|
||||
},
|
||||
typography: {
|
||||
@@ -84,10 +108,7 @@ export const emberTheme: DashboardTheme = {
|
||||
label: "Ember",
|
||||
description: "Warm crimson and bronze — forge vibes",
|
||||
palette: {
|
||||
background: { hex: "#1a0a06", alpha: 1 },
|
||||
midground: { hex: "#ffd8b0", alpha: 1 },
|
||||
foreground: { hex: "#ffffff", alpha: 0 },
|
||||
warmGlow: "rgba(249, 115, 22, 0.38)",
|
||||
...webPresetFromShared(THEME_PRESET_PALETTES.ember),
|
||||
noiseOpacity: 1,
|
||||
},
|
||||
typography: {
|
||||
@@ -112,10 +133,7 @@ export const monoTheme: DashboardTheme = {
|
||||
label: "Mono",
|
||||
description: "Clean grayscale — minimal and focused",
|
||||
palette: {
|
||||
background: { hex: "#0e0e0e", alpha: 1 },
|
||||
midground: { hex: "#eaeaea", alpha: 1 },
|
||||
foreground: { hex: "#ffffff", alpha: 0 },
|
||||
warmGlow: "rgba(255, 255, 255, 0.1)",
|
||||
...webPresetFromShared(THEME_PRESET_PALETTES.mono),
|
||||
noiseOpacity: 0.6,
|
||||
},
|
||||
typography: {
|
||||
@@ -136,10 +154,7 @@ export const cyberpunkTheme: DashboardTheme = {
|
||||
label: "Cyberpunk",
|
||||
description: "Neon green on black — matrix terminal",
|
||||
palette: {
|
||||
background: { hex: "#040608", alpha: 1 },
|
||||
midground: { hex: "#9bffcf", alpha: 1 },
|
||||
foreground: { hex: "#ffffff", alpha: 0 },
|
||||
warmGlow: "rgba(0, 255, 136, 0.22)",
|
||||
...webPresetFromShared(THEME_PRESET_PALETTES.cyberpunk),
|
||||
noiseOpacity: 1.2,
|
||||
},
|
||||
typography: {
|
||||
|
||||
Reference in New Issue
Block a user