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" />
<title>UMBRA</title>
<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() {
var s = document.documentElement.style;
try {
var c = localStorage.getItem('umbra-accent-color');
if (c) {
var p = JSON.parse(c);
var s = document.documentElement.style;
s.setProperty('--accent-h', p.h);
s.setProperty('--accent-s', p.s);
s.setProperty('--accent-l', p.l);
return;
}
} 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>
<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() {
const { settings } = useSettings();
// Ensure localStorage always has an accent color (even default cyan)
// so the inline script in index.html can prevent flashes on every load
// Only apply accent color once settings have loaded from the API.
// 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(() => {
const colorName = settings?.accent_color || 'cyan';
if (!settings) return;
const colorName = settings.accent_color || 'cyan';
const preset = ACCENT_PRESETS[colorName];
if (!preset) return;
@ -31,7 +34,7 @@ export function useTheme() {
try {
localStorage.setItem('umbra-accent-color', JSON.stringify({ h, s, l }));
} catch {}
}, [settings?.accent_color]);
}, [settings]);
return {
accentColor: settings?.accent_color || 'cyan',

View File

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