dorfteich/apps/web/public/theme-init.js
Claude Fable 5 2c571f9f5e #180: dark mode — Light/Dark/System setting with token-based dark palette
The dark palette lives as a single :root[data-theme='dark'] block in
tokens.css; theme.ts and the pre-paint public/theme-init.js (external file
because the prod CSP forbids inline scripts) always resolve the stored
ui.theme.mode to a concrete data-theme, so 'system' needs no @media
duplicate and follows live OS changes via matchMedia. color-scheme flips
per theme (native controls/scrollbars), paired theme-color metas track the
effective theme, and the new Appearance settings section offers the
three-way choice as native radios (device-local, like #170). Label chips
gain a chip-outline ring so arbitrary user colors stay separated on the
dark canvas; useEffectiveTheme() is exported for the later pond-scoped
theming stage (ADR 0018).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX
2026-07-28 20:40:47 +02:00

35 lines
1.4 KiB
JavaScript

/* Pre-paint theme boot (issue #180). Must stay a plain classic script in a
* separate file: the prod CSP (nginx.conf, script-src 'self') forbids inline
* scripts, and executing during <head> parsing — before the deferred module
* bundle — is what prevents a white flash for dark users. Reads the same
* JSON-encoded localStorage keys as src/theme/theme.ts; keep them in sync. */
(function () {
var theme = 'light';
try {
var raw = window.localStorage.getItem('ui.theme.mode');
var mode = raw === null ? 'system' : JSON.parse(raw);
var dark =
mode === 'dark' ||
(mode !== 'light' && window.matchMedia('(prefers-color-scheme: dark)').matches);
if (dark) theme = 'dark';
} catch {
/* Broken storage/JSON: default to light. */
}
document.documentElement.dataset.theme = theme;
document.documentElement.style.colorScheme = theme;
try {
/* Hook for the accent-theming stage (ADR 0018): a pre-derived stylesheet
* cached under ui.theme.css is injected before first paint so custom
* accents do not flash either. Unset until that stage ships. */
var css = window.localStorage.getItem('ui.theme.css');
if (css) {
var style = document.createElement('style');
style.id = 'user-theme';
style.textContent = JSON.parse(css);
document.head.appendChild(style);
}
} catch {
/* Optional enhancement only. */
}
})();