Zum Inhalt
← Zugang & Robustheit

Fehlerzusammenfassung im Formular

Nach dem Absenden steht oben, wie viele Angaben fehlen, mit Sprunglinks zu den Feldern; beim Korrigieren hakt sich die Liste live ab.

Lange Formulare ab etwa fünf Feldern: Kursanmeldung, Mitgliedsantrag, Bewerbung, Checkout.

Nach einem Absende-Versuch mit Fehlern erscheint über dem Formular ein Kasten: Überschrift mit der Zahl der offenen Angaben, darunter je Fehler ein Link zum Feld. Der Fokus springt auf diesen Kasten, Screenreader lesen Überschrift und Liste vor. Am Feld selbst steht derselbe Fehlertext, verknüpft über aria-describedby, das Feld trägt aria-invalid="true". Wer korrigiert, sieht den Eintrag in der Liste sofort als erledigt; eine höfliche Live-Region meldet, wie viele Angaben noch fehlen. Ergänzt validierung-nach-interaktion: dort geht es um das einzelne Feld, hier um den Überblick.

Bei langen Formularen sieht man sonst nur den ersten Fehler oder gar keinen, weil er außerhalb des Bildschirms liegt. Die Liste sagt sofort, wie viel noch fehlt und wo. Ein Klick führt hin. Das hilft allen, besonders Menschen mit Screenreader, Bildschirmlupe oder wenig Konzentration.

Wann einsetzen

Formulare ab etwa fünf Feldern oder mit Feldern außerhalb des sichtbaren Bereichs: Kursanmeldung, Mitgliedsantrag, Bewerbung, Schadensmeldung, Checkout, Mehrschritt-Anfragen (je Schritt).

Wann nicht

Bei ein bis drei Feldern (Newsletter, Suche) reicht die Meldung am Feld plus Fokus auf das erste fehlerhafte Feld. Nicht als einzige Fehleranzeige: der Text am Feld bleibt Pflicht. Nicht schon beim Tippen anzeigen.

Varianten

  • Liste mit Sprunglinks über den Feldern, Fokus auf den Kasten (GOV.UK-Prinzip)
  • Kompakt: ein Satz mit Links über dem Absende-Knopf, Fokus aufs erste fehlerhafte Feld
  • Je Schritt einer Mehrschritt-Anfrage, oben im aktuellen Schritt
  • Serverseitig gerendert ohne JS, Seitentitel beginnt mit „Fehler:“

Umsetzung

Formular mit novalidate, Prüfung beim Absenden je Feld. Bei Fehlern den Kasten <div role="group" tabindex="-1" aria-labelledby="zf-titel"> über den Feldern füllen (h3 „Noch 3 Angaben, dann bist du dabei.“, je Fehler <a href="#feld-id"><small>Label</small><b>Fehlertext</b></a>), hidden entfernen und kasten.focus() aufrufen. Linkklick: preventDefault, Feldblock samt Label ins Bild scrollen (window.scrollTo mit 24 px Rand, Rücksicht auf feste Kopfzeilen), dann feld.focus({preventScroll:true}); bei Radios das gewählte oder erste Radio. Am Feld: <p id="f-email-fehler"><span class="sr">Fehler: </span>…</p>, input aria-describedby="f-email-fehler" und aria-invalid. Radiogruppen: fieldset aria-describedby="hinweis fehler" (aria-invalid gibt es für radio nicht). Nach dem ersten Absenden: input entlastet ein Feld sofort und hakt den Eintrag ab, change meldet neue Fehler und aktualisiert die Statuszeile role="status" („Noch 2 Angaben offen.“). Fokus nur beim Absenden verschieben. Erfolg: Formular ersetzen und die Dankes-Überschrift (tabindex=-1) fokussieren. Variante Kompakt: statt Kasten ein Satz über dem Knopf („2 Angaben fehlen: E-Mail, Starttermin.“) und Fokus direkt auf das erste fehlerhafte Feld. In der Demo setzt die Vorführung keinen echten Fokus (sonst stiehlt sie der Galerie den Fokus), sondern zeigt ihn als Klasse.

Robust umsetzen

Telefon. Kasten volle Breite, Links mit mindestens 44 px Höhe. Beim Sprung zum Feld wird das Label mit sichtbar, damit klar ist, welches Feld gemeint ist. Eingaben mindestens 16 px, sonst zoomt iOS.

Ohne Bewegung. Kasten und Einträge erscheinen ohne Weg und ohne Feder, nur Farbwechsel. Kein weiches Scrollen beim Sprung.

Barrierefrei. WCAG 3.3.1 Fehlererkennung: Fehler werden als Text benannt, nicht nur rot. 3.3.3 Korrekturvorschlag: jeder Text sagt, wie es richtig geht. 3.3.2 Beschriftungen: sichtbare Labels, Pflicht im Label. 4.1.2 Name, Rolle, Wert: aria-invalid und aria-describedby synchron. 4.1.3 Statusmeldungen: Restzahl über role="status" ohne Fokuswechsel. 2.4.3 Fokusreihenfolge: Fokus nach dem Absenden auf den Kasten, danach in DOM-Reihenfolge. 2.4.11 Fokus nicht verdeckt: scroll-margin-top. 2.4.7 Fokus sichtbar. 1.4.1 Farbe: Fehler und Erledigt immer mit Zeichen und Text. 1.4.3 und 1.4.11 Kontrast: Text 4,5 : 1, Feldrahmen 3 : 1. 3.2.2 Bei Eingabe: kein Fokussprung beim Korrigieren. 2.5.8 Zielgröße: Links und Radios mindestens 24 × 24 px.

Code

<!DOCTYPE html>
<!--
  Fehlerzusammenfassung im Formular
  Fundus · Zugang & Robustheit · https://cecodesign.de/lab/fundus/zugang/formular-fehlerzusammenfassung/
  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>Anmeldung · Kontenklar</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: 'Bricolage Grotesque'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/bricolage-grotesque:vf@latest/latin-opsz-normal.woff2') format('woff2-variations'); font-weight: 200 800; font-display: swap; }
