Zum Inhalt
← Zugang & Robustheit

Kontrastmodus und contrast-color()

Eine Farbberatungsseite, die im Windows-Kontrastmodus bedienbar bleibt: Knöpfe, Fokus und Auswahl in Systemfarben, nur Farbtöne behalten ihre Farbe, Text auf jedem Ton wählt contrast-color().

Die Seite bleibt bedienbar, wenn Windows einen Kontrastmodus erzwingt. Dann ersetzt der Browser alle Farben durch wenige Systemfarben, box-shadow, text-shadow und Hintergrundverläufe fallen weg. Kleine Korrekturen unter @media (forced-colors: active) halten Knöpfe, Fokus und Auswahlzustände sichtbar: transparente Rahmen und outline statt Schatten, Systemfarben wie CanvasText, LinkText, ButtonText und Highlight. Nur echte Farbmuster behalten mit forced-color-adjust: none ihre Farbe. Für Text auf Markenfarben wählt contrast-color() automatisch Schwarz oder Weiß, mit festem Fallback für ältere Browser.

Menschen mit Sehbehinderung, Migräne oder Lichtempfindlichkeit nutzen den Kontrastmodus. Seiten, die Zustände nur über Hintergrundfarbe oder Schatten zeigen, werden dort unbedienbar: Knöpfe verlieren ihre Kante, der Fokus verschwindet, die Auswahl ist nicht mehr zu erkennen. Mit wenigen Zeilen bleibt alles lesbar, und die Markenfarben im Normalmodus bleiben unangetastet.

Wann einsetzen

Auf jeder Seite, besonders bei Knöpfen ohne Rahmen, Fokusringen per box-shadow, Auswahl über Hintergrundfarbe, Icons als Hintergrundbild und Farbflächen mit Text. contrast-color() überall, wo Kunden Markenfarben wechseln oder Farben aus Daten kommen (Farbtonkarten, Kategorien, Themes).

Wann nicht

Keine eigene Gestaltung für den Kontrastmodus bauen: nur kleine Korrekturen. forced-color-adjust: none nicht großflächig setzen, sonst übersteuert die Seite die Wahl der Nutzer. contrast-color() nicht für Mitteltöne verlassen: dort reichen weder Schwarz noch Weiß sicher für 4,5 : 1.

Varianten

  • Korrekturen per transparentem Rahmen und outline, die der Kontrastmodus sichtbar macht
  • Auswahlzustand mit Highlight und HighlightText statt Hintergrundfarbe
  • Farbmuster und Logos mit forced-color-adjust: none, alles andere mit Systemfarben
  • contrast-color() mit @supports-Fallback für Text auf frei wählbaren Markenfarben
  • Simulation per eigener Klasse mit Systemfarben für Präsentation und Abnahme

Umsetzung

Grundregel: Kanten und Zustände nie nur über Hintergrund oder Schatten. Entweder im Grundstil transparenter Rahmen und transparente outline (der Kontrastmodus färbt sie ein) oder, wie in dieser Demo, alle Korrekturen gesammelt in @media (forced-colors: active): .knopf { border: 2px solid ButtonText } :focus-visible { outline: 3px solid Highlight; outline-offset: 3px } .gewaehlt { background-color: Highlight; color: HighlightText } a { color: LinkText }. Icons als Inline-SVG mit currentColor. forced-color-adjust: none nur für Farbmuster und Vorschauen, in denen die Farbe die Information ist, dazu immer der Name als Text. Text auf Markenfarben: color: var(--auf-marke); @supports (color: contrast-color(red)) { color: contrast-color(var(--marke)) }; den Fallback pro Farbe vorberechnen (Schwarz oder Weiß nach WCAG-Kontrast). Mitteltöne meiden. Die Simulation der Demo ist eine Klasse mit Systemfarben als Variablen und übernimmt die Regeln aus dem echten Block per Script; echt prüfen in Chrome DevTools, Rendering, „Emulate CSS media feature forced-colors“, oder unter Windows, Einstellungen, Barrierefreiheit, Kontrastdesigns.

Robust umsetzen

Telefon. Windows-Kontrastmodus betrifft vor allem Desktop und Tablets mit Windows. contrast-color() wirkt überall. Auf iOS und Android gibt es eigene Einstellungen (z. B. erhöhter Kontrast), die prefers-contrast melden, nicht forced-colors.

Ohne Bewegung. Nicht betroffen; der Wechsel in die Simulation ist ohne Überblendung.

Barrierefrei. 1.4.3 Kontrast (AA): Text auf Markenfarben über contrast-color() oder vorberechneten Fallback mindestens 4,5 : 1; Mitteltöne meiden. 1.4.11 Nicht-Text-Kontrast (AA): Kanten von Knöpfen, Feldern und Auswahl bleiben im Kontrastmodus sichtbar (transparente Rahmen werden zu ButtonText bzw. CanvasText). 2.4.7 Fokus sichtbar (AA): Fokus als outline statt nur box-shadow. 1.4.1 Benutzung von Farbe (A): Auswahl zusätzlich mit Haken und Text, Farbmuster mit Namen. 1.1.1 Nicht-Text-Inhalt (A): Icons als Inline-SVG mit currentColor statt Hintergrundbild. forced-color-adjust: none nur für Farbmuster, weil dort die Farbe selbst die Information ist.

Code

<!DOCTYPE html>
<!--
  Kontrastmodus und contrast-color()
  Fundus · Zugang & Robustheit · https://cecodesign.de/lab/fundus/zugang/kontrastmodus-forced-colors/
  Eigenständige Datei. Bilder liegen hier unter bilder/ – eigene einsetzen.
