Fix accent color flash on refresh by eliminating CSS/JS race

Guard useTheme effect to skip when settings are undefined, preventing
it from overwriting the inline script's cached color with cyan defaults.
Move CSS accent var defaults from index.css :root into the index.html
inline script so they are always set synchronously before paint.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kyle 2026-03-12 20:02:13 +08:00
parent 3d7166740e
commit 988dc37b64
3 changed files with 18 additions and 10 deletions

View File

@ -9,18 +9,24 @@
<meta name="mobile-web-app-capable" content="yes" /> <meta name="mobile-web-app-capable" content="yes" />
<title>UMBRA</title> <title>UMBRA</title>
<script> <script>
// Apply cached accent color before React hydrates to prevent cyan flash // Apply accent color before React hydrates to prevent FOUC.
// Reads from localStorage cache; falls back to cyan on first visit.
(function() { (function() {
var s = document.documentElement.style;
try { try {
var c = localStorage.getItem('umbra-accent-color'); var c = localStorage.getItem('umbra-accent-color');
if (c) { if (c) {
var p = JSON.parse(c); var p = JSON.parse(c);
var s = document.documentElement.style;
s.setProperty('--accent-h', p.h); s.setProperty('--accent-h', p.h);
s.setProperty('--accent-s', p.s); s.setProperty('--accent-s', p.s);
s.setProperty('--accent-l', p.l); s.setProperty('--accent-l', p.l);
return;
} }
} catch(e) {} } catch(e) {}
// First visit or corrupt cache — apply cyan defaults
s.setProperty('--accent-h', '187');
s.setProperty('--accent-s', '85.7%');
s.setProperty('--accent-l', '53.3%');
})(); })();
</script> </script>
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />

View File

@ -15,10 +15,13 @@ const ACCENT_PRESETS: Record<string, { h: number; s: number; l: number }> = {
export function useTheme() { export function useTheme() {
const { settings } = useSettings(); const { settings } = useSettings();
// Ensure localStorage always has an accent color (even default cyan) // Only apply accent color once settings have loaded from the API.
// so the inline script in index.html can prevent flashes on every load // The inline script in index.html handles the initial paint from localStorage cache.
// Firing this effect with settings=undefined would overwrite the cache with cyan.
useEffect(() => { useEffect(() => {
const colorName = settings?.accent_color || 'cyan'; if (!settings) return;
const colorName = settings.accent_color || 'cyan';
const preset = ACCENT_PRESETS[colorName]; const preset = ACCENT_PRESETS[colorName];
if (!preset) return; if (!preset) return;
@ -31,7 +34,7 @@ export function useTheme() {
try { try {
localStorage.setItem('umbra-accent-color', JSON.stringify({ h, s, l })); localStorage.setItem('umbra-accent-color', JSON.stringify({ h, s, l }));
} catch {} } catch {}
}, [settings?.accent_color]); }, [settings]);
return { return {
accentColor: settings?.accent_color || 'cyan', accentColor: settings?.accent_color || 'cyan',

View File

@ -26,10 +26,9 @@
--ring: var(--accent-h) var(--accent-s) var(--accent-l); --ring: var(--accent-h) var(--accent-s) var(--accent-l);
--radius: 0.5rem; --radius: 0.5rem;
/* Default accent: cyan */ /* Accent vars are set by the inline script in index.html (from localStorage
--accent-h: 187; cache or cyan fallback). No CSS defaults here they would race with the
--accent-s: 85.7%; inline script and cause a flash of wrong color on refresh. */
--accent-l: 53.3%;
/* Transitions */ /* Transitions */
--transition-fast: 150ms; --transition-fast: 150ms;