11import { useEffect , useState } from "react" ;
22import type { MacBackgroundEffect , Space } from "@/types" ;
33import { computeGlassTintColor } from "@/lib/color-utils" ;
4- import { isMac } from "@/lib/utils" ;
4+ import { isMac , isWindows } from "@/lib/utils" ;
55
66const TINT_VARS = [
77 "--space-hue" , "--space-chroma" ,
@@ -13,6 +13,9 @@ const TINT_VARS = [
1313
1414const DARK_SURFACE_BRIGHTNESS_MULTIPLIER = 1.3 ;
1515const LIGHT_SURFACE_WHITE_MIX = 0.3 ;
16+ const NON_NATIVE_TINT_DARKEN_FACTOR = 0.25 ;
17+ const BASE_TINT_OVERLAY_LIGHTNESS = 0.5 ;
18+ const NEUTRAL_HUE = 0 ;
1619
1720function clamp01 ( value : number ) : number {
1821 return Math . max ( 0 , Math . min ( 1 , value ) ) ;
@@ -31,6 +34,82 @@ function brightenLightLightness(value: number): number {
3134 return clamp01 ( value + ( 1 - value ) * LIGHT_SURFACE_WHITE_MIX ) ;
3235}
3336
37+ function darkenTintLightness ( value : number , isDark : boolean , enabled : boolean ) : number {
38+ if ( ! enabled ) return clamp01 ( value ) ;
39+ if ( isDark ) return clamp01 ( value * ( 1 - NON_NATIVE_TINT_DARKEN_FACTOR ) ) ;
40+ return clamp01 ( 1 - ( 1 - value ) * ( 1 + NON_NATIVE_TINT_DARKEN_FACTOR ) ) ;
41+ }
42+
43+ function darkenShellLightness ( value : number , enabled : boolean ) : number {
44+ if ( ! enabled ) return clamp01 ( value ) ;
45+ return clamp01 ( value * ( 1 - NON_NATIVE_TINT_DARKEN_FACTOR ) ) ;
46+ }
47+
48+ function applySidebarTokens (
49+ root : HTMLElement ,
50+ options : {
51+ isDark : boolean ;
52+ isGlass : boolean ;
53+ hue : number ;
54+ tintStrength : number ;
55+ sidebarChroma : number ;
56+ surfaceChroma : number ;
57+ borderChroma : number ;
58+ sidebarLightness : number ;
59+ sidebarAccentLightness : number ;
60+ sidebarBorderLightness : number ;
61+ } ,
62+ ) : void {
63+ const {
64+ isDark,
65+ isGlass,
66+ hue,
67+ tintStrength,
68+ sidebarChroma,
69+ surfaceChroma,
70+ borderChroma,
71+ sidebarLightness,
72+ sidebarAccentLightness,
73+ sidebarBorderLightness,
74+ } = options ;
75+
76+ if ( ! isGlass ) {
77+ root . style . setProperty ( "--sidebar" , `oklch(${ sidebarLightness } ${ sidebarChroma } ${ hue } )` ) ;
78+ root . style . setProperty ( "--sidebar-accent" , `oklch(${ sidebarAccentLightness } ${ surfaceChroma } ${ hue } )` ) ;
79+ root . style . setProperty ( "--sidebar-border" , `oklch(${ sidebarBorderLightness } ${ borderChroma } ${ hue } )` ) ;
80+ return ;
81+ }
82+
83+ if ( isDark ) {
84+ root . style . setProperty (
85+ "--sidebar" ,
86+ `oklch(${ sidebarLightness } ${ sidebarChroma } ${ hue } / ${ 0.34 + 0.08 * tintStrength } )` ,
87+ ) ;
88+ root . style . setProperty (
89+ "--sidebar-accent" ,
90+ `oklch(${ sidebarAccentLightness } ${ surfaceChroma } ${ hue } / ${ 0.46 + 0.08 * tintStrength } )` ,
91+ ) ;
92+ root . style . setProperty (
93+ "--sidebar-border" ,
94+ `oklch(${ sidebarBorderLightness } ${ borderChroma } ${ hue } / ${ 0.32 + 0.06 * tintStrength } )` ,
95+ ) ;
96+ return ;
97+ }
98+
99+ root . style . setProperty (
100+ "--sidebar" ,
101+ `oklch(${ sidebarLightness } ${ sidebarChroma } ${ hue } / ${ 0.22 + 0.12 * tintStrength } )` ,
102+ ) ;
103+ root . style . setProperty (
104+ "--sidebar-accent" ,
105+ `oklch(${ sidebarAccentLightness } ${ surfaceChroma } ${ hue } / ${ 0.22 + 0.14 * tintStrength } )` ,
106+ ) ;
107+ root . style . setProperty (
108+ "--sidebar-border" ,
109+ `oklch(${ sidebarBorderLightness } ${ borderChroma } ${ hue } / ${ 0.08 + 0.08 * tintStrength } )` ,
110+ ) ;
111+ }
112+
34113function bindNativeGlassTintOnFocus ( tintColor : string | null ) : ( ) => void {
35114 const applyTint = ( ) => window . claude . glass ?. setTintColor ( tintColor ) ;
36115 applyTint ( ) ;
@@ -63,11 +142,59 @@ export function useSpaceTheme(
63142 const isDark = resolvedTheme === "dark" ;
64143 // Native macOS glass supports tintColor via addView()
65144 const isNativeGlass = isGlass && isMac && macBackgroundEffect === "liquid-glass" ;
145+ const isWindowsMica = isGlass && isWindows ;
146+ const shouldDarkenTint = ! isNativeGlass && ! isWindowsMica ;
147+ const neutralTintStrength = 0 ;
148+ const neutralSidebarChroma = 0 ;
149+ const neutralSurfaceChroma = 0 ;
150+ const neutralBorderChroma = 0 ;
151+ const neutralLightSidebarLightness = darkenShellLightness (
152+ isGlass ? 1 : 0.99 ,
153+ shouldDarkenTint ,
154+ ) ;
155+ const neutralLightSidebarAccentLightness = darkenShellLightness (
156+ 0.976 ,
157+ shouldDarkenTint ,
158+ ) ;
159+ const neutralLightSidebarBorderLightness = darkenShellLightness (
160+ isGlass ? 0 : 0.945 ,
161+ shouldDarkenTint ,
162+ ) ;
163+ const neutralDarkSidebarLightness = darkenShellLightness (
164+ isGlass ? 0.139 : 0.254 ,
165+ shouldDarkenTint ,
166+ ) ;
167+ const neutralDarkSidebarAccentLightness = darkenShellLightness (
168+ isGlass ? 0.334 : 0.411 ,
169+ shouldDarkenTint ,
170+ ) ;
171+ const neutralDarkSidebarBorderLightness = darkenShellLightness (
172+ isGlass ? 0.468 : 0.494 ,
173+ shouldDarkenTint ,
174+ ) ;
66175
67176 if ( ! space || space . color . chroma === 0 ) {
68177 // Clear all tinted vars so the CSS base values take over
69178 for ( const v of TINT_VARS ) root . style . removeProperty ( v ) ;
70- setGlassOverlayStyle ( null ) ;
179+ if ( shouldDarkenTint ) {
180+ applySidebarTokens ( root , {
181+ isDark,
182+ isGlass,
183+ hue : NEUTRAL_HUE ,
184+ tintStrength : neutralTintStrength ,
185+ sidebarChroma : neutralSidebarChroma ,
186+ surfaceChroma : neutralSurfaceChroma ,
187+ borderChroma : neutralBorderChroma ,
188+ sidebarLightness : isDark ? neutralDarkSidebarLightness : neutralLightSidebarLightness ,
189+ sidebarAccentLightness : isDark ? neutralDarkSidebarAccentLightness : neutralLightSidebarAccentLightness ,
190+ sidebarBorderLightness : isDark ? neutralDarkSidebarBorderLightness : neutralLightSidebarBorderLightness ,
191+ } ) ;
192+ }
193+ setGlassOverlayStyle (
194+ isGlass && shouldDarkenTint
195+ ? { background : `oklch(0 0 0 / ${ NON_NATIVE_TINT_DARKEN_FACTOR } )` }
196+ : null ,
197+ ) ;
71198 const releaseFocusTint = isNativeGlass
72199 ? bindNativeGlassTintOnFocus ( null )
73200 : null ;
@@ -95,19 +222,95 @@ export function useSpaceTheme(
95222 const surfaceChroma = ( isDark ? 0.04 : 0.055 ) * tintStrength ;
96223 const borderChroma = ( isDark ? 0.026 : 0.034 ) * tintStrength ;
97224 const sidebarChroma = ( isDark ? 0.024 : 0.03 ) * tintStrength ;
98- const lightBgLightness = brightenLightLightness ( 0.985 - 0.012 * tintStrength ) ;
99- const lightSurfaceLightness = brightenLightLightness ( 0.955 - 0.02 * tintStrength ) ;
100- const lightBorderLightness = brightenLightLightness ( 0.91 ) ;
101- const lightCardLightness = brightenLightLightness ( 0.98 ) ;
102- const lightSidebarLightness = brightenLightLightness ( 0.968 ) ;
103- const lightSidebarAccentLightness = brightenLightLightness ( 0.947 ) ;
104- const darkBgLightness = brightenDarkLightness ( 0.12 - 0.013 * tintStrength ) ;
105- const darkSurfaceLightness = brightenDarkLightness ( 0.355 - 0.04 * tintStrength ) ;
106- const darkBorderLightness = brightenDarkLightness ( 0.39 ) ;
107- const darkCardLightness = brightenDarkLightness ( 0.25 ) ;
108- const darkSidebarLightness = brightenDarkLightness ( 0.2 ) ;
109- const darkSidebarAccentLightness = brightenDarkLightness ( 0.31 ) ;
110- const darkSidebarBorderLightness = brightenDarkLightness ( 0.4 ) ;
225+ const lightBgLightness = darkenTintLightness (
226+ brightenLightLightness ( 0.985 - 0.012 * tintStrength ) ,
227+ false ,
228+ shouldDarkenTint ,
229+ ) ;
230+ const lightSurfaceLightness = darkenTintLightness (
231+ brightenLightLightness ( 0.955 - 0.02 * tintStrength ) ,
232+ false ,
233+ shouldDarkenTint ,
234+ ) ;
235+ const lightBorderLightness = darkenTintLightness (
236+ brightenLightLightness ( 0.91 ) ,
237+ false ,
238+ shouldDarkenTint ,
239+ ) ;
240+ const lightCardLightness = darkenTintLightness (
241+ brightenLightLightness ( 0.98 ) ,
242+ false ,
243+ shouldDarkenTint ,
244+ ) ;
245+ const lightSidebarLightness = darkenTintLightness (
246+ brightenLightLightness ( 0.968 ) ,
247+ false ,
248+ shouldDarkenTint ,
249+ ) ;
250+ const lightSidebarAccentLightness = darkenTintLightness (
251+ brightenLightLightness ( 0.947 ) ,
252+ false ,
253+ shouldDarkenTint ,
254+ ) ;
255+ const darkBgLightness = darkenTintLightness (
256+ brightenDarkLightness ( 0.12 - 0.013 * tintStrength ) ,
257+ true ,
258+ shouldDarkenTint ,
259+ ) ;
260+ const darkSurfaceLightness = darkenTintLightness (
261+ brightenDarkLightness ( 0.355 - 0.04 * tintStrength ) ,
262+ true ,
263+ shouldDarkenTint ,
264+ ) ;
265+ const darkBorderLightness = darkenTintLightness (
266+ brightenDarkLightness ( 0.39 ) ,
267+ true ,
268+ shouldDarkenTint ,
269+ ) ;
270+ const darkCardLightness = darkenTintLightness (
271+ brightenDarkLightness ( 0.25 ) ,
272+ true ,
273+ shouldDarkenTint ,
274+ ) ;
275+ const darkSidebarLightness = darkenTintLightness (
276+ brightenDarkLightness ( 0.2 ) ,
277+ true ,
278+ shouldDarkenTint ,
279+ ) ;
280+ const darkSidebarAccentLightness = darkenTintLightness (
281+ brightenDarkLightness ( 0.31 ) ,
282+ true ,
283+ shouldDarkenTint ,
284+ ) ;
285+ const darkSidebarBorderLightness = darkenTintLightness (
286+ brightenDarkLightness ( 0.4 ) ,
287+ true ,
288+ shouldDarkenTint ,
289+ ) ;
290+ const shellLightSidebarLightness = darkenShellLightness (
291+ brightenLightLightness ( 0.968 ) ,
292+ shouldDarkenTint ,
293+ ) ;
294+ const shellLightSidebarAccentLightness = darkenShellLightness (
295+ brightenLightLightness ( 0.947 ) ,
296+ shouldDarkenTint ,
297+ ) ;
298+ const shellLightSidebarBorderLightness = darkenShellLightness (
299+ brightenLightLightness ( 0.91 ) ,
300+ shouldDarkenTint ,
301+ ) ;
302+ const shellDarkSidebarLightness = darkenShellLightness (
303+ brightenDarkLightness ( 0.2 ) ,
304+ shouldDarkenTint ,
305+ ) ;
306+ const shellDarkSidebarAccentLightness = darkenShellLightness (
307+ brightenDarkLightness ( 0.31 ) ,
308+ shouldDarkenTint ,
309+ ) ;
310+ const shellDarkSidebarBorderLightness = darkenShellLightness (
311+ brightenDarkLightness ( 0.4 ) ,
312+ shouldDarkenTint ,
313+ ) ;
111314
112315 root . style . setProperty ( "--space-hue" , String ( hue ) ) ;
113316 root . style . setProperty ( "--space-chroma" , String ( chroma ) ) ;
@@ -126,11 +329,18 @@ export function useSpaceTheme(
126329 } else {
127330 root . style . removeProperty ( "--island-fill" ) ;
128331 }
129- if ( ! isGlass ) {
130- root . style . setProperty ( "--sidebar" , `oklch(${ darkSidebarLightness } ${ sidebarChroma } ${ hue } )` ) ;
131- root . style . setProperty ( "--sidebar-accent" , `oklch(${ darkSidebarAccentLightness } ${ surfaceChroma } ${ hue } )` ) ;
132- root . style . setProperty ( "--sidebar-border" , `oklch(${ darkSidebarBorderLightness } ${ borderChroma } ${ hue } )` ) ;
133- }
332+ applySidebarTokens ( root , {
333+ isDark,
334+ isGlass,
335+ hue,
336+ tintStrength,
337+ sidebarChroma,
338+ surfaceChroma,
339+ borderChroma,
340+ sidebarLightness : isGlass ? shellDarkSidebarLightness : darkSidebarLightness ,
341+ sidebarAccentLightness : isGlass ? shellDarkSidebarAccentLightness : darkSidebarAccentLightness ,
342+ sidebarBorderLightness : isGlass ? shellDarkSidebarBorderLightness : darkSidebarBorderLightness ,
343+ } ) ;
134344 } else {
135345 root . style . setProperty ( "--background" , `oklch(${ lightBgLightness } ${ bgChroma } ${ hue } )` ) ;
136346 root . style . setProperty ( "--accent" , `oklch(${ lightSurfaceLightness } ${ surfaceChroma } ${ hue } )` ) ;
@@ -145,20 +355,25 @@ export function useSpaceTheme(
145355 } else {
146356 root . style . removeProperty ( "--island-fill" ) ;
147357 }
148- if ( ! isGlass ) {
149- root . style . setProperty ( "--sidebar" , `oklch(${ lightSidebarLightness } ${ sidebarChroma } ${ hue } )` ) ;
150- root . style . setProperty ( "--sidebar-accent" , `oklch(${ lightSidebarAccentLightness } ${ surfaceChroma } ${ hue } )` ) ;
151- root . style . setProperty ( "--sidebar-border" , `oklch(${ lightBorderLightness } ${ borderChroma } ${ hue } )` ) ;
152- } else {
153- // Glass + light: show more native glass while keeping a subtle space tint.
154- root . style . setProperty ( "--sidebar" , `oklch(1 ${ sidebarChroma } ${ hue } / ${ 0.22 + 0.12 * tintStrength } )` ) ;
155- root . style . setProperty ( "--sidebar-accent" , `oklch(${ brightenLightLightness ( 0.965 ) } ${ surfaceChroma } ${ hue } / ${ 0.22 + 0.14 * tintStrength } )` ) ;
156- root . style . setProperty ( "--sidebar-border" , `oklch(0 ${ borderChroma } ${ hue } / ${ 0.08 + 0.08 * tintStrength } )` ) ;
157- }
358+ applySidebarTokens ( root , {
359+ isDark,
360+ isGlass,
361+ hue,
362+ tintStrength,
363+ sidebarChroma,
364+ surfaceChroma,
365+ borderChroma,
366+ sidebarLightness : isGlass ? shellLightSidebarLightness : lightSidebarLightness ,
367+ sidebarAccentLightness : isGlass ? shellLightSidebarAccentLightness : lightSidebarAccentLightness ,
368+ sidebarBorderLightness : isGlass ? shellLightSidebarBorderLightness : lightBorderLightness ,
369+ } ) ;
158370 }
159371
160372 const gradientHue = space . color . gradientHue ;
161373 const overlayChroma = Math . min ( 0.18 , 0.04 + 0.12 * tintStrength ) ;
374+ const overlayLightness = shouldDarkenTint
375+ ? BASE_TINT_OVERLAY_LIGHTNESS * ( 1 - NON_NATIVE_TINT_DARKEN_FACTOR )
376+ : BASE_TINT_OVERLAY_LIGHTNESS ;
162377
163378 // ── Glass tinting ──
164379 let releaseFocusTint : ( ( ) => void ) | null = null ;
@@ -172,8 +387,8 @@ export function useSpaceTheme(
172387 // Non-macOS glass (Windows Mica, etc.) — CSS overlay for tinting
173388 const a = 0.04 + 0.12 * tintStrength ;
174389 const bg = gradientHue !== undefined
175- ? `linear-gradient(135deg, oklch(0.5 ${ overlayChroma } ${ hue } / ${ a } ), oklch(0.5 ${ overlayChroma } ${ gradientHue } / ${ a } ))`
176- : `oklch(0.5 ${ overlayChroma } ${ hue } / ${ a } )` ;
390+ ? `linear-gradient(135deg, oklch(${ overlayLightness } ${ overlayChroma } ${ hue } / ${ a } ), oklch(${ overlayLightness } ${ overlayChroma } ${ gradientHue } / ${ a } ))`
391+ : `oklch(${ overlayLightness } ${ overlayChroma } ${ hue } / ${ a } )` ;
177392 setGlassOverlayStyle ( { background : bg } ) ;
178393 } else {
179394 setGlassOverlayStyle ( null ) ;
@@ -184,7 +399,7 @@ export function useSpaceTheme(
184399 // Set CSS custom prop so .island::before picks up the gradient on ALL islands
185400 root . style . setProperty (
186401 "--island-overlay-bg" ,
187- `linear-gradient(135deg, oklch(0.5 ${ overlayChroma } ${ hue } / ${ a } ), oklch(0.5 ${ overlayChroma } ${ gradientHue } / ${ a } ))` ,
402+ `linear-gradient(135deg, oklch(${ overlayLightness } ${ overlayChroma } ${ hue } / ${ a } ), oklch(${ overlayLightness } ${ overlayChroma } ${ gradientHue } / ${ a } ))` ,
188403 ) ;
189404 } else {
190405 root . style . removeProperty ( "--island-overlay-bg" ) ;
0 commit comments