@font-face { font-family: 'Onest'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/onest:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-display: swap; }
@font-face { font-family: 'Shantell Sans'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/shantell-sans:vf@latest/latin-full-normal.woff2') format('woff2-variations'); font-weight: 300 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 kurs-coaching-lernweg: heller kühler Grund, fast schwarze Tinte, ein Markenton, eigener Erfolgston.
     Alle Texttöne erreichen 4,5 : 1, Feldrahmen und Zeichen 3 : 1. */
  --grund: #F4F6FA; --karte: #FFFFFF; --tinte: #141414; --leise: #5B6170;
  --rahmen: #7A8091;            /* Feldrahmen 3,9 : 1 auf Weiß */
  --linie: #DDE1EA;             /* nur Deko-Linien */
  --marke: #4459D6;             /* Knopf, Link, Fokus: 5,8 : 1 auf Weiß */
  --marke-hell: #7491FF;        /* nur Flächen und Illustration */
  --marke-zart: #E9EDFF;
  --erfolg: #0B7A4E;            /* Text 5,4 : 1 */
  --erfolg-hell: #10BF7A;       /* nur Fläche, Häkchen in Weiß darauf */
  --erfolg-zart: #EAF7F0;
  --gelb: #F7C325;
  --fehler: #B3261E;            /* Text und Rahmen 6,5 : 1 */
  --fehler-zart: #FFF4F2;
  --kurve: cubic-bezier(0.22, 1, 0.36, 1);      /* easeOutQuint */
  --feder: cubic-bezier(0.34, 1.56, 0.64, 1);   /* nur für den Erfolgsmoment */
  --kasten: 300ms; --eintrag: 200ms; --farbe: 150ms; --erfolg-dauer: 500ms;
  --fokus: 0 0 0 3px var(--karte), 0 0 0 6px var(--marke);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { background: var(--grund); color: var(--tinte); font: 400 17px/1.6 'Onest', system-ui, sans-serif; -webkit-font-smoothing: antialiased; }
body { min-height: 100svh; }
button, input { font: inherit; color: inherit; }
a { color: var(--marke); text-underline-offset: .18em; text-decoration-thickness: 1.5px; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.mono { font: 500 11px/1.35 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; color: var(--leise); }
.hand { font-family: 'Shantell Sans', 'Onest', sans-serif; font-variation-settings: 'INFM' 40, 'BNCE' 0; }
.ziffern { font-variant-numeric: tabular-nums; }
h1, h2, h3 { font-family: 'Bricolage Grotesque', 'Onest', sans-serif; font-optical-sizing: auto; letter-spacing: -.02em; text-wrap: balance; }

.sprung { position: absolute; left: 16px; top: -60px; z-index: 10; background: var(--tinte); color: #fff; padding: 10px 16px; border-radius: 10px; font-weight: 600; text-decoration: none; transition: top var(--farbe) var(--kurve); }
.sprung:focus { top: 12px; outline: none; box-shadow: var(--fokus); }

/* ---------- Kopf ---------- */
.kopf { max-width: 1240px; margin: 0 auto; padding: 22px clamp(16px, 3.4vw, 40px); display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.marke { display: inline-flex; align-items: center; gap: 10px; color: var(--tinte); text-decoration: none; font: 700 21px/1 'Bricolage Grotesque', sans-serif; letter-spacing: -.035em; }
.marke svg { width: 30px; height: 30px; flex: none; }
.marke small { font: 500 13px/1 'Onest', sans-serif; letter-spacing: 0; color: var(--leise); margin-left: 4px; }
.varianten { display: flex; align-items: center; gap: 10px; }
.varianten > .mono { font-size: 10.5px; }
.varianten div { display: flex; background: var(--karte); border: 1px solid var(--linie); border-radius: 999px; padding: 3px; }
.varianten button { font: 500 10.5px/1 'Geist Mono', monospace; letter-spacing: .12em; text-transform: uppercase; background: none; border: 0; border-radius: 999px; padding: 9px 13px; cursor: pointer; color: var(--leise); transition: background-color var(--farbe) var(--kurve), color var(--farbe) var(--kurve); }
.varianten button[aria-pressed="true"] { background: var(--tinte); color: #fff; }
.varianten button:focus-visible { outline: none; box-shadow: var(--fokus); }

/* ---------- Bühne ---------- */
.buehne { max-width: 1240px; margin: 0 auto; padding: 0 clamp(16px, 3.4vw, 40px) 80px; display: grid; grid-template-columns: minmax(0, .82fr) minmax(0, 1fr); gap: clamp(20px, 3vw, 44px); align-items: start; }

/* Kurs-Tafel */
.kurs { position: sticky; top: 20px; background: var(--tinte); color: #fff; border-radius: 28px; padding: clamp(24px, 2.6vw, 36px); overflow: hidden; isolation: isolate; display: grid; gap: 22px; }
.kurs .mono { color: rgba(255, 255, 255, .72); }
.kurs h1 { font-size: clamp(2.25rem, 1.4rem + 2.4vw, 3.6rem); line-height: 1.02; font-weight: 700; }
.kurs h1 em { font-style: normal; color: var(--gelb); }
.kurs > p:not(.mono) { color: rgba(255, 255, 255, .82); max-width: 40ch; font-size: 16px; }
.eckdaten { display: grid; grid-template-columns: repeat(3, auto); justify-content: start; gap: 6px 28px; padding: 16px 0; border-block: 1px solid rgba(255, 255, 255, .16); }
.eckdaten dt { font: 500 10.5px/1.3 'Geist Mono', monospace; letter-spacing: .14em; text-transform: uppercase; color: rgba(255, 255, 255, .66); }
.eckdaten dd { font: 650 19px/1.25 'Bricolage Grotesque', sans-serif; letter-spacing: -.01em; }
.module { list-style: none; display: grid; gap: 2px; }
.module li { display: grid; grid-template-columns: 84px 1fr; gap: 12px; align-items: baseline; padding: 7px 0; font-size: 15px; color: rgba(255, 255, 255, .88); }
.module .hand { font-size: 13.5px; color: var(--marke-hell); }
.kurs-kopf { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; margin-bottom: -18px; }
.kurs-kopf .mono { padding-top: 4px; }
.belege { width: 132px; height: auto; flex: none; margin: -6px -6px 0 0; }
.kurs .person { display: flex; gap: 12px; align-items: center; font-size: 14px; color: rgba(255, 255, 255, .8); }
.kurs .person svg { width: 40px; height: 40px; flex: none; }

/* Formular-Karte */
.anmeldung { background: var(--karte); border-radius: 28px; padding: clamp(22px, 3vw, 44px); box-shadow: 0 1px 0 rgba(20, 20, 20, .04), 0 24px 60px -30px rgba(40, 52, 110, .25); }
.anmeldung > header { display: grid; gap: 6px; margin-bottom: 26px; }
.anmeldung h2 { font-size: clamp(1.75rem, 1.2rem + 1.4vw, 2.5rem); line-height: 1.05; font-weight: 700; }
.anmeldung > header p:not(.mono) { color: var(--leise); font-size: 16px; max-width: 52ch; }

/* ---------- Kern: Fehlerzusammenfassung ---------- */
.zf { border: 2px solid var(--fehler); background: var(--fehler-zart); border-radius: 18px; padding: 20px 22px 18px; margin-bottom: 30px; scroll-margin-top: 24px;
  transition: border-color var(--farbe) var(--kurve), background-color var(--farbe) var(--kurve); }
.zf:focus, .zf.fokus-sim { outline: 3px solid var(--marke); outline-offset: 4px; }
.zf.neu { animation: zf-rein var(--kasten) var(--kurve) both; }
@keyframes zf-rein { from { opacity: 0; translate: 0 -8px; } }
.zf-kopf { display: flex; gap: 14px; align-items: flex-start; }
.zf-zeichen { flex: none; width: 34px; height: 34px; border-radius: 50%; background: var(--fehler); color: #fff; display: grid; place-items: center; font: 700 17px/1 'Bricolage Grotesque', sans-serif; transition: background-color var(--farbe) var(--kurve); }
.zf h3 { font-size: 21px; line-height: 1.2; font-weight: 700; color: var(--tinte); padding-top: 3px; }
.zf-kopf p { font-size: 15px; color: var(--leise); margin-top: 2px; }
.zf ul { list-style: none; margin: 14px 0 0 48px; display: grid; gap: 2px; }
.zf li { animation: eintrag-rein var(--eintrag) var(--kurve) both; animation-delay: calc(var(--i, 0) * 40ms); }
@keyframes eintrag-rein { from { opacity: 0; translate: 0 4px; } }
.zf li a { display: grid; grid-template-columns: 1fr auto; gap: 2px 12px; align-items: center; min-height: 44px; padding: 8px 12px; margin-inline: -12px; border-radius: 10px; color: var(--fehler); text-decoration: none; transition: background-color var(--farbe) var(--kurve), color var(--farbe) var(--kurve); }
.zf li a b { font-weight: 600; text-decoration: underline; text-decoration-thickness: 1.5px; text-underline-offset: .18em; }
.zf li a small { grid-column: 1; font: 500 10.5px/1.3 'Geist Mono', monospace; letter-spacing: .12em; text-transform: uppercase; color: var(--leise); }
.zf li a .pfeil { grid-row: 1 / span 2; grid-column: 2; font-size: 18px; transition: translate var(--farbe) var(--kurve); }
.zf li a:hover, .zf li a.zeigt { background: rgba(179, 38, 30, .08); }
.zf li a:hover .pfeil, .zf li a.zeigt .pfeil { translate: 0 3px; }
.zf li a:focus-visible { outline: none; box-shadow: var(--fokus); }
.zf li.erledigt a { color: var(--erfolg); }
.zf li.erledigt a b { text-decoration-line: line-through; text-decoration-thickness: 1.5px; }
.zf li .haken { display: none; }
.zf li.erledigt .pfeil { display: none; }
.zf li.erledigt .haken { display: inline-flex; grid-row: 1 / span 2; grid-column: 2; align-items: center; gap: 6px; font-size: 14px; animation: haken-rein var(--erfolg-dauer) var(--feder) both; }
.zf li.erledigt .haken svg { width: 22px; height: 22px; }
@keyframes haken-rein { from { opacity: 0; scale: .6; } }
.zf.fertig { border-color: var(--erfolg); background: var(--erfolg-zart); }
.zf.fertig .zf-zeichen { background: var(--erfolg); }

/* Kompakt-Variante: ein Satz über dem Knopf */
.kompakt { display: none; border-left: 4px solid var(--fehler); background: var(--fehler-zart); border-radius: 4px 12px 12px 4px; padding: 12px 16px; margin-bottom: 18px; font-size: 15.5px; color: var(--tinte); }
.kompakt.an { display: block; animation: zf-rein var(--kasten) var(--kurve) both; }
.kompakt a { color: var(--fehler); font-weight: 600; }
.kompakt a.zeigt { background: rgba(179, 38, 30, .1); border-radius: 4px; }
.kompakt.fertig { border-color: var(--erfolg); background: var(--erfolg-zart); }
.kompakt a:focus-visible { outline: none; box-shadow: var(--fokus); border-radius: 4px; }

/* ---------- Felder ---------- */
.reihe { display: grid; grid-template-columns: 1fr 1fr; gap: 0 18px; }
.feld { border: 0; min-width: 0; margin-bottom: 22px; scroll-margin-top: 24px; }
.feld > label, .feld > legend { display: block; font-weight: 600; font-size: 15.5px; margin-bottom: 6px; }
.feld .optional { font-weight: 400; color: var(--leise); }
.hinweis { font-size: 14px; line-height: 1.45; color: var(--leise); margin: -2px 0 8px; }
.feld input[type="text"], .feld input[type="email"], .feld input[type="tel"] {
  width: 100%; min-height: 52px; font-size: 17px; padding: 12px 15px; border: 1.5px solid var(--rahmen); border-radius: 12px; background: var(--karte); outline: none;
  transition: border-color var(--farbe) var(--kurve), box-shadow var(--farbe) var(--kurve), background-color var(--farbe) var(--kurve);
}
.feld input:focus-visible, .feld input.fokus, .feld input.fokus-sim { border-color: var(--marke); box-shadow: 0 0 0 3px var(--marke-zart), 0 0 0 4.5px var(--marke); }
.feld input[aria-invalid="true"] { border-color: var(--fehler); border-width: 2px; background: var(--fehler-zart); }
.fehler { display: flex; gap: 7px; align-items: flex-start; font-size: 14.5px; line-height: 1.4; color: var(--fehler); font-weight: 600; margin-top: 7px; }
.fehler:empty { display: none; }
.fehler::before { content: '!'; flex: none; width: 18px; height: 18px; margin-top: 1px; border-radius: 50%; background: var(--fehler); color: #fff; font: 700 12px/18px 'Onest', sans-serif; text-align: center; }

/* Radio-Karten für den Starttermin */
.termine { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.termin { position: relative; display: grid; grid-template-columns: 22px 1fr; gap: 4px 12px; padding: 14px 16px; border: 1.5px solid var(--rahmen); border-radius: 14px; cursor: pointer; transition: border-color var(--farbe) var(--kurve), background-color var(--farbe) var(--kurve), box-shadow var(--farbe) var(--kurve); }
.termin input { width: 20px; height: 20px; margin-top: 3px; accent-color: var(--marke); cursor: pointer; }
.termin b { font-weight: 600; font-size: 16px; }
.termin small { grid-column: 2; font-size: 13.5px; line-height: 1.4; color: var(--leise); }
.termin .hand { position: absolute; right: 12px; top: -11px; background: var(--gelb); color: var(--tinte); font-size: 12px; line-height: 1; padding: 5px 8px; border-radius: 999px; rotate: 2deg; }
.termin:has(input:checked) { border-color: var(--marke); background: var(--marke-zart); box-shadow: inset 0 0 0 1px var(--marke); }
.termin:has(input:focus-visible), .termin:has(input.fokus-sim) { box-shadow: var(--fokus); }
.feld[data-ungueltig] .termin { border-color: var(--fehler); }

.zustimmung, .feld > .zustimmung { display: grid; grid-template-columns: 22px 1fr; gap: 12px; align-items: start; font-size: 15.5px; cursor: pointer; }
.zustimmung input { width: 22px; height: 22px; margin-top: 2px; accent-color: var(--marke); cursor: pointer; }
.zustimmung input:focus-visible, .zustimmung input.fokus-sim { outline: 3px solid var(--marke); outline-offset: 3px; }
.zustimmung input[aria-invalid="true"] { outline: 2px solid var(--fehler); outline-offset: 2px; }

/* Absenden */
.absenden { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 20px; padding-top: 8px; border-top: 1px solid var(--linie); margin-top: 6px; padding-top: 22px; }
.knopf { min-height: 54px; padding: 0 26px; border: 0; border-radius: 14px; background: var(--marke); color: #fff; font-weight: 600; font-size: 16.5px; cursor: pointer; display: inline-flex; align-items: center; gap: 10px; transition: background-color var(--farbe) var(--kurve), scale var(--farbe) var(--kurve); }
.knopf:hover { background: #3346B8; }
.knopf:active, .knopf.gedrueckt { scale: .97; }
.knopf:focus-visible { outline: none; box-shadow: var(--fokus); }
.status { font-size: 14.5px; color: var(--leise); min-height: 1.5em; }
.status.offen { color: var(--fehler); font-weight: 600; }
.status.gut { color: var(--erfolg); font-weight: 600; }

/* Erfolg */
.danke { display: none; text-align: left; padding: 10px 0 4px; }
.erfolg .danke { display: grid; gap: 16px; }
.erfolg form { display: none; }
.danke-zeichen { width: 64px; height: 64px; border-radius: 50%; background: var(--erfolg-hell); display: grid; place-items: center; animation: haken-rein var(--erfolg-dauer) var(--feder) both; }
.danke-zeichen svg { width: 30px; height: 30px; }
.danke h3 { font-size: clamp(1.75rem, 1.3rem + 1.2vw, 2.4rem); line-height: 1.05; outline: none; }
.danke dl { display: grid; grid-template-columns: auto 1fr; gap: 8px 20px; font-size: 15.5px; padding: 16px 0; border-block: 1px solid var(--linie); }
.danke dt { color: var(--leise); }
.danke .hand { color: var(--marke); font-size: 15px; }
.zweit { justify-self: start; background: none; border: 1.5px solid var(--rahmen); color: var(--tinte); border-radius: 12px; min-height: 46px; padding: 0 18px; font-weight: 600; cursor: pointer; }
.zweit:focus-visible { outline: none; box-shadow: var(--fokus); }

.werte { max-width: 1240px; margin: -56px auto 0; padding: 0 clamp(16px, 3.4vw, 40px) 40px; display: flex; flex-wrap: wrap; gap: 4px 16px; }
.werte b { font-weight: 500; color: var(--tinte); }

/* Gedachter Zeiger der Vorführung */
.zeiger { position: fixed; left: 0; top: 0; width: 22px; height: 22px; z-index: 50; pointer-events: none; opacity: 0; transition: transform 600ms var(--kurve), opacity var(--farbe) linear; }
.zeiger svg { width: 22px; height: 22px; filter: drop-shadow(0 2px 4px rgba(0, 0, 0, .25)); }
.vorfuehrung .zeiger { opacity: 1; }

/* Varianten */
[data-variante="kompakt"] .zf { display: none; }

/* Reduzierte Bewegung: keine Wege, keine Feder, nur Farbe */
.reduziert .zf.neu, .reduziert .zf li, .reduziert .kompakt.an, .reduziert .zf li.erledigt .haken, .reduziert .danke-zeichen { animation: none; }
.reduziert .zeiger { transition: opacity var(--farbe) linear; }
.reduziert .knopf.gedrueckt, .reduziert .knopf:active { scale: 1; }
.reduziert .zf li a .pfeil { transition: none; }

@media (hover: none) {
  .knopf:hover { background: var(--marke); }
  .zf li a:hover { background: none; }
  .varianten button { padding: 12px 14px; }
}
@media (max-width: 900px) {
  .buehne { grid-template-columns: 1fr; }
  .kurs { position: relative; top: 0; }
  .module { display: none; }
  .werte { margin-top: -50px; }
}
@media (max-width: 560px) {
  html { font-size: 16px; }
  .kopf { flex-wrap: wrap; padding-bottom: 14px; }
  .marke small { display: none; }
  .varianten { width: 100%; justify-content: space-between; }
  .reihe, .termine { grid-template-columns: 1fr; }
  .termine { gap: 16px; }
  .eckdaten { gap: 6px 20px; }
  .belege { width: 104px; }
  .kurs .person { display: none; }
  .zf { padding: 16px 16px 14px; }
  .zf ul { margin-left: 0; }
  .knopf { width: 100%; justify-content: center; }
  .anmeldung, .kurs { border-radius: 22px; }
}

/* Vorschau 960 × 720: nur die Bühne */
.vorschau body { overflow: hidden; }
.vorschau .kopf, .vorschau .werte, .vorschau .sprung { display: none; }
.vorschau .buehne { padding-top: 24px; grid-template-columns: 300px 1fr; gap: 24px; }
.vorschau .kurs .person, .vorschau .module { display: none; }
.vorschau .kurs h1 { font-size: 40px; }
.vorschau .eckdaten { gap: 6px 16px; }
.vorschau .eckdaten dd { font-size: 16px; }
.vorschau .belege { width: 96px; }

/* Windows-Kontrastmodus: Knöpfe behalten eine Kante, die gewählte Variante bleibt erkennbar */
@media (forced-colors: active) {
  .knopf, .varianten button { border: 1px solid ButtonText; }
  .varianten button[aria-pressed="true"] { outline: 2px solid Highlight; outline-offset: -4px; }
}
</style>
</head>
<body data-variante="liste">
<a class="sprung" href="#anmeldung">Zur Anmeldung springen</a>
<header class="kopf">
  <a class="marke" href="#" aria-label="Kontenklar, Startseite">
    <svg viewBox="0 0 30 30" aria-hidden="true"><rect x="2" y="5" width="20" height="22" rx="5" fill="#7491FF"/><rect x="8" y="2" width="20" height="22" rx="5" fill="#141414"/><path d="m12.5 13.5 3.2 3.2 6.3-6.6" fill="none" stroke="#F7C325" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
    kontenklar <small>Online-Akademie · Herford</small>
  </a>
  <div class="varianten">
    <span class="mono" id="v-titel">Variante</span>
    <div role="group" aria-labelledby="v-titel">
      <button type="button" data-v="liste" aria-pressed="true">Liste</button>
      <button type="button" data-v="kompakt" aria-pressed="false">Kompakt</button>
    </div>
  </div>
</header>

<main class="buehne">
  <aside class="kurs" aria-labelledby="kurs-titel">
    <div class="kurs-kopf">
      <p class="mono">Kurs 03 · Online, live</p>
      <svg class="belege" viewBox="0 0 150 112" aria-hidden="true">
        <g transform="rotate(-10 70 60)"><rect x="22" y="10" width="78" height="96" rx="10" fill="#7491FF" opacity=".38"/></g>
        <g transform="rotate(7 80 56)"><rect x="46" y="6" width="78" height="96" rx="10" fill="#7491FF"/>
          <path d="M60 28h48M60 41h36M60 54h44M60 67h28" stroke="#141414" stroke-opacity=".38" stroke-width="5" stroke-linecap="round"/></g>
        <circle cx="122" cy="86" r="18" fill="#F7C325"/><path d="m113.5 86 6 6 11-12" fill="none" stroke="#141414" stroke-width="3.6" stroke-linecap="round" stroke-linejoin="round"/>
      </svg>
    </div>
    <h1 id="kurs-titel">Buchhaltung, die du <em>verstehst.</em></h1>
    <p>In sechs Wochen von der Schuhschachtel voller Belege zur sauberen Einnahmen-Überschuss-Rechnung. Für Selbstständige ohne Vorwissen.</p>
    <dl class="eckdaten ziffern">
      <div><dt>Start</dt><dd>12.01.2027</dd></div>
      <div><dt>Dauer</dt><dd>6 Wochen</dd></div>
      <div><dt>Preis</dt><dd>390 €</dd></div>
    </dl>
    <ol class="module">
      <li><span class="hand">Modul 01</span>Belege sortieren, ohne zu verzweifeln</li>
      <li><span class="hand">Modul 02</span>Umsatzsteuer in einfachen Worten</li>
      <li><span class="hand">Modul 03</span>Die EÜR Zeile für Zeile</li>
      <li><span class="hand">Modul 04</span>Fristen, Vorauszahlungen, Rücklagen</li>
    </ol>
    <p class="person">
      <svg viewBox="0 0 40 40" aria-hidden="true"><circle cx="20" cy="20" r="20" fill="#7491FF"/><circle cx="20" cy="16" r="7" fill="#141414"/><path d="M7 34c3-7 8-10 13-10s10 3 13 10" fill="#141414"/></svg>
      <span>Mit Merle Tönsing, Steuerfachwirtin, seit 2016 Dozentin</span>
    </p>
  </aside>

  <section class="anmeldung" id="anmeldung" aria-labelledby="anmeldung-titel" tabindex="-1">
    <header>
      <p class="mono">Schritt 1 von 1 · ca. 2 Minuten</p>
      <h2 id="anmeldung-titel">Melde dich an</h2>
      <p>Alle Felder brauchen wir für Rechnung und Kurszugang. Nur die Telefonnummer ist freiwillig.</p>
    </header>

    <!-- Fehlerzusammenfassung: Fokus springt nach dem Absenden hierher -->
    <div class="zf" id="zf" role="group" tabindex="-1" aria-labelledby="zf-titel" hidden>
      <div class="zf-kopf">
        <span class="zf-zeichen ziffern" aria-hidden="true" data-zf-zahl>3</span>
        <div>
          <h3 id="zf-titel" data-zf-titel>Noch 3 Angaben, dann bist du dabei.</h3>
          <p data-zf-text>Ein Klick auf einen Eintrag führt direkt zum Feld.</p>
        </div>
      </div>
      <ul data-zf-liste></ul>
    </div>

    <form novalidate data-form>
      <div class="reihe">
        <div class="feld" data-feld="vorname">
          <label for="f-vorname">Vorname</label>
          <input id="f-vorname" name="vorname" type="text" autocomplete="given-name" required aria-describedby="f-vorname-fehler" aria-invalid="false">
          <p class="fehler" id="f-vorname-fehler"></p>
        </div>
        <div class="feld" data-feld="nachname">
          <label for="f-nachname">Nachname</label>
          <input id="f-nachname" name="nachname" type="text" autocomplete="family-name" required aria-describedby="f-nachname-fehler" aria-invalid="false">
          <p class="fehler" id="f-nachname-fehler"></p>
        </div>
      </div>
      <div class="reihe">
        <div class="feld" data-feld="email">
          <label for="f-email">E-Mail</label>
          <input id="f-email" name="email" type="email" autocomplete="email" spellcheck="false" required aria-describedby="f-email-fehler" aria-invalid="false">
          <p class="fehler" id="f-email-fehler"></p>
        </div>
        <div class="feld" data-feld="telefon">
          <label for="f-telefon">Telefon <span class="optional">(freiwillig)</span></label>
          <input id="f-telefon" name="telefon" type="tel" autocomplete="tel" inputmode="tel" aria-describedby="f-telefon-fehler" aria-invalid="false">
          <p class="fehler" id="f-telefon-fehler"></p>
        </div>
      </div>

      <fieldset class="feld" data-feld="start" aria-describedby="f-start-hinweis f-start-fehler">
        <legend>Starttermin</legend>
        <p class="hinweis" id="f-start-hinweis">Beide Termine haben dieselben Inhalte und dieselbe Trainerin.</p>
        <div class="termine">
          <label class="termin"><input type="radio" name="start" id="f-start-1" value="12.01.2027" required><b class="ziffern">12. Januar 2027</b><small>Live am Abend, Di und Do 18 bis 20 Uhr</small><span class="hand" aria-hidden="true">nächster Start</span></label>
          <label class="termin"><input type="radio" name="start" id="f-start-2" value="02.03.2027"><b class="ziffern">2. März 2027</b><small>Im eigenen Tempo, mittwochs offene Sprechstunde</small></label>
        </div>
        <p class="fehler" id="f-start-fehler"></p>
      </fieldset>

      <div class="feld" data-feld="agb">
        <label class="zustimmung" for="f-agb">
          <input type="checkbox" id="f-agb" name="agb" required aria-describedby="f-agb-fehler" aria-invalid="false">
          <span>Ich habe die <a href="#" data-leer>Teilnahmebedingungen</a> und die <a href="#" data-leer>Widerrufsbelehrung</a> gelesen.</span>
        </label>
        <p class="fehler" id="f-agb-fehler"></p>
      </div>

      <p class="kompakt" data-kompakt></p>
      <div class="absenden">
        <button class="knopf" type="submit" data-knopf>Verbindlich anmelden · 390 € <span aria-hidden="true">→</span></button>
        <p class="status" role="status" data-status></p>
      </div>
    </form>

    <div class="danke" data-danke>
      <span class="danke-zeichen" aria-hidden="true"><svg viewBox="0 0 30 30"><path d="m7 15.5 5.5 5.5L23.5 9.5" fill="none" stroke="#fff" stroke-width="3.4" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
      <h3 tabindex="-1" data-danke-titel>Du bist dabei.</h3>
      <dl class="ziffern">
        <dt>Start</dt><dd data-danke-start></dd>
        <dt>Zugang</dt><dd>kommt per E-Mail an <b data-danke-mail></b></dd>
        <dt>Rechnung</dt><dd>390 € inkl. MwSt., 14 Tage Zahlungsziel</dd>
      </dl>
      <p class="hand">Bis bald im ersten Live-Termin! Merle</p>
      <button type="button" class="zweit" data-nochmal>Neue Anmeldung</button>
    </div>
  </section>
</main>
<p class="werte mono" data-werte aria-hidden="true"></p>
<div class="zeiger" aria-hidden="true"><svg viewBox="0 0 22 22"><path d="M4 2.5v15.2l4.1-3.6 2.7 6.1 2.9-1.3-2.7-6h5.5z" fill="#141414" stroke="#fff" stroke-width="1.4" stroke-linejoin="round"/></svg></div>

<script>
(() => {
  const html = document.documentElement;
  const reduziert = () => html.classList.contains('reduziert');
  const $ = (s, r = document) => r.querySelector(s);
  const form = $('[data-form]'), zf = $('#zf'), liste = $('[data-zf-liste]'), kompakt = $('[data-kompakt]');
  const status = $('[data-status]'), knopf = $('[data-knopf]'), karte = $('.anmeldung');
  const TITEL = document.title;

  // Regeln und Texte: in der Liste und am Feld wortgleich
  const FELDER = {
    vorname: { label: 'Vorname', pruefe: f => f.value.trim() ? '' : 'Gib deinen Vornamen ein.' },
    nachname: { label: 'Nachname', pruefe: f => f.value.trim() ? '' : 'Gib deinen Nachnamen ein.' },
    email: { label: 'E-Mail', pruefe: f => {
      const v = f.value.trim();
      if (!v) return 'Gib deine E-Mail-Adresse ein. Dorthin schicken wir den Kurszugang.';
      return /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(v) ? '' : 'Die E-Mail-Adresse ist unvollständig. So sieht sie richtig aus: name@beispiel.de';
    } },
    telefon: { label: 'Telefon', pruefe: f => {
      const v = f.value.trim();
      if (!v) return '';
      return /^[+\d][\d\s\/()-]{5,}$/.test(v) ? '' : 'Die Telefonnummer enthält Zeichen, die wir nicht wählen können. Erlaubt sind Ziffern, Leerzeichen, + und -.';
    } },
    start: { label: 'Starttermin', pruefe: () => form.start.value ? '' : 'Wähle einen Starttermin aus.' },
    agb: { label: 'Teilnahmebedingungen', pruefe: f => f.checked ? '' : 'Bestätige, dass du die Teilnahmebedingungen gelesen hast.' }
  };
  const REIHE = Object.keys(FELDER);
  const feldEl = n => $(`[data-feld="${n}"]`);
  const eingabe = n => n === 'start' ? (form.querySelector('[name="start"]:checked') || $('#f-start-1')) : form.elements[n];
  const fehlerEl = n => $(`#f-${n}-fehler`);

  let versucht = false;           // nach dem ersten Absenden prüfen Eingaben live
  let gemeldet = new Set();       // Felder, die in der Liste stehen
  let variante = 'liste';

  function zeigeFeldfehler(n, text) {
    const p = fehlerEl(n);
    p.innerHTML = text ? `<span class="sr">Fehler: </span>${text}` : '';
    if (n === 'start') { feldEl(n).toggleAttribute('data-ungueltig', !!text); }
    else eingabe(n).setAttribute('aria-invalid', text ? 'true' : 'false');
  }
  const fehlerVon = n => FELDER[n].pruefe(n === 'start' ? null : form.elements[n]);

  function render(neu) {
    const offen = REIHE.filter(n => gemeldet.has(n) && fehlerVon(n));
    const zahl = offen.length;
    // Liste: gemeldete Felder in Formular-Reihenfolge, erledigte bleiben sichtbar
    if (neu) {
      liste.innerHTML = REIHE.filter(n => gemeldet.has(n)).map((n, i) => `
        <li data-zf="${n}" style="--i:${i}"><a href="#${eingabe(n).id}">
          <small>${FELDER[n].label}</small><b>${fehlerVon(n)}</b>
          <span class="pfeil" aria-hidden="true">↓</span>
          <span class="haken"><span class="sr">Erledigt: </span><span class="hand" aria-hidden="true">erledigt</span><svg viewBox="0 0 22 22" aria-hidden="true"><circle cx="11" cy="11" r="11" fill="#0B7A4E"/><path d="m6.5 11.5 3 3 6-6.5" fill="none" stroke="#fff" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
        </a></li>`).join('');
    }
    liste.querySelectorAll('li').forEach(li => {
      const n = li.dataset.zf, t = fehlerVon(n);
      li.classList.toggle('erledigt', !t);
      if (t) li.querySelector('b').textContent = t;
    });
    $('[data-zf-zahl]').textContent = zahl || '✓';
    $('[data-zf-titel]').textContent = zahl ? `Noch ${zahl} ${zahl === 1 ? 'Angabe' : 'Angaben'}, dann bist du dabei.` : 'Alles da. Du kannst dich jetzt anmelden.';
    $('[data-zf-text]').textContent = zahl ? 'Ein Klick auf einen Eintrag führt direkt zum Feld.' : 'Prüfe kurz deine Angaben und schick die Anmeldung ab.';
    zf.classList.toggle('fertig', !zahl);
    // Kompakt: ein Satz mit Links
    if (zahl) {
      kompakt.innerHTML = `<b>${zahl === 1 ? 'Eine Angabe fehlt' : zahl + ' Angaben fehlen'}:</b> ` +
        offen.map(n => `<a href="#${eingabe(n).id}" data-zf-link="${n}">${FELDER[n].label}</a>`).join(', ') + '.';
    } else kompakt.innerHTML = '<b>Alles da.</b> Du kannst dich jetzt anmelden.';
    kompakt.classList.toggle('fertig', !zahl);
    document.title = zahl ? 'Fehler: ' + TITEL : TITEL;
    return zahl;
  }

  function melde(zahl) {
    status.className = 'status ' + (zahl ? 'offen' : 'gut');
    status.textContent = zahl ? `Noch ${zahl} ${zahl === 1 ? 'Angabe' : 'Angaben'} offen.` : 'Alles ausgefüllt.';
  }

  // Sprung zum Feld: Label oder Legend sichtbar machen, dann Feld fokussieren
  // Nur das eigene Fenster scrollen (scrollIntoView würde in der Galerie auch die Elternseite bewegen)
  function rolle(el, rand = 24, mitte = false) {
    const r = el.getBoundingClientRect();
    const y = mitte ? r.top + scrollY - (innerHeight - r.height) / 2 : r.top + scrollY - rand;
    window.scrollTo({ top: Math.max(0, y), behavior: reduziert() ? 'auto' : 'smooth' });
  }
  // In der Vorführung keinen echten Fokus setzen (würde der Galerie den Fokus stehlen), sondern sichtbar nachstellen
  function fokussiere(el) {
    document.querySelectorAll('.fokus-sim').forEach(x => x.classList.remove('fokus-sim'));
    if (html.classList.contains('vorfuehrung')) el.classList.add('fokus-sim');
    else el.focus({ preventScroll: true });
  }
  function springe(n) {
    rolle(feldEl(n));
    fokussiere(eingabe(n));
  }
  document.addEventListener('click', e => {
    const a = e.target.closest('.zf a, .kompakt a');
    if (a) { e.preventDefault(); springe(a.closest('[data-zf]')?.dataset.zf || a.dataset.zfLink); return; }
    if (e.target.closest('[data-leer]')) e.preventDefault();
  });

  form.addEventListener('submit', e => {
    e.preventDefault();
    versucht = true;
    const falsch = REIHE.filter(n => fehlerVon(n));
    REIHE.forEach(n => zeigeFeldfehler(n, fehlerVon(n)));
    if (falsch.length) {
      gemeldet = new Set(falsch);
      const zahl = render(true);
      melde(zahl);
      if (variante === 'liste') {
        zf.hidden = false; zf.classList.remove('neu'); void zf.offsetWidth; zf.classList.add('neu');
        rolle(zf);
        fokussiere(zf);
      } else {
        kompakt.classList.remove('an'); void kompakt.offsetWidth; kompakt.classList.add('an');
        springe(falsch[0]);
      }
      return;
    }
    // Erfolg
    document.title = TITEL;
    $('[data-danke-start]').textContent = form.start.value === '12.01.2027' ? '12. Januar 2027, 18 Uhr (live)' : '2. März 2027 (im eigenen Tempo)';
    $('[data-danke-mail]').textContent = form.email.value.trim();
    $('[data-danke-titel]').textContent = `Du bist dabei, ${form.vorname.value.trim()}.`;
    karte.classList.add('erfolg'); zf.hidden = true;
    fokussiere($('[data-danke-titel]'));
  });

  // Nach dem ersten Absenden: Feld sofort entlasten, Liste mitziehen. Neue Fehler erst beim Verlassen.
  function nachEingabe(e, verlassen) {
    if (!versucht) return;
    const n = e.target.closest('[data-feld]')?.dataset.feld;
    if (!n) return;
    const t = fehlerVon(n);
    if (!t) zeigeFeldfehler(n, '');
    else if (verlassen) { zeigeFeldfehler(n, t); if (!gemeldet.has(n)) { gemeldet.add(n); render(true); } }
    else if (eingabe(n).getAttribute('aria-invalid') === 'true' || feldEl(n).hasAttribute('data-ungueltig')) zeigeFeldfehler(n, t);
    const zahl = render(false);
    if (verlassen) melde(zahl);       // Live-Region nur bei change, nicht bei jedem Tastendruck
  }
  form.addEventListener('input', e => nachEingabe(e, false));
  form.addEventListener('change', e => nachEingabe(e, true));

  function zuruecksetzen() {
    form.reset(); versucht = false; gemeldet = new Set();
    REIHE.forEach(n => zeigeFeldfehler(n, ''));
    zf.hidden = true; zf.classList.remove('neu', 'fertig'); liste.innerHTML = '';
    kompakt.classList.remove('an', 'fertig'); kompakt.innerHTML = '';
    status.textContent = ''; status.className = 'status';
    karte.classList.remove('erfolg'); knopf.classList.remove('gedrueckt');
    document.querySelectorAll('.fokus, .zeigt, .fokus-sim').forEach(x => x.classList.remove('fokus', 'zeigt', 'fokus-sim'));
    document.title = TITEL;
  }
  $('[data-nochmal]').addEventListener('click', () => { zuruecksetzen(); form.elements.vorname.focus(); });

  // Varianten (?v=liste | ?v=kompakt)
  const WERTE = {
    gemeinsam: '<span><b>easeOutQuint</b> cubic-bezier(0.22, 1, 0.36, 1)</span><span>Farbe <b>150 ms</b></span><span>Fehler <b>#B3261E</b> 6,5 : 1</span><span>Fokus <b>3 px</b> #4459D6</span>',
    liste: '<span>Kasten <b>300 ms</b> · 8 px</span><span>Einträge <b>200 ms</b> · Versatz 40 ms</span><span>Fokus → Kasten</span>',
    kompakt: '<span>Satz <b>300 ms</b> · 8 px</span><span>Fokus → erstes Feld</span>'
  };
  const schalter = [...document.querySelectorAll('[data-v]')];
  function setzeVariante(v, url) {
    variante = WERTE[v] && v !== 'gemeinsam' ? v : 'liste';
    document.body.dataset.variante = variante;
    schalter.forEach(b => b.setAttribute('aria-pressed', String(b.dataset.v === variante)));
    $('[data-werte]').innerHTML = WERTE.gemeinsam + WERTE[variante];
    if (url) { const q = new URLSearchParams(location.search); q.set('v', variante); history.replaceState(null, '', '?' + q.toString()); }
  }
  schalter.forEach(b => b.addEventListener('click', () => { const lief = html.classList.contains('vorfuehrung'); neustart(); setzeVariante(b.dataset.v, true); if (lief) vorfuehren(); }));
  setzeVariante(new URLSearchParams(location.search).get('v'), false);

  // ---------- Vorführung: Jana meldet sich an, vergisst drei Dinge, korrigiert über die Liste ----------
  const zeiger = $('.zeiger');
  let zeit = [], laeuft = false;
  const nach = (ms, f) => zeit.push(setTimeout(f, ms));
  function zeigeAuf(el, dx = .5, dy = .5) {
    if (!el) return;
    const r = el.getBoundingClientRect();
    zeiger.style.transform = `translate(${Math.round(r.left + r.width * dx)}px, ${Math.round(r.top + r.height * dy)}px)`;
  }
  function tippe(t0, n, text, ms = 60) {
    const f = form.elements[n];
    [...text].forEach((z, k) => nach(t0 + k * ms, () => { f.value += z; f.dispatchEvent(new Event('input', { bubbles: true })); }));
    return t0 + text.length * ms;
  }
  const fokus = n => { document.querySelectorAll('.fokus').forEach(x => x.classList.remove('fokus')); if (n) form.elements[n].classList.add('fokus'); };
  const verlasse = n => form.elements[n].dispatchEvent(new Event('change', { bubbles: true }));
  function klickeLink(t, n) {
    const finde = () => variante === 'liste' ? $(`.zf [data-zf="${n}"] a`) : $(`.kompakt [data-zf-link="${n}"]`);
    nach(t, () => rolle(variante === 'liste' ? zf : kompakt, 24, variante !== 'liste'));
    nach(t + 500, () => zeigeAuf(finde(), .3, .5));
    nach(t + 1050, () => finde()?.classList.add('zeigt'));
    nach(t + 1300, () => { finde()?.classList.remove('zeigt'); springe(n); });
    nach(t + 1900, () => zeigeAuf(eingabe(n), .25, .6));
    return t + 2100;
  }
  function runde() {
    zuruecksetzen();
    window.scrollTo(0, 0);
    let t = 150;
    nach(t, () => { zeigeAuf(form.elements.vorname, .2, .6); fokus('vorname'); });
    t = tippe(t + 150, 'vorname', 'Jana', 45);
    nach(t + 60, () => { fokus('email'); zeigeAuf(form.elements.email, .2, .6); });
    t = tippe(t + 200, 'email', 'jana.kruse@web', 30);
    nach(t + 100, () => { fokus(null); zeigeAuf(knopf, .5, .5); });
    nach(t + 500, () => knopf.classList.add('gedrueckt'));
    nach(t + 650, () => { knopf.classList.remove('gedrueckt'); form.requestSubmit(); });
    t += 3200;                                                       // Liste lesen lassen
    t = klickeLink(t, 'email');
    t = tippe(t, 'email', '.de', 120); nach(t + 50, () => verlasse('email'));
    t = klickeLink(t + 500, 'nachname');
    t = tippe(t, 'nachname', 'Kruse', 80); nach(t + 50, () => verlasse('nachname'));
    t = klickeLink(t + 500, 'start');
    nach(t, () => zeigeAuf($('#f-start-1').closest('.termin'), .3, .4));
    nach(t + 650, () => { $('#f-start-1').checked = true; $('#f-start-1').dispatchEvent(new Event('change', { bubbles: true })); });
    t = klickeLink(t + 1300, 'agb');
    nach(t, () => zeigeAuf($('#f-agb'), .5, .5));
    nach(t + 650, () => { $('#f-agb').checked = true; $('#f-agb').dispatchEvent(new Event('change', { bubbles: true })); });
    nach(t + 1400, () => rolle(knopf, 0, true));
    nach(t + 2100, () => zeigeAuf(knopf, .5, .5));
    nach(t + 2800, () => knopf.classList.add('gedrueckt'));
    nach(t + 2950, () => { knopf.classList.remove('gedrueckt'); form.requestSubmit(); window.scrollTo(0, 0); zeiger.style.transform += ' translateY(400px)'; });
    nach(t + 6200, runde);
  }
  function vorfuehren() { neustart(); html.classList.add('vorfuehrung'); laeuft = true; runde(); }
  function pause() { zeit.forEach(clearTimeout); zeit = []; laeuft = false; }
  function neustart() { pause(); html.classList.remove('vorfuehrung'); zuruecksetzen(); }
  function weiter() { if (!laeuft && html.classList.contains('vorfuehrung')) { laeuft = true; runde(); } }

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