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
35 lines
1.4 KiB
JavaScript
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. */
|
|
}
|
|
})();
|