d0d5e76abe
Adds an Auto / Dark / Light radio group to the Options page that overrides the OS `prefers-color-scheme` setting. Stored in chrome.storage.local under tuner.theme. Defaults to Auto. Architecture: - src/lib/theme.js (new, ~55 lines) — getTheme/setTheme/applyTheme/initTheme helpers. applyTheme sets/removes `data-theme` attribute on <html>; initTheme reads storage + applies. - popup.js + newtab.js + options.js: call initTheme() FIRST in their init() so the theme paints before anything else. All three also listen for chrome.storage.onChanged on the THEME_KEY and live-apply changes — pick Light in Options, popup + newtab flip instantly. - options.html: new 'Appearance' card with 3 radio buttons (Auto/Dark/ Light) above the existing Playback card. - options.css: styled radio group (pill-shaped, accent border on :checked, hover state). Plus the Auto/Dark/Light CSS overrides themselves. - popup.css, newtab.css, options.css: each gets html[data-theme=light] and html[data-theme=dark] blocks that override :root vars. Attribute selector specificity beats both :root and the @media :root, so the manual override wins when set. UX: - Default = Auto (follows OS via existing @media block) - Pick Dark → overrides OS, forces dark palette - Pick Light → overrides OS, forces light palette - Selection persists across reloads, syncs across all three surfaces 7 files, ~150 lines added. No new permissions, no new dependencies. Bundled with v0.4.1-prep back-link + Phase 1 OS-follow light mode for the v0.5.0 release.
56 lines
2.0 KiB
JavaScript
56 lines
2.0 KiB
JavaScript
// RangerHQ Tuner — Theme helpers (v0.5.0)
|
|
//
|
|
// Three modes:
|
|
// auto — follow OS via @media (prefers-color-scheme: light)
|
|
// dark — force dark regardless of OS
|
|
// light — force light regardless of OS
|
|
//
|
|
// Stored in chrome.storage.local under `tuner.theme`. Default: auto.
|
|
//
|
|
// Application strategy: set/remove `data-theme` attribute on <html>.
|
|
// - "auto" → attribute removed → @media in each stylesheet decides
|
|
// - "dark" → data-theme="dark" → CSS forces dark vars
|
|
// - "light" → data-theme="light" → CSS forces light vars
|
|
//
|
|
// Used from popup.js, newtab.js, and options.js on init + on cross-surface
|
|
// sync.
|
|
|
|
export const THEME_KEY = 'tuner.theme';
|
|
export const THEME_AUTO = 'auto';
|
|
export const THEME_DARK = 'dark';
|
|
export const THEME_LIGHT = 'light';
|
|
export const THEME_DEFAULT = THEME_AUTO;
|
|
export const VALID_THEMES = [THEME_AUTO, THEME_DARK, THEME_LIGHT];
|
|
|
|
/** Read the stored theme preference, falling back to "auto". */
|
|
export async function getTheme() {
|
|
if (typeof chrome === 'undefined' || !chrome.storage?.local) return THEME_DEFAULT;
|
|
const o = await chrome.storage.local.get(THEME_KEY);
|
|
const v = o[THEME_KEY];
|
|
return VALID_THEMES.includes(v) ? v : THEME_DEFAULT;
|
|
}
|
|
|
|
/** Persist a theme preference. Any cross-surface listeners will pick it up. */
|
|
export async function setTheme(theme) {
|
|
if (!VALID_THEMES.includes(theme)) theme = THEME_DEFAULT;
|
|
if (typeof chrome === 'undefined' || !chrome.storage?.local) return theme;
|
|
await chrome.storage.local.set({ [THEME_KEY]: theme });
|
|
return theme;
|
|
}
|
|
|
|
/** Apply a theme to the current document. Pass "auto" to clear the override. */
|
|
export function applyTheme(theme) {
|
|
const t = VALID_THEMES.includes(theme) ? theme : THEME_DEFAULT;
|
|
const html = document.documentElement;
|
|
if (t === THEME_AUTO) {
|
|
html.removeAttribute('data-theme');
|
|
} else {
|
|
html.setAttribute('data-theme', t);
|
|
}
|
|
}
|
|
|
|
/** Convenience: read the stored theme and apply it. */
|
|
export async function initTheme() {
|
|
applyTheme(await getTheme());
|
|
}
|