From fd6bfc0ccd47ecf5c7bcdd4cb399bbc583881a06 Mon Sep 17 00:00:00 2001 From: Val Alexander Date: Sun, 29 Mar 2026 01:53:05 -0500 Subject: [PATCH] Refresh UI fonts and theme presets - Add selectable interface fonts with persisted preference - Replace the old Cursor Dark preset with new color themes - Update base typography and markdown preview font variables --- apps/web/index.html | 2 +- apps/web/src/components/MarkdownPreview.tsx | 2 +- apps/web/src/hooks/useTheme.ts | 74 ++++++- apps/web/src/index.css | 8 +- apps/web/src/routes/_chat.settings.tsx | 39 +++- apps/web/src/themes.css | 214 +++++++++++++++----- 6 files changed, 278 insertions(+), 61 deletions(-) diff --git a/apps/web/index.html b/apps/web/index.html index 790591cb..dd444f9a 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -8,7 +8,7 @@ OK Code diff --git a/apps/web/src/components/MarkdownPreview.tsx b/apps/web/src/components/MarkdownPreview.tsx index cd648258..b968e177 100644 --- a/apps/web/src/components/MarkdownPreview.tsx +++ b/apps/web/src/components/MarkdownPreview.tsx @@ -99,7 +99,7 @@ export const MarkdownPreview = memo(function MarkdownPreview({ contents }: Markd "--cm-callout-bg": "var(--secondary)", "--cm-radius": "12px", "--cm-font": - '"DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif', + 'var(--font-ui, "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif)', "--cm-mono": '"SF Mono", "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace', } as CSSProperties diff --git a/apps/web/src/hooks/useTheme.ts b/apps/web/src/hooks/useTheme.ts index 5d0d2118..79291476 100644 --- a/apps/web/src/hooks/useTheme.ts +++ b/apps/web/src/hooks/useTheme.ts @@ -5,25 +5,39 @@ type ColorTheme = | "default" | "iridescent-void" | "solar-witch" - | "cursor-dark" + | "midnight-clarity" + | "carbon" + | "vapor" | "cathedral-circuit"; +type FontFamily = "dm-sans" | "inter" | "plus-jakarta-sans"; + type ThemeSnapshot = { theme: Theme; systemDark: boolean; colorTheme: ColorTheme; + fontFamily: FontFamily; }; export const COLOR_THEMES: { id: ColorTheme; label: string }[] = [ { id: "default", label: "Default" }, { id: "iridescent-void", label: "Iridescent Void" }, { id: "solar-witch", label: "Solar Witch" }, - { id: "cursor-dark", label: "Cursor Dark" }, + { id: "midnight-clarity", label: "Midnight Clarity" }, + { id: "carbon", label: "Carbon" }, + { id: "vapor", label: "Vapor" }, { id: "cathedral-circuit", label: "Cathedral Circuit" }, ]; +export const FONT_FAMILIES: { id: FontFamily; label: string }[] = [ + { id: "inter", label: "Inter" }, + { id: "dm-sans", label: "DM Sans" }, + { id: "plus-jakarta-sans", label: "Plus Jakarta Sans" }, +]; + const STORAGE_KEY = "okcode:theme"; const COLOR_THEME_STORAGE_KEY = "okcode:color-theme"; +const FONT_FAMILY_STORAGE_KEY = "okcode:font-family"; const MEDIA_QUERY = "(prefers-color-scheme: dark)"; let listeners: Array<() => void> = []; @@ -49,7 +63,9 @@ function getStoredColorTheme(): ColorTheme { raw === "default" || raw === "iridescent-void" || raw === "solar-witch" || - raw === "cursor-dark" || + raw === "midnight-clarity" || + raw === "carbon" || + raw === "vapor" || raw === "cathedral-circuit" ) { return raw; @@ -57,6 +73,26 @@ function getStoredColorTheme(): ColorTheme { return "default"; } +function getStoredFontFamily(): FontFamily { + const raw = localStorage.getItem(FONT_FAMILY_STORAGE_KEY); + if (raw === "dm-sans" || raw === "inter" || raw === "plus-jakarta-sans") { + return raw; + } + return "inter"; +} + +const FONT_FAMILY_MAP: Record = { + inter: '"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif', + "dm-sans": '"DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif', + "plus-jakarta-sans": + '"Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif', +}; + +function applyFont(fontFamily?: FontFamily) { + const font = fontFamily ?? getStoredFontFamily(); + document.documentElement.style.setProperty("--font-ui", FONT_FAMILY_MAP[font]); +} + function applyTheme(theme: Theme, suppressTransitions = false) { if (suppressTransitions) { document.documentElement.classList.add("no-transitions"); @@ -78,6 +114,9 @@ function applyTheme(theme: Theme, suppressTransitions = false) { document.documentElement.classList.add(`theme-${colorTheme}`); } + // Apply font family + applyFont(); + syncDesktopTheme(theme); if (suppressTransitions) { // Force a reflow so the no-transitions class takes effect before removal @@ -110,17 +149,19 @@ function getSnapshot(): ThemeSnapshot { const theme = getStored(); const systemDark = theme === "system" ? getSystemDark() : false; const colorTheme = getStoredColorTheme(); + const fontFamily = getStoredFontFamily(); if ( lastSnapshot && lastSnapshot.theme === theme && lastSnapshot.systemDark === systemDark && - lastSnapshot.colorTheme === colorTheme + lastSnapshot.colorTheme === colorTheme && + lastSnapshot.fontFamily === fontFamily ) { return lastSnapshot; } - lastSnapshot = { theme, systemDark, colorTheme }; + lastSnapshot = { theme, systemDark, colorTheme, fontFamily }; return lastSnapshot; } @@ -137,7 +178,11 @@ function subscribe(listener: () => void): () => void { // Listen for storage changes from other tabs const handleStorage = (e: StorageEvent) => { - if (e.key === STORAGE_KEY || e.key === COLOR_THEME_STORAGE_KEY) { + if ( + e.key === STORAGE_KEY || + e.key === COLOR_THEME_STORAGE_KEY || + e.key === FONT_FAMILY_STORAGE_KEY + ) { applyTheme(getStored(), true); emitChange(); } @@ -155,6 +200,7 @@ export function useTheme() { const snapshot = useSyncExternalStore(subscribe, getSnapshot); const theme = snapshot.theme; const colorTheme = snapshot.colorTheme; + const fontFamily = snapshot.fontFamily; const resolvedTheme: "light" | "dark" = theme === "system" ? (snapshot.systemDark ? "dark" : "light") : theme; @@ -171,10 +217,24 @@ export function useTheme() { emitChange(); }, []); + const setFontFamily = useCallback((next: FontFamily) => { + localStorage.setItem(FONT_FAMILY_STORAGE_KEY, next); + applyFont(next); + emitChange(); + }, []); + // Keep DOM in sync on mount/change useEffect(() => { applyTheme(theme); }, [theme]); - return { theme, setTheme, resolvedTheme, colorTheme, setColorTheme } as const; + return { + theme, + setTheme, + resolvedTheme, + colorTheme, + setColorTheme, + fontFamily, + setFontFamily, + } as const; } diff --git a/apps/web/src/index.css b/apps/web/src/index.css index da1a706c..c94a29cd 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -122,13 +122,15 @@ } body { - font-family: - "DM Sans", + font-family: var( + --font-ui, + "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, - sans-serif; + sans-serif + ); margin: 0; padding: 0; min-height: 100vh; diff --git a/apps/web/src/routes/_chat.settings.tsx b/apps/web/src/routes/_chat.settings.tsx index 6b4d83db..657b556b 100644 --- a/apps/web/src/routes/_chat.settings.tsx +++ b/apps/web/src/routes/_chat.settings.tsx @@ -34,7 +34,7 @@ import { SidebarInset } from "../components/ui/sidebar"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../components/ui/tooltip"; import { resolveAndPersistPreferredEditor } from "../editorPreferences"; import { isElectron } from "../env"; -import { useTheme, COLOR_THEMES } from "../hooks/useTheme"; +import { useTheme, COLOR_THEMES, FONT_FAMILIES } from "../hooks/useTheme"; import { environmentVariablesQueryKeys, globalEnvironmentVariablesQueryOptions, @@ -209,7 +209,7 @@ function getErrorMessage(error: unknown): string { } function SettingsRouteView() { - const { theme, setTheme, colorTheme, setColorTheme } = useTheme(); + const { theme, setTheme, colorTheme, setColorTheme, fontFamily, setFontFamily } = useTheme(); const { settings, defaults, updateSettings, resetSettings } = useAppSettings(); const serverConfigQuery = useQuery(serverConfigQueryOptions()); const queryClient = useQueryClient(); @@ -300,6 +300,7 @@ function SettingsRouteView() { const changedSettingLabels = [ ...(theme !== "system" ? ["Theme"] : []), ...(colorTheme !== "default" ? ["Color theme"] : []), + ...(fontFamily !== "inter" ? ["Font"] : []), ...(settings.timestampFormat !== defaults.timestampFormat ? ["Time format"] : []), ...(settings.diffWordWrap !== defaults.diffWordWrap ? ["Diff line wrapping"] : []), ...(settings.enableAssistantStreaming !== defaults.enableAssistantStreaming @@ -445,6 +446,7 @@ function SettingsRouteView() { setTheme("system"); setColorTheme("default"); + setFontFamily("inter"); resetSettings(); setOpenInstallProviders({ codex: false, @@ -571,6 +573,39 @@ function SettingsRouteView() { } /> + setFontFamily("inter")} /> + ) : null + } + control={ + + } + /> +