feat: add new themes, Zed fonts, and sort theme/font lists

New themes added:
- Dark: Ayu Dark, Ayu Mirage, Ember, Matcha
- Light: Ayu Light, One Light, Bluloco, Feather

Other changes:
- Bundle Zed Sans and Zed Mono fonts from zed-industries/zed-fonts
- Sort font options alphabetically (default first)
- Sort theme options alphabetically (Dark/Light first)
- Improve Ayu Dark text contrast for better readability
- Fix Matcha theme to have green undertone instead of blue
This commit is contained in:
Stefan de Vogelaere
2026-01-17 01:58:29 +01:00
parent f3b00d0f78
commit 1a7bf27ead
33 changed files with 1904 additions and 224 deletions

View File

@@ -48,6 +48,22 @@
"@dnd-kit/core": "6.3.1",
"@dnd-kit/sortable": "10.0.0",
"@dnd-kit/utilities": "3.2.2",
"@fontsource/cascadia-code": "^5.2.3",
"@fontsource/fira-code": "^5.2.7",
"@fontsource/ibm-plex-mono": "^5.2.7",
"@fontsource/inconsolata": "^5.2.8",
"@fontsource/inter": "^5.2.8",
"@fontsource/iosevka": "^5.2.5",
"@fontsource/jetbrains-mono": "^5.2.8",
"@fontsource/lato": "^5.2.7",
"@fontsource/montserrat": "^5.2.8",
"@fontsource/open-sans": "^5.2.7",
"@fontsource/poppins": "^5.2.7",
"@fontsource/raleway": "^5.2.8",
"@fontsource/roboto": "^5.2.9",
"@fontsource/source-code-pro": "^5.2.7",
"@fontsource/source-sans-3": "^5.2.9",
"@fontsource/work-sans": "^5.2.8",
"@lezer/highlight": "1.2.3",
"@radix-ui/react-checkbox": "1.3.3",
"@radix-ui/react-collapsible": "1.1.12",

View File

@@ -8,6 +8,7 @@ import { useCursorStatusInit } from './hooks/use-cursor-status-init';
import { useProviderAuthInit } from './hooks/use-provider-auth-init';
import './styles/global.css';
import './styles/theme-imports';
import './styles/font-imports';
const logger = createLogger('App');

View File

