mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 09:13:08 +00:00
Merge pull request #535 from stefandevo/v0.12.0rc
feat: add font customization and 8 new themes
This commit is contained in:
@@ -48,6 +48,22 @@
|
|||||||
"@dnd-kit/core": "6.3.1",
|
"@dnd-kit/core": "6.3.1",
|
||||||
"@dnd-kit/sortable": "10.0.0",
|
"@dnd-kit/sortable": "10.0.0",
|
||||||
"@dnd-kit/utilities": "3.2.2",
|
"@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",
|
"@lezer/highlight": "1.2.3",
|
||||||
"@radix-ui/react-checkbox": "1.3.3",
|
"@radix-ui/react-checkbox": "1.3.3",
|
||||||
"@radix-ui/react-collapsible": "1.1.12",
|
"@radix-ui/react-collapsible": "1.1.12",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useCursorStatusInit } from './hooks/use-cursor-status-init';
|
|||||||
import { useProviderAuthInit } from './hooks/use-provider-auth-init';
|
import { useProviderAuthInit } from './hooks/use-provider-auth-init';
|
||||||
import './styles/global.css';
|
import './styles/global.css';
|
||||||
import './styles/theme-imports';
|
import './styles/theme-imports';
|
||||||
|
import './styles/font-imports';
|
||||||
|
|
||||||
const logger = createLogger('App');
|
const logger = createLogger('App');
|
||||||
|
|
||||||
|
|||||||
67
apps/ui/src/assets/fonts/zed/zed-fonts.css
Normal file
67
apps/ui/src/assets/fonts/zed/zed-fonts.css
Normal 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');
|
||||||
|
}
|
||||||
BIN
apps/ui/src/assets/fonts/zed/zed-mono-extended.ttf
Normal file
BIN
apps/ui/src/assets/fonts/zed/zed-mono-extended.ttf
Normal file
Binary file not shown.
BIN
apps/ui/src/assets/fonts/zed/zed-mono-extendedbold.ttf
Normal file
BIN
apps/ui/src/assets/fonts/zed/zed-mono-extendedbold.ttf
Normal file
Binary file not shown.
BIN
apps/ui/src/assets/fonts/zed/zed-mono-extendedbolditalic.ttf
Normal file
BIN
apps/ui/src/assets/fonts/zed/zed-mono-extendedbolditalic.ttf
Normal file
Binary file not shown.
BIN
apps/ui/src/assets/fonts/zed/zed-mono-extendeditalic.ttf
Normal file
BIN
apps/ui/src/assets/fonts/zed/zed-mono-extendeditalic.ttf
Normal file
Binary file not shown.
BIN
apps/ui/src/assets/fonts/zed/zed-sans-extended.ttf
Normal file
BIN
apps/ui/src/assets/fonts/zed/zed-sans-extended.ttf
Normal file
Binary file not shown.
BIN
apps/ui/src/assets/fonts/zed/zed-sans-extendedbold.ttf
Normal file
BIN
apps/ui/src/assets/fonts/zed/zed-sans-extendedbold.ttf
Normal file
Binary file not shown.
BIN
apps/ui/src/assets/fonts/zed/zed-sans-extendedbolditalic.ttf
Normal file
BIN
apps/ui/src/assets/fonts/zed/zed-sans-extendedbolditalic.ttf
Normal file
Binary file not shown.
BIN
apps/ui/src/assets/fonts/zed/zed-sans-extendeditalic.ttf
Normal file
BIN
apps/ui/src/assets/fonts/zed/zed-sans-extendeditalic.ttf
Normal file
Binary file not shown.
@@ -1,8 +1,20 @@
|
|||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import { Palette, Moon, Sun } from 'lucide-react';
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
|
import { Palette, Moon, Sun, Type } from 'lucide-react';
|
||||||
import { darkThemes, lightThemes, type Theme } from '@/config/theme-options';
|
import { darkThemes, lightThemes, type Theme } from '@/config/theme-options';
|
||||||
|
import {
|
||||||
|
UI_SANS_FONT_OPTIONS,
|
||||||
|
UI_MONO_FONT_OPTIONS,
|
||||||
|
DEFAULT_FONT_VALUE,
|
||||||
|
} from '@/config/ui-font-options';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useAppStore } from '@/store/app-store';
|
import { useAppStore } from '@/store/app-store';
|
||||||
import type { Project } from '@/lib/electron';
|
import type { Project } from '@/lib/electron';
|
||||||
@@ -12,29 +24,125 @@ interface ProjectThemeSectionProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ProjectThemeSection({ project }: ProjectThemeSectionProps) {
|
export function ProjectThemeSection({ project }: ProjectThemeSectionProps) {
|
||||||
const { theme: globalTheme, setProjectTheme } = useAppStore();
|
const {
|
||||||
const [activeTab, setActiveTab] = useState<'dark' | 'light'>('dark');
|
theme: globalTheme,
|
||||||
|
fontFamilySans: globalFontSans,
|
||||||
|
fontFamilyMono: globalFontMono,
|
||||||
|
setProjectTheme,
|
||||||
|
setProjectFontSans,
|
||||||
|
setProjectFontMono,
|
||||||
|
} = useAppStore();
|
||||||
|
|
||||||
|
// Theme state
|
||||||
const projectTheme = project.theme as Theme | undefined;
|
const projectTheme = project.theme as Theme | undefined;
|
||||||
const hasCustomTheme = projectTheme !== undefined;
|
const hasCustomTheme = projectTheme !== undefined;
|
||||||
const effectiveTheme = projectTheme || globalTheme;
|
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): boolean =>
|
||||||
|
!!font && UI_SANS_FONT_OPTIONS.some((opt) => opt.value === font);
|
||||||
|
const isValidMonoFont = (font?: string): boolean =>
|
||||||
|
!!font && UI_MONO_FONT_OPTIONS.some((opt) => opt.value === font);
|
||||||
|
|
||||||
|
// Helper to get initial font value with validation
|
||||||
|
const getInitialFontValue = (font: string | undefined, validator: (f?: string) => boolean) =>
|
||||||
|
font && validator(font) ? font : DEFAULT_FONT_VALUE;
|
||||||
|
|
||||||
|
// 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>(() =>
|
||||||
|
getInitialFontValue(project.fontFamilySans, isValidSansFont)
|
||||||
|
);
|
||||||
|
const [fontMonoLocal, setFontMonoLocal] = useState<string>(() =>
|
||||||
|
getInitialFontValue(project.fontFamilyMono, isValidMonoFont)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sync state when project changes
|
||||||
|
useEffect(() => {
|
||||||
|
setFontSansLocal(getInitialFontValue(project.fontFamilySans, isValidSansFont));
|
||||||
|
setFontMonoLocal(getInitialFontValue(project.fontFamilyMono, isValidMonoFont));
|
||||||
|
// 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;
|
||||||
|
|
||||||
const themesToShow = activeTab === 'dark' ? darkThemes : lightThemes;
|
const themesToShow = activeTab === 'dark' ? darkThemes : lightThemes;
|
||||||
|
|
||||||
|
// Theme handlers
|
||||||
const handleThemeChange = (theme: Theme) => {
|
const handleThemeChange = (theme: Theme) => {
|
||||||
setProjectTheme(project.id, theme);
|
setProjectTheme(project.id, theme);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUseGlobalTheme = (checked: boolean) => {
|
const handleUseGlobalTheme = (checked: boolean) => {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
// Clear project theme to use global
|
|
||||||
setProjectTheme(project.id, null);
|
setProjectTheme(project.id, null);
|
||||||
} else {
|
} else {
|
||||||
// Set project theme to current global theme
|
|
||||||
setProjectTheme(project.id, globalTheme);
|
setProjectTheme(project.id, globalTheme);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Font handlers
|
||||||
|
const handleUseGlobalFontSans = (checked: boolean) => {
|
||||||
|
if (checked) {
|
||||||
|
// Clear project font to use global
|
||||||
|
setProjectFontSans(project.id, null);
|
||||||
|
setFontSansLocal(DEFAULT_FONT_VALUE);
|
||||||
|
} else {
|
||||||
|
// Set explicit project override - use 'default' value to indicate explicit default choice
|
||||||
|
const fontToSet = globalFontSans || DEFAULT_FONT_VALUE;
|
||||||
|
setFontSansLocal(fontToSet);
|
||||||
|
// Store the actual value (including 'default') so hasCustomFontSans stays true
|
||||||
|
setProjectFontSans(project.id, fontToSet);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUseGlobalFontMono = (checked: boolean) => {
|
||||||
|
if (checked) {
|
||||||
|
// Clear project font to use global
|
||||||
|
setProjectFontMono(project.id, null);
|
||||||
|
setFontMonoLocal(DEFAULT_FONT_VALUE);
|
||||||
|
} else {
|
||||||
|
// Set explicit project override - use 'default' value to indicate explicit default choice
|
||||||
|
const fontToSet = globalFontMono || DEFAULT_FONT_VALUE;
|
||||||
|
setFontMonoLocal(fontToSet);
|
||||||
|
// Store the actual value (including 'default') so hasCustomFontMono stays true
|
||||||
|
setProjectFontMono(project.id, fontToSet);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFontSansChange = (value: string) => {
|
||||||
|
setFontSansLocal(value);
|
||||||
|
// Store the actual value (including 'default') - only null clears to use global
|
||||||
|
setProjectFontSans(project.id, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFontMonoChange = (value: string) => {
|
||||||
|
setFontMonoLocal(value);
|
||||||
|
// Store the actual value (including 'default') - only null clears to use global
|
||||||
|
setProjectFontMono(project.id, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get display label for global font
|
||||||
|
const getGlobalFontSansLabel = () => {
|
||||||
|
if (!globalFontSans) return 'Default (Geist Sans)';
|
||||||
|
const option = UI_SANS_FONT_OPTIONS.find((o) => o.value === globalFontSans);
|
||||||
|
return option?.label || globalFontSans;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getGlobalFontMonoLabel = () => {
|
||||||
|
if (!globalFontMono) return 'Default (Geist Mono)';
|
||||||
|
const option = UI_MONO_FONT_OPTIONS.find((o) => o.value === globalFontMono);
|
||||||
|
return option?.label || globalFontMono;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -49,10 +157,10 @@ export function ProjectThemeSection({ project }: ProjectThemeSectionProps) {
|
|||||||
<div className="w-9 h-9 rounded-xl bg-gradient-to-br from-brand-500/20 to-brand-600/10 flex items-center justify-center border border-brand-500/20">
|
<div className="w-9 h-9 rounded-xl bg-gradient-to-br from-brand-500/20 to-brand-600/10 flex items-center justify-center border border-brand-500/20">
|
||||||
<Palette className="w-5 h-5 text-brand-500" />
|
<Palette className="w-5 h-5 text-brand-500" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-lg font-semibold text-foreground tracking-tight">Theme</h2>
|
<h2 className="text-lg font-semibold text-foreground tracking-tight">Theme & Fonts</h2>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground/80 ml-12">
|
<p className="text-sm text-muted-foreground/80 ml-12">
|
||||||
Customize the theme for this project.
|
Customize the appearance for this project.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-6 space-y-6">
|
<div className="p-6 space-y-6">
|
||||||
@@ -158,6 +266,122 @@ export function ProjectThemeSection({ project }: ProjectThemeSectionProps) {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Fonts Section */}
|
||||||
|
<div className="space-y-4 pt-6 border-t border-border/50">
|
||||||
|
<div className="flex items-center gap-2 mb-4">
|
||||||
|
<Type className="w-4 h-4 text-muted-foreground" />
|
||||||
|
<Label className="text-foreground font-medium">Fonts</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* UI Font */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="group flex items-start space-x-3 p-3 rounded-xl hover:bg-accent/30 transition-colors duration-200 -mx-3">
|
||||||
|
<Checkbox
|
||||||
|
id="use-global-font-sans"
|
||||||
|
checked={!hasCustomFontSans}
|
||||||
|
onCheckedChange={handleUseGlobalFontSans}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
<div className="flex-1 space-y-1.5">
|
||||||
|
<Label
|
||||||
|
htmlFor="use-global-font-sans"
|
||||||
|
className="text-foreground cursor-pointer font-medium"
|
||||||
|
>
|
||||||
|
Use Global UI Font
|
||||||
|
</Label>
|
||||||
|
{!hasCustomFontSans && (
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Currently using:{' '}
|
||||||
|
<span className="font-medium">{getGlobalFontSansLabel()}</span>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{hasCustomFontSans && (
|
||||||
|
<div className="ml-6 space-y-2">
|
||||||
|
<Label htmlFor="ui-font-select" className="text-sm">
|
||||||
|
Project UI Font
|
||||||
|
</Label>
|
||||||
|
<Select value={fontSansLocal} onValueChange={handleFontSansChange}>
|
||||||
|
<SelectTrigger id="ui-font-select" className="w-full">
|
||||||
|
<SelectValue placeholder="Default (Geist Sans)" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{UI_SANS_FONT_OPTIONS.map((option) => (
|
||||||
|
<SelectItem key={option.value} value={option.value}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontFamily:
|
||||||
|
option.value === DEFAULT_FONT_VALUE ? undefined : option.value,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Code Font */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="group flex items-start space-x-3 p-3 rounded-xl hover:bg-accent/30 transition-colors duration-200 -mx-3">
|
||||||
|
<Checkbox
|
||||||
|
id="use-global-font-mono"
|
||||||
|
checked={!hasCustomFontMono}
|
||||||
|
onCheckedChange={handleUseGlobalFontMono}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
<div className="flex-1 space-y-1.5">
|
||||||
|
<Label
|
||||||
|
htmlFor="use-global-font-mono"
|
||||||
|
className="text-foreground cursor-pointer font-medium"
|
||||||
|
>
|
||||||
|
Use Global Code Font
|
||||||
|
</Label>
|
||||||
|
{!hasCustomFontMono && (
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Currently using:{' '}
|
||||||
|
<span className="font-medium">{getGlobalFontMonoLabel()}</span>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{hasCustomFontMono && (
|
||||||
|
<div className="ml-6 space-y-2">
|
||||||
|
<Label htmlFor="code-font-select" className="text-sm">
|
||||||
|
Project Code Font
|
||||||
|
</Label>
|
||||||
|
<Select value={fontMonoLocal} onValueChange={handleFontMonoChange}>
|
||||||
|
<SelectTrigger id="code-font-select" className="w-full">
|
||||||
|
<SelectValue placeholder="Default (Geist Mono)" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{UI_MONO_FONT_OPTIONS.map((option) => (
|
||||||
|
<SelectItem key={option.value} value={option.value}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontFamily:
|
||||||
|
option.value === DEFAULT_FONT_VALUE ? undefined : option.value,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,21 @@
|
|||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Palette, Moon, Sun } from 'lucide-react';
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
|
import { Palette, Moon, Sun, Type } from 'lucide-react';
|
||||||
import { darkThemes, lightThemes } from '@/config/theme-options';
|
import { darkThemes, lightThemes } from '@/config/theme-options';
|
||||||
|
import {
|
||||||
|
UI_SANS_FONT_OPTIONS,
|
||||||
|
UI_MONO_FONT_OPTIONS,
|
||||||
|
DEFAULT_FONT_VALUE,
|
||||||
|
} from '@/config/ui-font-options';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { useAppStore } from '@/store/app-store';
|
||||||
import type { Theme } from '../shared/types';
|
import type { Theme } from '../shared/types';
|
||||||
|
|
||||||
interface AppearanceSectionProps {
|
interface AppearanceSectionProps {
|
||||||
@@ -11,10 +24,43 @@ interface AppearanceSectionProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function AppearanceSection({ effectiveTheme, onThemeChange }: 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;
|
const themesToShow = activeTab === 'dark' ? darkThemes : lightThemes;
|
||||||
|
|
||||||
|
// Convert null to 'default' for Select component
|
||||||
|
// 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFontMonoChange = (value: string) => {
|
||||||
|
setFontMono(value === DEFAULT_FONT_VALUE ? null : value);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -102,6 +148,77 @@ export function AppearanceSection({ effectiveTheme, onThemeChange }: AppearanceS
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Fonts Section */}
|
||||||
|
<div className="space-y-4 pt-6 border-t border-border/50">
|
||||||
|
<div className="flex items-center gap-2 mb-4">
|
||||||
|
<Type className="w-4 h-4 text-muted-foreground" />
|
||||||
|
<Label className="text-foreground font-medium">Fonts</Label>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground -mt-2 mb-4">
|
||||||
|
Set default fonts for all projects. Individual projects can override these settings.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
{/* UI Font Selector */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="global-ui-font-select" className="text-sm">
|
||||||
|
UI Font
|
||||||
|
</Label>
|
||||||
|
<Select value={fontSansValue} onValueChange={handleFontSansChange}>
|
||||||
|
<SelectTrigger id="global-ui-font-select" className="w-full">
|
||||||
|
<SelectValue placeholder="Default (Geist Sans)" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{UI_SANS_FONT_OPTIONS.map((option) => (
|
||||||
|
<SelectItem key={option.value} value={option.value}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontFamily:
|
||||||
|
option.value === DEFAULT_FONT_VALUE ? undefined : option.value,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Used for headings, labels, and UI text
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Code Font Selector */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="global-code-font-select" className="text-sm">
|
||||||
|
Code Font
|
||||||
|
</Label>
|
||||||
|
<Select value={fontMonoValue} onValueChange={handleFontMonoChange}>
|
||||||
|
<SelectTrigger id="global-code-font-select" className="w-full">
|
||||||
|
<SelectValue placeholder="Default (Geist Mono)" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{UI_MONO_FONT_OPTIONS.map((option) => (
|
||||||
|
<SelectItem key={option.value} value={option.value}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontFamily:
|
||||||
|
option.value === DEFAULT_FONT_VALUE ? undefined : option.value,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Used for code blocks and monospaced text
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ export interface Project {
|
|||||||
name: string;
|
name: string;
|
||||||
path: string;
|
path: string;
|
||||||
theme?: string;
|
theme?: string;
|
||||||
|
fontFamilySans?: string;
|
||||||
|
fontFamilyMono?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
customIconPath?: string;
|
customIconPath?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,19 @@ import { Label } from '@/components/ui/label';
|
|||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Slider } from '@/components/ui/slider';
|
import { Slider } from '@/components/ui/slider';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
import { SquareTerminal } from 'lucide-react';
|
import { SquareTerminal } from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useAppStore } from '@/store/app-store';
|
import { useAppStore } from '@/store/app-store';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { TERMINAL_FONT_OPTIONS } from '@/config/terminal-themes';
|
import { TERMINAL_FONT_OPTIONS } from '@/config/terminal-themes';
|
||||||
|
import { DEFAULT_FONT_VALUE } from '@/config/ui-font-options';
|
||||||
|
|
||||||
export function TerminalSection() {
|
export function TerminalSection() {
|
||||||
const {
|
const {
|
||||||
@@ -53,27 +61,32 @@ export function TerminalSection() {
|
|||||||
{/* Font Family */}
|
{/* Font Family */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<Label className="text-foreground font-medium">Font Family</Label>
|
<Label className="text-foreground font-medium">Font Family</Label>
|
||||||
<select
|
<Select
|
||||||
value={fontFamily}
|
value={fontFamily || DEFAULT_FONT_VALUE}
|
||||||
onChange={(e) => {
|
onValueChange={(value) => {
|
||||||
setTerminalFontFamily(e.target.value);
|
setTerminalFontFamily(value);
|
||||||
toast.info('Font family changed', {
|
toast.info('Font family changed', {
|
||||||
description: 'Restart terminal for changes to take effect',
|
description: 'Restart terminal for changes to take effect',
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
className={cn(
|
|
||||||
'w-full px-3 py-2 rounded-lg',
|
|
||||||
'bg-accent/30 border border-border/50',
|
|
||||||
'text-foreground text-sm',
|
|
||||||
'focus:outline-none focus:ring-2 focus:ring-green-500/30'
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
{TERMINAL_FONT_OPTIONS.map((font) => (
|
<SelectTrigger className="w-full">
|
||||||
<option key={font.value} value={font.value}>
|
<SelectValue placeholder="Default (Menlo / Monaco)" />
|
||||||
{font.label}
|
</SelectTrigger>
|
||||||
</option>
|
<SelectContent>
|
||||||
|
{TERMINAL_FONT_OPTIONS.map((option) => (
|
||||||
|
<SelectItem key={option.value} value={option.value}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontFamily: option.value === DEFAULT_FONT_VALUE ? undefined : option.value,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</select>
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Default Font Size */}
|
{/* Default Font Size */}
|
||||||
|
|||||||
@@ -25,8 +25,17 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Slider } from '@/components/ui/slider';
|
import { Slider } from '@/components/ui/slider';
|
||||||
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
import { TERMINAL_FONT_OPTIONS } from '@/config/terminal-themes';
|
import { TERMINAL_FONT_OPTIONS } from '@/config/terminal-themes';
|
||||||
|
import { DEFAULT_FONT_VALUE } from '@/config/ui-font-options';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||||
import { TerminalPanel } from './terminal-view/terminal-panel';
|
import { TerminalPanel } from './terminal-view/terminal-panel';
|
||||||
@@ -232,6 +241,8 @@ export function TerminalView() {
|
|||||||
setTerminalDefaultRunScript,
|
setTerminalDefaultRunScript,
|
||||||
setTerminalFontFamily,
|
setTerminalFontFamily,
|
||||||
setTerminalLineHeight,
|
setTerminalLineHeight,
|
||||||
|
setTerminalScrollbackLines,
|
||||||
|
setTerminalScreenReaderMode,
|
||||||
updateTerminalPanelSizes,
|
updateTerminalPanelSizes,
|
||||||
} = useAppStore();
|
} = useAppStore();
|
||||||
|
|
||||||
@@ -1457,9 +1468,9 @@ export function TerminalView() {
|
|||||||
<Settings className="h-4 w-4" />
|
<Settings className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-80" align="end">
|
<PopoverContent className="w-72" align="end">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-1">
|
||||||
<h4 className="font-medium text-sm">Terminal Settings</h4>
|
<h4 className="font-medium text-sm">Terminal Settings</h4>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Configure global terminal appearance
|
Configure global terminal appearance
|
||||||
@@ -1469,15 +1480,15 @@ export function TerminalView() {
|
|||||||
{/* Default Font Size */}
|
{/* Default Font Size */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Label className="text-sm">Default Font Size</Label>
|
<Label className="text-xs font-medium">Default Font Size</Label>
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
{terminalState.defaultFontSize}px
|
{terminalState.defaultFontSize}px
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Slider
|
<Slider
|
||||||
value={[terminalState.defaultFontSize]}
|
value={[terminalState.defaultFontSize]}
|
||||||
min={8}
|
min={8}
|
||||||
max={24}
|
max={32}
|
||||||
step={1}
|
step={1}
|
||||||
onValueChange={([value]) => setTerminalDefaultFontSize(value)}
|
onValueChange={([value]) => setTerminalDefaultFontSize(value)}
|
||||||
onValueCommit={() => {
|
onValueCommit={() => {
|
||||||
@@ -1488,37 +1499,79 @@ export function TerminalView() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Default Run Script */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label className="text-xs font-medium">Run on New Terminal</Label>
|
||||||
|
<Input
|
||||||
|
value={terminalState.defaultRunScript}
|
||||||
|
onChange={(e) => setTerminalDefaultRunScript(e.target.value)}
|
||||||
|
placeholder="e.g., claude"
|
||||||
|
className="h-7 text-xs"
|
||||||
|
/>
|
||||||
|
<p className="text-[10px] text-muted-foreground">
|
||||||
|
Command to run when creating a new terminal
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Font Family */}
|
{/* Font Family */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-sm">Font Family</Label>
|
<Label className="text-xs font-medium">Font Family</Label>
|
||||||
<select
|
<Select
|
||||||
value={terminalState.fontFamily}
|
value={terminalState.fontFamily || DEFAULT_FONT_VALUE}
|
||||||
onChange={(e) => {
|
onValueChange={(value) => {
|
||||||
setTerminalFontFamily(e.target.value);
|
setTerminalFontFamily(value);
|
||||||
toast.info('Font family changed', {
|
toast.info('Font family changed', {
|
||||||
description: 'Restart terminal for changes to take effect',
|
description: 'Restart terminal for changes to take effect',
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
className={cn(
|
|
||||||
'w-full px-2 py-1.5 rounded-md text-sm',
|
|
||||||
'bg-accent/50 border border-border',
|
|
||||||
'text-foreground',
|
|
||||||
'focus:outline-none focus:ring-2 focus:ring-ring'
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
{TERMINAL_FONT_OPTIONS.map((font) => (
|
<SelectTrigger className="w-full h-8 text-xs">
|
||||||
<option key={font.value} value={font.value}>
|
<SelectValue placeholder="Default (Menlo / Monaco)" />
|
||||||
{font.label}
|
</SelectTrigger>
|
||||||
</option>
|
<SelectContent>
|
||||||
|
{TERMINAL_FONT_OPTIONS.map((option) => (
|
||||||
|
<SelectItem key={option.value} value={option.value}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontFamily:
|
||||||
|
option.value === DEFAULT_FONT_VALUE ? undefined : option.value,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</select>
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Scrollback */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label className="text-xs font-medium">Scrollback</Label>
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{(terminalState.scrollbackLines / 1000).toFixed(0)}k lines
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Slider
|
||||||
|
value={[terminalState.scrollbackLines]}
|
||||||
|
min={1000}
|
||||||
|
max={100000}
|
||||||
|
step={1000}
|
||||||
|
onValueChange={([value]) => setTerminalScrollbackLines(value)}
|
||||||
|
onValueCommit={() => {
|
||||||
|
toast.info('Scrollback changed', {
|
||||||
|
description: 'Restart terminal for changes to take effect',
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Line Height */}
|
{/* Line Height */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Label className="text-sm">Line Height</Label>
|
<Label className="text-xs font-medium">Line Height</Label>
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
{terminalState.lineHeight.toFixed(1)}
|
{terminalState.lineHeight.toFixed(1)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -1536,18 +1589,21 @@ export function TerminalView() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Default Run Script */}
|
{/* Screen Reader */}
|
||||||
<div className="space-y-2">
|
<div className="flex items-center justify-between">
|
||||||
<Label className="text-sm">Default Run Script</Label>
|
<div className="space-y-0.5">
|
||||||
<Input
|
<Label className="text-xs font-medium">Screen Reader</Label>
|
||||||
value={terminalState.defaultRunScript}
|
<p className="text-[10px] text-muted-foreground">Enable accessibility mode</p>
|
||||||
onChange={(e) => setTerminalDefaultRunScript(e.target.value)}
|
</div>
|
||||||
placeholder="e.g., claude, npm run dev"
|
<Switch
|
||||||
className="h-8 text-sm"
|
checked={terminalState.screenReaderMode}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
setTerminalScreenReaderMode(checked);
|
||||||
|
toast.info(checked ? 'Screen reader enabled' : 'Screen reader disabled', {
|
||||||
|
description: 'Restart terminal for changes to take effect',
|
||||||
|
});
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
Command to run when opening new terminals
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
|
|||||||
@@ -30,6 +30,13 @@ import { Slider } from '@/components/ui/slider';
|
|||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
import { useDraggable, useDroppable } from '@dnd-kit/core';
|
import { useDraggable, useDroppable } from '@dnd-kit/core';
|
||||||
import { useAppStore, DEFAULT_KEYBOARD_SHORTCUTS, type KeyboardShortcuts } from '@/store/app-store';
|
import { useAppStore, DEFAULT_KEYBOARD_SHORTCUTS, type KeyboardShortcuts } from '@/store/app-store';
|
||||||
import { useShallow } from 'zustand/react/shallow';
|
import { useShallow } from 'zustand/react/shallow';
|
||||||
@@ -38,7 +45,9 @@ import {
|
|||||||
getTerminalTheme,
|
getTerminalTheme,
|
||||||
TERMINAL_FONT_OPTIONS,
|
TERMINAL_FONT_OPTIONS,
|
||||||
DEFAULT_TERMINAL_FONT,
|
DEFAULT_TERMINAL_FONT,
|
||||||
|
getTerminalFontFamily,
|
||||||
} from '@/config/terminal-themes';
|
} from '@/config/terminal-themes';
|
||||||
|
import { DEFAULT_FONT_VALUE } from '@/config/ui-font-options';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { getElectronAPI } from '@/lib/electron';
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
import { getApiKey, getSessionToken, getServerUrlSync } from '@/lib/http-api-client';
|
import { getApiKey, getSessionToken, getServerUrlSync } from '@/lib/http-api-client';
|
||||||
@@ -567,7 +576,7 @@ export function TerminalPanel({
|
|||||||
// Get settings from store (read at initialization time)
|
// Get settings from store (read at initialization time)
|
||||||
const terminalSettings = useAppStore.getState().terminalState;
|
const terminalSettings = useAppStore.getState().terminalState;
|
||||||
const screenReaderEnabled = terminalSettings.screenReaderMode;
|
const screenReaderEnabled = terminalSettings.screenReaderMode;
|
||||||
const terminalFontFamily = terminalSettings.fontFamily || DEFAULT_TERMINAL_FONT;
|
const terminalFontFamily = getTerminalFontFamily(terminalSettings.fontFamily);
|
||||||
const terminalScrollback = terminalSettings.scrollbackLines || 5000;
|
const terminalScrollback = terminalSettings.scrollbackLines || 5000;
|
||||||
const terminalLineHeight = terminalSettings.lineHeight || 1.0;
|
const terminalLineHeight = terminalSettings.lineHeight || 1.0;
|
||||||
|
|
||||||
@@ -1269,7 +1278,7 @@ export function TerminalPanel({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (xtermRef.current && isTerminalReady) {
|
if (xtermRef.current && isTerminalReady) {
|
||||||
xtermRef.current.options.fontFamily = fontFamily;
|
xtermRef.current.options.fontFamily = getTerminalFontFamily(fontFamily);
|
||||||
fitAddonRef.current?.fit();
|
fitAddonRef.current?.fit();
|
||||||
}
|
}
|
||||||
}, [fontFamily, isTerminalReady]);
|
}, [fontFamily, isTerminalReady]);
|
||||||
@@ -1902,22 +1911,33 @@ export function TerminalPanel({
|
|||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-xs font-medium">Font Family</Label>
|
<Label className="text-xs font-medium">Font Family</Label>
|
||||||
<select
|
<Select
|
||||||
value={fontFamily}
|
value={fontFamily || DEFAULT_FONT_VALUE}
|
||||||
onChange={(e) => {
|
onValueChange={(value) => {
|
||||||
setTerminalFontFamily(e.target.value);
|
setTerminalFontFamily(value);
|
||||||
toast.info('Font family changed', {
|
toast.info('Font family changed', {
|
||||||
description: 'Restart terminal for changes to take effect',
|
description: 'Restart terminal for changes to take effect',
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
className="w-full h-7 text-xs bg-background border border-input rounded-md px-2"
|
|
||||||
>
|
>
|
||||||
{TERMINAL_FONT_OPTIONS.map((font) => (
|
<SelectTrigger className="w-full h-8 text-xs">
|
||||||
<option key={font.value} value={font.value}>
|
<SelectValue placeholder="Default (Menlo / Monaco)" />
|
||||||
{font.label}
|
</SelectTrigger>
|
||||||
</option>
|
<SelectContent>
|
||||||
|
{TERMINAL_FONT_OPTIONS.map((option) => (
|
||||||
|
<SelectItem key={option.value} value={option.value}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontFamily:
|
||||||
|
option.value === DEFAULT_FONT_VALUE ? undefined : option.value,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</select>
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|||||||
@@ -4,6 +4,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ThemeMode } from '@/store/app-store';
|
import type { ThemeMode } from '@/store/app-store';
|
||||||
|
import {
|
||||||
|
UI_MONO_FONT_OPTIONS,
|
||||||
|
DEFAULT_FONT_VALUE,
|
||||||
|
type UIFontOption,
|
||||||
|
} from '@/config/ui-font-options';
|
||||||
|
|
||||||
export interface TerminalTheme {
|
export interface TerminalTheme {
|
||||||
background: string;
|
background: string;
|
||||||
@@ -37,27 +42,44 @@ export interface TerminalTheme {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Terminal font options for user selection
|
* Terminal font options for user selection
|
||||||
* These are monospace fonts commonly available on different platforms
|
*
|
||||||
|
* Uses the same fonts as UI_MONO_FONT_OPTIONS for consistency across the app.
|
||||||
|
* All fonts listed here are bundled with the app via @fontsource packages
|
||||||
|
* or are system fonts with appropriate fallbacks.
|
||||||
*/
|
*/
|
||||||
export interface TerminalFontOption {
|
|
||||||
value: string;
|
|
||||||
label: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TERMINAL_FONT_OPTIONS: TerminalFontOption[] = [
|
// Re-export for backwards compatibility
|
||||||
{ value: "Menlo, Monaco, 'Courier New', monospace", label: 'Menlo / Monaco' },
|
export type TerminalFontOption = UIFontOption;
|
||||||
{ value: "'SF Mono', Menlo, Monaco, monospace", label: 'SF Mono' },
|
|
||||||
{ value: "'JetBrains Mono', monospace", label: 'JetBrains Mono' },
|
|
||||||
{ value: "'Fira Code', monospace", label: 'Fira Code' },
|
|
||||||
{ value: "'Source Code Pro', monospace", label: 'Source Code Pro' },
|
|
||||||
{ value: "Consolas, 'Courier New', monospace", label: 'Consolas' },
|
|
||||||
{ value: "'Ubuntu Mono', monospace", label: 'Ubuntu Mono' },
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default terminal font family (first option)
|
* Terminal font options - reuses UI_MONO_FONT_OPTIONS with terminal-specific default
|
||||||
|
*
|
||||||
|
* The 'default' value means "use the default terminal font" (Menlo/Monaco)
|
||||||
*/
|
*/
|
||||||
export const DEFAULT_TERMINAL_FONT = TERMINAL_FONT_OPTIONS[0].value;
|
export const TERMINAL_FONT_OPTIONS: readonly UIFontOption[] = UI_MONO_FONT_OPTIONS.map((option) => {
|
||||||
|
// Replace the UI default label with terminal-specific default
|
||||||
|
if (option.value === DEFAULT_FONT_VALUE) {
|
||||||
|
return { value: option.value, label: 'Default (Menlo / Monaco)' };
|
||||||
|
}
|
||||||
|
return option;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default terminal font family
|
||||||
|
* Uses the DEFAULT_FONT_VALUE sentinel which maps to Menlo/Monaco
|
||||||
|
*/
|
||||||
|
export const DEFAULT_TERMINAL_FONT = DEFAULT_FONT_VALUE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the actual font family CSS value for terminal
|
||||||
|
* Converts DEFAULT_FONT_VALUE to the actual Menlo/Monaco font stack
|
||||||
|
*/
|
||||||
|
export function getTerminalFontFamily(fontValue: string | undefined): string {
|
||||||
|
if (!fontValue || fontValue === DEFAULT_FONT_VALUE) {
|
||||||
|
return "Menlo, Monaco, 'Courier New', monospace";
|
||||||
|
}
|
||||||
|
return fontValue;
|
||||||
|
}
|
||||||
|
|
||||||
// Dark theme (default)
|
// Dark theme (default)
|
||||||
const darkTheme: TerminalTheme = {
|
const darkTheme: TerminalTheme = {
|
||||||
@@ -542,9 +564,10 @@ const grayTheme: TerminalTheme = {
|
|||||||
|
|
||||||
// Theme mapping
|
// Theme mapping
|
||||||
const terminalThemes: Record<ThemeMode, TerminalTheme> = {
|
const terminalThemes: Record<ThemeMode, TerminalTheme> = {
|
||||||
light: lightTheme,
|
// Special
|
||||||
dark: darkTheme,
|
|
||||||
system: darkTheme, // Will be resolved at runtime
|
system: darkTheme, // Will be resolved at runtime
|
||||||
|
// Dark themes
|
||||||
|
dark: darkTheme,
|
||||||
retro: retroTheme,
|
retro: retroTheme,
|
||||||
dracula: draculaTheme,
|
dracula: draculaTheme,
|
||||||
nord: nordTheme,
|
nord: nordTheme,
|
||||||
@@ -556,9 +579,35 @@ const terminalThemes: Record<ThemeMode, TerminalTheme> = {
|
|||||||
onedark: onedarkTheme,
|
onedark: onedarkTheme,
|
||||||
synthwave: synthwaveTheme,
|
synthwave: synthwaveTheme,
|
||||||
red: redTheme,
|
red: redTheme,
|
||||||
cream: creamTheme,
|
|
||||||
sunset: sunsetTheme,
|
sunset: sunsetTheme,
|
||||||
gray: grayTheme,
|
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
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -47,7 +47,11 @@ export type Theme =
|
|||||||
| 'gray'
|
| 'gray'
|
||||||
| 'forest'
|
| 'forest'
|
||||||
| 'ocean'
|
| 'ocean'
|
||||||
// Light themes (16)
|
| 'ember'
|
||||||
|
| 'ayu-dark'
|
||||||
|
| 'ayu-mirage'
|
||||||
|
| 'matcha'
|
||||||
|
// Light themes
|
||||||
| 'light'
|
| 'light'
|
||||||
| 'cream'
|
| 'cream'
|
||||||
| 'solarizedlight'
|
| 'solarizedlight'
|
||||||
@@ -63,7 +67,11 @@ export type Theme =
|
|||||||
| 'sepia'
|
| 'sepia'
|
||||||
| 'gruvboxlight'
|
| 'gruvboxlight'
|
||||||
| 'nordlight'
|
| 'nordlight'
|
||||||
| 'blossom';
|
| 'blossom'
|
||||||
|
| 'ayu-light'
|
||||||
|
| 'onelight'
|
||||||
|
| 'bluloco'
|
||||||
|
| 'feather';
|
||||||
|
|
||||||
export interface ThemeOption {
|
export interface ThemeOption {
|
||||||
value: Theme;
|
value: Theme;
|
||||||
@@ -74,9 +82,9 @@ export interface ThemeOption {
|
|||||||
color: string; // Primary/brand color for icon display
|
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> = [
|
export const themeOptions: ReadonlyArray<ThemeOption> = [
|
||||||
// Dark themes (16)
|
// Dark themes (20) - alphabetical, Dark first
|
||||||
{
|
{
|
||||||
value: 'dark',
|
value: 'dark',
|
||||||
label: 'Dark',
|
label: 'Dark',
|
||||||
@@ -86,60 +94,20 @@ export const themeOptions: ReadonlyArray<ThemeOption> = [
|
|||||||
color: '#3b82f6',
|
color: '#3b82f6',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'retro',
|
value: 'ayu-dark',
|
||||||
label: 'Retro',
|
label: 'Ayu Dark',
|
||||||
Icon: Terminal,
|
Icon: Moon,
|
||||||
testId: 'retro-mode-button',
|
testId: 'ayu-dark-mode-button',
|
||||||
isDark: true,
|
isDark: true,
|
||||||
color: '#22c55e',
|
color: '#E6B450',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'dracula',
|
value: 'ayu-mirage',
|
||||||
label: 'Dracula',
|
label: 'Ayu Mirage',
|
||||||
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',
|
|
||||||
Icon: Sparkles,
|
Icon: Sparkles,
|
||||||
testId: 'tokyonight-mode-button',
|
testId: 'ayu-mirage-mode-button',
|
||||||
isDark: true,
|
isDark: true,
|
||||||
color: '#bb9af7',
|
color: '#FFCC66',
|
||||||
},
|
|
||||||
{
|
|
||||||
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',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'catppuccin',
|
value: 'catppuccin',
|
||||||
@@ -150,44 +118,20 @@ export const themeOptions: ReadonlyArray<ThemeOption> = [
|
|||||||
color: '#cba6f7',
|
color: '#cba6f7',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'onedark',
|
value: 'dracula',
|
||||||
label: 'One Dark',
|
label: 'Dracula',
|
||||||
Icon: Atom,
|
Icon: Ghost,
|
||||||
testId: 'onedark-mode-button',
|
testId: 'dracula-mode-button',
|
||||||
isDark: true,
|
isDark: true,
|
||||||
color: '#61afef',
|
color: '#bd93f9',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'synthwave',
|
value: 'ember',
|
||||||
label: 'Synthwave',
|
label: 'Ember',
|
||||||
Icon: Radio,
|
Icon: Sunrise,
|
||||||
testId: 'synthwave-mode-button',
|
testId: 'ember-mode-button',
|
||||||
isDark: true,
|
isDark: true,
|
||||||
color: '#ff7edb',
|
color: '#fd971f',
|
||||||
},
|
|
||||||
{
|
|
||||||
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',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'forest',
|
value: 'forest',
|
||||||
@@ -197,6 +141,46 @@ export const themeOptions: ReadonlyArray<ThemeOption> = [
|
|||||||
isDark: true,
|
isDark: true,
|
||||||
color: '#22c55e',
|
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',
|
value: 'ocean',
|
||||||
label: 'Ocean',
|
label: 'Ocean',
|
||||||
@@ -205,7 +189,63 @@ export const themeOptions: ReadonlyArray<ThemeOption> = [
|
|||||||
isDark: true,
|
isDark: true,
|
||||||
color: '#06b6d4',
|
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',
|
value: 'light',
|
||||||
label: 'Light',
|
label: 'Light',
|
||||||
@@ -214,6 +254,30 @@ export const themeOptions: ReadonlyArray<ThemeOption> = [
|
|||||||
isDark: false,
|
isDark: false,
|
||||||
color: '#3b82f6',
|
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',
|
value: 'cream',
|
||||||
label: 'Cream',
|
label: 'Cream',
|
||||||
@@ -223,12 +287,12 @@ export const themeOptions: ReadonlyArray<ThemeOption> = [
|
|||||||
color: '#b45309',
|
color: '#b45309',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'solarizedlight',
|
value: 'feather',
|
||||||
label: 'Solarized Light',
|
label: 'Feather',
|
||||||
Icon: Sunrise,
|
Icon: Feather,
|
||||||
testId: 'solarizedlight-mode-button',
|
testId: 'feather-mode-button',
|
||||||
isDark: false,
|
isDark: false,
|
||||||
color: '#268bd2',
|
color: '#FF7B2E',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'github',
|
value: 'github',
|
||||||
@@ -239,28 +303,12 @@ export const themeOptions: ReadonlyArray<ThemeOption> = [
|
|||||||
color: '#0969da',
|
color: '#0969da',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'paper',
|
value: 'gruvboxlight',
|
||||||
label: 'Paper',
|
label: 'Gruvbox Light',
|
||||||
Icon: Scroll,
|
Icon: Trees,
|
||||||
testId: 'paper-mode-button',
|
testId: 'gruvboxlight-mode-button',
|
||||||
isDark: false,
|
isDark: false,
|
||||||
color: '#374151',
|
color: '#d65d0e',
|
||||||
},
|
|
||||||
{
|
|
||||||
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',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'lavender',
|
value: 'lavender',
|
||||||
@@ -271,52 +319,12 @@ export const themeOptions: ReadonlyArray<ThemeOption> = [
|
|||||||
color: '#8b5cf6',
|
color: '#8b5cf6',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'sand',
|
value: 'mint',
|
||||||
label: 'Sand',
|
label: 'Mint',
|
||||||
Icon: Palmtree,
|
Icon: Wind,
|
||||||
testId: 'sand-mode-button',
|
testId: 'mint-mode-button',
|
||||||
isDark: false,
|
isDark: false,
|
||||||
color: '#d97706',
|
color: '#0d9488',
|
||||||
},
|
|
||||||
{
|
|
||||||
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',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'nordlight',
|
value: 'nordlight',
|
||||||
@@ -327,12 +335,76 @@ export const themeOptions: ReadonlyArray<ThemeOption> = [
|
|||||||
color: '#5e81ac',
|
color: '#5e81ac',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'blossom',
|
value: 'onelight',
|
||||||
label: 'Blossom',
|
label: 'One Light',
|
||||||
Icon: Cherry,
|
Icon: Atom,
|
||||||
testId: 'blossom-mode-button',
|
testId: 'onelight-mode-button',
|
||||||
isDark: false,
|
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',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
77
apps/ui/src/config/ui-font-options.ts
Normal file
77
apps/ui/src/config/ui-font-options.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* Font options for per-project font customization
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
export const DEFAULT_FONT_VALUE = 'default';
|
||||||
|
|
||||||
|
export interface UIFontOption {
|
||||||
|
value: string; // CSS font-family value ('default' means "use default")
|
||||||
|
label: string; // Display label for the dropdown
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)' },
|
||||||
|
// 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: "'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 (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)' },
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the display label for a font value
|
||||||
|
*/
|
||||||
|
export function getFontLabel(
|
||||||
|
fontValue: string | undefined,
|
||||||
|
options: readonly UIFontOption[]
|
||||||
|
): string {
|
||||||
|
if (!fontValue || fontValue === DEFAULT_FONT_VALUE) return options[0].label;
|
||||||
|
const option = options.find((o) => o.value === fontValue);
|
||||||
|
return option?.label ?? fontValue;
|
||||||
|
}
|
||||||
@@ -533,6 +533,8 @@ export function hydrateStoreFromSettings(settings: GlobalSettings): void {
|
|||||||
path: ref.path,
|
path: ref.path,
|
||||||
lastOpened: ref.lastOpened,
|
lastOpened: ref.lastOpened,
|
||||||
theme: ref.theme,
|
theme: ref.theme,
|
||||||
|
fontFamilySans: ref.fontFamilySans,
|
||||||
|
fontFamilyMono: ref.fontFamilyMono,
|
||||||
isFavorite: ref.isFavorite,
|
isFavorite: ref.isFavorite,
|
||||||
icon: ref.icon,
|
icon: ref.icon,
|
||||||
customIconPath: ref.customIconPath,
|
customIconPath: ref.customIconPath,
|
||||||
@@ -556,6 +558,8 @@ export function hydrateStoreFromSettings(settings: GlobalSettings): void {
|
|||||||
|
|
||||||
useAppStore.setState({
|
useAppStore.setState({
|
||||||
theme: settings.theme as unknown as import('@/store/app-store').ThemeMode,
|
theme: settings.theme as unknown as import('@/store/app-store').ThemeMode,
|
||||||
|
fontFamilySans: settings.fontFamilySans ?? null,
|
||||||
|
fontFamilyMono: settings.fontFamilyMono ?? null,
|
||||||
sidebarOpen: settings.sidebarOpen ?? true,
|
sidebarOpen: settings.sidebarOpen ?? true,
|
||||||
chatHistoryOpen: settings.chatHistoryOpen ?? false,
|
chatHistoryOpen: settings.chatHistoryOpen ?? false,
|
||||||
maxConcurrency: settings.maxConcurrency ?? 3,
|
maxConcurrency: settings.maxConcurrency ?? 3,
|
||||||
@@ -596,6 +600,13 @@ export function hydrateStoreFromSettings(settings: GlobalSettings): void {
|
|||||||
worktreePanelCollapsed: settings.worktreePanelCollapsed ?? false,
|
worktreePanelCollapsed: settings.worktreePanelCollapsed ?? false,
|
||||||
lastProjectDir: settings.lastProjectDir ?? '',
|
lastProjectDir: settings.lastProjectDir ?? '',
|
||||||
recentFolders: settings.recentFolders ?? [],
|
recentFolders: settings.recentFolders ?? [],
|
||||||
|
// Terminal font (nested in terminalState)
|
||||||
|
...(settings.terminalFontFamily && {
|
||||||
|
terminalState: {
|
||||||
|
...current.terminalState,
|
||||||
|
fontFamily: settings.terminalFontFamily,
|
||||||
|
},
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hydrate setup wizard state from global settings (API-backed)
|
// Hydrate setup wizard state from global settings (API-backed)
|
||||||
@@ -649,6 +660,7 @@ function buildSettingsUpdateFromStore(): Record<string, unknown> {
|
|||||||
worktreePanelCollapsed: state.worktreePanelCollapsed,
|
worktreePanelCollapsed: state.worktreePanelCollapsed,
|
||||||
lastProjectDir: state.lastProjectDir,
|
lastProjectDir: state.lastProjectDir,
|
||||||
recentFolders: state.recentFolders,
|
recentFolders: state.recentFolders,
|
||||||
|
terminalFontFamily: state.terminalState.fontFamily,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ const SYNC_DEBOUNCE_MS = 1000;
|
|||||||
// Fields to sync to server (subset of AppState that should be persisted)
|
// Fields to sync to server (subset of AppState that should be persisted)
|
||||||
const SETTINGS_FIELDS_TO_SYNC = [
|
const SETTINGS_FIELDS_TO_SYNC = [
|
||||||
'theme',
|
'theme',
|
||||||
|
'fontFamilySans',
|
||||||
|
'fontFamilyMono',
|
||||||
|
'terminalFontFamily', // Maps to terminalState.fontFamily
|
||||||
'sidebarOpen',
|
'sidebarOpen',
|
||||||
'chatHistoryOpen',
|
'chatHistoryOpen',
|
||||||
'maxConcurrency',
|
'maxConcurrency',
|
||||||
@@ -157,6 +160,9 @@ export function useSettingsSync(): SettingsSyncState {
|
|||||||
if (field === 'currentProjectId') {
|
if (field === 'currentProjectId') {
|
||||||
// Special handling: extract ID from currentProject object
|
// Special handling: extract ID from currentProject object
|
||||||
updates[field] = appState.currentProject?.id ?? null;
|
updates[field] = appState.currentProject?.id ?? null;
|
||||||
|
} else if (field === 'terminalFontFamily') {
|
||||||
|
// Special handling: map terminalState.fontFamily to terminalFontFamily
|
||||||
|
updates[field] = appState.terminalState.fontFamily;
|
||||||
} else {
|
} else {
|
||||||
updates[field] = appState[field as keyof typeof appState];
|
updates[field] = appState[field as keyof typeof appState];
|
||||||
}
|
}
|
||||||
@@ -258,6 +264,8 @@ export function useSettingsSync(): SettingsSyncState {
|
|||||||
for (const field of SETTINGS_FIELDS_TO_SYNC) {
|
for (const field of SETTINGS_FIELDS_TO_SYNC) {
|
||||||
if (field === 'currentProjectId') {
|
if (field === 'currentProjectId') {
|
||||||
updates[field] = appState.currentProject?.id ?? null;
|
updates[field] = appState.currentProject?.id ?? null;
|
||||||
|
} else if (field === 'terminalFontFamily') {
|
||||||
|
updates[field] = appState.terminalState.fontFamily;
|
||||||
} else {
|
} else {
|
||||||
updates[field] = appState[field as keyof typeof appState];
|
updates[field] = appState[field as keyof typeof appState];
|
||||||
}
|
}
|
||||||
@@ -320,6 +328,12 @@ export function useSettingsSync(): SettingsSyncState {
|
|||||||
changed = true;
|
changed = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
} else if (field === 'terminalFontFamily') {
|
||||||
|
// Special handling: compare terminalState.fontFamily
|
||||||
|
if (newState.terminalState.fontFamily !== prevState.terminalState.fontFamily) {
|
||||||
|
changed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const key = field as keyof typeof newState;
|
const key = field as keyof typeof newState;
|
||||||
if (newState[key] !== prevState[key]) {
|
if (newState[key] !== prevState[key]) {
|
||||||
@@ -401,6 +415,8 @@ export async function forceSyncSettingsToServer(): Promise<boolean> {
|
|||||||
for (const field of SETTINGS_FIELDS_TO_SYNC) {
|
for (const field of SETTINGS_FIELDS_TO_SYNC) {
|
||||||
if (field === 'currentProjectId') {
|
if (field === 'currentProjectId') {
|
||||||
updates[field] = appState.currentProject?.id ?? null;
|
updates[field] = appState.currentProject?.id ?? null;
|
||||||
|
} else if (field === 'terminalFontFamily') {
|
||||||
|
updates[field] = appState.terminalState.fontFamily;
|
||||||
} else {
|
} else {
|
||||||
updates[field] = appState[field as keyof typeof appState];
|
updates[field] = appState[field as keyof typeof appState];
|
||||||
}
|
}
|
||||||
@@ -503,6 +519,13 @@ export async function refreshSettingsFromServer(): Promise<boolean> {
|
|||||||
worktreePanelCollapsed: serverSettings.worktreePanelCollapsed ?? false,
|
worktreePanelCollapsed: serverSettings.worktreePanelCollapsed ?? false,
|
||||||
lastProjectDir: serverSettings.lastProjectDir ?? '',
|
lastProjectDir: serverSettings.lastProjectDir ?? '',
|
||||||
recentFolders: serverSettings.recentFolders ?? [],
|
recentFolders: serverSettings.recentFolders ?? [],
|
||||||
|
// Terminal font (nested in terminalState)
|
||||||
|
...(serverSettings.terminalFontFamily && {
|
||||||
|
terminalState: {
|
||||||
|
...currentAppState.terminalState,
|
||||||
|
fontFamily: serverSettings.terminalFontFamily,
|
||||||
|
},
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Also refresh setup wizard state
|
// Also refresh setup wizard state
|
||||||
|
|||||||
@@ -3292,6 +3292,8 @@ export interface Project {
|
|||||||
path: string;
|
path: string;
|
||||||
lastOpened?: string;
|
lastOpened?: string;
|
||||||
theme?: string; // Per-project theme override (uses ThemeMode from app-store)
|
theme?: string; // Per-project theme override (uses ThemeMode from app-store)
|
||||||
|
fontFamilySans?: string; // Per-project UI/sans font override
|
||||||
|
fontFamilyMono?: string; // Per-project code/mono font override
|
||||||
isFavorite?: boolean; // Pin project to top of dashboard
|
isFavorite?: boolean; // Pin project to top of dashboard
|
||||||
icon?: string; // Lucide icon name for project identification
|
icon?: string; // Lucide icon name for project identification
|
||||||
customIconPath?: string; // Path to custom uploaded icon image in .automaker/images/
|
customIconPath?: string; // Path to custom uploaded icon image in .automaker/images/
|
||||||
|
|||||||
@@ -158,6 +158,12 @@ function RootLayoutContent() {
|
|||||||
projectHistory,
|
projectHistory,
|
||||||
upsertAndSetCurrentProject,
|
upsertAndSetCurrentProject,
|
||||||
getEffectiveTheme,
|
getEffectiveTheme,
|
||||||
|
getEffectiveFontSans,
|
||||||
|
getEffectiveFontMono,
|
||||||
|
// Subscribe to theme and font state to trigger re-renders when they change
|
||||||
|
theme,
|
||||||
|
fontFamilySans,
|
||||||
|
fontFamilyMono,
|
||||||
skipSandboxWarning,
|
skipSandboxWarning,
|
||||||
setSkipSandboxWarning,
|
setSkipSandboxWarning,
|
||||||
fetchCodexModels,
|
fetchCodexModels,
|
||||||
@@ -248,6 +254,17 @@ function RootLayoutContent() {
|
|||||||
// Defer the theme value to keep UI responsive during rapid hover changes
|
// Defer the theme value to keep UI responsive during rapid hover changes
|
||||||
const deferredTheme = useDeferredValue(effectiveTheme);
|
const deferredTheme = useDeferredValue(effectiveTheme);
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsMounted(true);
|
setIsMounted(true);
|
||||||
}, []);
|
}, []);
|
||||||
@@ -727,6 +744,23 @@ function RootLayoutContent() {
|
|||||||
}
|
}
|
||||||
}, [deferredTheme]);
|
}, [deferredTheme]);
|
||||||
|
|
||||||
|
// Apply font CSS variables for project-specific font overrides
|
||||||
|
useEffect(() => {
|
||||||
|
const root = document.documentElement;
|
||||||
|
|
||||||
|
if (effectiveFontSans) {
|
||||||
|
root.style.setProperty('--font-sans', effectiveFontSans);
|
||||||
|
} else {
|
||||||
|
root.style.removeProperty('--font-sans');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effectiveFontMono) {
|
||||||
|
root.style.setProperty('--font-mono', effectiveFontMono);
|
||||||
|
} else {
|
||||||
|
root.style.removeProperty('--font-mono');
|
||||||
|
}
|
||||||
|
}, [effectiveFontSans, effectiveFontMono]);
|
||||||
|
|
||||||
// Show sandbox rejection screen if user denied the risk warning
|
// Show sandbox rejection screen if user denied the risk warning
|
||||||
if (sandboxStatus === 'denied') {
|
if (sandboxStatus === 'denied') {
|
||||||
return <SandboxRejectionScreen />;
|
return <SandboxRejectionScreen />;
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ import type { Project, TrashedProject } from '@/lib/electron';
|
|||||||
import { getElectronAPI } from '@/lib/electron';
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
import { createLogger } from '@automaker/utils/logger';
|
import { createLogger } from '@automaker/utils/logger';
|
||||||
import { setItem, getItem } from '@/lib/storage';
|
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 {
|
import type {
|
||||||
Feature as BaseFeature,
|
Feature as BaseFeature,
|
||||||
FeatureImagePath,
|
FeatureImagePath,
|
||||||
@@ -65,9 +70,10 @@ export type ViewMode =
|
|||||||
| 'ideation';
|
| 'ideation';
|
||||||
|
|
||||||
export type ThemeMode =
|
export type ThemeMode =
|
||||||
| 'light'
|
// Special modes
|
||||||
| 'dark'
|
|
||||||
| 'system'
|
| 'system'
|
||||||
|
// Dark themes
|
||||||
|
| 'dark'
|
||||||
| 'retro'
|
| 'retro'
|
||||||
| 'dracula'
|
| 'dracula'
|
||||||
| 'nord'
|
| 'nord'
|
||||||
@@ -79,12 +85,40 @@ export type ThemeMode =
|
|||||||
| 'onedark'
|
| 'onedark'
|
||||||
| 'synthwave'
|
| 'synthwave'
|
||||||
| 'red'
|
| 'red'
|
||||||
| 'cream'
|
|
||||||
| 'sunset'
|
| '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 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)
|
// Maximum number of output lines to keep in init script state (prevents unbounded memory growth)
|
||||||
export const MAX_INIT_OUTPUT_LINES = 500;
|
export const MAX_INIT_OUTPUT_LINES = 500;
|
||||||
@@ -123,6 +157,40 @@ function saveThemeToStorage(theme: ThemeMode): void {
|
|||||||
setItem(THEME_STORAGE_KEY, theme);
|
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 {
|
function persistEffectiveThemeForProject(project: Project | null, fallbackTheme: ThemeMode): void {
|
||||||
const projectTheme = project?.theme as ThemeMode | undefined;
|
const projectTheme = project?.theme as ThemeMode | undefined;
|
||||||
const themeToStore = projectTheme ?? fallbackTheme;
|
const themeToStore = projectTheme ?? fallbackTheme;
|
||||||
@@ -510,6 +578,10 @@ export interface AppState {
|
|||||||
// Theme
|
// Theme
|
||||||
theme: ThemeMode;
|
theme: ThemeMode;
|
||||||
|
|
||||||
|
// Fonts (global defaults)
|
||||||
|
fontFamilySans: string | null; // null = use default Geist Sans
|
||||||
|
fontFamilyMono: string | null; // null = use default Geist Mono
|
||||||
|
|
||||||
// Features/Kanban
|
// Features/Kanban
|
||||||
features: Feature[];
|
features: Feature[];
|
||||||
|
|
||||||
@@ -920,6 +992,14 @@ export interface AppActions {
|
|||||||
getEffectiveTheme: () => ThemeMode; // Get the effective theme (project, global, or preview if set)
|
getEffectiveTheme: () => ThemeMode; // Get the effective theme (project, global, or preview if set)
|
||||||
setPreviewTheme: (theme: ThemeMode | null) => void; // Set preview theme for hover preview (null to clear)
|
setPreviewTheme: (theme: ThemeMode | null) => void; // Set preview theme for hover preview (null to clear)
|
||||||
|
|
||||||
|
// Font actions (global + per-project override)
|
||||||
|
setFontSans: (fontFamily: string | null) => void; // Set global UI/sans font (null to clear)
|
||||||
|
setFontMono: (fontFamily: string | null) => void; // Set global code/mono font (null to clear)
|
||||||
|
setProjectFontSans: (projectId: string, fontFamily: string | null) => void; // Set per-project UI/sans font override (null = use global)
|
||||||
|
setProjectFontMono: (projectId: string, fontFamily: string | null) => void; // Set per-project code/mono font override (null = use global)
|
||||||
|
getEffectiveFontSans: () => string | null; // Get effective UI font (project override -> global -> null for default)
|
||||||
|
getEffectiveFontMono: () => string | null; // Get effective code font (project override -> global -> null for default)
|
||||||
|
|
||||||
// Feature actions
|
// Feature actions
|
||||||
setFeatures: (features: Feature[]) => void;
|
setFeatures: (features: Feature[]) => void;
|
||||||
updateFeature: (id: string, updates: Partial<Feature>) => void;
|
updateFeature: (id: string, updates: Partial<Feature>) => void;
|
||||||
@@ -1258,6 +1338,8 @@ const initialState: AppState = {
|
|||||||
mobileSidebarHidden: false, // Sidebar visible by default on mobile
|
mobileSidebarHidden: false, // Sidebar visible by default on mobile
|
||||||
lastSelectedSessionByProject: {},
|
lastSelectedSessionByProject: {},
|
||||||
theme: getStoredTheme() || 'dark', // Use localStorage theme as initial value, fallback to 'dark'
|
theme: getStoredTheme() || 'dark', // Use localStorage theme as initial value, fallback to 'dark'
|
||||||
|
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: [],
|
features: [],
|
||||||
appSpec: '',
|
appSpec: '',
|
||||||
ipcConnected: false,
|
ipcConnected: false,
|
||||||
@@ -1733,6 +1815,103 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
|||||||
|
|
||||||
setPreviewTheme: (theme) => set({ previewTheme: theme }),
|
setPreviewTheme: (theme) => set({ previewTheme: theme }),
|
||||||
|
|
||||||
|
// Font actions (global + per-project override)
|
||||||
|
setFontSans: (fontFamily) => {
|
||||||
|
// Save to localStorage for fallback when server settings aren't available
|
||||||
|
saveFontSansToStorage(fontFamily);
|
||||||
|
set({ fontFamilySans: 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 }
|
||||||
|
: p
|
||||||
|
);
|
||||||
|
set({ projects });
|
||||||
|
|
||||||
|
// Also update currentProject if it's the same project
|
||||||
|
const currentProject = get().currentProject;
|
||||||
|
if (currentProject?.id === projectId) {
|
||||||
|
set({
|
||||||
|
currentProject: {
|
||||||
|
...currentProject,
|
||||||
|
fontFamilySans: fontFamily === null ? undefined : fontFamily,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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 }
|
||||||
|
: p
|
||||||
|
);
|
||||||
|
set({ projects });
|
||||||
|
|
||||||
|
// Also update currentProject if it's the same project
|
||||||
|
const currentProject = get().currentProject;
|
||||||
|
if (currentProject?.id === projectId) {
|
||||||
|
set({
|
||||||
|
currentProject: {
|
||||||
|
...currentProject,
|
||||||
|
fontFamilyMono: fontFamily === null ? undefined : fontFamily,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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) {
|
||||||
|
const font = currentProject.fontFamilySans;
|
||||||
|
if (!isValidFont(font)) return null; // Fallback to default if font not in list
|
||||||
|
return font === DEFAULT_FONT_VALUE ? null : font;
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
const font = currentProject.fontFamilyMono;
|
||||||
|
if (!isValidFont(font)) return null; // Fallback to default if font not in list
|
||||||
|
return font === DEFAULT_FONT_VALUE ? null : font;
|
||||||
|
}
|
||||||
|
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
|
// Feature actions
|
||||||
setFeatures: (features) => set({ features }),
|
setFeatures: (features) => set({ features }),
|
||||||
|
|
||||||
|
|||||||
113
apps/ui/src/styles/font-imports.ts
Normal file
113
apps/ui/src/styles/font-imports.ts
Normal 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
|
||||||
@@ -18,6 +18,10 @@
|
|||||||
@custom-variant gray (&:is(.gray *));
|
@custom-variant gray (&:is(.gray *));
|
||||||
@custom-variant forest (&:is(.forest *));
|
@custom-variant forest (&:is(.forest *));
|
||||||
@custom-variant ocean (&:is(.ocean *));
|
@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 */
|
/* Light themes */
|
||||||
@custom-variant cream (&:is(.cream *));
|
@custom-variant cream (&:is(.cream *));
|
||||||
@@ -35,6 +39,10 @@
|
|||||||
@custom-variant gruvboxlight (&:is(.gruvboxlight *));
|
@custom-variant gruvboxlight (&:is(.gruvboxlight *));
|
||||||
@custom-variant nordlight (&:is(.nordlight *));
|
@custom-variant nordlight (&:is(.nordlight *));
|
||||||
@custom-variant blossom (&:is(.blossom *));
|
@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 {
|
@theme inline {
|
||||||
--color-background: var(--background);
|
--color-background: var(--background);
|
||||||
@@ -372,6 +380,15 @@
|
|||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
background-color: var(--background);
|
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 */
|
/* Text selection styling for readability */
|
||||||
@@ -863,6 +880,11 @@
|
|||||||
background: var(--muted-foreground);
|
background: var(--muted-foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Terminal padding for better readability */
|
||||||
|
.xterm {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ========================================
|
/* ========================================
|
||||||
DEPENDENCY GRAPH STYLES
|
DEPENDENCY GRAPH STYLES
|
||||||
Theme-aware styling for React Flow graph
|
Theme-aware styling for React Flow graph
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* doesn't tree-shake their CSS when imported dynamically.
|
* doesn't tree-shake their CSS when imported dynamically.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Dark themes (16)
|
// Dark themes (20)
|
||||||
import './themes/dark.css';
|
import './themes/dark.css';
|
||||||
import './themes/retro.css';
|
import './themes/retro.css';
|
||||||
import './themes/dracula.css';
|
import './themes/dracula.css';
|
||||||
@@ -20,8 +20,12 @@ import './themes/sunset.css';
|
|||||||
import './themes/gray.css';
|
import './themes/gray.css';
|
||||||
import './themes/forest.css';
|
import './themes/forest.css';
|
||||||
import './themes/ocean.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/light.css';
|
||||||
import './themes/cream.css';
|
import './themes/cream.css';
|
||||||
import './themes/solarizedlight.css';
|
import './themes/solarizedlight.css';
|
||||||
@@ -38,3 +42,7 @@ import './themes/sepia.css';
|
|||||||
import './themes/gruvboxlight.css';
|
import './themes/gruvboxlight.css';
|
||||||
import './themes/nordlight.css';
|
import './themes/nordlight.css';
|
||||||
import './themes/blossom.css';
|
import './themes/blossom.css';
|
||||||
|
import './themes/ayu-light.css';
|
||||||
|
import './themes/onelight.css';
|
||||||
|
import './themes/bluloco.css';
|
||||||
|
import './themes/feather.css';
|
||||||
|
|||||||
151
apps/ui/src/styles/themes/ayu-dark.css
Normal file
151
apps/ui/src/styles/themes/ayu-dark.css
Normal 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);
|
||||||
|
}
|
||||||
151
apps/ui/src/styles/themes/ayu-light.css
Normal file
151
apps/ui/src/styles/themes/ayu-light.css
Normal 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);
|
||||||
|
}
|
||||||
151
apps/ui/src/styles/themes/ayu-mirage.css
Normal file
151
apps/ui/src/styles/themes/ayu-mirage.css
Normal 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);
|
||||||
|
}
|
||||||
93
apps/ui/src/styles/themes/bluloco.css
Normal file
93
apps/ui/src/styles/themes/bluloco.css
Normal 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);
|
||||||
|
}
|
||||||
151
apps/ui/src/styles/themes/ember.css
Normal file
151
apps/ui/src/styles/themes/ember.css
Normal 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);
|
||||||
|
}
|
||||||
93
apps/ui/src/styles/themes/feather.css
Normal file
93
apps/ui/src/styles/themes/feather.css
Normal 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);
|
||||||
|
}
|
||||||
103
apps/ui/src/styles/themes/matcha.css
Normal file
103
apps/ui/src/styles/themes/matcha.css
Normal 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);
|
||||||
|
}
|
||||||
93
apps/ui/src/styles/themes/onelight.css
Normal file
93
apps/ui/src/styles/themes/onelight.css
Normal 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);
|
||||||
|
}
|
||||||
@@ -396,6 +396,10 @@ export interface ProjectRef {
|
|||||||
lastOpened?: string;
|
lastOpened?: string;
|
||||||
/** Project-specific theme override (or undefined to use global) */
|
/** Project-specific theme override (or undefined to use global) */
|
||||||
theme?: string;
|
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 */
|
/** Whether project is pinned to favorites on dashboard */
|
||||||
isFavorite?: boolean;
|
isFavorite?: boolean;
|
||||||
/** Lucide icon name for project identification */
|
/** Lucide icon name for project identification */
|
||||||
@@ -463,6 +467,14 @@ export interface GlobalSettings {
|
|||||||
/** Currently selected theme */
|
/** Currently selected theme */
|
||||||
theme: ThemeMode;
|
theme: ThemeMode;
|
||||||
|
|
||||||
|
// Font Configuration
|
||||||
|
/** Global UI/Sans font family (undefined = use default Geist Sans) */
|
||||||
|
fontFamilySans?: string;
|
||||||
|
/** Global Code/Mono font family (undefined = use default Geist Mono) */
|
||||||
|
fontFamilyMono?: string;
|
||||||
|
/** Terminal font family (undefined = use default Menlo/Monaco) */
|
||||||
|
terminalFontFamily?: string;
|
||||||
|
|
||||||
// UI State Preferences
|
// UI State Preferences
|
||||||
/** Whether sidebar is currently open */
|
/** Whether sidebar is currently open */
|
||||||
sidebarOpen: boolean;
|
sidebarOpen: boolean;
|
||||||
@@ -718,6 +730,12 @@ export interface ProjectSettings {
|
|||||||
/** Project theme (undefined = use global setting) */
|
/** Project theme (undefined = use global setting) */
|
||||||
theme?: ThemeMode;
|
theme?: ThemeMode;
|
||||||
|
|
||||||
|
// Font Configuration (project-specific override)
|
||||||
|
/** UI/Sans font family override (undefined = use default Geist Sans) */
|
||||||
|
fontFamilySans?: string;
|
||||||
|
/** Code/Mono font family override (undefined = use default Geist Mono) */
|
||||||
|
fontFamilyMono?: string;
|
||||||
|
|
||||||
// Worktree Management
|
// Worktree Management
|
||||||
/** Project-specific worktree preference override */
|
/** Project-specific worktree preference override */
|
||||||
useWorktrees?: boolean;
|
useWorktrees?: boolean;
|
||||||
|
|||||||
1208
package-lock.json
generated
1208
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user