@@ -5,25 +5,39 @@ type ColorTheme =
55 | "default"
66 | "iridescent-void"
77 | "solar-witch"
8- | "cursor-dark"
8+ | "midnight-clarity"
9+ | "carbon"
10+ | "vapor"
911 | "cathedral-circuit" ;
1012
13+ type FontFamily = "dm-sans" | "inter" | "plus-jakarta-sans" ;
14+
1115type ThemeSnapshot = {
1216 theme : Theme ;
1317 systemDark : boolean ;
1418 colorTheme : ColorTheme ;
19+ fontFamily : FontFamily ;
1520} ;
1621
1722export const COLOR_THEMES : { id : ColorTheme ; label : string } [ ] = [
1823 { id : "default" , label : "Default" } ,
1924 { id : "iridescent-void" , label : "Iridescent Void" } ,
2025 { id : "solar-witch" , label : "Solar Witch" } ,
21- { id : "cursor-dark" , label : "Cursor Dark" } ,
26+ { id : "midnight-clarity" , label : "Midnight Clarity" } ,
27+ { id : "carbon" , label : "Carbon" } ,
28+ { id : "vapor" , label : "Vapor" } ,
2229 { id : "cathedral-circuit" , label : "Cathedral Circuit" } ,
2330] ;
2431
32+ export const FONT_FAMILIES : { id : FontFamily ; label : string } [ ] = [
33+ { id : "inter" , label : "Inter" } ,
34+ { id : "dm-sans" , label : "DM Sans" } ,
35+ { id : "plus-jakarta-sans" , label : "Plus Jakarta Sans" } ,
36+ ] ;
37+
2538const STORAGE_KEY = "okcode:theme" ;
2639const COLOR_THEME_STORAGE_KEY = "okcode:color-theme" ;
40+ const FONT_FAMILY_STORAGE_KEY = "okcode:font-family" ;
2741const MEDIA_QUERY = "(prefers-color-scheme: dark)" ;
2842
2943let listeners : Array < ( ) => void > = [ ] ;
@@ -49,14 +63,36 @@ function getStoredColorTheme(): ColorTheme {
4963 raw === "default" ||
5064 raw === "iridescent-void" ||
5165 raw === "solar-witch" ||
52- raw === "cursor-dark" ||
66+ raw === "midnight-clarity" ||
67+ raw === "carbon" ||
68+ raw === "vapor" ||
5369 raw === "cathedral-circuit"
5470 ) {
5571 return raw ;
5672 }
5773 return "default" ;
5874}
5975
76+ function getStoredFontFamily ( ) : FontFamily {
77+ const raw = localStorage . getItem ( FONT_FAMILY_STORAGE_KEY ) ;
78+ if ( raw === "dm-sans" || raw === "inter" || raw === "plus-jakarta-sans" ) {
79+ return raw ;
80+ }
81+ return "inter" ;
82+ }
83+
84+ const FONT_FAMILY_MAP : Record < FontFamily , string > = {
85+ inter : '"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif' ,
86+ "dm-sans" : '"DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif' ,
87+ "plus-jakarta-sans" :
88+ '"Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif' ,
89+ } ;
90+
91+ function applyFont ( fontFamily ?: FontFamily ) {
92+ const font = fontFamily ?? getStoredFontFamily ( ) ;
93+ document . documentElement . style . setProperty ( "--font-ui" , FONT_FAMILY_MAP [ font ] ) ;
94+ }
95+
6096function applyTheme ( theme : Theme , suppressTransitions = false ) {
6197 if ( suppressTransitions ) {
6298 document . documentElement . classList . add ( "no-transitions" ) ;
@@ -78,6 +114,9 @@ function applyTheme(theme: Theme, suppressTransitions = false) {
78114 document . documentElement . classList . add ( `theme-${ colorTheme } ` ) ;
79115 }
80116
117+ // Apply font family
118+ applyFont ( ) ;
119+
81120 syncDesktopTheme ( theme ) ;
82121 if ( suppressTransitions ) {
83122 // Force a reflow so the no-transitions class takes effect before removal
@@ -110,17 +149,19 @@ function getSnapshot(): ThemeSnapshot {
110149 const theme = getStored ( ) ;
111150 const systemDark = theme === "system" ? getSystemDark ( ) : false ;
112151 const colorTheme = getStoredColorTheme ( ) ;
152+ const fontFamily = getStoredFontFamily ( ) ;
113153
114154 if (
115155 lastSnapshot &&
116156 lastSnapshot . theme === theme &&
117157 lastSnapshot . systemDark === systemDark &&
118- lastSnapshot . colorTheme === colorTheme
158+ lastSnapshot . colorTheme === colorTheme &&
159+ lastSnapshot . fontFamily === fontFamily
119160 ) {
120161 return lastSnapshot ;
121162 }
122163
123- lastSnapshot = { theme, systemDark, colorTheme } ;
164+ lastSnapshot = { theme, systemDark, colorTheme, fontFamily } ;
124165 return lastSnapshot ;
125166}
126167
@@ -137,7 +178,11 @@ function subscribe(listener: () => void): () => void {
137178
138179 // Listen for storage changes from other tabs
139180 const handleStorage = ( e : StorageEvent ) => {
140- if ( e . key === STORAGE_KEY || e . key === COLOR_THEME_STORAGE_KEY ) {
181+ if (
182+ e . key === STORAGE_KEY ||
183+ e . key === COLOR_THEME_STORAGE_KEY ||
184+ e . key === FONT_FAMILY_STORAGE_KEY
185+ ) {
141186 applyTheme ( getStored ( ) , true ) ;
142187 emitChange ( ) ;
143188 }
@@ -155,6 +200,7 @@ export function useTheme() {
155200 const snapshot = useSyncExternalStore ( subscribe , getSnapshot ) ;
156201 const theme = snapshot . theme ;
157202 const colorTheme = snapshot . colorTheme ;
203+ const fontFamily = snapshot . fontFamily ;
158204
159205 const resolvedTheme : "light" | "dark" =
160206 theme === "system" ? ( snapshot . systemDark ? "dark" : "light" ) : theme ;
@@ -171,10 +217,24 @@ export function useTheme() {
171217 emitChange ( ) ;
172218 } , [ ] ) ;
173219
220+ const setFontFamily = useCallback ( ( next : FontFamily ) => {
221+ localStorage . setItem ( FONT_FAMILY_STORAGE_KEY , next ) ;
222+ applyFont ( next ) ;
223+ emitChange ( ) ;
224+ } , [ ] ) ;
225+
174226 // Keep DOM in sync on mount/change
175227 useEffect ( ( ) => {
176228 applyTheme ( theme ) ;
177229 } , [ theme ] ) ;
178230
179- return { theme, setTheme, resolvedTheme, colorTheme, setColorTheme } as const ;
231+ return {
232+ theme,
233+ setTheme,
234+ resolvedTheme,
235+ colorTheme,
236+ setColorTheme,
237+ fontFamily,
238+ setFontFamily,
239+ } as const ;
180240}
0 commit comments