Files
onepager/tools/patch-light-overrides.py
mAi a06a94ff58 feat: #13 Light/Dark + EN/DE Toggle — Shift-2 Rollout
Rollout des Toggle-Patterns auf alle 57 statischen Sites (dasbes.de + dumusst.com sind dynamic, kein index.html).

1. **Bulk-Wiring (53 Sites)** via tools/patch-theme.py:
   - Anti-FOUC inline IIFE im <head> (vor erstem Paint)
   - <link rel="stylesheet" href="/shared/css/theme.css">
   - <script src="/shared/theme.js"> + toggles.js (i18n.js bleibt, hängt sich ans neue Widget)

2. **Per-Site Light-Overrides (14 Sites)** via tools/patch-light-overrides.py:
   - 6034, allainallain, commanderkin, hallofraumaier, heygoldi, keinefreun, lexsiebels, machesdocheinfach, matthiasbreier, orakil, osterai, patentonkel, traihard, wartebitte
   - Pro Site nur die failing accent-vars darkened (--green-dim, --text-faint, --warm-dim, --gold-dim, etc.)
   - AA 4.5:1+ auf white bg gesichert; Brand-Akzent erhalten

3. **data-theme-lock="dark" (4 Sites)** auf <html>:
   - kilibri, killusion, killionaer, killuminati
   - Aesthetisch dark-only — toggles.js blendet Theme-Button automatisch aus, Lang-Button bleibt

4. **Footer-Toggle Removal (52 Sites)** via tools/remove-footer-toggle.py:
   - Bestehende footer [data-i18n-toggle] Buttons entfernt — top-right widget übernimmt
   - Disclaimer-Information in tooltip des neuen Buttons + ai-disclosure.js footer

QA:
- ./build.sh: 59/59 sites built clean
- contrast-audit.py --both: 0/59 dark fail, 0/59 light fail
- anti-ai-lint: 0/57 sites flagged

Tools committed (idempotent, für Wiederverwendung):
- tools/patch-theme.py (--all wired alle Sites)
- tools/patch-light-overrides.py (per-site OVERRIDES dict)
- tools/remove-footer-toggle.py (4 Patterns für versch. Footer-Strukturen)
2026-05-08 11:16:15 +02:00

114 lines
2.9 KiB
Python

#!/usr/bin/env python3
"""Insert per-site [data-theme="light"] override block after :root.
Each entry maps site -> dict of var -> light-value. Vars not listed
fall through to shared/css/theme.css defaults.
"""
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
SITES_DIR = ROOT / "sites"
OVERRIDES = {
"6034.de": {
"--green-dim": "#15803d", # AA 4.7:1 on white
},
"allainallain.de": {
"--text-faint": "#5a608a",
},
"commanderkin.de": {
"--green-dim": "#166e16",
},
"hallofraumaier.de": {
"--text-light": "#6e6452",
},
"heygoldi.de": {
"--text-gentle": "#6e6452",
},
"keinefreun.de": {
"--text-dimmer": "#5a5662",
"--accent-dim": "#5d28b8",
},
"lexsiebels.de": {
"--text-bright": "#1a1a1a", # high-contrast text inverts on light bg
},
"machesdocheinfach.de": {
"--text-faint": "#6e6452",
},
"matthiasbreier.de": {
"--text-light": "#475569",
},
"orakil.de": {
"--gold-dim": "#6c5b1a",
},
"osterai.de": {
"--text-faint": "#6e6452",
},
"patentonkel.de": {
"--warm-dim": "#6e5418",
},
"traihard.de": {
"--muted": "#555568",
},
"wartebitte.de": {
"--text-faint": "#6e685e",
},
}
def find_root_block(html):
"""Find the first `:root {...}` block. Returns (start, end) of `}` char or None."""
m = re.search(r":root\s*\{", html)
if not m:
return None
depth = 1
i = m.end()
while i < len(html) and depth > 0:
if html[i] == "{":
depth += 1
elif html[i] == "}":
depth -= 1
i += 1
if depth != 0:
return None
return i # position right after the closing brace
def detect_root_indent(html, root_end):
"""Look at line containing :root to detect indent."""
# find start of line with :root
m = re.search(r"^([ \t]*):root\s*\{", html, re.MULTILINE)
if m:
return m.group(1)
return " "
def patch_site(site, vars_map):
path = SITES_DIR / site / "index.html"
if not path.exists():
return f"missing index.html"
html = path.read_text()
if '[data-theme="light"]' in html:
return f"already has [data-theme=\"light\"] block — skipped"
end = find_root_block(html)
if end is None:
return f"no :root block found"
indent = detect_root_indent(html, end)
inner = "\n".join(f"{indent} {k}: {v};" for k, v in vars_map.items())
block = f"\n\n{indent}[data-theme=\"light\"] {{\n{inner}\n{indent}}}"
new_html = html[:end] + block + html[end:]
path.write_text(new_html)
return f"patched ({len(vars_map)} vars)"
def main():
for site, vars_map in OVERRIDES.items():
result = patch_site(site, vars_map)
print(f" {site}: {result}")
if __name__ == "__main__":
main()