@@ -0,0 +1,67 @@
/* Zed Fonts - https://github.com/zed-industries/zed-fonts */
/* Zed Sans - UI Font */
@font-face {
font-family: 'Zed Sans';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('./zed-sans-extended.ttf') format('truetype');
}
@font-face {
font-family: 'Zed Sans';
font-style: italic;
font-weight: 400;
font-display: swap;
src: url('./zed-sans-extendeditalic.ttf') format('truetype');
}
@font-face {
font-family: 'Zed Sans';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('./zed-sans-extendedbold.ttf') format('truetype');
}
@font-face {
font-family: 'Zed Sans';
font-style: italic;
font-weight: 700;
font-display: swap;
src: url('./zed-sans-extendedbolditalic.ttf') format('truetype');
}
/* Zed Mono - Code Font */
@font-face {
font-family: 'Zed Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('./zed-mono-extended.ttf') format('truetype');
}
@font-face {
font-family: 'Zed Mono';
font-style: italic;
font-weight: 400;
font-display: swap;
src: url('./zed-mono-extendeditalic.ttf') format('truetype');
}
@font-face {
font-family: 'Zed Mono';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('./zed-mono-extendedbold.ttf') format('truetype');
}
@font-face {
font-family: 'Zed Mono';
font-style: italic;
font-weight: 700;
font-display: swap;
src: url('./zed-mono-extendedbolditalic.ttf') format('truetype');
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -32,27 +32,56 @@ export function ProjectThemeSection({ project }: ProjectThemeSectionProps) {
setProjectFontSans,
setProjectFontMono,
} = useAppStore();
const [activeTab, setActiveTab] = useState<'dark' | 'light'>('dark');
// Font local state - tracks what's selected when using custom fonts
const [fontSansLocal, setFontSansLocal] = useState<string>(
project.fontFamilySans || DEFAULT_FONT_VALUE
);
const [fontMonoLocal, setFontMonoLocal] = useState<string>(
project.fontFamilyMono || DEFAULT_FONT_VALUE
);
// Sync font state when project changes
useEffect(() => {
setFontSansLocal(project.fontFamilySans || DEFAULT_FONT_VALUE);
setFontMonoLocal(project.fontFamilyMono || DEFAULT_FONT_VALUE);
}, [project]);
// Theme state
const projectTheme = project.theme as Theme | undefined;
const hasCustomTheme = projectTheme !== undefined;
const effectiveTheme = projectTheme || globalTheme;
// Determine if current theme is light or dark
const isLightTheme = lightThemes.some((t) => t.value === effectiveTheme);
const [activeTab, setActiveTab] = useState<'dark' | 'light'>(isLightTheme ? 'light' : 'dark');
// Helper to validate fonts against available options
const isValidSansFont = (font: string | undefined): boolean => {
if (!font) return false;
return UI_SANS_FONT_OPTIONS.some((opt) => opt.value === font);
};
const isValidMonoFont = (font: string | undefined): boolean => {
if (!font) return false;
return UI_MONO_FONT_OPTIONS.some((opt) => opt.value === font);
};
// Font local state - tracks what's selected when using custom fonts
// Falls back to default if stored font is not in available options
const [fontSansLocal, setFontSansLocal] = useState<string>(
project.fontFamilySans && isValidSansFont(project.fontFamilySans)
? project.fontFamilySans
: DEFAULT_FONT_VALUE
);
const [fontMonoLocal, setFontMonoLocal] = useState<string>(
project.fontFamilyMono && isValidMonoFont(project.fontFamilyMono)
? project.fontFamilyMono
: DEFAULT_FONT_VALUE
);
// Sync state when project changes
useEffect(() => {
setFontSansLocal(
project.fontFamilySans && isValidSansFont(project.fontFamilySans)
? project.fontFamilySans
: DEFAULT_FONT_VALUE
);
setFontMonoLocal(
project.fontFamilyMono && isValidMonoFont(project.fontFamilyMono)
? project.fontFamilyMono
: DEFAULT_FONT_VALUE
);
// Also sync the active tab based on current theme
const currentIsLight = lightThemes.some((t) => t.value === (project.theme || globalTheme));
setActiveTab(currentIsLight ? 'light' : 'dark');
}, [project, globalTheme]);
// Font state - check if project has custom fonts set
const hasCustomFontSans = project.fontFamilySans !== undefined;
const hasCustomFontMono = project.fontFamilyMono !== undefined;
@@ -79,10 +108,11 @@ export function ProjectThemeSection({ project }: ProjectThemeSectionProps) {
setProjectFontSans(project.id, null);
setFontSansLocal(DEFAULT_FONT_VALUE);
} else {
// Set to current global font or default
// Set explicit project override - use 'default' value to indicate explicit default choice
const fontToSet = globalFontSans || DEFAULT_FONT_VALUE;
setFontSansLocal(fontToSet);
setProjectFontSans(project.id, fontToSet === DEFAULT_FONT_VALUE ? null : fontToSet);
// Store the actual value (including 'default') so hasCustomFontSans stays true
setProjectFontSans(project.id, fontToSet);
}
};
@@ -92,21 +122,24 @@ export function ProjectThemeSection({ project }: ProjectThemeSectionProps) {
setProjectFontMono(project.id, null);
setFontMonoLocal(DEFAULT_FONT_VALUE);
} else {
// Set to current global font or default
// Set explicit project override - use 'default' value to indicate explicit default choice
const fontToSet = globalFontMono || DEFAULT_FONT_VALUE;
setFontMonoLocal(fontToSet);
setProjectFontMono(project.id, fontToSet === DEFAULT_FONT_VALUE ? null : fontToSet);
// Store the actual value (including 'default') so hasCustomFontMono stays true
setProjectFontMono(project.id, fontToSet);
}
};
const handleFontSansChange = (value: string) => {
setFontSansLocal(value);
setProjectFontSans(project.id, value === DEFAULT_FONT_VALUE ? null : value);
// Store the actual value (including 'default') - only null clears to use global
setProjectFontSans(project.id, value);
};
const handleFontMonoChange = (value: string) => {
setFontMonoLocal(value);
setProjectFontMono(project.id, value === DEFAULT_FONT_VALUE ? null : value);
// Store the actual value (including 'default') - only null clears to use global
setProjectFontMono(project.id, value);
};
// Get display label for global font

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { Label } from '@/components/ui/label';
import {
Select,
@@ -24,14 +24,34 @@ interface AppearanceSectionProps {
}
export function AppearanceSection({ effectiveTheme, onThemeChange }: AppearanceSectionProps) {
const [activeTab, setActiveTab] = useState<'dark' | 'light'>('dark');
const { fontFamilySans, fontFamilyMono, setFontSans, setFontMono } = useAppStore();
// Determine if current theme is light or dark
const isLightTheme = lightThemes.some((t) => t.value === effectiveTheme);
const [activeTab, setActiveTab] = useState<'dark' | 'light'>(isLightTheme ? 'light' : 'dark');
// Sync active tab when theme changes
useEffect(() => {
const currentIsLight = lightThemes.some((t) => t.value === effectiveTheme);
setActiveTab(currentIsLight ? 'light' : 'dark');
}, [effectiveTheme]);
const themesToShow = activeTab === 'dark' ? darkThemes : lightThemes;
// Convert null to 'default' for Select component
const fontSansValue = fontFamilySans || DEFAULT_FONT_VALUE;
const fontMonoValue = fontFamilyMono || DEFAULT_FONT_VALUE;
// Also fallback to default if the stored font is not in the available options
const isValidSansFont = (font: string | null): boolean => {
if (!font) return false;
return UI_SANS_FONT_OPTIONS.some((opt) => opt.value === font);
};
const isValidMonoFont = (font: string | null): boolean => {
if (!font) return false;
return UI_MONO_FONT_OPTIONS.some((opt) => opt.value === font);
};
const fontSansValue =
fontFamilySans && isValidSansFont(fontFamilySans) ? fontFamilySans : DEFAULT_FONT_VALUE;
const fontMonoValue =
fontFamilyMono && isValidMonoFont(fontFamilyMono) ? fontFamilyMono : DEFAULT_FONT_VALUE;
const handleFontSansChange = (value: string) => {
setFontSans(value === DEFAULT_FONT_VALUE ? null : value);

View File

@@ -542,9 +542,10 @@ const grayTheme: TerminalTheme = {
// Theme mapping
const terminalThemes: Record<ThemeMode, TerminalTheme> = {
light: lightTheme,
dark: darkTheme,
// Special
system: darkTheme, // Will be resolved at runtime
// Dark themes
dark: darkTheme,
retro: retroTheme,
dracula: draculaTheme,
nord: nordTheme,
@@ -556,9 +557,35 @@ const terminalThemes: Record<ThemeMode, TerminalTheme> = {
onedark: onedarkTheme,
synthwave: synthwaveTheme,
red: redTheme,
cream: creamTheme,
sunset: sunsetTheme,
gray: grayTheme,
forest: gruvboxTheme, // Green-ish theme, gruvbox is close
ocean: nordTheme, // Blue-ish theme, nord is close
ember: monokaiTheme, // Warm orange theme, monokai is close
'ayu-dark': darkTheme, // Deep dark with warm accents
'ayu-mirage': darkTheme, // Soft dark with golden accents
matcha: nordTheme, // Calming blue-gray with sage green
// Light themes
light: lightTheme,
cream: creamTheme,
solarizedlight: lightTheme, // TODO: Create dedicated solarized light terminal theme
github: lightTheme, // TODO: Create dedicated github terminal theme
paper: lightTheme,
rose: lightTheme,
mint: lightTheme,
lavender: lightTheme,
sand: creamTheme, // Warm tones like cream
sky: lightTheme,
peach: creamTheme, // Warm tones like cream
snow: lightTheme,
sepia: creamTheme, // Warm tones like cream
gruvboxlight: creamTheme, // Warm light theme
nordlight: lightTheme, // Cool light theme
blossom: lightTheme,
'ayu-light': lightTheme, // Clean light with orange accents
onelight: lightTheme, // Atom One Light - blue accent
bluloco: lightTheme, // Bluloco - cyan-blue accent
feather: lightTheme, // Feather - orange accent
};
/**

View File

@@ -47,7 +47,11 @@ export type Theme =
| 'gray'
| 'forest'
| 'ocean'
// Light themes (16)
| 'ember'
| 'ayu-dark'
| 'ayu-mirage'
| 'matcha'
// Light themes
| 'light'
| 'cream'
| 'solarizedlight'
@@ -63,7 +67,11 @@ export type Theme =
| 'sepia'
| 'gruvboxlight'
| 'nordlight'
| 'blossom';
| 'blossom'
| 'ayu-light'
| 'onelight'
| 'bluloco'
| 'feather';
export interface ThemeOption {
value: Theme;
@@ -74,9 +82,9 @@ export interface ThemeOption {
color: string; // Primary/brand color for icon display
}
// All theme options with dark/light categorization
// All theme options with dark/light categorization (alphabetically sorted, Dark/Light first)
export const themeOptions: ReadonlyArray<ThemeOption> = [
// Dark themes (16)
// Dark themes (20) - alphabetical, Dark first
{
value: 'dark',
label: 'Dark',
@@ -86,60 +94,20 @@ export const themeOptions: ReadonlyArray<ThemeOption> = [
color: '#3b82f6',
},
{
value: 'retro',
label: 'Retro',
Icon: Terminal,
testId: 'retro-mode-button',
value: 'ayu-dark',
label: 'Ayu Dark',
Icon: Moon,
testId: 'ayu-dark-mode-button',
isDark: true,
color: '#22c55e',
color: '#E6B450',
},
{
value: 'dracula',
label: 'Dracula',
Icon: Ghost,
testId: 'dracula-mode-button',
isDark: true,
color: '#bd93f9',
},
{
value: 'nord',
label: 'Nord',
Icon: Snowflake,
testId: 'nord-mode-button',
isDark: true,
color: '#88c0d0',
},
{
value: 'monokai',
label: 'Monokai',
Icon: Flame,
testId: 'monokai-mode-button',
isDark: true,
color: '#f92672',
},
{
value: 'tokyonight',
label: 'Tokyo Night',
value: 'ayu-mirage',
label: 'Ayu Mirage',
Icon: Sparkles,
testId: 'tokyonight-mode-button',
testId: 'ayu-mirage-mode-button',
isDark: true,
color: '#bb9af7',
},
{
value: 'solarized',
label: 'Solarized Dark',
Icon: Eclipse,
testId: 'solarized-mode-button',
isDark: true,
color: '#268bd2',
},
{
value: 'gruvbox',
label: 'Gruvbox',
Icon: Trees,
testId: 'gruvbox-mode-button',
isDark: true,
color: '#fe8019',
color: '#FFCC66',
},
{
value: 'catppuccin',
@@ -150,44 +118,20 @@ export const themeOptions: ReadonlyArray<ThemeOption> = [
color: '#cba6f7',
},
{
value: 'onedark',
label: 'One Dark',
Icon: Atom,
testId: 'onedark-mode-button',
value: 'dracula',
label: 'Dracula',
Icon: Ghost,
testId: 'dracula-mode-button',
isDark: true,
color: '#61afef',
color: '#bd93f9',
},
{
value: 'synthwave',
label: 'Synthwave',
Icon: Radio,
testId: 'synthwave-mode-button',
value: 'ember',
label: 'Ember',
Icon: Sunrise,
testId: 'ember-mode-button',
isDark: true,
color: '#ff7edb',
},
{
value: 'red',
label: 'Red',
Icon: Heart,
testId: 'red-mode-button',
isDark: true,
color: '#ef4444',
},
{
value: 'sunset',
label: 'Sunset',
Icon: CloudSun,
testId: 'sunset-mode-button',
isDark: true,
color: '#f97316',
},
{
value: 'gray',
label: 'Gray',
Icon: Square,
testId: 'gray-mode-button',
isDark: true,
color: '#6b7280',
color: '#fd971f',
},
{
value: 'forest',
@@ -197,6 +141,46 @@ export const themeOptions: ReadonlyArray<ThemeOption> = [
isDark: true,
color: '#22c55e',
},
{
value: 'gray',
label: 'Gray',
Icon: Square,
testId: 'gray-mode-button',
isDark: true,
color: '#6b7280',
},
{
value: 'gruvbox',
label: 'Gruvbox',
Icon: Trees,
testId: 'gruvbox-mode-button',
isDark: true,
color: '#fe8019',
},
{
value: 'matcha',
label: 'Matcha',
Icon: Leaf,
testId: 'matcha-mode-button',
isDark: true,
color: '#A4B07E',
},
{
value: 'monokai',
label: 'Monokai',
Icon: Flame,
testId: 'monokai-mode-button',
isDark: true,
color: '#f92672',
},
{
value: 'nord',
label: 'Nord',
Icon: Snowflake,
testId: 'nord-mode-button',
isDark: true,
color: '#88c0d0',
},
{
value: 'ocean',
label: 'Ocean',
@@ -205,7 +189,63 @@ export const themeOptions: ReadonlyArray<ThemeOption> = [
isDark: true,
color: '#06b6d4',
},
// Light themes (16)
{
value: 'onedark',
label: 'One Dark',
Icon: Atom,
testId: 'onedark-mode-button',
isDark: true,
color: '#61afef',
},
{
value: 'red',
label: 'Red',
Icon: Heart,
testId: 'red-mode-button',
isDark: true,
color: '#ef4444',
},
{
value: 'retro',
label: 'Retro',
Icon: Terminal,
testId: 'retro-mode-button',
isDark: true,
color: '#22c55e',
},
{
value: 'solarized',
label: 'Solarized Dark',
Icon: Eclipse,
testId: 'solarized-mode-button',
isDark: true,
color: '#268bd2',
},
{
value: 'sunset',
label: 'Sunset',
Icon: CloudSun,
testId: 'sunset-mode-button',
isDark: true,
color: '#f97316',
},
{
value: 'synthwave',
label: 'Synthwave',
Icon: Radio,
testId: 'synthwave-mode-button',
isDark: true,
color: '#ff7edb',
},
{
value: 'tokyonight',
label: 'Tokyo Night',
Icon: Sparkles,
testId: 'tokyonight-mode-button',
isDark: true,
color: '#bb9af7',
},
// Light themes (20) - alphabetical, Light first
{
value: 'light',
label: 'Light',
@@ -214,6 +254,30 @@ export const themeOptions: ReadonlyArray<ThemeOption> = [
isDark: false,
color: '#3b82f6',
},
{
value: 'ayu-light',
label: 'Ayu Light',
Icon: Sun,
testId: 'ayu-light-mode-button',
isDark: false,
color: '#F29718',
},
{
value: 'blossom',
label: 'Blossom',
Icon: Cherry,
testId: 'blossom-mode-button',
isDark: false,
color: '#ec4899',
},
{
value: 'bluloco',
label: 'Bluloco',
Icon: Waves,
testId: 'bluloco-mode-button',
isDark: false,
color: '#0099e1',
},
{
value: 'cream',
label: 'Cream',
@@ -223,12 +287,12 @@ export const themeOptions: ReadonlyArray<ThemeOption> = [
color: '#b45309',
},
{
value: 'solarizedlight',
label: 'Solarized Light',
Icon: Sunrise,
testId: 'solarizedlight-mode-button',
value: 'feather',
label: 'Feather',
Icon: Feather,
testId: 'feather-mode-button',
isDark: false,
color: '#268bd2',
color: '#FF7B2E',
},
{
value: 'github',
@@ -239,28 +303,12 @@ export const themeOptions: ReadonlyArray<ThemeOption> = [
color: '#0969da',
},
{
value: 'paper',
label: 'Paper',
Icon: Scroll,
testId: 'paper-mode-button',
value: 'gruvboxlight',
label: 'Gruvbox Light',
Icon: Trees,
testId: 'gruvboxlight-mode-button',
isDark: false,
color: '#374151',
},
{
value: 'rose',
label: 'Rose',
Icon: Flower2,
testId: 'rose-mode-button',
isDark: false,
color: '#e11d48',
},
{
value: 'mint',
label: 'Mint',
Icon: Wind,
testId: 'mint-mode-button',
isDark: false,
color: '#0d9488',
color: '#d65d0e',
},
{
value: 'lavender',
@@ -271,52 +319,12 @@ export const themeOptions: ReadonlyArray<ThemeOption> = [
color: '#8b5cf6',
},
{
value: 'sand',
label: 'Sand',
Icon: Palmtree,
testId: 'sand-mode-button',
value: 'mint',
label: 'Mint',
Icon: Wind,
testId: 'mint-mode-button',
isDark: false,
color: '#d97706',
},
{
value: 'sky',
label: 'Sky',
Icon: Sun,
testId: 'sky-mode-button',
isDark: false,
color: '#0284c7',
},
{
value: 'peach',
label: 'Peach',
Icon: Cherry,
testId: 'peach-mode-button',
isDark: false,
color: '#ea580c',
},
{
value: 'snow',
label: 'Snow',
Icon: Snowflake,
testId: 'snow-mode-button',
isDark: false,
color: '#3b82f6',
},
{
value: 'sepia',
label: 'Sepia',
Icon: Coffee,
testId: 'sepia-mode-button',
isDark: false,
color: '#92400e',
},
{
value: 'gruvboxlight',
label: 'Gruvbox Light',
Icon: Trees,
testId: 'gruvboxlight-mode-button',
isDark: false,
color: '#d65d0e',
color: '#0d9488',
},
{
value: 'nordlight',
@@ -327,12 +335,76 @@ export const themeOptions: ReadonlyArray<ThemeOption> = [
color: '#5e81ac',
},
{
value: 'blossom',
label: 'Blossom',
Icon: Cherry,
testId: 'blossom-mode-button',
value: 'onelight',
label: 'One Light',
Icon: Atom,
testId: 'onelight-mode-button',
isDark: false,
color: '#ec4899',
color: '#526FFF',
},
{
value: 'paper',
label: 'Paper',
Icon: Scroll,
testId: 'paper-mode-button',
isDark: false,
color: '#374151',
},
{
value: 'peach',
label: 'Peach',
Icon: Cherry,
testId: 'peach-mode-button',
isDark: false,
color: '#ea580c',
},
{
value: 'rose',
label: 'Rose',
Icon: Flower2,
testId: 'rose-mode-button',
isDark: false,
color: '#e11d48',
},
{
value: 'sand',
label: 'Sand',
Icon: Palmtree,
testId: 'sand-mode-button',
isDark: false,
color: '#d97706',
},
{
value: 'sepia',
label: 'Sepia',
Icon: Coffee,
testId: 'sepia-mode-button',
isDark: false,
color: '#92400e',
},
{
value: 'sky',
label: 'Sky',
Icon: Sun,
testId: 'sky-mode-button',
isDark: false,
color: '#0284c7',
},
{
value: 'snow',
label: 'Snow',
Icon: Snowflake,
testId: 'snow-mode-button',
isDark: false,
color: '#3b82f6',
},
{
value: 'solarizedlight',
label: 'Solarized Light',
Icon: Sunrise,
testId: 'solarizedlight-mode-button',
isDark: false,
color: '#268bd2',
},
];

View File

@@ -1,8 +1,9 @@
/**
* Font options for per-project font customization
*
* These are system fonts (no bundled @fontsource packages required).
* Users must have the fonts installed on their system for them to work.
* All fonts listed here are bundled with the app via @fontsource packages
* or custom font files (Zed fonts from zed-industries/zed-fonts).
* They are self-hosted and will work without any system installation.
*/
// Sentinel value for "use default font" - Radix Select doesn't allow empty strings
@@ -14,34 +15,53 @@ export interface UIFontOption {
}
/**
* Sans/UI fonts for headings, labels, and general text
* Sans/UI fonts for headings, labels, and general text (Top 10)
*
* 'default' value means "use the theme default" (Geist Sans for all themes)
*/
export const UI_SANS_FONT_OPTIONS: readonly UIFontOption[] = [
{ value: DEFAULT_FONT_VALUE, label: 'Default (Geist Sans)' },
{ value: "'Inter', system-ui, sans-serif", label: 'Inter' },
{ value: "'SF Pro', system-ui, sans-serif", label: 'SF Pro' },
// Sans fonts (alphabetical)
{ value: 'Inter, system-ui, sans-serif', label: 'Inter' },
{ value: 'Lato, system-ui, sans-serif', label: 'Lato' },
{ value: 'Montserrat, system-ui, sans-serif', label: 'Montserrat' },
{ value: "'Open Sans', system-ui, sans-serif", label: 'Open Sans' },
{ value: 'Poppins, system-ui, sans-serif', label: 'Poppins' },
{ value: 'Raleway, system-ui, sans-serif', label: 'Raleway' },
{ value: 'Roboto, system-ui, sans-serif', label: 'Roboto' },
{ value: "'Source Sans 3', system-ui, sans-serif", label: 'Source Sans' },
{ value: "'IBM Plex Sans', system-ui, sans-serif", label: 'IBM Plex Sans' },
{ value: "'Roboto', system-ui, sans-serif", label: 'Roboto' },
{ value: 'system-ui, sans-serif', label: 'System Default' },
{ value: "'Work Sans', system-ui, sans-serif", label: 'Work Sans' },
{ value: "'Zed Sans', system-ui, sans-serif", label: 'Zed Sans' },
// Monospace fonts (alphabetical, for users who prefer mono UI)
{ value: "'Cascadia Code', monospace", label: 'Cascadia Code' },
{ value: "'Fira Code', monospace", label: 'Fira Code' },
{ value: "'IBM Plex Mono', monospace", label: 'IBM Plex Mono' },
{ value: 'Inconsolata, monospace', label: 'Inconsolata' },
{ value: 'Iosevka, monospace', label: 'Iosevka' },
{ value: "'JetBrains Mono', monospace", label: 'JetBrains Mono' },
{ value: "'Source Code Pro', monospace", label: 'Source Code Pro' },
{ value: "'Zed Mono', monospace", label: 'Zed Mono' },
] as const;
/**
* Mono/code fonts for code blocks, terminals, and monospaced text
* Mono/code fonts for code blocks, terminals, and monospaced text (Top 10)
*
* 'default' value means "use the theme default" (Geist Mono for all themes)
* Many of these support ligatures for coding symbols (-> => != etc.)
*/
export const UI_MONO_FONT_OPTIONS: readonly UIFontOption[] = [
{ value: DEFAULT_FONT_VALUE, label: 'Default (Geist Mono)' },
{ value: "'JetBrains Mono', monospace", label: 'JetBrains Mono' },
{ value: "'Fira Code', monospace", label: 'Fira Code' },
{ value: "'SF Mono', Menlo, Monaco, monospace", label: 'SF Mono' },
{ value: "'Source Code Pro', monospace", label: 'Source Code Pro' },
{ value: "'IBM Plex Mono', monospace", label: 'IBM Plex Mono' },
{ value: "Menlo, Monaco, 'Courier New', monospace", label: 'Menlo / Monaco' },
// Bundled fonts (alphabetical)
{ value: "'Cascadia Code', monospace", label: 'Cascadia Code' },
{ value: "'Fira Code', monospace", label: 'Fira Code' },
{ value: "'IBM Plex Mono', monospace", label: 'IBM Plex Mono' },
{ value: 'Inconsolata, monospace', label: 'Inconsolata' },
{ value: 'Iosevka, monospace', label: 'Iosevka' },
{ value: "'JetBrains Mono', monospace", label: 'JetBrains Mono' },
{ value: "'Source Code Pro', monospace", label: 'Source Code Pro' },
{ value: "'Zed Mono', monospace", label: 'Zed Mono' },
// System fonts
{ value: 'Menlo, Monaco, monospace', label: 'Menlo / Monaco (macOS)' },
] as const;
/**

View File

@@ -533,6 +533,8 @@ export function hydrateStoreFromSettings(settings: GlobalSettings): void {
path: ref.path,
lastOpened: ref.lastOpened,
theme: ref.theme,
fontFamilySans: ref.fontFamilySans,
fontFamilyMono: ref.fontFamilyMono,
isFavorite: ref.isFavorite,
icon: ref.icon,
customIconPath: ref.customIconPath,

View File

@@ -160,6 +160,10 @@ function RootLayoutContent() {
getEffectiveTheme,
getEffectiveFontSans,
getEffectiveFontMono,
// Subscribe to theme and font state to trigger re-renders when they change
theme,
fontFamilySans,
fontFamilyMono,
skipSandboxWarning,
setSkipSandboxWarning,
fetchCodexModels,
@@ -250,7 +254,14 @@ function RootLayoutContent() {
// Defer the theme value to keep UI responsive during rapid hover changes
const deferredTheme = useDeferredValue(effectiveTheme);
// Get effective fonts for the current project
// Get effective theme and fonts for the current project
// Note: theme/fontFamilySans/fontFamilyMono are destructured above to ensure re-renders when they change
// eslint-disable-next-line @typescript-eslint/no-unused-vars
void theme; // Used for subscription
// eslint-disable-next-line @typescript-eslint/no-unused-vars
void fontFamilySans; // Used for subscription
// eslint-disable-next-line @typescript-eslint/no-unused-vars
void fontFamilyMono; // Used for subscription
const effectiveFontSans = getEffectiveFontSans();
const effectiveFontMono = getEffectiveFontMono();

View File

@@ -4,6 +4,11 @@ import type { Project, TrashedProject } from '@/lib/electron';
import { getElectronAPI } from '@/lib/electron';
import { createLogger } from '@automaker/utils/logger';
import { setItem, getItem } from '@/lib/storage';
import {
UI_SANS_FONT_OPTIONS,
UI_MONO_FONT_OPTIONS,
DEFAULT_FONT_VALUE,
} from '@/config/ui-font-options';
import type {
Feature as BaseFeature,
FeatureImagePath,
@@ -65,9 +70,10 @@ export type ViewMode =
| 'ideation';
export type ThemeMode =
| 'light'
| 'dark'
// Special modes
| 'system'
// Dark themes
| 'dark'
| 'retro'
| 'dracula'
| 'nord'
@@ -79,12 +85,40 @@ export type ThemeMode =
| 'onedark'
| 'synthwave'
| 'red'
| 'cream'
| 'sunset'
| 'gray';
| 'gray'
| 'forest'
| 'ocean'
| 'ember'
| 'ayu-dark'
| 'ayu-mirage'
| 'matcha'
// Light themes
| 'light'
| 'cream'
| 'solarizedlight'
| 'github'
| 'paper'
| 'rose'
| 'mint'
| 'lavender'
| 'sand'
| 'sky'
| 'peach'
| 'snow'
| 'sepia'
| 'gruvboxlight'
| 'nordlight'
| 'blossom'
| 'ayu-light'
| 'onelight'
| 'bluloco'
| 'feather';
// LocalStorage key for theme persistence (fallback when server settings aren't available)
// LocalStorage keys for persistence (fallback when server settings aren't available)
export const THEME_STORAGE_KEY = 'automaker:theme';
export const FONT_SANS_STORAGE_KEY = 'automaker:font-sans';
export const FONT_MONO_STORAGE_KEY = 'automaker:font-mono';
// Maximum number of output lines to keep in init script state (prevents unbounded memory growth)
export const MAX_INIT_OUTPUT_LINES = 500;
@@ -123,6 +157,40 @@ function saveThemeToStorage(theme: ThemeMode): void {
setItem(THEME_STORAGE_KEY, theme);
}
/**
* Get fonts from localStorage as a fallback
* Used before server settings are loaded (e.g., on login/setup pages)
*/
export function getStoredFontSans(): string | null {
return getItem(FONT_SANS_STORAGE_KEY);
}
export function getStoredFontMono(): string | null {
return getItem(FONT_MONO_STORAGE_KEY);
}
/**
* Save fonts to localStorage for immediate persistence
* This is used as a fallback when server settings can't be loaded
*/
function saveFontSansToStorage(fontFamily: string | null): void {
if (fontFamily) {
setItem(FONT_SANS_STORAGE_KEY, fontFamily);
} else {
// Remove from storage if null (using default)
localStorage.removeItem(FONT_SANS_STORAGE_KEY);
}
}
function saveFontMonoToStorage(fontFamily: string | null): void {
if (fontFamily) {
setItem(FONT_MONO_STORAGE_KEY, fontFamily);
} else {
// Remove from storage if null (using default)
localStorage.removeItem(FONT_MONO_STORAGE_KEY);
}
}
function persistEffectiveThemeForProject(project: Project | null, fallbackTheme: ThemeMode): void {
const projectTheme = project?.theme as ThemeMode | undefined;
const themeToStore = projectTheme ?? fallbackTheme;
@@ -1270,8 +1338,8 @@ const initialState: AppState = {
mobileSidebarHidden: false, // Sidebar visible by default on mobile
lastSelectedSessionByProject: {},
theme: getStoredTheme() || 'dark', // Use localStorage theme as initial value, fallback to 'dark'
fontFamilySans: null, // Global UI font (null = use default Geist Sans)
fontFamilyMono: null, // Global code font (null = use default Geist Mono)
fontFamilySans: getStoredFontSans(), // Use localStorage font as initial value (null = use default Geist Sans)
fontFamilyMono: getStoredFontMono(), // Use localStorage font as initial value (null = use default Geist Mono)
features: [],
appSpec: '',
ipcConnected: false,
@@ -1748,12 +1816,21 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
setPreviewTheme: (theme) => set({ previewTheme: theme }),
// Font actions (global + per-project override)
setFontSans: (fontFamily) => set({ fontFamilySans: fontFamily }),
setFontSans: (fontFamily) => {
// Save to localStorage for fallback when server settings aren't available
saveFontSansToStorage(fontFamily);
set({ fontFamilySans: fontFamily });
},
setFontMono: (fontFamily) => set({ fontFamilyMono: fontFamily }),
setFontMono: (fontFamily) => {
// Save to localStorage for fallback when server settings aren't available
saveFontMonoToStorage(fontFamily);
set({ fontFamilyMono: fontFamily });
},
setProjectFontSans: (projectId, fontFamily) => {
// Update the project's fontFamilySans property
// null means "clear to use global", any string (including 'default') means explicit override
const projects = get().projects.map((p) =>
p.id === projectId
? { ...p, fontFamilySans: fontFamily === null ? undefined : fontFamily }
@@ -1775,6 +1852,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
setProjectFontMono: (projectId, fontFamily) => {
// Update the project's fontFamilyMono property
// null means "clear to use global", any string (including 'default') means explicit override
const projects = get().projects.map((p) =>
p.id === projectId
? { ...p, fontFamilyMono: fontFamily === null ? undefined : fontFamily }
@@ -1797,19 +1875,41 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
getEffectiveFontSans: () => {
const currentProject = get().currentProject;
// Return project override if set, otherwise global, otherwise null for default
// 'default' value means explicitly using default font, so return null for CSS
// Also validate that the font is in the available options list
const isValidFont = (font: string | null | undefined): boolean => {
if (!font || font === DEFAULT_FONT_VALUE) return true;
return UI_SANS_FONT_OPTIONS.some((opt) => opt.value === font);
};
if (currentProject?.fontFamilySans) {
return currentProject.fontFamilySans;
const font = currentProject.fontFamilySans;
if (!isValidFont(font)) return null; // Fallback to default if font not in list
return font === DEFAULT_FONT_VALUE ? null : font;
}
return get().fontFamilySans;
const globalFont = get().fontFamilySans;
if (!isValidFont(globalFont)) return null; // Fallback to default if font not in list
return globalFont === DEFAULT_FONT_VALUE ? null : globalFont;
},
getEffectiveFontMono: () => {
const currentProject = get().currentProject;
// Return project override if set, otherwise global, otherwise null for default
// 'default' value means explicitly using default font, so return null for CSS
// Also validate that the font is in the available options list
const isValidFont = (font: string | null | undefined): boolean => {
if (!font || font === DEFAULT_FONT_VALUE) return true;
return UI_MONO_FONT_OPTIONS.some((opt) => opt.value === font);
};
if (currentProject?.fontFamilyMono) {
return currentProject.fontFamilyMono;
const font = currentProject.fontFamilyMono;
if (!isValidFont(font)) return null; // Fallback to default if font not in list
return font === DEFAULT_FONT_VALUE ? null : font;
}
return get().fontFamilyMono;
const globalFont = get().fontFamilyMono;
if (!isValidFont(globalFont)) return null; // Fallback to default if font not in list
return globalFont === DEFAULT_FONT_VALUE ? null : globalFont;
},
// Feature actions

View File

@@ -0,0 +1,113 @@
/**
* Bundles all web font packages so they're available
* for use in the font customization settings.
*
* These fonts are self-hosted with the app, so users don't need
* to have them installed on their system.
*/
// Zed Fonts (from zed-industries/zed-fonts)
import '@/assets/fonts/zed/zed-fonts.css';
// ============================================
// Sans-serif / UI Fonts (Top 10)
// ============================================
// Inter - Designed specifically for screens; excellent legibility at small sizes
import '@fontsource/inter/400.css';
import '@fontsource/inter/500.css';
import '@fontsource/inter/600.css';
import '@fontsource/inter/700.css';
// Roboto - Highly versatile and clean; the standard for Google-based interfaces
import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';
// Open Sans - Neutral and friendly; optimized for web and mobile readability
import '@fontsource/open-sans/400.css';
import '@fontsource/open-sans/500.css';
import '@fontsource/open-sans/600.css';
import '@fontsource/open-sans/700.css';
// Montserrat - Geometric and modern; best for high-impact titles and branding
import '@fontsource/montserrat/400.css';
import '@fontsource/montserrat/500.css';
import '@fontsource/montserrat/600.css';
import '@fontsource/montserrat/700.css';
// Lato - Blends professionalism with warmth; ideal for longer body text
import '@fontsource/lato/400.css';
import '@fontsource/lato/700.css';
// Poppins - Geometric and energetic; popular for modern, friendly brand identities
import '@fontsource/poppins/400.css';
import '@fontsource/poppins/500.css';
import '@fontsource/poppins/600.css';
import '@fontsource/poppins/700.css';
// Raleway - Elegant with unique characteristics; great for creative portfolios
import '@fontsource/raleway/400.css';
import '@fontsource/raleway/500.css';
import '@fontsource/raleway/600.css';
import '@fontsource/raleway/700.css';
// Work Sans - Optimized for screen readability; feels clean and contemporary
import '@fontsource/work-sans/400.css';
import '@fontsource/work-sans/500.css';
import '@fontsource/work-sans/600.css';
import '@fontsource/work-sans/700.css';
// Source Sans 3 - Adobe's first open-source font; highly functional for complex interfaces
import '@fontsource/source-sans-3/400.css';
import '@fontsource/source-sans-3/500.css';
import '@fontsource/source-sans-3/600.css';
import '@fontsource/source-sans-3/700.css';
// ============================================
// Monospace / Code Fonts (Top 10)
// ============================================
// Fira Code - Excellent legibility and stylish ligatures (=>, !=, etc.)
import '@fontsource/fira-code/400.css';
import '@fontsource/fira-code/500.css';
import '@fontsource/fira-code/600.css';
import '@fontsource/fira-code/700.css';
// JetBrains Mono - Designed by JetBrains for developers, focusing on readability
import '@fontsource/jetbrains-mono/400.css';
import '@fontsource/jetbrains-mono/500.css';
import '@fontsource/jetbrains-mono/600.css';
import '@fontsource/jetbrains-mono/700.css';
// Cascadia Code - Microsoft's font, popular in Windows Terminal, with ligatures
import '@fontsource/cascadia-code/400.css';
import '@fontsource/cascadia-code/600.css';
import '@fontsource/cascadia-code/700.css';
// Iosevka - Highly customizable, slender sans-serif/slab-serif font
import '@fontsource/iosevka/400.css';
import '@fontsource/iosevka/500.css';
import '@fontsource/iosevka/600.css';
import '@fontsource/iosevka/700.css';
// Inconsolata - Popular, clean, and highly readable choice for screens
import '@fontsource/inconsolata/400.css';
import '@fontsource/inconsolata/500.css';
import '@fontsource/inconsolata/600.css';
import '@fontsource/inconsolata/700.css';
// Source Code Pro - Adobe's clean, geometric, open-source font
import '@fontsource/source-code-pro/400.css';
import '@fontsource/source-code-pro/500.css';
import '@fontsource/source-code-pro/600.css';
import '@fontsource/source-code-pro/700.css';
// IBM Plex Mono - Clean, modern monospaced font from IBM
import '@fontsource/ibm-plex-mono/400.css';
import '@fontsource/ibm-plex-mono/500.css';
import '@fontsource/ibm-plex-mono/600.css';
import '@fontsource/ibm-plex-mono/700.css';
// Note: Monaco/Menlo are macOS system fonts (not bundled)
// Note: Hack font is not available via @fontsource

View File

@@ -18,6 +18,10 @@
@custom-variant gray (&:is(.gray *));
@custom-variant forest (&:is(.forest *));
@custom-variant ocean (&:is(.ocean *));
@custom-variant ember (&:is(.ember *));
@custom-variant ayu-dark (&:is(.ayu-dark *));
@custom-variant ayu-mirage (&:is(.ayu-mirage *));
@custom-variant matcha (&:is(.matcha *));
/* Light themes */
@custom-variant cream (&:is(.cream *));
@@ -35,6 +39,10 @@
@custom-variant gruvboxlight (&:is(.gruvboxlight *));
@custom-variant nordlight (&:is(.nordlight *));
@custom-variant blossom (&:is(.blossom *));
@custom-variant ayu-light (&:is(.ayu-light *));
@custom-variant onelight (&:is(.onelight *));
@custom-variant bluloco (&:is(.bluloco *));
@custom-variant feather (&:is(.feather *));
@theme inline {
--color-background: var(--background);
@@ -372,6 +380,15 @@
body {
@apply bg-background text-foreground;
background-color: var(--background);
font-family: var(--font-sans);
}
/* Apply monospace font to code elements */
code,
pre,
kbd,
samp {
font-family: var(--font-mono);
}
/* Text selection styling for readability */

View File

@@ -3,7 +3,7 @@
* doesn't tree-shake their CSS when imported dynamically.
*/
// Dark themes (16)
// Dark themes (20)
import './themes/dark.css';
import './themes/retro.css';
import './themes/dracula.css';
@@ -20,8 +20,12 @@ import './themes/sunset.css';
import './themes/gray.css';
import './themes/forest.css';
import './themes/ocean.css';
import './themes/ember.css';
import './themes/ayu-dark.css';
import './themes/ayu-mirage.css';
import './themes/matcha.css';
// Light themes (16)
// Light themes (20)
import './themes/light.css';
import './themes/cream.css';
import './themes/solarizedlight.css';
@@ -38,3 +42,7 @@ import './themes/sepia.css';
import './themes/gruvboxlight.css';
import './themes/nordlight.css';
import './themes/blossom.css';
import './themes/ayu-light.css';
import './themes/onelight.css';
import './themes/bluloco.css';
import './themes/feather.css';

View File

@@ -0,0 +1,151 @@
/* Ayu Dark Theme - Deep dark with warm orange accents */
.ayu-dark {
/* Backgrounds - from editor.bg #10141C, ui.bg #0D1017 */
--background: oklch(0.12 0.02 250); /* #0D1017 */
--background-50: oklch(0.12 0.02 250 / 0.5);
--background-80: oklch(0.12 0.02 250 / 0.8);
/* Text - from editor.fg #BFBDB6, ui.fg #5A6378 - boosted for contrast */
--foreground: oklch(0.95 0.01 90); /* Brighter for better readability */
--foreground-secondary: oklch(0.75 0.02 240);
--foreground-muted: oklch(0.6 0.03 240);
/* Card/Popover - from panel.bg #141821, popup.bg #0F131A */
--card: oklch(0.15 0.02 250); /* #141821 */
--card-foreground: oklch(0.95 0.01 90);
--popover: oklch(0.13 0.02 250); /* #0F131A */
--popover-foreground: oklch(0.95 0.01 90);
/* Primary/Brand - from accent.tint #E6B450, orange #FF8F40 */
--primary: oklch(0.78 0.15 75); /* #E6B450 golden */
--primary-foreground: oklch(0.12 0.02 250);
--brand-400: oklch(0.82 0.14 75);
--brand-500: oklch(0.78 0.15 75); /* #E6B450 */
--brand-600: oklch(0.72 0.17 70);
/* Secondary - from ui.line #1B1F29 */
--secondary: oklch(0.18 0.02 250);
--secondary-foreground: oklch(0.95 0.01 90);
/* Muted */
--muted: oklch(0.18 0.02 250);
--muted-foreground: oklch(0.45 0.04 240);
/* Accent */
--accent: oklch(0.22 0.03 250);
--accent-foreground: oklch(0.95 0.01 90);
/* Destructive - from error #D95757 */
--destructive: oklch(0.55 0.2 25); /* #D95757 */
/* Borders - from editor.line #161A24 */
--border: oklch(0.2 0.02 250);
--border-glass: oklch(0.78 0.15 75 / 0.3);
--input: oklch(0.15 0.02 250);
--ring: oklch(0.78 0.15 75);
/* Charts - using Ayu palette */
--chart-1: oklch(0.78 0.15 75); /* Gold accent */
--chart-2: oklch(0.75 0.2 130); /* Green #AAD94C */
--chart-3: oklch(0.7 0.15 220); /* Blue #59C2FF */
--chart-4: oklch(0.7 0.2 320); /* Purple #D2A6FF */
--chart-5: oklch(0.65 0.2 15); /* Red #F07178 */
/* Sidebar - from ui.bg #0D1017 */
--sidebar: oklch(0.1 0.02 250);
--sidebar-foreground: oklch(0.95 0.01 90);
--sidebar-primary: oklch(0.78 0.15 75);
--sidebar-primary-foreground: oklch(0.12 0.02 250);
--sidebar-accent: oklch(0.18 0.02 250);
--sidebar-accent-foreground: oklch(0.95 0.01 90);
--sidebar-border: oklch(0.2 0.02 250);
--sidebar-ring: oklch(0.78 0.15 75);
/* Action buttons */
--action-view: oklch(0.78 0.15 75); /* Gold */
--action-view-hover: oklch(0.72 0.17 70);
--action-followup: oklch(0.7 0.15 220); /* Blue #59C2FF */
--action-followup-hover: oklch(0.65 0.17 220);
--action-commit: oklch(0.75 0.2 130); /* Green #AAD94C */
--action-commit-hover: oklch(0.7 0.22 130);
--action-verify: oklch(0.75 0.2 130);
--action-verify-hover: oklch(0.7 0.22 130);
/* Running indicator */
--running-indicator: oklch(0.78 0.15 75);
--running-indicator-text: oklch(0.82 0.14 75);
}
/* Theme-specific overrides */
.ayu-dark .animated-outline-gradient {
background: conic-gradient(from 90deg at 50% 50%, #e6b450 0%, #ff8f40 50%, #e6b450 100%);
}
.ayu-dark .animated-outline-inner {
background: oklch(0.12 0.02 250) !important;
color: #e6b450 !important;
}
.ayu-dark [data-slot='button'][class*='animated-outline']:hover .animated-outline-inner {
background: oklch(0.18 0.02 250) !important;
color: #ffb454 !important;
}
.ayu-dark .slider-track {
background: oklch(0.18 0.02 250);
}
.ayu-dark .slider-range {
background: linear-gradient(to right, #e6b450, #ffb454);
}
.ayu-dark .slider-thumb {
background: oklch(0.12 0.02 250);
border-color: #e6b450;
}
/* XML Syntax Highlighting */
.ayu-dark .xml-highlight {
color: oklch(0.95 0.01 90);
}
.ayu-dark .xml-tag-bracket {
color: #39bae6; /* Indigo */
}
.ayu-dark .xml-tag-name {
color: #39bae6;
}
.ayu-dark .xml-attribute-name {
color: #ffb454; /* Yellow */
}
.ayu-dark .xml-attribute-equals {
color: oklch(0.95 0.01 90);
}
.ayu-dark .xml-attribute-value {
color: #aad94c; /* Green */
}
.ayu-dark .xml-comment {
color: #5a6673; /* Gray */
font-style: italic;
}
.ayu-dark .xml-cdata {
color: #95e6cb; /* Teal */
}
.ayu-dark .xml-doctype {
color: #d2a6ff; /* Purple */
}
.ayu-dark .xml-text {
color: oklch(0.95 0.01 90);
}

View File

@@ -0,0 +1,151 @@
/* Ayu Light Theme - Clean light with warm orange accents */
.ayu-light {
/* Backgrounds - from editor.bg #FCFCFC, ui.bg #F8F9FA */
--background: oklch(0.98 0.005 90); /* #FCFCFC */
--background-50: oklch(0.98 0.005 90 / 0.5);
--background-80: oklch(0.98 0.005 90 / 0.8);
/* Text - from editor.fg #5C6166, ui.fg #828E9F */
--foreground: oklch(0.42 0.02 240); /* #5C6166 */
--foreground-secondary: oklch(0.52 0.02 240);
--foreground-muted: oklch(0.58 0.03 240); /* #828E9F */
/* Card/Popover - from panel.bg #FAFAFA, popup.bg #FFFFFF */
--card: oklch(0.98 0.003 90); /* #FAFAFA */
--card-foreground: oklch(0.42 0.02 240);
--popover: oklch(1 0 0); /* #FFFFFF */
--popover-foreground: oklch(0.42 0.02 240);
/* Primary/Brand - from accent.tint #F29718 */
--primary: oklch(0.72 0.18 55); /* #F29718 orange */
--primary-foreground: oklch(1 0 0);
--brand-400: oklch(0.78 0.16 55);
--brand-500: oklch(0.72 0.18 55); /* #F29718 */
--brand-600: oklch(0.65 0.2 50);
/* Secondary */
--secondary: oklch(0.96 0.005 90);
--secondary-foreground: oklch(0.42 0.02 240);
/* Muted */
--muted: oklch(0.96 0.005 90);
--muted-foreground: oklch(0.58 0.03 240);
/* Accent */
--accent: oklch(0.94 0.01 90);
--accent-foreground: oklch(0.42 0.02 240);
/* Destructive - from error #E65050 */
--destructive: oklch(0.55 0.22 25); /* #E65050 */
/* Borders */
--border: oklch(0.9 0.01 90);
--border-glass: oklch(0.42 0.02 240 / 0.1);
--input: oklch(1 0 0);
--ring: oklch(0.72 0.18 55);
/* Charts - using Ayu Light palette */
--chart-1: oklch(0.72 0.18 55); /* Orange accent */
--chart-2: oklch(0.6 0.18 130); /* Green #86B300 */
--chart-3: oklch(0.65 0.15 210); /* Blue #22A4E6 */
--chart-4: oklch(0.55 0.18 310); /* Purple #A37ACC */
--chart-5: oklch(0.58 0.2 20); /* Red #F07171 */
/* Sidebar */
--sidebar: oklch(0.97 0.005 90); /* #F8F9FA */
--sidebar-foreground: oklch(0.42 0.02 240);
--sidebar-primary: oklch(0.72 0.18 55);
--sidebar-primary-foreground: oklch(1 0 0);
--sidebar-accent: oklch(0.94 0.01 90);
--sidebar-accent-foreground: oklch(0.42 0.02 240);
--sidebar-border: oklch(0.9 0.01 90);
--sidebar-ring: oklch(0.72 0.18 55);
/* Action buttons */
--action-view: oklch(0.72 0.18 55); /* Orange */
--action-view-hover: oklch(0.65 0.2 50);
--action-followup: oklch(0.65 0.15 210); /* Blue */
--action-followup-hover: oklch(0.58 0.17 210);
--action-commit: oklch(0.6 0.18 130); /* Green */
--action-commit-hover: oklch(0.55 0.2 130);
--action-verify: oklch(0.6 0.18 130);
--action-verify-hover: oklch(0.55 0.2 130);
/* Running indicator */
--running-indicator: oklch(0.72 0.18 55);
--running-indicator-text: oklch(0.65 0.2 50);
}
/* Theme-specific overrides */
.ayu-light .animated-outline-gradient {
background: conic-gradient(from 90deg at 50% 50%, #f29718 0%, #fa8532 50%, #f29718 100%);
}
.ayu-light .animated-outline-inner {
background: oklch(0.98 0.005 90) !important;
color: #f29718 !important;
}
.ayu-light [data-slot='button'][class*='animated-outline']:hover .animated-outline-inner {
background: oklch(0.96 0.01 90) !important;
color: #fa8532 !important;
}
.ayu-light .slider-track {
background: oklch(0.92 0.01 90);
}
.ayu-light .slider-range {
background: linear-gradient(to right, #f29718, #fa8532);
}
.ayu-light .slider-thumb {
background: oklch(1 0 0);
border-color: #f29718;
}
/* XML Syntax Highlighting */
.ayu-light .xml-highlight {
color: oklch(0.42 0.02 240);
}
.ayu-light .xml-tag-bracket {
color: #55b4d4; /* Indigo/Cyan */
}
.ayu-light .xml-tag-name {
color: #55b4d4;
}
.ayu-light .xml-attribute-name {
color: #f29718; /* Orange */
}
.ayu-light .xml-attribute-equals {
color: oklch(0.42 0.02 240);
}
.ayu-light .xml-attribute-value {
color: #86b300; /* Green */
}
.ayu-light .xml-comment {
color: #adaeb1; /* Gray */
font-style: italic;
}
.ayu-light .xml-cdata {
color: #4cbf99; /* Teal */
}
.ayu-light .xml-doctype {
color: #a37acc; /* Purple */
}
.ayu-light .xml-text {
color: oklch(0.42 0.02 240);
}

View File

@@ -0,0 +1,151 @@
/* Ayu Mirage Theme - Soft dark with golden yellow accents */
.ayu-mirage {
/* Backgrounds - from editor.bg #242936, ui.bg #1F2430 */
--background: oklch(0.2 0.02 260); /* #1F2430 */
--background-50: oklch(0.2 0.02 260 / 0.5);
--background-80: oklch(0.2 0.02 260 / 0.8);
/* Text - from editor.fg #CCCAC2, ui.fg #707A8C */
--foreground: oklch(0.82 0.01 90); /* #CCCAC2 */
--foreground-secondary: oklch(0.58 0.03 240);
--foreground-muted: oklch(0.52 0.04 240); /* #707A8C */
/* Card/Popover - from panel.bg #282E3B, popup.bg #1C212C */
--card: oklch(0.24 0.02 260); /* #282E3B */
--card-foreground: oklch(0.82 0.01 90);
--popover: oklch(0.18 0.02 260); /* #1C212C */
--popover-foreground: oklch(0.82 0.01 90);
/* Primary/Brand - from accent.tint #FFCC66 */
--primary: oklch(0.85 0.14 85); /* #FFCC66 golden yellow */
--primary-foreground: oklch(0.18 0.02 260);
--brand-400: oklch(0.88 0.12 85);
--brand-500: oklch(0.85 0.14 85); /* #FFCC66 */
--brand-600: oklch(0.78 0.16 80);
/* Secondary - from ui.line #171B24 */
--secondary: oklch(0.22 0.02 260);
--secondary-foreground: oklch(0.82 0.01 90);
/* Muted */
--muted: oklch(0.22 0.02 260);
--muted-foreground: oklch(0.52 0.04 240);
/* Accent */
--accent: oklch(0.28 0.03 260);
--accent-foreground: oklch(0.82 0.01 90);
/* Destructive - from error #FF6666 */
--destructive: oklch(0.62 0.22 20); /* #FF6666 */
/* Borders - from editor.line #1A1F29 */
--border: oklch(0.22 0.02 260);
--border-glass: oklch(0.85 0.14 85 / 0.3);
--input: oklch(0.2 0.02 260);
--ring: oklch(0.85 0.14 85);
/* Charts - using Ayu Mirage palette */
--chart-1: oklch(0.85 0.14 85); /* Yellow accent */
--chart-2: oklch(0.85 0.2 120); /* Green #D5FF80 */
--chart-3: oklch(0.78 0.12 200); /* Blue #73D0FF */
--chart-4: oklch(0.82 0.15 310); /* Purple #DFBFFF */
--chart-5: oklch(0.68 0.18 20); /* Red #F28779 */
/* Sidebar - slightly darker than main bg */
--sidebar: oklch(0.16 0.02 260);
--sidebar-foreground: oklch(0.82 0.01 90);
--sidebar-primary: oklch(0.85 0.14 85);
--sidebar-primary-foreground: oklch(0.18 0.02 260);
--sidebar-accent: oklch(0.22 0.02 260);
--sidebar-accent-foreground: oklch(0.82 0.01 90);
--sidebar-border: oklch(0.22 0.02 260);
--sidebar-ring: oklch(0.85 0.14 85);
/* Action buttons */
--action-view: oklch(0.85 0.14 85); /* Yellow */
--action-view-hover: oklch(0.78 0.16 80);
--action-followup: oklch(0.78 0.12 200); /* Blue #73D0FF */
--action-followup-hover: oklch(0.72 0.14 200);
--action-commit: oklch(0.85 0.2 120); /* Green #D5FF80 */
--action-commit-hover: oklch(0.78 0.22 120);
--action-verify: oklch(0.85 0.2 120);
--action-verify-hover: oklch(0.78 0.22 120);
/* Running indicator */
--running-indicator: oklch(0.85 0.14 85);
--running-indicator-text: oklch(0.88 0.12 85);
}
/* Theme-specific overrides */
.ayu-mirage .animated-outline-gradient {
background: conic-gradient(from 90deg at 50% 50%, #ffcc66 0%, #ffa659 50%, #ffcc66 100%);
}
.ayu-mirage .animated-outline-inner {
background: oklch(0.2 0.02 260) !important;
color: #ffcc66 !important;
}
.ayu-mirage [data-slot='button'][class*='animated-outline']:hover .animated-outline-inner {
background: oklch(0.24 0.02 260) !important;
color: #ffcd66 !important;
}
.ayu-mirage .slider-track {
background: oklch(0.22 0.02 260);
}
.ayu-mirage .slider-range {
background: linear-gradient(to right, #ffcc66, #ffa659);
}
.ayu-mirage .slider-thumb {
background: oklch(0.2 0.02 260);
border-color: #ffcc66;
}
/* XML Syntax Highlighting */
.ayu-mirage .xml-highlight {
color: oklch(0.82 0.01 90);
}
.ayu-mirage .xml-tag-bracket {
color: #5ccfe6; /* Indigo/Cyan */
}
.ayu-mirage .xml-tag-name {
color: #5ccfe6;
}
.ayu-mirage .xml-attribute-name {
color: #ffcd66; /* Yellow */
}
.ayu-mirage .xml-attribute-equals {
color: oklch(0.82 0.01 90);
}
.ayu-mirage .xml-attribute-value {
color: #d5ff80; /* Green */
}
.ayu-mirage .xml-comment {
color: #6e7c8f; /* Gray */
font-style: italic;
}
.ayu-mirage .xml-cdata {
color: #95e6cb; /* Teal */
}
.ayu-mirage .xml-doctype {
color: #dfbfff; /* Purple */
}
.ayu-mirage .xml-text {
color: oklch(0.82 0.01 90);
}

View File

@@ -0,0 +1,93 @@
/* Bluloco Light Theme - Clean light with cyan-blue accent */
.bluloco {
/* Backgrounds */
--background: oklch(0.98 0.002 250);
--background-50: oklch(0.98 0.002 250 / 0.5);
--background-80: oklch(0.98 0.002 250 / 0.8);
/* Text */
--foreground: oklch(0.3 0.02 250);
--foreground-secondary: oklch(0.45 0.02 250);
--foreground-muted: oklch(0.55 0.015 250);
/* Card/Popover */
--card: oklch(0.96 0.003 250);
--card-foreground: oklch(0.3 0.02 250);
--popover: oklch(0.98 0.002 250);
--popover-foreground: oklch(0.3 0.02 250);
/* Primary/Brand - cyan-blue #0099e1 */
--primary: oklch(0.62 0.18 220);
--primary-foreground: oklch(1 0 0);
--brand-400: oklch(0.67 0.16 220);
--brand-500: oklch(0.62 0.18 220);
--brand-600: oklch(0.55 0.2 220);
/* Secondary */
--secondary: oklch(0.92 0.005 250);
--secondary-foreground: oklch(0.3 0.02 250);
/* Muted */
--muted: oklch(0.93 0.005 250);
--muted-foreground: oklch(0.5 0.015 250);
/* Accent */
--accent: oklch(0.94 0.005 250);
--accent-foreground: oklch(0.3 0.02 250);
/* Destructive - red #d52753 */
--destructive: oklch(0.5 0.22 15);
/* Borders */
--border: oklch(0.88 0.005 250);
--border-glass: oklch(0.3 0.02 250 / 0.1);
--input: oklch(0.98 0.002 250);
--ring: oklch(0.62 0.18 220);
/* Charts */
--chart-1: oklch(0.62 0.18 220);
--chart-2: oklch(0.55 0.15 145);
--chart-3: oklch(0.5 0.22 15);
--chart-4: oklch(0.6 0.2 320);
--chart-5: oklch(0.7 0.15 85);
/* Sidebar */
--sidebar: oklch(0.93 0.005 250);
--sidebar-foreground: oklch(0.3 0.02 250);
--sidebar-primary: oklch(0.62 0.18 220);
--sidebar-primary-foreground: oklch(1 0 0);
--sidebar-accent: oklch(0.9 0.005 250);
--sidebar-accent-foreground: oklch(0.3 0.02 250);
--sidebar-border: oklch(0.88 0.005 250);
--sidebar-ring: oklch(0.62 0.18 220);
/* Action buttons */
--action-view: oklch(0.62 0.18 220);
--action-view-hover: oklch(0.55 0.2 220);
--action-followup: oklch(0.55 0.15 265);
--action-followup-hover: oklch(0.5 0.17 265);
--action-commit: oklch(0.55 0.15 145);
--action-commit-hover: oklch(0.5 0.17 145);
--action-verify: oklch(0.55 0.15 145);
--action-verify-hover: oklch(0.5 0.17 145);
/* Running indicator */
--running-indicator: oklch(0.62 0.18 220);
--running-indicator-text: oklch(0.55 0.2 220);
/* Status colors */
--status-success: oklch(0.55 0.15 145);
--status-success-bg: oklch(0.55 0.15 145 / 0.15);
--status-warning: oklch(0.7 0.15 85);
--status-warning-bg: oklch(0.7 0.15 85 / 0.15);
--status-error: oklch(0.5 0.22 15);
--status-error-bg: oklch(0.5 0.22 15 / 0.15);
--status-info: oklch(0.62 0.18 220);
--status-info-bg: oklch(0.62 0.18 220 / 0.15);
--status-backlog: oklch(0.5 0 0);
--status-in-progress: oklch(0.7 0.15 85);
--status-waiting: oklch(0.65 0.18 50);
}

View File

@@ -0,0 +1,151 @@
/* Ember Theme - Warm orange accent on dark background */
.ember {
/* Backgrounds - from bgMain #272822 */
--background: oklch(0.2 0.02 100); /* #272822 */
--background-50: oklch(0.2 0.02 100 / 0.5);
--background-80: oklch(0.2 0.02 100 / 0.8);
/* Text - from textMain #f8f8f2, textDim #8f908a */
--foreground: oklch(0.97 0.01 100); /* #f8f8f2 */
--foreground-secondary: oklch(0.65 0.02 100);
--foreground-muted: oklch(0.6 0.02 100); /* #8f908a */
/* Card/Popover - derived from bgActivity #3e3d32 */
--card: oklch(0.28 0.02 100); /* #3e3d32 */
--card-foreground: oklch(0.97 0.01 100);
--popover: oklch(0.24 0.02 100);
--popover-foreground: oklch(0.97 0.01 100);
/* Primary/Brand - from accent #fd971f (orange) */
--primary: oklch(0.75 0.18 60); /* #fd971f */
--primary-foreground: oklch(0.18 0.02 100); /* #1e1f1c */
--brand-400: oklch(0.8 0.16 60); /* #fdbf6f lighter */
--brand-500: oklch(0.75 0.18 60); /* #fd971f */
--brand-600: oklch(0.68 0.2 55);
/* Secondary - from bgActivity #3e3d32 */
--secondary: oklch(0.28 0.02 100);
--secondary-foreground: oklch(0.97 0.01 100);
/* Muted */
--muted: oklch(0.28 0.02 100);
--muted-foreground: oklch(0.6 0.02 100);
/* Accent - from accentDim */
--accent: oklch(0.32 0.04 80);
--accent-foreground: oklch(0.97 0.01 100);
/* Destructive - from error #f92672 */
--destructive: oklch(0.6 0.28 350); /* #f92672 */
/* Borders - from border #49483e */
--border: oklch(0.35 0.03 90); /* #49483e */
--border-glass: oklch(0.75 0.18 60 / 0.3);
--input: oklch(0.24 0.02 100);
--ring: oklch(0.75 0.18 60);
/* Charts - using theme colors */
--chart-1: oklch(0.75 0.18 60); /* Orange accent */
--chart-2: oklch(0.75 0.22 130); /* Green #a6e22e */
--chart-3: oklch(0.85 0.15 95); /* Yellow #e6db74 */
--chart-4: oklch(0.6 0.28 350); /* Pink #f92672 */
--chart-5: oklch(0.7 0.2 200); /* Blue for variety */
/* Sidebar - from bgSidebar #1e1f1c */
--sidebar: oklch(0.16 0.02 100); /* #1e1f1c */
--sidebar-foreground: oklch(0.97 0.01 100);
--sidebar-primary: oklch(0.75 0.18 60);
--sidebar-primary-foreground: oklch(0.18 0.02 100);
--sidebar-accent: oklch(0.28 0.02 100);
--sidebar-accent-foreground: oklch(0.97 0.01 100);
--sidebar-border: oklch(0.35 0.03 90);
--sidebar-ring: oklch(0.75 0.18 60);
/* Action buttons - orange/green theme */
--action-view: oklch(0.75 0.18 60); /* Orange */
--action-view-hover: oklch(0.7 0.2 55);
--action-followup: oklch(0.7 0.2 200); /* Blue */
--action-followup-hover: oklch(0.65 0.22 200);
--action-commit: oklch(0.75 0.22 130); /* Green #a6e22e */
--action-commit-hover: oklch(0.7 0.24 130);
--action-verify: oklch(0.75 0.22 130);
--action-verify-hover: oklch(0.7 0.24 130);
/* Running indicator - Orange */
--running-indicator: oklch(0.75 0.18 60);
--running-indicator-text: oklch(0.8 0.16 60);
}
/* Theme-specific overrides */
.ember .animated-outline-gradient {
background: conic-gradient(from 90deg at 50% 50%, #fd971f 0%, #f92672 50%, #fd971f 100%);
}
.ember .animated-outline-inner {
background: oklch(0.2 0.02 100) !important;
color: #fd971f !important;
}
.ember [data-slot='button'][class*='animated-outline']:hover .animated-outline-inner {
background: oklch(0.28 0.02 100) !important;
color: #fdbf6f !important;
}
.ember .slider-track {
background: oklch(0.28 0.02 100);
}
.ember .slider-range {
background: linear-gradient(to right, #fd971f, #fdbf6f);
}
.ember .slider-thumb {
background: oklch(0.2 0.02 100);
border-color: #fd971f;
}
/* XML Syntax Highlighting */
.ember .xml-highlight {
color: oklch(0.97 0.01 100);
}
.ember .xml-tag-bracket {
color: oklch(0.6 0.28 350); /* Pink */
}
.ember .xml-tag-name {
color: oklch(0.6 0.28 350); /* Pink */
}
.ember .xml-attribute-name {
color: oklch(0.75 0.22 130); /* Green */
}
.ember .xml-attribute-equals {
color: oklch(0.97 0.01 100);
}
.ember .xml-attribute-value {
color: oklch(0.85 0.15 95); /* Yellow */
}
.ember .xml-comment {
color: oklch(0.6 0.02 100);
font-style: italic;
}
.ember .xml-cdata {
color: oklch(0.7 0.2 200); /* Blue */
}
.ember .xml-doctype {
color: oklch(0.75 0.18 60); /* Orange */
}
.ember .xml-text {
color: oklch(0.97 0.01 100);
}

View File

@@ -0,0 +1,93 @@
/* Feather Theme - Clean white with orange accent */
.feather {
/* Backgrounds */
--background: oklch(1 0 0);
--background-50: oklch(1 0 0 / 0.5);
--background-80: oklch(1 0 0 / 0.8);
/* Text - dark blue-gray #395063 */
--foreground: oklch(0.38 0.03 230);
--foreground-secondary: oklch(0.5 0.025 230);
--foreground-muted: oklch(0.6 0.02 230);
/* Card/Popover */
--card: oklch(0.99 0.001 250);
--card-foreground: oklch(0.38 0.03 230);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.38 0.03 230);
/* Primary/Brand - orange #FF7B2E */
--primary: oklch(0.7 0.2 45);
--primary-foreground: oklch(1 0 0);
--brand-400: oklch(0.75 0.18 45);
--brand-500: oklch(0.7 0.2 45);
--brand-600: oklch(0.62 0.22 40);
/* Secondary */
--secondary: oklch(0.95 0.003 250);
--secondary-foreground: oklch(0.38 0.03 230);
/* Muted */
--muted: oklch(0.96 0.003 250);
--muted-foreground: oklch(0.55 0.02 230);
/* Accent */
--accent: oklch(0.97 0.003 250);
--accent-foreground: oklch(0.38 0.03 230);
/* Destructive - orange-red */
--destructive: oklch(0.55 0.22 30);
/* Borders */
--border: oklch(0.9 0.003 250);
--border-glass: oklch(0.38 0.03 230 / 0.1);
--input: oklch(1 0 0);
--ring: oklch(0.7 0.2 45);
/* Charts */
--chart-1: oklch(0.7 0.2 45);
--chart-2: oklch(0.6 0.15 175);
--chart-3: oklch(0.6 0.18 320);
--chart-4: oklch(0.55 0.15 220);
--chart-5: oklch(0.55 0.22 30);
/* Sidebar */
--sidebar: oklch(0.99 0.001 250);
--sidebar-foreground: oklch(0.38 0.03 230);
--sidebar-primary: oklch(0.7 0.2 45);
--sidebar-primary-foreground: oklch(1 0 0);
--sidebar-accent: oklch(0.96 0.003 250);
--sidebar-accent-foreground: oklch(0.38 0.03 230);
--sidebar-border: oklch(0.92 0.003 250);
--sidebar-ring: oklch(0.7 0.2 45);
/* Action buttons */
--action-view: oklch(0.7 0.2 45);
--action-view-hover: oklch(0.62 0.22 40);
--action-followup: oklch(0.55 0.15 220);
--action-followup-hover: oklch(0.5 0.17 220);
--action-commit: oklch(0.6 0.15 175);
--action-commit-hover: oklch(0.55 0.17 175);
--action-verify: oklch(0.6 0.15 175);
--action-verify-hover: oklch(0.55 0.17 175);
/* Running indicator */
--running-indicator: oklch(0.7 0.2 45);
--running-indicator-text: oklch(0.62 0.22 40);
/* Status colors */
--status-success: oklch(0.6 0.15 175);
--status-success-bg: oklch(0.6 0.15 175 / 0.15);
--status-warning: oklch(0.7 0.2 45);
--status-warning-bg: oklch(0.7 0.2 45 / 0.15);
--status-error: oklch(0.55 0.22 30);
--status-error-bg: oklch(0.55 0.22 30 / 0.15);
--status-info: oklch(0.55 0.15 220);
--status-info-bg: oklch(0.55 0.15 220 / 0.15);
--status-backlog: oklch(0.5 0 0);
--status-in-progress: oklch(0.7 0.2 45);
--status-waiting: oklch(0.65 0.18 50);
}

View File

@@ -0,0 +1,103 @@
/**
* Matcha Theme
* A calming dark theme with sage green and warm beige accents
* Inspired by: https://github.com/lucafalasco/matcha
*/
.matcha {
/* Background layers - dark with subtle green undertone */
--background: oklch(20% 0.015 145);
--background-secondary: oklch(24% 0.015 145);
--background-tertiary: oklch(16% 0.015 145);
--background-50: oklch(20% 0.015 145 / 0.5);
--background-80: oklch(20% 0.015 145 / 0.8);
/* Foreground / text - light with green tint */
--foreground: oklch(90% 0.025 145);
--foreground-secondary: oklch(70% 0.02 145);
--foreground-muted: oklch(60% 0.015 145);
/* Brand / accent - sage green */
--brand-400: oklch(72% 0.12 135);
--brand-500: oklch(68% 0.14 135);
--brand-600: oklch(62% 0.16 135);
--primary: oklch(68% 0.14 135);
--primary-foreground: oklch(15% 0.02 135);
/* Sidebar - slightly lighter with green tint */
--sidebar: oklch(22% 0.02 145);
--sidebar-foreground: oklch(90% 0.025 145);
--sidebar-primary: oklch(68% 0.14 135);
--sidebar-primary-foreground: oklch(15% 0.02 135);
--sidebar-accent: oklch(28% 0.025 145);
--sidebar-accent-foreground: oklch(90% 0.025 145);
--sidebar-border: oklch(30% 0.02 145);
--sidebar-ring: oklch(68% 0.14 135);
/* Cards */
--card: oklch(22% 0.02 145);
--card-foreground: oklch(90% 0.025 145);
/* Inputs */
--input: oklch(18% 0.015 145);
/* Borders */
--border: oklch(32% 0.025 145);
--border-glass: oklch(90% 0.02 145 / 0.1);
/* Popover / Dropdown */
--popover: oklch(20% 0.02 145);
--popover-foreground: oklch(90% 0.025 145);
/* Secondary */
--secondary: oklch(28% 0.025 145);
--secondary-foreground: oklch(90% 0.025 145);
/* Muted */
--muted: oklch(26% 0.02 145);
--muted-foreground: oklch(65% 0.02 145);
/* Accent */
--accent: oklch(30% 0.03 145);
--accent-foreground: oklch(90% 0.025 145);
/* Destructive */
--destructive: oklch(60% 0.18 25);
/* Ring / Focus */
--ring: oklch(68% 0.14 135);
/* Action buttons - warm beige */
--action-view: oklch(78% 0.06 90);
--action-view-hover: oklch(72% 0.07 90);
--action-followup: oklch(68% 0.14 135);
--action-followup-hover: oklch(62% 0.16 135);
--action-commit: oklch(68% 0.14 135);
--action-commit-hover: oklch(62% 0.16 135);
--action-verify: oklch(68% 0.14 135);
--action-verify-hover: oklch(62% 0.16 135);
/* Running indicator - sage green */
--running-indicator: oklch(68% 0.14 135);
--running-indicator-text: oklch(72% 0.12 135);
/* Status colors */
--status-success: oklch(68% 0.14 135);
--status-success-bg: oklch(68% 0.14 135 / 0.2);
--status-warning: oklch(78% 0.12 85);
--status-warning-bg: oklch(78% 0.12 85 / 0.2);
--status-error: oklch(65% 0.18 25);
--status-error-bg: oklch(65% 0.18 25 / 0.2);
--status-info: oklch(68% 0.14 135);
--status-info-bg: oklch(68% 0.14 135 / 0.2);
--status-backlog: oklch(55% 0 0);
--status-in-progress: oklch(78% 0.12 85);
--status-waiting: oklch(70% 0.15 55);
/* Chart colors */
--chart-1: oklch(68% 0.14 135);
--chart-2: oklch(70% 0.1 170);
--chart-3: oklch(78% 0.12 85);
--chart-4: oklch(65% 0.12 280);
--chart-5: oklch(60% 0.15 25);
}

View File

@@ -0,0 +1,93 @@
/* Atom One Light Theme - Clean light with blue accent */
.onelight {
/* Backgrounds */
--background: oklch(0.98 0.002 250);
--background-50: oklch(0.98 0.002 250 / 0.5);
--background-80: oklch(0.98 0.002 250 / 0.8);
/* Text */
--foreground: oklch(0.3 0.02 250);
--foreground-secondary: oklch(0.45 0.02 250);
--foreground-muted: oklch(0.55 0.015 250);
/* Card/Popover */
--card: oklch(0.97 0.002 250);
--card-foreground: oklch(0.3 0.02 250);
--popover: oklch(0.98 0.002 250);
--popover-foreground: oklch(0.3 0.02 250);
/* Primary/Brand - blue #526FFF */
--primary: oklch(0.55 0.22 265);
--primary-foreground: oklch(1 0 0);
--brand-400: oklch(0.6 0.2 265);
--brand-500: oklch(0.55 0.22 265);
--brand-600: oklch(0.5 0.24 265);
/* Secondary */
--secondary: oklch(0.92 0.005 250);
--secondary-foreground: oklch(0.3 0.02 250);
/* Muted */
--muted: oklch(0.93 0.005 250);
--muted-foreground: oklch(0.5 0.015 250);
/* Accent */
--accent: oklch(0.94 0.005 250);
--accent-foreground: oklch(0.3 0.02 250);
/* Destructive - red #E45649 */
--destructive: oklch(0.55 0.2 25);
/* Borders */
--border: oklch(0.88 0.005 250);
--border-glass: oklch(0.3 0.02 250 / 0.1);
--input: oklch(0.98 0.002 250);
--ring: oklch(0.55 0.22 265);
/* Charts */
--chart-1: oklch(0.55 0.22 265);
--chart-2: oklch(0.55 0.15 145);
--chart-3: oklch(0.55 0.2 25);
--chart-4: oklch(0.55 0.18 320);
--chart-5: oklch(0.55 0.15 70);
/* Sidebar */
--sidebar: oklch(0.93 0.005 250);
--sidebar-foreground: oklch(0.3 0.02 250);
--sidebar-primary: oklch(0.55 0.22 265);
--sidebar-primary-foreground: oklch(1 0 0);
--sidebar-accent: oklch(0.9 0.005 250);
--sidebar-accent-foreground: oklch(0.3 0.02 250);
--sidebar-border: oklch(0.88 0.005 250);
--sidebar-ring: oklch(0.55 0.22 265);
/* Action buttons */
--action-view: oklch(0.55 0.22 265);
--action-view-hover: oklch(0.5 0.24 265);
--action-followup: oklch(0.55 0.18 230);
--action-followup-hover: oklch(0.5 0.2 230);
--action-commit: oklch(0.55 0.15 145);
--action-commit-hover: oklch(0.5 0.17 145);
--action-verify: oklch(0.55 0.15 145);
--action-verify-hover: oklch(0.5 0.17 145);
/* Running indicator */
--running-indicator: oklch(0.55 0.22 265);
--running-indicator-text: oklch(0.5 0.24 265);
/* Status colors */
--status-success: oklch(0.55 0.15 145);
--status-success-bg: oklch(0.55 0.15 145 / 0.15);
--status-warning: oklch(0.7 0.15 70);
--status-warning-bg: oklch(0.7 0.15 70 / 0.15);
--status-error: oklch(0.55 0.2 25);
--status-error-bg: oklch(0.55 0.2 25 / 0.15);
--status-info: oklch(0.55 0.22 265);
--status-info-bg: oklch(0.55 0.22 265 / 0.15);
--status-backlog: oklch(0.5 0 0);
--status-in-progress: oklch(0.7 0.15 70);
--status-waiting: oklch(0.65 0.18 50);
}

View File

@@ -396,6 +396,10 @@ export interface ProjectRef {
lastOpened?: string;
/** Project-specific theme override (or undefined to use global) */
theme?: string;
/** Project-specific UI/sans font override (or undefined to use global) */
fontFamilySans?: string;
/** Project-specific code/mono font override (or undefined to use global) */
fontFamilyMono?: string;
/** Whether project is pinned to favorites on dashboard */
isFavorite?: boolean;
/** Lucide icon name for project identification */

178
package-lock.json generated
View File

@@ -13,6 +13,13 @@
"libs/*"
],
"dependencies": {
"@fontsource/cascadia-code": "^5.2.3",
"@fontsource/iosevka": "^5.2.5",
"@fontsource/lato": "^5.2.7",
"@fontsource/montserrat": "^5.2.8",
"@fontsource/playfair-display": "^5.2.8",
"@fontsource/raleway": "^5.2.8",
"@fontsource/source-sans-3": "^5.2.9",
"cross-spawn": "7.0.6",
"rehype-sanitize": "6.0.0",
"tree-kill": "1.2.2"
@@ -93,6 +100,22 @@
"@dnd-kit/core": "6.3.1",
"@dnd-kit/sortable": "10.0.0",
"@dnd-kit/utilities": "3.2.2",
"@fontsource/cascadia-code": "^5.2.3",
"@fontsource/fira-code": "^5.2.7",
"@fontsource/ibm-plex-mono": "^5.2.7",
"@fontsource/inconsolata": "^5.2.8",
"@fontsource/inter": "^5.2.8",
"@fontsource/iosevka": "^5.2.5",
"@fontsource/jetbrains-mono": "^5.2.8",
"@fontsource/lato": "^5.2.7",
"@fontsource/montserrat": "^5.2.8",
"@fontsource/open-sans": "^5.2.7",
"@fontsource/poppins": "^5.2.7",
"@fontsource/raleway": "^5.2.8",
"@fontsource/roboto": "^5.2.9",
"@fontsource/source-code-pro": "^5.2.7",
"@fontsource/source-sans-3": "^5.2.9",
"@fontsource/work-sans": "^5.2.8",
"@lezer/highlight": "1.2.3",
"@radix-ui/react-checkbox": "1.3.3",
"@radix-ui/react-collapsible": "1.1.12",
@@ -1480,7 +1503,7 @@
},
"node_modules/@electron/node-gyp": {
"version": "10.2.0-electron.1",
"resolved": "git+https://github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2",
"resolved": "git+ssh://git@github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2",
"integrity": "sha512-4MSBTT8y07YUDqf69/vSh80Hh791epYqGtWHO3zSKhYFwQg+gx9wi1PqbqP6YqC4WMsNxZ5l9oDmnWdK5pfCKQ==",
"dev": true,
"license": "MIT",
@@ -2814,6 +2837,159 @@
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
"license": "MIT"
},
"node_modules/@fontsource/cascadia-code": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@fontsource/cascadia-code/-/cascadia-code-5.2.3.tgz",
"integrity": "sha512-jnKfdZ+0UL25XUqYV7iq9gDlaUMWXxZNA7KnYzO4v8De6jBJmTkLHxDXs+1iZwl/8w7eKlDrTJWcyalOHdE2eg==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fontsource/fira-code": {
"version": "5.2.7",
"resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-5.2.7.tgz",
"integrity": "sha512-tnB9NNund9TwIym8/7DMJe573nlPEQb+fKUV5GL8TBYXjIhDvL0D7mgmNVNQUPhXp+R7RylQeiBdkA4EbOHPGQ==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fontsource/ibm-plex-mono": {
"version": "5.2.7",
"resolved": "https://registry.npmjs.org/@fontsource/ibm-plex-mono/-/ibm-plex-mono-5.2.7.tgz",
"integrity": "sha512-MKAb8qV+CaiMQn2B0dIi1OV3565NYzp3WN5b4oT6LTkk+F0jR6j0ZN+5BKJiIhffDC3rtBULsYZE65+0018z9w==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fontsource/inconsolata": {
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/@fontsource/inconsolata/-/inconsolata-5.2.8.tgz",
"integrity": "sha512-lIZW+WOZYpUH91g9r6rYYhfTmptF3YPPM54ZOs8IYVeeL4SeiAu4tfj7mdr8llYEq31DLYgi6JtGIJa192gB0Q==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fontsource/inter": {
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-5.2.8.tgz",
"integrity": "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fontsource/iosevka": {
"version": "5.2.5",
"resolved": "https://registry.npmjs.org/@fontsource/iosevka/-/iosevka-5.2.5.tgz",
"integrity": "sha512-Zv/UHJodDug1LcnWv2u2+GPp3oWP3U6Xp16cJOsqqZQNsCu8sA/ttT331N0NypxBZ+7c8szlSRlYDcy9liZ8pw==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fontsource/jetbrains-mono": {
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.2.8.tgz",
"integrity": "sha512-6w8/SG4kqvIMu7xd7wt6x3idn1Qux3p9N62s6G3rfldOUYHpWcc2FKrqf+Vo44jRvqWj2oAtTHrZXEP23oSKwQ==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fontsource/lato": {
"version": "5.2.7",
"resolved": "https://registry.npmjs.org/@fontsource/lato/-/lato-5.2.7.tgz",
"integrity": "sha512-k5mum1ADbDW5cTw1Ett1eQVWeoZ6gq0ct6SFBibEhB4LRxhniChJZTBgd6ph5yBxLkN1fcnsnmicBNA4S/3nbw==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fontsource/montserrat": {
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/@fontsource/montserrat/-/montserrat-5.2.8.tgz",
"integrity": "sha512-xTjLxSbSfCycDB0pwmNsfNvdfWPaDaRQ2LC6yt/ZI7SdvXG52zHnzNYC/09mzuAuWNJyShkteutfCoDgym56hQ==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fontsource/open-sans": {
"version": "5.2.7",
"resolved": "https://registry.npmjs.org/@fontsource/open-sans/-/open-sans-5.2.7.tgz",
"integrity": "sha512-8yfgDYjE5O0vmTPdrcjV35y4yMnctsokmi9gN49Gcsr0sjzkMkR97AnKDe6OqZh2SFkYlR28fxOvi21bYEgMSw==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fontsource/playfair-display": {
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/@fontsource/playfair-display/-/playfair-display-5.2.8.tgz",
"integrity": "sha512-fUEhib70SszNhQVsGbUMSsWJhr7Je0rdeuZdtGpDNu0GKF1xJM8QhpI/y0pckU25GcChXm9TLOmeZupkvvZo2g==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fontsource/poppins": {
"version": "5.2.7",
"resolved": "https://registry.npmjs.org/@fontsource/poppins/-/poppins-5.2.7.tgz",
"integrity": "sha512-6uQyPmseo4FgI97WIhA4yWRlNaoLk4vSDK/PyRwdqqZb5zAEuc+Kunt8JTMcsHYUEGYBtN15SNkMajMdqUSUmg==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fontsource/raleway": {
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/@fontsource/raleway/-/raleway-5.2.8.tgz",
"integrity": "sha512-OATNM+J4XniKQRlI1e67v3euOnCUiXwpqgMofomKc5JNYOq0JPE6E5uTXMnFaZejKN1Z7s05JrHpmYB7/tg1Fw==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fontsource/roboto": {
"version": "5.2.9",
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.2.9.tgz",
"integrity": "sha512-ZTkyHiPk74B/aj8BZWbsxD5Yu+Lq+nR64eV4wirlrac2qXR7jYk2h6JlLYuOuoruTkGQWNw2fMuKNavw7/rg0w==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fontsource/source-code-pro": {
"version": "5.2.7",
"resolved": "https://registry.npmjs.org/@fontsource/source-code-pro/-/source-code-pro-5.2.7.tgz",
"integrity": "sha512-7papq9TH94KT+S5VSY8cU7tFmwuGkIe3qxXRMscuAXH6AjMU+KJI75f28FzgBVDrlMfA0jjlTV4/x5+H5o/5EQ==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fontsource/source-sans-3": {
"version": "5.2.9",
"resolved": "https://registry.npmjs.org/@fontsource/source-sans-3/-/source-sans-3-5.2.9.tgz",
"integrity": "sha512-u3ymIq4rfmCCyB9MEw/sFR5lPVJ1yTNXmIMbUz+9kVCFIHvNtfzXOEBuvkg3Tk0zhmioPeJ28ZK5smZ7TurezQ==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fontsource/work-sans": {
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/@fontsource/work-sans/-/work-sans-5.2.8.tgz",
"integrity": "sha512-6LaHjVVgts+rnrcqvEkP2+iUB/jw1oDSYsGO0+TltAhnWki9Hnf/UGpgMQh2jcm0GEH8VqCPnq4PpmHLFzxXtQ==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@gar/promisify": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",

View File

@@ -62,6 +62,13 @@
]
},
"dependencies": {
"@fontsource/cascadia-code": "^5.2.3",
"@fontsource/iosevka": "^5.2.5",
"@fontsource/lato": "^5.2.7",
"@fontsource/montserrat": "^5.2.8",
"@fontsource/playfair-display": "^5.2.8",
"@fontsource/raleway": "^5.2.8",
"@fontsource/source-sans-3": "^5.2.9",
"cross-spawn": "7.0.6",
"rehype-sanitize": "6.0.0",
"tree-kill": "1.2.2"