-->
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Kontrastmodus und contrast-color() · Fundus-Demo</title>
<script>document.documentElement.classList.add('js');if(matchMedia('(prefers-reduced-motion: reduce)').matches)document.documentElement.classList.add('reduziert')</script>
<style>
@font-face { font-family: 'Special Gothic'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/special-gothic:vf@latest/latin-standard-normal.woff2') format('woff2-variations'); font-weight: 400 700; font-stretch: 75% 125%; font-display: swap; }
@font-face { font-family: 'Atkinson Hyperlegible Next'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/atkinson-hyperlegible-next:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 200 800; font-display: swap; }
@font-face { font-family: 'Geist Mono'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/geist-mono:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-display: swap; }

:root {
  /* Rezept Handwerk: Materialfarben als Flächen, ein Signal nur für Anfrage */
  --grund: #efedea; --karte: #f8f7f4; --zement: #d3cdc2; --holz: #c3b4a5; --salbei: #bdc1a9;
  --text: #1a1a1a; --leise: #4a4640; --linie: #c9c2b6;
  --signal: #fb6100;
  --kurve: cubic-bezier(0.4, 0, 0.2, 1);     /* Holzrausch, Tailwind-Standard */
  --ui: 150ms;
  --wand: #9daa8a; --auf-wand: #000000;      /* gesetzt aus der gewählten Musterkarte */
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { background: var(--grund); color: var(--text); font-family: 'Atkinson Hyperlegible Next', system-ui, sans-serif; -webkit-font-smoothing: antialiased; }
body { min-height: 100svh; }
button, input { font: inherit; color: inherit; }
a { color: inherit; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.mono { font: 500 11px/1.3 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; }
.gewerk { font: 600 13px/1.2 'Special Gothic', sans-serif; font-stretch: 75%; letter-spacing: .04em; text-transform: uppercase; }
.ziffern { font-variant-numeric: tabular-nums lining-nums; }

/* ================= Kundenseite ================= */
.seite { min-height: 100svh; display: flex; flex-direction: column; background: var(--grund); }
.kopf { display: flex; align-items: center; gap: 12px 28px; flex-wrap: wrap; padding: 14px clamp(16px, 3vw, 44px); border-bottom: 1px solid var(--linie); }
.marke { display: inline-flex; align-items: center; gap: 12px; text-decoration: none; margin-right: auto; }
.marke svg { width: 40px; height: 40px; flex: none; }
.marke b { display: block; font: 700 25px/1 'Special Gothic', sans-serif; letter-spacing: -.01em; }
.marke small { display: block; font-size: 13px; color: var(--leise); margin-top: 3px; }
.kopf nav ul { list-style: none; display: flex; gap: 2px; flex-wrap: wrap; }
.kopf nav a { display: inline-flex; align-items: center; min-height: 44px; padding: 0 12px; font-weight: 600; font-size: 15.5px; text-decoration: none; border-radius: 4px; transition: background-color var(--ui) var(--kurve); }
.kopf nav a:hover { background: var(--zement); }
.kopf nav a[aria-current="page"] { text-decoration: underline; text-underline-offset: 7px; text-decoration-thickness: 2px; }

/* Knopf: bewusst ohne Rahmen, Fokus als Schatten. Die Korrekturen stehen gesammelt im Kontrastmodus-Block unten. */
.knopf { display: inline-flex; align-items: center; justify-content: center; gap: 10px; min-height: 48px; padding: 0 22px; border: 0; border-radius: 4px; font-weight: 700; font-size: 16px; text-decoration: none; cursor: pointer; transition: background-color var(--ui) var(--kurve), scale var(--ui) var(--kurve); }
.knopf svg { width: 18px; height: 18px; flex: none; }
.knopf.signal { background: var(--signal); color: var(--auf-signal, #000); }
.knopf.zweit { background: var(--zement); color: var(--text); }
.knopf:active { scale: .98; }
.knopf.signal:hover { background: #e35800; }
.knopf.zweit:hover { background: #c7c0b3; }
:focus-visible, .sim-fokus { outline: none; box-shadow: 0 0 0 3px var(--grund), 0 0 0 5px var(--text); }
@supports (color: contrast-color(red)) { .knopf.signal { color: contrast-color(var(--signal)); } }

.buehne { flex: 1; display: grid; grid-template-columns: minmax(0, .95fr) minmax(0, 1.05fr); gap: clamp(20px, 3.2vw, 48px); padding: clamp(22px, 3.4vw, 44px) clamp(16px, 3vw, 44px); align-items: start; }
.text { display: flex; flex-direction: column; gap: 20px; min-width: 0; }
.text h1 { font: 700 clamp(2.25rem, 1.5rem + 3vw, 3.5rem)/1.05 'Special Gothic', sans-serif; letter-spacing: -.01em; text-wrap: balance; }
.text .vorspann { font-size: 1.0625rem; line-height: 1.55; max-width: 46ch; color: var(--leise); text-wrap: pretty; }
.status { display: inline-flex; align-items: center; gap: 8px; font-size: 15px; font-weight: 600; }
.status svg { width: 18px; height: 18px; color: #2f6b3a; }

.wahl-titel { display: flex; justify-content: space-between; gap: 12px; margin-bottom: 8px; }
.ort { position: relative; display: inline-grid; grid-template-columns: 1fr 1fr; padding: 4px; border-radius: 6px; background: var(--zement); isolation: isolate; min-width: 280px; }
.ort::before { content: ''; position: absolute; z-index: -1; inset: 4px auto 4px 4px; width: calc(50% - 4px); border-radius: 4px; background: var(--text); transition: translate var(--ui) var(--kurve); }
.ort:has(input[value="aussen"]:checked)::before { translate: 100% 0; }
.ort label { position: relative; display: grid; place-items: center; min-height: 44px; padding: 0 14px; font-weight: 700; cursor: pointer; transition: color var(--ui) var(--kurve); }
.ort label:has(input:checked) { color: #fff; }
.ort input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.ort label:has(input:focus-visible) { box-shadow: 0 0 0 3px var(--grund), 0 0 0 5px var(--text); border-radius: 4px; }

.leistungen { display: flex; flex-wrap: wrap; gap: 8px; }
.chip { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; padding: 0 16px 0 12px; border: 0; border-radius: 999px; background: var(--karte); font-weight: 600; font-size: 15px; cursor: pointer; transition: background-color var(--ui) var(--kurve), color var(--ui) var(--kurve); }
.chip svg { width: 16px; height: 16px; flex: none; }
.chip .haken { display: none; }
.chip[aria-pressed="true"] { background: var(--text); color: #fff; }
.chip[aria-pressed="true"] .haken { display: block; }
.chip[aria-pressed="true"] .plus { display: none; }

.aktion { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
.preis { font-size: 14.5px; line-height: 1.45; color: var(--leise); }
.preis b { color: var(--text); }

/* Wand-Vorschau: die Farbe ist hier die Information */
.wand { position: relative; border-radius: 6px; overflow: hidden; background: #2a2723; aspect-ratio: 5 / 4; min-width: 0; }
.wand svg { width: 100%; height: 100%; display: block; }
.wand .farbe { fill: var(--wand); transition: fill var(--ui) var(--kurve); }
.wand .farbe-seite { fill: color-mix(in srgb, var(--wand) 80%, #000); transition: fill var(--ui) var(--kurve); }
.wand-text { position: absolute; left: 8%; top: 9%; max-width: 42%; color: var(--auf-wand); transition: color var(--ui) var(--kurve); }
.wand-text b { display: block; font: 700 clamp(1.5rem, 1rem + 1.6vw, 2.5rem)/1 'Special Gothic', sans-serif; }
.wand-text span { display: block; margin-top: 6px; font-size: 15px; line-height: 1.4; }
.wand-text .mono { font-size: 10.5px; margin-top: 10px; opacity: .9; }
@supports (color: contrast-color(red)) { .wand-text { color: contrast-color(var(--wand)); } }

/* Farbtonkarte: Text auf jedem Ton per contrast-color(), Fallback aus data-Wert */
.karte-titel { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; flex-wrap: wrap; padding: 0 clamp(16px, 3vw, 44px); }
.karte-titel h2 { font: 700 1.5rem/1.1 'Special Gothic', sans-serif; }
.karte-titel p { font-size: 14.5px; color: var(--leise); }
.toene { border: 0; padding: 12px clamp(16px, 3vw, 44px) clamp(22px, 3vw, 36px); }
.toene-liste { display: grid; grid-template-columns: repeat(8, minmax(0, 1fr)); gap: 8px; }
.ton { position: relative; display: flex; flex-direction: column; justify-content: space-between; gap: 18px; min-height: 120px; padding: 12px; border-radius: 4px; background: var(--ton); color: var(--auf-ton); cursor: pointer; transition: translate var(--ui) var(--kurve); }
@supports (color: contrast-color(red)) { .ton { color: contrast-color(var(--ton)); } }
.ton input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.ton b { font: 700 17px/1.1 'Special Gothic', sans-serif; }
.ton small { display: block; font: 500 10.5px/1.3 'Geist Mono', monospace; letter-spacing: .06em; }
.ton .wert { display: flex; justify-content: space-between; align-items: end; gap: 6px; }
.ton .knapp { font: 600 10px/1.2 'Geist Mono', monospace; letter-spacing: .04em; text-transform: uppercase; }
.ton svg { width: 20px; height: 20px; flex: none; display: none; }
.ton:has(input:checked) { translate: 0 -6px; box-shadow: 0 0 0 2px var(--grund), 0 0 0 4px var(--text); }
.ton:has(input:checked) svg { display: block; }
.ton:has(input:focus-visible) { box-shadow: 0 0 0 3px var(--grund), 0 0 0 6px var(--text); }

.fuss { display: flex; justify-content: space-between; gap: 8px 24px; flex-wrap: wrap; padding: 16px clamp(16px, 3vw, 44px); border-top: 1px solid var(--linie); font-size: 14px; color: var(--leise); }
.fuss a { text-underline-offset: 3px; }

/* ================= Kontrastmodus: kleine Korrekturen, eine Quelle =================
   Gilt im echten Windows-Kontrastmodus und wird für die Simulation unten 1:1 übernommen. */
@media (forced-colors: active) {
  .knopf { border: 2px solid ButtonText; }
  .knopf.signal { border-width: 3px; }
  :focus-visible, .sim-fokus { outline: 3px solid Highlight; outline-offset: 3px; }
  .ort label:has(input:focus-visible) { outline: 3px solid Highlight; outline-offset: 2px; }
  .ort { border: 1px solid ButtonText; }
  .ort::before { background-color: Highlight; }
  .ort label:has(input:checked) { color: HighlightText; }
  .chip { border: 1px solid ButtonText; }
  .chip[aria-pressed="true"] { background-color: Highlight; color: HighlightText; }
  .ton, .wand { forced-color-adjust: none; }
  .ton { outline: 1px solid CanvasText; }
  .ton:has(input:checked) { outline: 4px solid Highlight; outline-offset: 2px; }
  .ton:has(input:focus-visible) { outline: 4px solid Highlight; outline-offset: 2px; }
  .kopf nav a[aria-current="page"] { color: Highlight; }
  .marke svg circle { fill: CanvasText; }
  .marke svg path { stroke: Canvas; }
}

/* ================= Simulation (eigene Klasse, Systemfarben als Variablen) =================
   Näherung an das, was der Browser erzwingt: Farben auf wenige Systemfarben, Schatten und Verläufe weg.
   Werte nach den klassischen Windows-Designs „Kontrast Schwarz“ und „Kontrast Weiß“. */
.fc-sim { --sys-Canvas: #000000; --sys-CanvasText: #ffffff; --sys-LinkText: #ffff00; --sys-ButtonFace: #000000; --sys-ButtonText: #ffffff; --sys-Highlight: #1aebff; --sys-HighlightText: #000000; --sys-GrayText: #3ff23f; }
.fc-sim.hell { --sys-Canvas: #ffffff; --sys-CanvasText: #000000; --sys-LinkText: #00009f; --sys-ButtonFace: #ffffff; --sys-ButtonText: #000000; --sys-Highlight: #37006e; --sys-HighlightText: #ffffff; --sys-GrayText: #600000; }
.fc-sim, .fc-sim :where(:not([data-fca], [data-fca] *)), .fc-sim :where(:not([data-fca], [data-fca] *))::before, .fc-sim :where(:not([data-fca], [data-fca] *))::after {
  color: var(--sys-CanvasText) !important;
  border-color: var(--sys-CanvasText) !important; outline-color: var(--sys-CanvasText) !important;
  text-decoration-color: currentColor !important; box-shadow: none !important; text-shadow: none !important; background-image: none !important;
}
.fc-sim :where(:not([data-fca], [data-fca] *))::before, .fc-sim :where(:not([data-fca], [data-fca] *))::after { background-color: transparent !important; }
/* Hintergrund nur dort ersetzen, wo einer ist (der Browser behält die Transparenz) */
.fc-sim:where([data-bg]), .fc-sim :where([data-bg]:not([data-fca], [data-fca] *)) { background-color: var(--sys-Canvas) !important; }
.fc-sim :where(a:not([data-fca] *), a:not([data-fca] *) *) { color: var(--sys-LinkText) !important; }
.fc-sim :where(button:not([data-fca] *), button *, input:not([data-fca] *)) { color: var(--sys-ButtonText) !important; }
.fc-sim :where(button[data-bg], input[data-bg]):where(:not([data-fca] *)) { background-color: var(--sys-ButtonFace) !important; }
.fc-sim :where(svg [fill]:not([fill="none"]), svg .farbe, svg .farbe-seite):where(:not([data-fca] *)) { fill: var(--sys-CanvasText) !important; }
.fc-sim :where(svg [stroke]:not([stroke="none"])):where(:not([data-fca] *)) { stroke: var(--sys-CanvasText) !important; }
/* Rangfolge: diese Regeln haben nur die Spezifität von .fc-sim; die übernommenen Korrekturen (.fc-sim.korr …) gewinnen wie im echten Modus die Systemfarben des Autors. */

/* ================= Demo-Ebene (nicht Teil der Kundenseite) ================= */
.marken { position: absolute; inset: 0 auto auto 0; width: 100%; pointer-events: none; z-index: 30; }
.marker { position: absolute; display: grid; place-items: center; width: 28px; height: 28px; margin: -14px 0 0 -14px; border-radius: 50%; font: 700 13px/1 'Geist Mono', monospace; color: #fff; background: #d6263b; box-shadow: 0 0 0 3px #fff, 0 6px 18px rgba(0, 0, 0, .35); opacity: 0; scale: .6; transition: opacity var(--ui) var(--kurve), scale var(--ui) var(--kurve), background-color var(--ui) var(--kurve); }
.marker.zu-sehen { opacity: 1; scale: 1; }
.marker.gut { background: #1f8a4c; }
.reduziert .marker { scale: 1; transition: opacity var(--ui) linear; }
.lage { position: fixed; z-index: 35; left: 50%; top: 82px; translate: -50% 0; display: inline-flex; align-items: center; gap: 10px; padding: 8px 14px 8px 10px; border-radius: 999px; background: #121212; color: #f3f2ee; font: 500 11px/1.2 'Geist Mono', monospace; letter-spacing: .1em; text-transform: uppercase; box-shadow: 0 8px 24px rgba(0, 0, 0, .3); white-space: nowrap; opacity: 0; transition: opacity var(--ui) var(--kurve); pointer-events: none; }
.lage.an { opacity: 1; }
.lage i { width: 9px; height: 9px; border-radius: 50%; background: #d6263b; }
.lage.gut i { background: #39c06e; }

/* Pult kompakt in der rechten oberen Ecke der Raumansicht, scrollt mit der Seite; Befund aufklappbar */
.pult { position: absolute; z-index: 40; right: calc(clamp(16px, 3vw, 44px) + 14px); top: calc(clamp(22px, 3.4vw, 44px) + 90px); width: 272px; display: grid; gap: 10px; padding: 12px; border-radius: 12px; background: rgba(18, 18, 18, .95); color: #f3f2ee; font-size: 13px; box-shadow: 0 20px 50px rgba(0, 0, 0, .35); }
.pult .mono { color: rgba(243, 242, 238, .62); font-size: 10.5px; }
.mehr summary { display: flex; justify-content: space-between; align-items: center; min-height: 32px; padding: 0 2px; cursor: pointer; list-style: none; font: 500 10.5px/1.2 'Geist Mono', monospace; letter-spacing: .14em; text-transform: uppercase; color: rgba(243, 242, 238, .8); border-top: 1px solid rgba(255, 255, 255, .12); }
.mehr summary::-webkit-details-marker { display: none; }
.mehr summary::after { content: '+'; font-size: 15px; letter-spacing: 0; }
.mehr[open] summary::after { content: '−'; }
.mehr[open] { max-height: calc(100svh - 300px); overflow: auto; }
.mehr > :not(summary) { margin-top: 8px; }
.pult-reihe { display: grid; gap: 6px; }
.segment { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; padding: 3px; border-radius: 8px; background: rgba(255, 255, 255, .08); }
.segment button { border: 0; background: none; color: inherit; font: 500 12.5px/1.2 'Atkinson Hyperlegible Next', sans-serif; padding: 8px 6px; min-height: 34px; border-radius: 6px; cursor: pointer; }
.segment button[aria-pressed="true"] { background: #f3f2ee; color: #121212; font-weight: 700; }
.segment button:disabled { opacity: .4; cursor: not-allowed; }
.pult :focus-visible { box-shadow: none; outline: 2px solid #8ec5ff; outline-offset: 2px; }
.befund { list-style: none; display: grid; gap: 4px; counter-reset: b; }
.befund li { display: grid; grid-template-columns: 20px 1fr; gap: 8px; align-items: start; font-size: 12.5px; line-height: 1.35; color: rgba(243, 242, 238, .78); }
.befund li::before { counter-increment: b; content: counter(b); display: grid; place-items: center; width: 18px; height: 18px; border-radius: 50%; background: rgba(255, 255, 255, .14); font: 700 10.5px/1 'Geist Mono', monospace; }
.befund li b { color: #f3f2ee; font-weight: 600; }
.befund.kaputt li::before { background: #d6263b; color: #fff; }
.befund.heil li::before { background: #1f8a4c; color: #fff; }
.hinweis { font-size: 12px; line-height: 1.45; color: rgba(243, 242, 238, .72); border-top: 1px solid rgba(255, 255, 255, .12); padding-top: 10px; }
.hinweis b { color: #f3f2ee; font-weight: 600; }
.hinweis code { font: 500 11px/1.4 'Geist Mono', monospace; color: #ffd8a8; }

/* Reduzierte Bewegung */
.reduziert .ton, .reduziert .ort::before, .reduziert .wand .farbe, .reduziert .wand .farbe-seite, .reduziert .wand-text { transition: none; }
.reduziert .ton:has(input:checked) { translate: none; }

/* Touch */
@media (hover: none) {
  .kopf nav a:hover { background: none; }
  .knopf.signal:hover { background: var(--signal); }
  .knopf.zweit:hover { background: var(--zement); }
}

/* Schmal */
@media (max-width: 1180px) { .toene-liste { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 860px) {
  .buehne { grid-template-columns: minmax(0, 1fr); }
  .kopf nav { order: 3; flex-basis: 100%; }
  .kopf nav ul { margin-inline: -12px; }
  .pult { position: fixed; top: auto; bottom: 12px; right: 12px; max-width: calc(100vw - 24px); }
  .fuss { padding-bottom: 60px; }
}
@media (max-width: 520px) {
  .toene-liste { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ort { min-width: 0; width: 100%; }
  .kopf .knopf.zweit { display: none; }
  .marke b { font-size: 22px; }
  .marke small { font-size: 12px; }
  .wand { aspect-ratio: 4 / 4; }
  .wand-text { max-width: 60%; }
  .lage { font-size: 9.5px; letter-spacing: .06em; }
}

/* Vorschau 960 × 720 */
.vorschau body { overflow: hidden; }
.vorschau .vorspann, .vorschau .preis, .vorschau .fuss, .vorschau .karte-titel p, .vorschau .kopf nav { display: none; }
.vorschau .buehne { padding: 20px 28px 14px; gap: 26px; grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr); }
.vorschau .text { gap: 14px; }
.vorschau .text h1 { font-size: 2.4rem; }
.vorschau .kopf { padding: 12px 28px; }
.vorschau .toene { padding: 10px 28px 0; }
.vorschau .toene-liste { grid-template-columns: repeat(8, minmax(0, 1fr)); }
.vorschau .ton { min-height: 96px; padding: 9px; gap: 10px; }
.vorschau .ton b { font-size: 14px; }
.vorschau .ton small { font-size: 9px; }
.vorschau .karte-titel { padding: 0 28px; }
.vorschau .karte-titel h2 { font-size: 1.2rem; }
.vorschau .wand { aspect-ratio: 16 / 12; }
</style>
</head>
<body>
<div class="seite" data-seite>
  <header class="kopf">
    <a class="marke" href="#" aria-label="Muldenton Malerei und Fassade, Startseite">
      <svg viewBox="0 0 40 40" aria-hidden="true"><circle cx="20" cy="20" r="20" fill="#1a1a1a"/><path d="M11 27V13l9 9 9-9v14" fill="none" stroke="#fb6100" stroke-width="3.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
      <span><b>Muldenton</b><small>Malerei &amp; Fassade · Zwickau</small></span>
    </a>
    <nav aria-label="Hauptnavigation">
      <ul>
        <li><a href="#">Leistungen</a></li>
        <li><a href="#" aria-current="page">Farbberatung</a></li>
        <li><a href="#">Referenzen</a></li>
        <li><a href="#">Kontakt</a></li>
      </ul>
    </nav>
    <a class="knopf zweit" href="#">Rückruf</a>
  </header>

  <main class="buehne" id="inhalt">
    <div class="text">
      <p class="gewerk">Farbberatung vor Ort</p>
      <h1>Erst den Ton finden, dann streichen.</h1>
      <p class="vorspann">Wählen Sie aus unserer Werkstattkarte. Wir bringen Musterflächen mit, streichen sie auf Ihre Wand und lassen sie einen Tag trocknen, bevor die Rolle läuft.</p>
      <p class="status"><svg viewBox="0 0 18 18" aria-hidden="true"><circle cx="9" cy="9" r="8" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M5.2 9.3 7.8 11.8 12.8 6.5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>Termine frei ab KW 48</p>

      <fieldset style="border:0">
        <legend class="wahl-titel"><span class="gewerk">Wo wird gestrichen?</span></legend>
        <div class="ort" data-ort>
          <label><input type="radio" name="ort" value="innen" checked>Innenräume</label>
          <label><input type="radio" name="ort" value="aussen">Fassade</label>
        </div>
      </fieldset>

      <div>
        <p class="wahl-titel" id="leistungen-titel"><span class="gewerk">Was gehört dazu?</span><span class="mono" style="color:var(--leise)" data-zahl>2 gewählt</span></p>
        <div class="leistungen" role="group" aria-labelledby="leistungen-titel">
          <button type="button" class="chip" aria-pressed="true" data-chip><svg class="plus" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg><svg class="haken" viewBox="0 0 16 16" aria-hidden="true"><path d="M3.2 8.4 6.5 11.5 12.8 4.8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Wände</button>
          <button type="button" class="chip" aria-pressed="false" data-chip><svg class="plus" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg><svg class="haken" viewBox="0 0 16 16" aria-hidden="true"><path d="M3.2 8.4 6.5 11.5 12.8 4.8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Decken</button>
          <button type="button" class="chip" aria-pressed="true" data-chip><svg class="plus" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg><svg class="haken" viewBox="0 0 16 16" aria-hidden="true"><path d="M3.2 8.4 6.5 11.5 12.8 4.8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Türen &amp; Lack</button>
          <button type="button" class="chip" aria-pressed="false" data-chip><svg class="plus" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg><svg class="haken" viewBox="0 0 16 16" aria-hidden="true"><path d="M3.2 8.4 6.5 11.5 12.8 4.8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Tapete</button>
        </div>
      </div>

      <div class="aktion">
        <a class="knopf signal" href="#" data-anfrage>Musterfläche anfragen <svg viewBox="0 0 18 18" aria-hidden="true"><path d="M3 9h11M10 4.5 14.5 9 10 13.5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></a>
        <p class="preis ziffern">Innenanstrich <b>ab 14 € pro m²</b>,<br>inklusive Abdecken und Ausbessern.</p>
      </div>
    </div>

    <figure class="wand" data-wand>
      <svg viewBox="0 0 600 480" aria-hidden="true">
        <defs>
          <linearGradient id="licht" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#fff" stop-opacity=".22"/><stop offset=".55" stop-color="#fff" stop-opacity="0"/></linearGradient>
          <linearGradient id="boden" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#b9a48f"/><stop offset="1" stop-color="#8f7864"/></linearGradient>
        </defs>
        <rect class="wand-hintergrund" width="600" height="480" fill="#2a2723"/>
        <!-- Rückwand und Seitenwand in der gewählten Farbe -->
        <path class="farbe" d="M0 0 H470 V372 H0 Z"/>
        <path class="farbe-seite" d="M470 0 L600 0 L600 480 L470 372 Z"/>
        <path d="M0 0 H470 V372 H0 Z" fill="url(#licht)"/>
        <!-- Fenster in der Seitenwand -->
        <path d="M505 70 L572 58 L572 260 L505 252 Z" fill="#e9eef0"/>
        <path d="M538.5 64 L538.5 256 M505 160 L572 158" stroke="#cfc8bc" stroke-width="5"/>
        <path d="M505 70 L572 58 L572 260 L505 252 Z" fill="none" stroke="#f4f1ec" stroke-width="7"/>
        <!-- Boden -->
        <path d="M0 372 H470 L600 480 H0 Z" fill="url(#boden)"/>
        <g stroke="#7d6856" stroke-width="1.5" opacity=".55"><path d="M0 398 H495"/><path d="M0 428 H531"/><path d="M0 458 H567"/></g>
        <!-- Sockelleiste -->
        <path d="M0 362 H470 V372 H0 Z" fill="#f4f1ec"/><path d="M470 362 L600 466 L600 480 L470 372 Z" fill="#e2ddd4"/>
        <!-- Abdeckvlies -->
        <path d="M150 380 L420 380 L470 432 L120 432 Z" fill="#ece8e1" opacity=".9"/>
        <!-- Leiter -->
        <g stroke="#d9d2c6" stroke-width="7" stroke-linecap="round"><path d="M360 418 L392 196"/><path d="M420 418 L428 196"/></g>
        <g stroke="#d9d2c6" stroke-width="5"><path d="M364 388 L416 388"/><path d="M369 348 L414 348"/><path d="M374 308 L412 308"/><path d="M379 268 L410 268"/><path d="M384 228 L408 228"/></g>
        <!-- Eimer mit Musterfarbe -->
        <g transform="translate(206 368)"><path d="M0 0 H64 L58 52 H6 Z" fill="#e6e1d8"/><ellipse cx="32" cy="0" rx="32" ry="7" class="farbe"/><path d="M4 2 Q32 -38 60 2" fill="none" stroke="#8a847a" stroke-width="2.5"/><rect x="14" y="18" width="36" height="16" fill="#1a1a1a"/><path d="M20 29 V23 l6 5 6 -5 v6" fill="none" stroke="#fb6100" stroke-width="2"/></g>
        <!-- Farbrolle an der Wand, frische Bahn -->
        <path class="farbe-seite" d="M88 40 H136 V300 H88 Z" opacity=".35"/>
        <g transform="translate(78 300)"><rect x="0" y="0" width="68" height="20" rx="10" fill="#f4f1ec"/><rect x="2" y="2" width="64" height="16" rx="8" class="farbe"/><path d="M34 20 V40 L60 52 V160" fill="none" stroke="#3a3632" stroke-width="5" stroke-linejoin="round"/></g>
      </svg>
      <figcaption class="wand-text" data-wand-text>
        <b data-wand-name>Salbei</b>
        <span>So wirkt der Ton auf Ihrer Wand, Tageslicht von rechts.</span>
        <span class="mono" data-wand-kontrast>Text 8,55 : 1</span>
      </figcaption>
    </figure>
  </main>

  <div class="karte-titel">
    <h2 id="toene-titel">Werkstattkarte, 8 Töne</h2>
    <p>Textfarbe auf jedem Ton: <code class="mono" style="letter-spacing:0;text-transform:none">contrast-color()</code>, sonst fester Wert.</p>
  </div>
  <fieldset class="toene" aria-labelledby="toene-titel">
    <div class="toene-liste" data-toene></div>
  </fieldset>

  <footer class="fuss">
    <span>Muldenton Malerei &amp; Fassade · Meisterbetrieb · Zwickau und Umgebung bis 40 km</span>
    <span><a href="#">Impressum</a> · <a href="#">Datenschutz</a> · <a href="#">Barrierefreiheit</a></span>
  </footer>
</div>

<div class="marken" data-marken aria-hidden="true"></div>
<div class="lage" data-lage aria-hidden="true"><i></i><span data-lage-text></span></div>

<section class="pult" aria-label="Demo-Steuerung">
  <div class="pult-reihe">
    <p class="mono">Darstellung</p>
    <div class="segment" role="group" aria-label="Darstellung">
      <button type="button" aria-pressed="true" data-modus="normal">Normal</button>
      <button type="button" aria-pressed="false" data-modus="sim">Kontrastmodus</button>
    </div>
  </div>
  <div class="pult-reihe">
    <p class="mono">Korrekturen (forced-colors-Block)</p>
    <div class="segment" role="group" aria-label="Korrekturen">
      <button type="button" aria-pressed="false" data-korr="aus">Aus (vorher)</button>
      <button type="button" aria-pressed="true" data-korr="an">An (nachher)</button>
    </div>
  </div>
  <div class="pult-reihe">
    <p class="mono">Systemfarben</p>
    <div class="segment" role="group" aria-label="Systemfarben">
      <button type="button" aria-pressed="true" data-thema="dunkel">Kontrast Schwarz</button>
      <button type="button" aria-pressed="false" data-thema="hell">Kontrast Weiß</button>
    </div>
  </div>
  <details class="mehr">
    <summary>Befund und Hinweise</summary>
    <ol class="befund" data-befund>
      <li><span><b>Signal-Knopf</b>: ohne Rahmen keine Kante, mit Rahmen in ButtonText</span></li>
      <li><span><b>Fokus</b>: box-shadow fällt weg, outline in Highlight bleibt</span></li>
      <li><span><b>Innen / Fassade</b>: Pille weg, mit Highlight wieder sichtbar</span></li>
      <li><span><b>Leistungen</b>: nur noch der Haken, mit Highlight eindeutig</span></li>
      <li><span><b>Farbtöne, Wand, Logo</b>: alles einfarbig; Töne und Wand mit forced-color-adjust: none, Logo in Systemfarben</span></li>
    </ol>
    <p class="hinweis" data-unterstuetzung></p>
    <p class="hinweis"><b>Echt prüfen:</b> Chrome DevTools, Rendering, „Emulate CSS media feature forced-colors“ auf <code>forced-colors: active</code>. Unter Windows: Einstellungen, Barrierefreiheit, Kontrastdesigns.</p>
  </details>
</section>

<script>
(() => {
  const html = document.documentElement;
  const reduziert = () => html.classList.contains('reduziert');
  const seite = document.querySelector('[data-seite]');
  const wand = document.querySelector('[data-wand]');
  const wandName = document.querySelector('[data-wand-name]');
  const wandKontrast = document.querySelector('[data-wand-kontrast]');
  const wandText = document.querySelector('[data-wand-text]');
  const toeneListe = document.querySelector('[data-toene]');
  const markenEbene = document.querySelector('[data-marken]');
  const lage = document.querySelector('[data-lage]');
  const lageText = document.querySelector('[data-lage-text]');
  const befund = document.querySelector('[data-befund]');
  const anfrage = document.querySelector('[data-anfrage]');
  const unterstuetzt = CSS.supports('color', 'contrast-color(red)');

  /* ---------- WCAG-Kontrast (für Fallback und Anzeige) ---------- */
  const lum = rgb => { const c = rgb.map(v => { v /= 255; return v <= .04045 ? v / 12.92 : ((v + .055) / 1.055) ** 2.4; }); return .2126 * c[0] + .7152 * c[1] + .0722 * c[2]; };
  const hex = h => [1, 3, 5].map(i => parseInt(h.slice(i, i + 2), 16));
  const verh = (a, b) => { const [x, y] = [lum(a), lum(b)].sort((p, q) => q - p); return (x + .05) / (y + .05); };
  const rgbAus = s => { const n = (s.match(/[\d.]+/g) || [0, 0, 0]).slice(0, 3).map(Number); return s.startsWith('color(') ? n.map(v => v * 255) : n; };
  const zahl = v => v.toLocaleString('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  // Fallback wie contrast-color(): Schwarz oder Weiß, was stärker kontrastiert; Gleichstand → Weiß
  const aufTon = h => verh(hex(h), [0, 0, 0]) > verh(hex(h), [255, 255, 255]) ? '#000000' : '#ffffff';

  /* Signalfarbe: Fallback vorberechnet, contrast-color() überschreibt, wo vorhanden */
  html.style.setProperty('--auf-signal', aufTon('#fb6100'));

  /* ---------- Farbtonkarte ---------- */
  const TOENE = [
    ['Kalkweiß', '#f2efe8', 'MT 01'], ['Lehm', '#c9a27e', 'MT 07'], ['Salbei', '#9daa8a', 'MT 12'], ['Ocker', '#c98a2b', 'MT 15'],
    ['Rost', '#b5603a', 'MT 21'], ['Muldenblau', '#3e6e9e', 'MT 28'], ['Tannengrün', '#2f4a3a', 'MT 33'], ['Anthrazit', '#33363a', 'MT 40']
  ];
  TOENE.forEach(([n, h, code], i) => {
    const l = document.createElement('label');
    l.className = 'ton';
    l.style.setProperty('--ton', h);
    l.style.setProperty('--auf-ton', aufTon(h));
    l.innerHTML = `<input type="radio" name="ton" value="${h}" ${n === 'Salbei' ? 'checked' : ''}><span><b>${n}</b><small>${code} · ${h.toUpperCase()}</small></span><span class="wert" aria-hidden="true"><span class="knapp ziffern" data-k></span><svg viewBox="0 0 20 20" aria-hidden="true"><circle cx="10" cy="10" r="9" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M5.8 10.2 8.6 13 14.2 7.2" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span>`;
    toeneListe.appendChild(l);
  });
  const tonLabels = [...toeneListe.querySelectorAll('.ton')];
  function kontrasteZeigen() {
    if (seite.classList.contains('fc-sim')) return;   // gemessen wird im Normalmodus
    tonLabels.forEach(l => {
      const t = hex(l.style.getPropertyValue('--ton').trim());
      const v = verh(t, rgbAus(getComputedStyle(l).color));
      const k = l.querySelector('[data-k]');
      k.textContent = `${zahl(v)} : 1${v < 5 ? ' · knapp' : ''}`;
      k.title = v < 5 ? 'Mittelton: rechnerisch über 4,5 : 1, aber schwer lesbar. Nur groß setzen.' : '';
    });
  }
  function tonWaehlen(h, name) {
    seite.style.setProperty('--wand', h);
    seite.style.setProperty('--auf-wand', aufTon(h));
    wandName.textContent = name;
    // erst nach dem Farbübergang (150 ms) messen, sonst liest man den Zwischenwert
    clearTimeout(tonWaehlen.t);
    tonWaehlen.t = setTimeout(() => {
      // in der Simulation dieselbe Regel wie contrast-color() rechnen, sonst den echten Wert messen
      const farbe = seite.classList.contains('fc-sim') ? hex(aufTon(h)) : rgbAus(getComputedStyle(wandText).color);
      const v = verh(hex(h), farbe);
      wandKontrast.textContent = `Text ${zahl(v)} : 1 · ${unterstuetzt ? 'contrast-color()' : 'Fallback'}`;
    }, reduziert() ? 0 : 220);
  }
  toeneListe.addEventListener('change', e => { const l = e.target.closest('.ton'); tonWaehlen(e.target.value, l.querySelector('b').textContent); });

  /* ---------- Chips und Zähler ---------- */
  const chips = [...document.querySelectorAll('[data-chip]')];
  const zaehler = document.querySelector('[data-zahl]');
  const zaehlen = () => { const n = chips.filter(c => c.getAttribute('aria-pressed') === 'true').length; zaehler.textContent = n ? `${n} gewählt` : 'nichts gewählt'; };
  chips.forEach(c => c.addEventListener('click', () => { c.setAttribute('aria-pressed', String(c.getAttribute('aria-pressed') !== 'true')); zaehlen(); }));
  document.querySelectorAll('a[href="#"]').forEach(a => a.addEventListener('click', e => e.preventDefault()));

  /* ---------- Simulation: Korrekturen aus dem echten @media-Block übernehmen ---------- */
  const SYS = /\b(canvastext|canvas|linktext|buttonface|buttontext|highlighttext|highlight|graytext)\b/gi;
  const SYS_NAME = { canvastext: 'CanvasText', canvas: 'Canvas', linktext: 'LinkText', buttonface: 'ButtonFace', buttontext: 'ButtonText', highlighttext: 'HighlightText', highlight: 'Highlight', graytext: 'GrayText' };
  let fcaSelektoren = [];
  (function korrekturenUebernehmen() {
    const regeln = [];
    for (const blatt of document.styleSheets) {
      let liste; try { liste = blatt.cssRules; } catch (e) { continue; }
      for (const r of liste) {
        if (!(r.media && /forced-colors:\s*active/.test(r.conditionText || r.media.mediaText))) continue;
        for (const innen of r.cssRules) {
          if (!innen.selectorText) continue;
          const sel = innen.selectorText.split(',').map(s => `.fc-sim.korr ${s.trim()}`).join(', ');
          const dekl = [];
          for (let i = 0; i < innen.style.length; i++) {
            const p = innen.style[i];
            const w = innen.style.getPropertyValue(p);
            if (p === 'forced-color-adjust') { if (w.trim() === 'none') fcaSelektoren.push(innen.selectorText); continue; }
            dekl.push(`${p}: ${w.replace(SYS, m => `var(--sys-${SYS_NAME[m.toLowerCase()]})`)} !important`);
          }
          if (dekl.length) regeln.push(`${sel} { ${dekl.join('; ')} }`);
        }
      }
    }
    const st = document.createElement('style');
    st.textContent = regeln.join('\n');
    document.head.appendChild(st);
  })();
  function fcaMarkieren(an) {
    seite.querySelectorAll('[data-fca]').forEach(e => e.removeAttribute('data-fca'));
    if (an) fcaSelektoren.forEach(s => seite.querySelectorAll(s).forEach(e => e.setAttribute('data-fca', '')));
  }

  /* ---------- Marker an den Problemstellen ---------- */
  const ZIELE = [() => anfrage, () => document.querySelector('.sim-fokus') || chips[1], () => document.querySelector('[data-ort]'), () => chips[0], () => document.getElementById('toene-titel')];
  const marker = ZIELE.map((z, i) => { const m = document.createElement('span'); m.className = 'marker'; m.textContent = i + 1; markenEbene.appendChild(m); return m; });
  function markerLage() {
    ZIELE.forEach((z, i) => {
      const el = z(); if (!el) return;
      const r = el.getBoundingClientRect();
      const rechts = el.tagName === 'H2';
      marker[i].style.left = (rechts ? r.right + 22 : r.left + Math.min(r.width - 6, 18)) + scrollX + 'px';
      marker[i].style.top = (rechts ? r.top + r.height / 2 + 2 : r.top + 2) + scrollY + 'px';
    });
  }

  /* ---------- Zustände ---------- */
  const st = { modus: 'normal', korr: true, thema: 'dunkel' };
  const knoepfe = sel => [...document.querySelectorAll(sel)];
  function hintergruendeMerken() {
    [seite, ...seite.querySelectorAll('*')].forEach(el => {
      const b = getComputedStyle(el).backgroundColor;
      const z = b.match(/[\d.]+/g) || [];
      const alpha = b === 'transparent' ? 0 : (b.includes('/') || b.startsWith('rgba')) ? +z[3] : 1;
      el.toggleAttribute('data-bg', alpha > 0);
    });
  }
  function setze(neu) {
    const warSim = seite.classList.contains('fc-sim');
    Object.assign(st, neu);
    const sim = st.modus === 'sim';
    if (sim && !warSim) hintergruendeMerken();   // im Normalzustand messen
    seite.classList.toggle('fc-sim', sim);
    seite.classList.toggle('korr', sim && st.korr);
    seite.classList.toggle('hell', st.thema === 'hell');
    fcaMarkieren(sim && st.korr);
    knoepfe('[data-modus]').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.modus === st.modus)));
    knoepfe('[data-korr]').forEach(b => { b.setAttribute('aria-pressed', String((b.dataset.korr === 'an') === st.korr)); b.disabled = !sim; });
    knoepfe('[data-thema]').forEach(b => { b.setAttribute('aria-pressed', String(b.dataset.thema === st.thema)); b.disabled = !sim; });
    befund.classList.toggle('kaputt', sim && !st.korr);
    befund.classList.toggle('heil', sim && st.korr);
    markerLage();
    marker.forEach(m => { m.classList.toggle('zu-sehen', sim); m.classList.toggle('gut', sim && st.korr); m.textContent = sim && st.korr ? '✓' : String(marker.indexOf(m) + 1); });
    lage.classList.toggle('an', sim);
    lage.classList.toggle('gut', sim && st.korr);
    lageText.textContent = sim ? (st.korr ? `Kontrastmodus simuliert · mit Korrekturen · 0 von 5 Problemen` : `Kontrastmodus simuliert · ohne Korrekturen · 5 Probleme`) : '';
    if (!sim) requestAnimationFrame(() => { kontrasteZeigen(); tonWaehlen(getComputedStyle(seite).getPropertyValue('--wand').trim() || '#9daa8a', wandName.textContent); });
  }
  knoepfe('[data-modus]').forEach(b => b.addEventListener('click', () => { stopp(); setze({ modus: b.dataset.modus }); }));
  knoepfe('[data-korr]').forEach(b => b.addEventListener('click', () => { stopp(); setze({ korr: b.dataset.korr === 'an' }); }));
  knoepfe('[data-thema]').forEach(b => b.addEventListener('click', () => { stopp(); setze({ thema: b.dataset.thema }); }));
  addEventListener('resize', markerLage);
  new ResizeObserver(markerLage).observe(document.body);

  document.querySelector('[data-unterstuetzung]').innerHTML = unterstuetzt
    ? '<b>contrast-color():</b> unterstützt. Der Browser wählt Schwarz oder Weiß, der Fallback-Wert bleibt ungenutzt.'
    : '<b>contrast-color():</b> nicht unterstützt. Es greift der vorberechnete Fallback (gleiche Regel: Schwarz oder Weiß nach WCAG-Kontrast).';

  tonWaehlen('#9daa8a', 'Salbei');
  document.fonts && document.fonts.ready.then(() => { kontrasteZeigen(); markerLage(); });
  setze({});

  /* ---------- Vorführung ---------- */
  let zeit = [], laeuft = false;
  const nach = (ms, f) => zeit.push(setTimeout(f, ms));
  function ton(i) { const inp = tonLabels[i].querySelector('input'); inp.checked = true; tonWaehlen(inp.value, tonLabels[i].querySelector('b').textContent); }
  function fokusSim(el) { document.querySelectorAll('.sim-fokus').forEach(e => e.classList.remove('sim-fokus')); if (el) el.classList.add('sim-fokus'); markerLage(); }
  function runde() {
    setze({ modus: 'normal', korr: true, thema: 'dunkel' }); fokusSim(null); ton(2);
    let t = 600;
    [4, 5, 7, 1].forEach(i => { nach(t, () => ton(i)); t += 900; });   // Wand und Text wechseln Schwarz/Weiß
    nach(t, () => fokusSim(chips[1]));
    t += 900;
    nach(t, () => setze({ modus: 'sim', korr: false }));                 // vorher: was der Modus kaputt macht
    t += 3000;
    nach(t, () => setze({ korr: true }));                                // nachher: dieselben Regeln, korrigiert
    t += 2600;
    nach(t, () => { setze({ thema: 'hell' }); ton(6); });
    t += 2400;
    nach(t, () => { setze({ modus: 'normal' }); fokusSim(null); });
    t += 1200;
    nach(t, runde);
  }
  function stopp() { zeit.forEach(clearTimeout); zeit = []; if (laeuft) { laeuft = false; html.classList.remove('vorfuehrung'); fokusSim(null); } }
  function neustart() { stopp(); fokusSim(null); setze({ modus: 'normal', korr: true, thema: 'dunkel' }); const i = 2; ton(i); }
  function vorfuehren() { neustart(); html.classList.add('vorfuehrung'); laeuft = true; runde(); }
  function pause() { zeit.forEach(clearTimeout); zeit = []; }
  function weiter() { if (html.classList.contains('vorfuehrung')) { laeuft = true; runde(); } else vorfuehren(); }

})();
</script>
</body>
</html>