Files
onepager/tools/remove-footer-toggle.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

112 lines
3.9 KiB
Python

#!/usr/bin/env python3
"""Remove the redundant footer [data-i18n-toggle] block.
After Issue #13 the top-right widget owns toggling. The legacy footer button
+ "Maschinell übersetzt" disclaimer line are now redundant — top-right button
has the same disclaimer in its title tooltip, and ai-disclosure.js still
adds the AI footnote.
Two patterns handled:
Pattern A: <div style="text-align:center;margin-top:16px..."> ... </div>
Pattern B (knzlmgmt, schulfrai): <p class="footer" style="margin-top:12px;">...</p>
Idempotent — sites without the patterns are skipped.
"""
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
SITES_DIR = ROOT / "sites"
# Pattern A: a div wrapping the button + br + small disclaimer.
# Allow extra style segments after margin-top:16px (some sites add ;opacity:1 etc.)
PATTERN_A = re.compile(
r'\s*<div\s+style="text-align:center;margin-top:16px[^"]*">\s*'
r'<button\s+data-i18n-toggle[\s\S]*?</button>\s*'
r'<br>\s*<small[^>]*data-de="Maschinell übersetzt"[^>]*>[\s\S]*?</small>\s*'
r'</div>',
re.IGNORECASE,
)
# Pattern B: <p class="footer" style="margin-top:12px;">...</p>
PATTERN_B = re.compile(
r'\s*<p\s+class="footer"\s+style="margin-top:12px;">'
r'<button\s+data-i18n-toggle[\s\S]*?'
r'</p>',
re.IGNORECASE,
)
# Pattern C: bare button (optionally followed by <br><small>...</small>),
# not enclosed in our standard wrappers. Used by sites that placed the
# toggle inline in a footer paragraph (ichbinotto, kainco, keinefreun,
# omakise) or in a custom-classed div (orakil).
PATTERN_C = re.compile(
r'\s*<button\s+data-i18n-toggle[\s\S]*?</button>'
r'(?:\s*<br>\s*<small[^>]*data-de="Maschinell übersetzt"[^>]*>[\s\S]*?</small>)?',
re.IGNORECASE,
)
# Pattern D: <div class="footer-toggle">...</div> (orakil only, but tolerant)
PATTERN_D = re.compile(
r'\s*<div\s+class="footer-toggle">\s*'
r'<button\s+data-i18n-toggle[\s\S]*?</button>\s*'
r'<br>\s*<small[^>]*data-de="Maschinell übersetzt"[^>]*>[\s\S]*?</small>\s*'
r'</div>',
re.IGNORECASE,
)
def patch(html):
new = html
# Wrappers first (they contain a button); then bare-button fallback.
new, n_a = PATTERN_A.subn("", new)
new, n_b = PATTERN_B.subn("", new)
new, n_d = PATTERN_D.subn("", new)
new, n_c = PATTERN_C.subn("", new)
return new, n_a + n_b + n_c + n_d
def main():
sites = sorted(p for p in SITES_DIR.iterdir() if p.is_dir() and (p / "index.html").exists())
total_removed = 0
sites_changed = 0
sites_with_attr = 0
sites_residual = []
for site in sites:
path = site / "index.html"
before = path.read_text()
if "data-i18n-toggle" not in before:
continue
sites_with_attr += 1
# Count old buttons before removal (we expect exactly 1 footer button per site;
# the new top-right widget injects its own at runtime, not in source).
before_count = len(re.findall(r"data-i18n-toggle", before))
after, removed = patch(before)
after_count = len(re.findall(r"data-i18n-toggle", after))
if removed > 0:
path.write_text(after)
sites_changed += 1
total_removed += removed
print(f" [removed {removed}] {site.name} (data-i18n-toggle: {before_count} -> {after_count})")
if after_count > 0:
sites_residual.append((site.name, after_count))
else:
sites_residual.append((site.name, before_count))
print(f" [no-match] {site.name} (still has {before_count} data-i18n-toggle)")
print(f"\nSites changed: {sites_changed}/{sites_with_attr}")
print(f"Total wrappers removed: {total_removed}")
if sites_residual:
print(f"\nSites with residual data-i18n-toggle attribute (manual review):")
for name, n in sites_residual:
print(f" - {name}: {n}")
if __name__ == "__main__":
main()