Architektur: - shared/theme.js — Logik (data-theme attr auf <html>, localStorage, prefers-color-scheme fallback, data-theme-lock opt-out) - shared/toggles.js — fixed top-right Pill mit Sun/Moon SVG + DE/EN Button (auto-injected, hängt sich an i18n.js's [data-i18n-toggle] Pattern) - shared/css/theme.css — neutrale Light-Defaults (cream bg, AA-konforme grays) - templates/base.html — Anti-FOUC inline IIFE im <head>, theme.css linked vor inline <style>, scripts in body - tools/contrast-audit.py — neue --light/--dark/--both Modi, parsed [data-theme="light"] + shared fallback Pilot auf 4 Sites: - ichbinotto.de (Octopus rot/teal) - paragraphenraiter.de (Gold) - kilitaer.de (Olive) - deinesei.de (Indigo) Audit-Ergebnis: - Dark mode: 0/59 Verstöße (regression-frei) - Light mode: 14/59 Sites brauchen per-site overrides für sub-AA Akzent-Vars (Shift-2 follow-up) Out of Scope (Shift-2): - Rollout auf restliche 55 Sites - Per-Site Light-Palette-Verfeinerung wo neutral-Default nicht trägt - Per-Site Opt-Out (data-theme-lock) für aesthetisch dark-only Satire-Sites Design-Doc: docs/plans/theme-toggle.md
64 lines
2.0 KiB
JavaScript
64 lines
2.0 KiB
JavaScript
/**
|
|
* Theme switcher for onepager sites.
|
|
*
|
|
* Sets data-theme="light"|"dark" on <html>. Persists to localStorage
|
|
* ("onepager-theme"). Initial-default falls back to prefers-color-scheme.
|
|
*
|
|
* Anti-FOUC companion: this file expects an inline IIFE in <head> to set
|
|
* data-theme BEFORE CSS loads, so the first paint is correct. This file
|
|
* only handles runtime toggling and exposes window.onepagerTheme.
|
|
*
|
|
* Per-site opt-out: <html data-theme-lock="dark"> forces dark and ignores
|
|
* localStorage. toggles.js consumes data-theme-lock to hide the theme button.
|
|
*/
|
|
(function () {
|
|
var KEY = 'onepager-theme';
|
|
var SUPPORTED = ['light', 'dark'];
|
|
|
|
function lock() {
|
|
var v = document.documentElement.getAttribute('data-theme-lock');
|
|
return v && SUPPORTED.indexOf(v) !== -1 ? v : null;
|
|
}
|
|
|
|
function detect() {
|
|
var locked = lock();
|
|
if (locked) return locked;
|
|
var stored = null;
|
|
try { stored = localStorage.getItem(KEY); } catch (e) { /* private browsing */ }
|
|
if (stored && SUPPORTED.indexOf(stored) !== -1) return stored;
|
|
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) {
|
|
return 'light';
|
|
}
|
|
return 'dark';
|
|
}
|
|
|
|
function get() {
|
|
return document.documentElement.getAttribute('data-theme') || 'dark';
|
|
}
|
|
|
|
function set(theme) {
|
|
if (lock()) return;
|
|
if (SUPPORTED.indexOf(theme) === -1) return;
|
|
document.documentElement.setAttribute('data-theme', theme);
|
|
try { localStorage.setItem(KEY, theme); } catch (e) { /* private browsing */ }
|
|
}
|
|
|
|
function toggle() {
|
|
set(get() === 'light' ? 'dark' : 'light');
|
|
}
|
|
|
|
// Reconcile with anti-FOUC inline script. If <html> already has data-theme
|
|
// set (by anti-FOUC IIFE), keep it. Otherwise apply detect().
|
|
if (!document.documentElement.getAttribute('data-theme')) {
|
|
document.documentElement.setAttribute('data-theme', detect());
|
|
}
|
|
|
|
window.onepagerTheme = {
|
|
get: get,
|
|
set: set,
|
|
toggle: toggle,
|
|
detect: detect,
|
|
isLocked: function () { return lock() !== null; }
|
|
};
|
|
})();
|