Skip to content

Commit cb8c8a2

Browse files
authored
fix: Safari theme switching on docs page (#74)
Remove background-color from body's CSS transition — Safari's transition engine fights inline style updates when the underlying CSS variable changes simultaneously. Body background is now set directly via JS in the theme apply function. Syntax highlight colors moved to CSS custom properties for reliable Safari inheritance.
1 parent 8551654 commit cb8c8a2

File tree

2 files changed

+58
-3
lines changed

2 files changed

+58
-3
lines changed

web/src/app.css

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737

3838
/* ---- Light Theme (default) ---- */
3939
:root, [data-theme="light"] {
40+
color-scheme: light;
4041
--bg-primary: #fdfbff;
4142
--bg-secondary: #f9f4fb;
4243
--bg-tertiary: #f2e9f4;
@@ -74,10 +75,19 @@
7475
--section-alt-bg: var(--bg-secondary);
7576

7677
--grid-color: rgba(148, 163, 184, 0.15);
78+
79+
--hl-comment: #64748b;
80+
--hl-arg: #15803d;
81+
--hl-operator:#6b7280;
82+
--hl-value: #a16207;
83+
--hl-keyword: #7c3aed;
84+
--hl-string: #c2410c;
85+
--hl-builtin: #0369a1;
7786
}
7887

7988
/* ---- Dark Theme ---- */
8089
[data-theme="dark"] {
90+
color-scheme: dark;
8191
--bg-primary: #0f1117;
8292
--bg-secondary: #151821;
8393
--bg-tertiary: #1a1d28;
@@ -115,6 +125,14 @@
115125
--section-alt-bg: var(--bg-secondary);
116126

117127
--grid-color: rgba(31, 41, 55, 0.25);
128+
129+
--hl-comment: #94a3b8;
130+
--hl-arg: #22c55e;
131+
--hl-operator:#e5e7eb;
132+
--hl-value: #eab308;
133+
--hl-keyword: #a855f7;
134+
--hl-string: #f97316;
135+
--hl-builtin: #0ea5e9;
118136
}
119137

120138
/* ---- Reset & Base ---- */
@@ -137,7 +155,7 @@ body {
137155
background-color: var(--bg-primary);
138156
-webkit-font-smoothing: antialiased;
139157
-moz-osx-font-smoothing: grayscale;
140-
transition: background-color var(--transition-base), color var(--transition-base);
158+
transition: color var(--transition-base);
141159
position: relative;
142160
overflow-x: hidden;
143161
max-width: 100vw;
@@ -242,6 +260,19 @@ p {
242260
color: white;
243261
}
244262

263+
/* ---- Docs syntax highlighting ---- */
264+
/* Colors are CSS variables so Safari picks them up via inheritance, not
265+
selector re-evaluation (which WebKit defers for complex layouts). */
266+
.hl-comment { color: var(--hl-comment); }
267+
.hl-command { color: var(--accent); }
268+
.hl-arg,
269+
.hl-flag { color: var(--hl-arg); }
270+
.hl-operator { color: var(--hl-operator); }
271+
.hl-value { color: var(--hl-value); }
272+
.hl-keyword { color: var(--hl-keyword); }
273+
.hl-string { color: var(--hl-string); }
274+
.hl-builtin { color: var(--hl-builtin); }
275+
245276
/* Scrollbar */
246277
::-webkit-scrollbar {
247278
width: 6px;

web/src/lib/theme.svelte.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,32 @@ import { browser } from '$app/environment';
22

33
type Theme = 'light' | 'dark';
44

5+
// Must match --bg-primary in app.css for each theme.
6+
// Safari doesn't repaint body.backgroundColor when CSS vars change on html,
7+
// so we set it directly in JS with the known values.
8+
const BG: Record<Theme, string> = {
9+
light: '#fdfbff',
10+
dark: '#0f1117',
11+
};
12+
13+
function safeLocalStorage(action: 'get' | 'set', key: string, value?: string): string | null {
14+
try {
15+
if (action === 'get') return localStorage.getItem(key);
16+
localStorage.setItem(key, value!);
17+
return null;
18+
} catch {
19+
return null;
20+
}
21+
}
22+
523
function getSystemTheme(): Theme {
624
if (!browser) return 'light';
725
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
826
}
927

1028
function getStoredPreference(): 'light' | 'dark' | 'system' {
1129
if (!browser) return 'system';
12-
return (localStorage.getItem('hashprep-theme') as 'light' | 'dark' | 'system') || 'system';
30+
return (safeLocalStorage('get', 'hashprep-theme') as 'light' | 'dark' | 'system') || 'system';
1331
}
1432

1533
let preference = $state<'light' | 'dark' | 'system'>(getStoredPreference());
@@ -18,12 +36,18 @@ let resolved = $derived<Theme>(preference === 'system' ? getSystemTheme() : pref
1836
function apply(t: Theme) {
1937
if (!browser) return;
2038
document.documentElement.setAttribute('data-theme', t);
39+
document.documentElement.style.colorScheme = t;
40+
// Safari bug: body's background-color using var(--bg-primary) is not repainted
41+
// when the CSS variable changes via a [data-theme] attribute selector on <html>.
42+
// The CSS transition on background-color also fights inline style updates in
43+
// Safari, so we removed that transition and set the value directly here.
44+
document.body.style.backgroundColor = BG[t];
2145
}
2246

2347
function setPreference(pref: 'light' | 'dark' | 'system') {
2448
preference = pref;
2549
if (browser) {
26-
localStorage.setItem('hashprep-theme', pref);
50+
safeLocalStorage('set', 'hashprep-theme', pref);
2751
}
2852
apply(pref === 'system' ? getSystemTheme() : pref);
2953
}

0 commit comments

Comments
 (0)