Settings page: themes (Dark, Light, Cyberpunk, Retro) with xterm palettes and hub-synced choice, change password, access tokens, about
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
254e0cdd58
commit
a4f39859b6
@@ -0,0 +1,83 @@
|
||||
// Global themes. Each one is a set of CSS variables applied to <html>, a matching xterm
|
||||
// palette, and a data-theme attribute the stylesheet can hang extra flourishes on.
|
||||
import type { ITheme } from '@xterm/xterm'
|
||||
|
||||
export interface Theme {
|
||||
id: string
|
||||
name: string
|
||||
blurb: string
|
||||
vars: Record<string, string>
|
||||
xterm: ITheme
|
||||
}
|
||||
|
||||
const base = {
|
||||
'--ok': '#3fb950', '--bad': '#ff7b72', '--s1': '#3987e5', '--s2': '#c98500', '--track': 'rgba(57, 135, 229, .18)',
|
||||
'--btn-fg': '#111', '--term-bg': '#0d1117', '--font': 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
|
||||
'--mono': 'ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace',
|
||||
}
|
||||
|
||||
export const THEMES: Theme[] = [
|
||||
{
|
||||
id: 'dark', name: 'Dark', blurb: 'The default. Graphite surfaces, amber brand, blue accents.',
|
||||
vars: { ...base, '--bg': '#111418', '--bg2': '#171b21', '--bg3': '#1e242c', '--border': '#2a313b', '--fg': '#d0d7de', '--muted': '#7d8590', '--accent': '#e3a33b', '--accent2': '#58a6ff' },
|
||||
xterm: {
|
||||
background: '#0d1117', foreground: '#d0d7de', cursor: '#d0d7de', selectionBackground: '#264f78',
|
||||
black: '#0d1117', red: '#ff7b72', green: '#3fb950', yellow: '#d29922', blue: '#58a6ff', magenta: '#bc8cff', cyan: '#39c5cf', white: '#b1bac4',
|
||||
brightBlack: '#6e7681', brightRed: '#ffa198', brightGreen: '#56d364', brightYellow: '#e3b341', brightBlue: '#79c0ff', brightMagenta: '#d2a8ff', brightCyan: '#56d4dd', brightWhite: '#f0f6fc',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'light', name: 'Light', blurb: 'Paper white with the same layout. For daylight and bright rooms.',
|
||||
vars: { ...base, '--bg': '#f6f8fa', '--bg2': '#ffffff', '--bg3': '#eaeef2', '--border': '#d0d7de', '--fg': '#1f2328', '--muted': '#59636e', '--accent': '#b35c00', '--accent2': '#0969da',
|
||||
'--ok': '#1a7f37', '--bad': '#cf222e', '--s1': '#2a78d6', '--s2': '#9a6700', '--track': 'rgba(42, 120, 214, .16)', '--btn-fg': '#ffffff', '--term-bg': '#ffffff' },
|
||||
xterm: {
|
||||
background: '#ffffff', foreground: '#1f2328', cursor: '#1f2328', selectionBackground: '#b6d6ff',
|
||||
black: '#24292f', red: '#cf222e', green: '#116329', yellow: '#4d2d00', blue: '#0969da', magenta: '#8250df', cyan: '#1b7c83', white: '#6e7781',
|
||||
brightBlack: '#57606a', brightRed: '#a40e26', brightGreen: '#1a7f37', brightYellow: '#633c01', brightBlue: '#218bff', brightMagenta: '#a475f9', brightCyan: '#3192aa', brightWhite: '#8c959f',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'cyberpunk', name: 'Cyberpunk', blurb: 'Deep violet, magenta brand, cyan accents, a little neon glow.',
|
||||
vars: { ...base, '--bg': '#0b0014', '--bg2': '#140026', '--bg3': '#1f0a3a', '--border': '#3b1f6b', '--fg': '#e8e0ff', '--muted': '#9b8bd0', '--accent': '#ff2bd6', '--accent2': '#00e5ff',
|
||||
'--ok': '#39ff14', '--bad': '#ff3860', '--s1': '#00e5ff', '--s2': '#ffe600', '--track': 'rgba(0, 229, 255, .16)', '--btn-fg': '#0b0014', '--term-bg': '#07000f' },
|
||||
xterm: {
|
||||
background: '#07000f', foreground: '#e8e0ff', cursor: '#ff2bd6', selectionBackground: '#3b1f6b',
|
||||
black: '#0b0014', red: '#ff3860', green: '#39ff14', yellow: '#ffe600', blue: '#00e5ff', magenta: '#ff2bd6', cyan: '#5cf6ff', white: '#c8bff0',
|
||||
brightBlack: '#6a55a3', brightRed: '#ff6b8a', brightGreen: '#7dff5c', brightYellow: '#fff27a', brightBlue: '#66eeff', brightMagenta: '#ff7de8', brightCyan: '#a5fbff', brightWhite: '#ffffff',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'retro', name: 'Retro', blurb: 'Amber phosphor terminal. Monospace everywhere, faint scanlines.',
|
||||
vars: { ...base, '--bg': '#0e0b05', '--bg2': '#171106', '--bg3': '#221909', '--border': '#3a2c10', '--fg': '#ffb000', '--muted': '#a8781c', '--accent': '#ffcc33', '--accent2': '#ffb000',
|
||||
'--ok': '#ffd166', '--bad': '#ff6b35', '--s1': '#ffb000', '--s2': '#ff8c00', '--track': 'rgba(255, 176, 0, .16)', '--btn-fg': '#0e0b05', '--term-bg': '#0a0803',
|
||||
'--font': 'ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace' },
|
||||
xterm: {
|
||||
background: '#0a0803', foreground: '#ffb000', cursor: '#ffcc33', selectionBackground: '#3a2c10',
|
||||
black: '#0a0803', red: '#ff6b35', green: '#ffd166', yellow: '#ffcc33', blue: '#e0a020', magenta: '#ff9f43', cyan: '#f5c76b', white: '#ffb000',
|
||||
brightBlack: '#6b4e14', brightRed: '#ff8c5a', brightGreen: '#ffe08a', brightYellow: '#ffe066', brightBlue: '#f0b040', brightMagenta: '#ffb870', brightCyan: '#ffdd99', brightWhite: '#fff3cc',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export const DEFAULT_THEME = 'dark'
|
||||
const KEY = 'hamlab.theme'
|
||||
|
||||
export function themeById(id: string | null | undefined): Theme {
|
||||
return THEMES.find((t) => t.id === id) ?? THEMES[0]
|
||||
}
|
||||
|
||||
export function currentThemeId(): string {
|
||||
return localStorage.getItem(KEY) || DEFAULT_THEME
|
||||
}
|
||||
|
||||
// applyTheme sets the variables on <html>, remembers the choice, and tells open
|
||||
// terminals to repaint. Cheap enough to call on every load before first render.
|
||||
export function applyTheme(id: string) {
|
||||
const t = themeById(id)
|
||||
const root = document.documentElement
|
||||
for (const [k, v] of Object.entries(t.vars)) root.style.setProperty(k, v)
|
||||
root.dataset.theme = t.id
|
||||
localStorage.setItem(KEY, t.id)
|
||||
document.querySelector('meta[name=theme-color]')?.setAttribute('content', t.vars['--bg2'])
|
||||
window.dispatchEvent(new CustomEvent('hamlab:theme', { detail: t }))
|
||||
}
|
||||
Reference in New Issue
Block a user