Zum Inhalt
← Sektionen & Funktionen

Mehrschritt-Anfrage mit Zusammenfassung

Anmeldung in vier kurzen Schritten mit Schrittleiste, Prüfseite mit „Ändern“ vor dem Senden und Zurück ohne Datenverlust; der Schritt gleitet per View Transition, der Fokus landet auf seiner Überschrift.

Standard für Anmeldungen mit drei bis vier benannten Schritten (Fahrschule, Kurs, Verein), wenn die Sektion volle Breite hat.

Eine Anmeldung oder Anfrage in drei bis vier kurzen Schritten statt eines langen Formulars. Oben steht, wo man ist (Schritt 2 von 4) und was noch kommt. Vor dem Senden zeigt ein eigener Schritt alle Angaben mit „Ändern“ je Abschnitt. Zurück verliert nichts, weil alle Schritte in einem Formular liegen und nur ausgeblendet werden. Fehler erscheinen erst nach Interaktion oder beim Versuch weiterzugehen. Der Schrittwechsel gleitet per View Transition, der Fokus springt auf die Überschrift des neuen Schritts.

Kleine Portionen wirken machbar. Der Fortschritt zeigt, dass es bald geschafft ist. Die Zusammenfassung gibt eine zweite Chance, Fehler zu sehen, bevor etwas abgeschickt ist. Wer zurückgeht und alles wiederfindet, vertraut dem Formular.

Wann einsetzen

Anmeldungen und Anfragen mit mehr als etwa sechs Feldern, die sich in Themen gliedern: Fahrschule (Klasse, Daten, Termin), Handwerk (Leistung, Objekt, Kontakt), Kanzlei (Anliegen, Kontakt), Kurse, Mitgliedsanträge im Verein.

Wann nicht

Bei drei oder vier Feldern (Kontakt, Newsletter): ein Schritt reicht. Nicht, um ein zu langes Formular zu verstecken; erst Felder streichen. Keine automatische Weiterleitung beim Antippen einer Auswahl, das überrascht und stört Tastatur und Screenreader.

Varianten

  • Schrittleiste mit Namen der Schritte über dem Formular
  • Kompakter Fortschrittsring mit „Schritt 2 von 4“ und „Als Nächstes“
  • Senkrechter Stepper: alle Schritte untereinander, erledigte zugeklappt mit Kurzfassung und „Ändern“
  • PLZ zuerst (Pflegedienst, Handwerk, Hol- und Bringdienst): „Kommen wir zu Ihnen?“ mit Ja, auf Anfrage oder Nein und Anfahrtspauschale, danach die kurze Anfrage
  • Eine Frage pro Seite (GOV.UK-Prinzip) für Behörden- und Kanzlei-Anfragen
  • Echte Seiten pro Schritt (Server, MPA) mit Cross-Document View Transitions

Umsetzung

Alle Schritte liegen in einem <form novalidate>, jeder als <section data-schritt> mit eigener Überschrift (tabindex=-1). Nicht aktive Schritte bekommen hidden, ihre Werte bleiben. „Weiter“ prüft nur die Felder des Schritts (validity), setzt .versucht am Schritt und fokussiert das erste fehlerhafte Feld; sonst zeigen :user-invalid und :user-valid erst nach Interaktion. Wechsel: html.dataset.richtung = 'vor' | 'zurueck'; document.startViewTransition(() => zeige(n)); danach Überschrift fokussieren, document.title = 'Schritt n von 4: …'. ::view-transition-old(schritt) 160 ms, ::view-transition-new(schritt) 220 ms, translateX ±24 px, cubic-bezier(0.4, 0, 0.2, 1). Mit :root { view-transition-name: none } bleibt der Rest der Seite live, Fortschrittslinie und Auto laufen parallel. Ohne API kurze CSS-Einblendung. Prüfseite aus den Formularwerten als <dl> je Abschnitt; „Ändern“ springt in den Schritt und zeigt statt Weiter „Zur Übersicht“ (GOV.UK-Prinzip). Entwurf mit Schritt in sessionStorage, nach dem Senden löschen. Echte Mehrseiten-Formulare (Server) nutzen dieselbe Bewegung über Cross-Document View Transitions (@view-transition { navigation: auto }).

Robust umsetzen

Telefon. Schrittleiste wird zu Balken mit „Schritt 2 von 4“. Weiter und Zurück als feste Leiste am unteren Rand, große Ziele. Eingaben mindestens 16 px gegen Zoom. Passende inputmode- und autocomplete-Werte.

Ohne Bewegung. Keine View Transition, der Schritt wechselt sofort. Fokus und Ansage bleiben.

Barrierefrei. Schrittliste als <ol> mit aria-current="step" und verstecktem Text „erledigt“. Fortschritt im Seitentitel und in der Überschrift. Fokus beim Wechsel auf die Überschrift des neuen Schritts, nicht auf das erste Feld (sonst fehlt der Zusammenhang). Fehler über aria-invalid und aria-describedby, Statuszeile mit role="status". „Ändern“-Knöpfe mit verstecktem Zusatz (z. B. „Ändern: Führerscheinklasse“). Keine Zeitlimits. Zurück ist ein echter Knopf, kein Link ins Leere (WCAG 3.3.4 Fehlervermeidung durch Prüfschritt, 2.4.3 Fokusreihenfolge, 3.3.1 Fehlererkennung).

Code

<!DOCTYPE html>
<!--
  Mehrschritt-Anfrage mit Zusammenfassung
  Fundus · Sektionen & Funktionen · https://cecodesign.de/lab/fundus/sektionen/mehrschritt-anfrage/
  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 · Fahrschule Lahnblick</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: '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; }
/* Variante PLZ zuerst: Rezept pflege-dienst-naehe */
@font-face { font-family: 'Literata'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/literata:vf@latest/latin-full-normal.woff2') format('woff2-variations'); font-weight: 200 900; font-display: swap; }
@font-face { font-family: 'Hanken Grotesk'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/hanken-grotesk:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 100 900; 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 fahrschule-freundlich-buchbar: Creme, Navy für Vertrauen, Himmelblau, Kennzeichen-Gelb nur als Fläche */
  --grund: #f8f8ec; --wechsel: #eef0d8; --karte: #ffffff;
  --navy: #142b50; --nacht: #0e1d38; --tinte: #17243c; --leise: #566175;
  --himmel: #3fbadf; --gelb: #ffd60a;
  --rahmen: #7b8497; --linie: #dfe2cf; --linie-weich: #eceee0;
  --fehler: #b42318; --fehler-grund: #fdf1ef; --gut: #1b7a4a;
  --kurve: cubic-bezier(0.4, 0, 0.2, 1);        /* Formulare: Standard, ohne Federung */
  --feder: cubic-bezier(0.34, 1.56, 0.64, 1);   /* nur Transform kleiner Elemente */
  --expo: cubic-bezier(0.16, 1, 0.3, 1);        /* Einblendungen, Auto auf der Straße */
  --raus: 160ms; --rein: 220ms; --feld: 150ms; --weg: 24px;
  view-transition-name: none;                   /* nur der Schritt wird fotografiert, der Rest bleibt live */
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { background: var(--grund); color: var(--tinte); font-family: 'Atkinson Hyperlegible Next', system-ui, sans-serif; -webkit-font-smoothing: antialiased; }
body { min-height: 100svh; padding: clamp(14px, 3vw, 40px); display: grid; place-items: center; }
button, input, select, textarea { font: inherit; color: inherit; }
.sr { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.etikett { font-weight: 650; font-size: 12px; letter-spacing: .08em; text-transform: uppercase; }
.ziffern { font-variant-numeric: tabular-nums; }

.seite { width: min(1120px, 100%); display: grid; grid-template-columns: minmax(0, .78fr) minmax(0, 1.22fr); gap: clamp(14px, 2vw, 24px); align-items: start; }

/* ---------- Linke Tafel: Marke, Versprechen, Straße ---------- */
.tafel { position: sticky; top: clamp(14px, 3vw, 40px); background: var(--navy); color: #f8f8ec; border-radius: 22px; padding: clamp(22px, 2.6vw, 34px); display: flex; flex-direction: column; gap: 20px; min-height: 620px; overflow: hidden; isolation: isolate; }
.marke { display: flex; align-items: center; gap: 12px; }
.schild { display: inline-flex; align-items: stretch; height: 34px; border-radius: 5px; background: var(--gelb); color: var(--nacht); box-shadow: inset 0 0 0 1.5px var(--nacht); overflow: hidden; flex: none; }
.schild i { width: 13px; background: #2a4fa8; display: grid; place-items: end center; padding-bottom: 3px; font: 700 7px/1 'Atkinson Hyperlegible Next', sans-serif; font-style: normal; color: #fff; }
.schild b { font: 800 17px/34px 'Bricolage Grotesque', sans-serif; letter-spacing: .06em; padding: 0 9px 0 7px; font-variation-settings: 'opsz' 24; }
.marke p { display: grid; gap: 3px; }
.marke strong { font: 700 16px/1 'Bricolage Grotesque', sans-serif; letter-spacing: -.005em; }
.marke p span { font-size: 12.5px; color: rgba(248, 248, 236, .66); }
.tafel h1 { font: 700 clamp(34px, 3.5vw, 50px)/.98 'Bricolage Grotesque', sans-serif; font-variation-settings: 'opsz' 96; letter-spacing: -.02em; hyphens: auto; text-wrap: balance; }
.tafel h1 mark { background: none; color: var(--gelb); }
.tafel .vorspann { font-size: 16.5px; line-height: 1.5; color: rgba(248, 248, 236, .8); max-width: 34ch; text-wrap: pretty; }
.strasse { position: relative; flex: 1; min-height: 170px; margin: 0 calc(-1 * clamp(22px, 2.6vw, 34px)); }
.strasse svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.strasse .belag { fill: none; stroke: var(--nacht); stroke-width: 30; stroke-linecap: round; }
.strasse .rand { fill: none; stroke: rgba(248, 248, 236, .14); stroke-width: 34; stroke-linecap: round; }
.strasse .mitte { fill: none; stroke: var(--gelb); stroke-width: 2.4; stroke-dasharray: 10 12; stroke-linecap: round; opacity: .9; }
.strasse .halt circle { fill: var(--navy); stroke: rgba(248, 248, 236, .4); stroke-width: 2; transition: fill var(--feld) var(--kurve), stroke var(--feld) var(--kurve); }
.strasse .halt text { font: 700 12px/1 'Bricolage Grotesque', sans-serif; fill: rgba(248, 248, 236, .55); text-anchor: middle; dominant-baseline: central; }
.strasse .halt.erledigt circle { fill: var(--gelb); stroke: var(--gelb); }
.strasse .halt.erledigt text { fill: var(--nacht); }
.strasse .halt.aktuell circle { fill: var(--karte); stroke: var(--gelb); stroke-width: 3; }
.strasse .halt.aktuell text { fill: var(--navy); }
.fuss { display: grid; gap: 6px; font-size: 14px; line-height: 1.45; color: rgba(248, 248, 236, .72); border-top: 1px solid rgba(248, 248, 236, .16); padding-top: 14px; }
.fuss b { color: #f8f8ec; font-weight: 650; }

/* ---------- Karte mit Formular ---------- */
.karte { background: var(--karte); border-radius: 22px; box-shadow: 0 1px 0 var(--linie), 0 24px 60px -36px rgba(20, 43, 80, .35); padding: clamp(18px, 2.6vw, 34px); min-height: 620px; display: flex; flex-direction: column; }

/* Fortschritt A: Schrittleiste */
.leiste { list-style: none; display: grid; grid-template-columns: repeat(4, 1fr); position: relative; margin-bottom: 26px; }
.leiste::before, .leiste::after { content: ''; position: absolute; left: 12.5%; right: 12.5%; top: 15px; height: 3px; border-radius: 3px; background: var(--linie-weich); }
.leiste::after { background: var(--navy); transform-origin: left; scale: var(--anteil, 0) 1; transition: scale var(--rein) var(--kurve); }
.leiste li { position: relative; display: grid; justify-items: center; gap: 8px; text-align: center; }
.leiste .punkt { position: relative; z-index: 1; width: 33px; height: 33px; border-radius: 50%; display: grid; place-items: center; font: 700 14px/1 'Bricolage Grotesque', sans-serif; background: var(--karte); color: var(--leise); box-shadow: inset 0 0 0 2px var(--linie); transition: background-color var(--feld) var(--kurve), color var(--feld) var(--kurve), box-shadow var(--feld) var(--kurve); }
.leiste .name { font-size: 13.5px; line-height: 1.2; color: var(--leise); }
.leiste button, .leiste .ziel { all: unset; display: grid; justify-items: center; gap: 8px; cursor: default; border-radius: 10px; padding: 0 4px 2px; }
.leiste button { cursor: pointer; }
.leiste button:hover .name { color: var(--navy); text-decoration: underline; text-underline-offset: 3px; }
.leiste button:focus-visible { outline: 3px solid var(--navy); outline-offset: 3px; }
.leiste li.erledigt .punkt { background: var(--navy); color: var(--gelb); box-shadow: none; }
.leiste li.erledigt .name { color: var(--tinte); }
.leiste li[aria-current="step"] .punkt { background: var(--gelb); color: var(--nacht); box-shadow: inset 0 0 0 2px var(--nacht); }
.leiste li[aria-current="step"] .name { color: var(--tinte); font-weight: 700; }
.leiste .haken { display: none; }
.leiste li.erledigt .haken { display: block; }
.leiste li.erledigt .zahl { display: none; }

/* Fortschritt B: Ring */
.ring-kopf { display: none; align-items: center; gap: 16px; margin-bottom: 24px; padding-bottom: 18px; border-bottom: 1px solid var(--linie-weich); }
.ring-kopf svg { width: 58px; height: 58px; flex: none; rotate: -90deg; }
.ring-kopf .spur { fill: none; stroke: var(--linie-weich); stroke-width: 5; }
.ring-kopf .bogen { fill: none; stroke: var(--navy); stroke-width: 5; stroke-linecap: round; stroke-dasharray: 145.3; stroke-dashoffset: calc(145.3 * (1 - var(--anteil-ring, .25))); transition: stroke-dashoffset var(--rein) var(--kurve); }
.ring-kopf .ring { position: relative; flex: none; }
.ring-kopf .ring b { position: absolute; inset: 0; display: grid; place-items: center; font: 700 15px/1 'Bricolage Grotesque', sans-serif; }
.ring-kopf p { display: grid; gap: 4px; }
.ring-kopf strong { font-size: 16px; }
.ring-kopf span { font-size: 14px; color: var(--leise); }

[data-modus="ring"] .leiste { display: none; }
[data-modus="ring"] .ring-kopf { display: flex; }
[data-modus="senkrecht"] .leiste { display: none; }

/* Schritte */
.schritte { view-transition-name: schritt; flex: 1; display: flex; flex-direction: column; }
[data-modus="senkrecht"] .schritte { view-transition-name: none; gap: 10px; }
.schritt { display: flex; flex-direction: column; flex: 1; }
.schritt[hidden], .schritte[hidden] { display: none; }
.schritt-kopf { display: grid; grid-template-columns: 1fr auto; align-items: start; gap: 4px 14px; margin-bottom: 18px; }
.schritt-kopf .nr { display: none; }
.titel { font: 700 clamp(25px, 2.3vw, 31px)/1.08 'Bricolage Grotesque', sans-serif; font-variation-settings: 'opsz' 48; letter-spacing: -.015em; outline: none; scroll-margin-top: 20px; }
.titel .zaehler { display: block; font: 650 12px/1.2 'Atkinson Hyperlegible Next', sans-serif; letter-spacing: .08em; text-transform: uppercase; color: var(--leise); margin-bottom: 8px; }
/* Fokus springt auf die Überschrift: dezente Markierung zeigt es */
.titel .text { background: linear-gradient(var(--gelb), var(--gelb)) 0 88% / 0 .32em no-repeat; transition: background-size var(--rein) var(--kurve); padding-inline: 1px; }
.titel:focus .text { background-size: 100% .32em; }
.schritt-kopf .kurz, .schritt-kopf .aendern { display: none; }
.hinweis-schritt { font-size: 15.5px; line-height: 1.5; color: var(--leise); grid-column: 1 / -1; max-width: 52ch; }

/* Senkrechter Stepper */
[data-modus="senkrecht"] .schritt { flex: none; border: 1.5px solid var(--linie); border-radius: 16px; padding: 16px 18px; }
[data-modus="senkrecht"] .schritt.aktuell { border-color: var(--navy); flex: 1; padding: 20px 20px 18px; }
[data-modus="senkrecht"] .schritt-kopf { grid-template-columns: auto 1fr auto; margin-bottom: 0; align-items: center; }
[data-modus="senkrecht"] .schritt.aktuell .schritt-kopf { margin-bottom: 16px; align-items: start; }
[data-modus="senkrecht"] .schritt-kopf .nr { display: grid; place-items: center; width: 30px; height: 30px; border-radius: 50%; font: 700 13px/1 'Bricolage Grotesque', sans-serif; box-shadow: inset 0 0 0 2px var(--linie); color: var(--leise); grid-row: span 2; }
[data-modus="senkrecht"] .schritt.aktuell .nr { background: var(--gelb); color: var(--nacht); box-shadow: inset 0 0 0 2px var(--nacht); }
[data-modus="senkrecht"] .schritt.erledigt .nr { background: var(--navy); color: var(--gelb); box-shadow: none; }
[data-modus="senkrecht"] .titel { font-size: 19px; }
[data-modus="senkrecht"] .schritt.aktuell .titel { font-size: clamp(23px, 2vw, 27px); }
[data-modus="senkrecht"] .titel .zaehler { display: none; }
[data-modus="senkrecht"] .schritt.aktuell .titel .zaehler { display: block; }
[data-modus="senkrecht"] .schritt.kommt .titel { color: var(--leise); }
[data-modus="senkrecht"] .schritt.erledigt .kurz { display: block; grid-column: 2; font-size: 14px; color: var(--leise); line-height: 1.35; }
[data-modus="senkrecht"] .schritt.erledigt .aendern { display: inline-flex; grid-column: 3; grid-row: 1 / span 2; }
[data-modus="senkrecht"] .schritt:not(.aktuell) .hinweis-schritt { display: none; }
[data-modus="senkrecht"] .schritt[data-schritt="1"] { view-transition-name: s1; }
[data-modus="senkrecht"] .schritt[data-schritt="2"] { view-transition-name: s2; }
[data-modus="senkrecht"] .schritt[data-schritt="3"] { view-transition-name: s3; }
[data-modus="senkrecht"] .schritt[data-schritt="4"] { view-transition-name: s4; }

.schritt-inhalt { display: flex; flex-direction: column; flex: 1; }
.schritt-inhalt[hidden] { display: none; }
.felder { display: grid; gap: 18px; align-content: start; flex: 1; }
.zwei { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }

/* Auswahlkarten (echte Radios) */
.gruppe { border: 0; display: grid; gap: 10px; min-width: 0; }
.gruppe legend { font-weight: 700; font-size: 15.5px; margin-bottom: 10px; padding: 0; }
.gruppe legend small { font-weight: 400; color: var(--leise); margin-left: 6px; font-size: 14px; }
.karten { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.wahl { position: relative; display: grid; align-content: start; gap: 3px; padding: 13px 15px 13px 46px; border: 1.5px solid var(--linie); border-radius: 14px; cursor: pointer; background: var(--karte); transition: border-color var(--feld) var(--kurve), background-color var(--feld) var(--kurve); }
.wahl input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
.wahl::before { content: ''; position: absolute; left: 15px; top: 15px; width: 20px; height: 20px; border-radius: 50%; box-shadow: inset 0 0 0 2px var(--rahmen); background: var(--karte); transition: box-shadow var(--feld) var(--kurve); }
.wahl::after { content: ''; position: absolute; left: 20px; top: 20px; width: 10px; height: 10px; border-radius: 50%; background: var(--navy); scale: 0; transition: scale 350ms var(--feder); }
.wahl.eckig::before { border-radius: 6px; }
.wahl.eckig::after { border-radius: 2px; left: 19px; top: 19px; width: 12px; height: 12px; background: var(--navy) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.6 6.3 5 8.6l4.4-5' fill='none' stroke='%23ffd60a' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 12px no-repeat; }
.wahl b { font-weight: 700; font-size: 16px; display: flex; gap: 8px; align-items: baseline; }
.wahl b em { font: 700 18px/1 'Bricolage Grotesque', sans-serif; font-style: normal; letter-spacing: -.01em; }
.wahl small { font-size: 13.5px; line-height: 1.35; color: var(--leise); }
@media (hover: hover) { .wahl:hover { border-color: var(--rahmen); } }
.wahl:has(input:checked) { border-color: var(--navy); background: #fbfbf3; box-shadow: inset 0 0 0 1px var(--navy); }
.wahl:has(input:checked)::before { box-shadow: inset 0 0 0 2px var(--navy); }
.wahl:has(input:checked)::after { scale: 1; }
.wahl:has(input:focus-visible) { outline: 3px solid var(--navy); outline-offset: 2px; box-shadow: inset 0 0 0 1px var(--navy), 0 0 0 7px var(--gelb); }
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chips .wahl { padding: 11px 15px 11px 42px; }
.chips .wahl::before { top: 12px; left: 13px; }
.chips .wahl::after { top: 17px; left: 18px; }
.chips .wahl.eckig::after { top: 16px; left: 17px; }

/* Textfelder */
.feld { display: grid; gap: 7px; align-content: start; min-width: 0; }
.feld > label, .gruppe-datum legend { font-weight: 700; font-size: 15.5px; }
.feld > label small { font-weight: 400; color: var(--leise); margin-left: 6px; font-size: 14px; }
.feld input, .feld select, .datum input { width: 100%; font-size: 17px; line-height: 1.2; padding: 13px 14px; border: 1.5px solid var(--rahmen); border-radius: 12px; background: var(--karte); outline: none; transition: border-color var(--feld) var(--kurve), box-shadow var(--feld) var(--kurve), background-color var(--feld) var(--kurve); }
.feld select { appearance: none; background: var(--karte) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%23142b50' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E") right 15px center / 12px no-repeat; padding-right: 40px; }
.feld input:focus-visible, .feld select:focus-visible, .datum input:focus-visible, .feld.fokus input, .datum label.fokus input { border-color: var(--navy); box-shadow: 0 0 0 3px var(--gelb); }
.datum { display: flex; gap: 10px; align-items: end; }
.datum label { display: grid; gap: 5px; font-size: 13.5px; color: var(--leise); }
.datum .tt { width: 4.6em; } .datum .jj { width: 6.4em; }
.datum input { text-align: center; }
.hilfe { font-size: 14px; line-height: 1.4; color: var(--leise); }

/* Validierung erst nach Interaktion (oder nach „Weiter“) */
.feld input:user-invalid, .datum input:user-invalid,
.versucht .feld input:invalid, .versucht .datum input:invalid { border-color: var(--fehler); background: var(--fehler-grund); }
.feld input:user-valid:not(:placeholder-shown) { border-color: var(--gut); }
.meldung { display: none; gap: 7px; align-items: flex-start; font-size: 14.5px; line-height: 1.35; font-weight: 650; color: var(--fehler); }
.meldung::before { content: '!'; flex: none; width: 18px; height: 18px; margin-top: 1px; border-radius: 50%; background: var(--fehler); color: #fff; font: 800 12px/18px 'Atkinson Hyperlegible Next', sans-serif; text-align: center; }
.feld:has(input:user-invalid) .meldung, .versucht .feld:has(:invalid) .meldung,
.gruppe:has(input:user-invalid) > .meldung, .versucht .gruppe:has(input:invalid) > .meldung { display: flex; }
.versucht .gruppe:has(input:invalid) .wahl { border-color: #e2a59f; }

/* Aktionen */
.aktionen { display: flex; align-items: center; justify-content: flex-end; gap: 10px 12px; margin-top: 24px; padding-top: 16px; border-top: 1px solid var(--linie-weich); flex-wrap: wrap; }
.aktionen .status { flex: 1 1 100%; order: -1; font-size: 14.5px; line-height: 1.35; color: var(--leise); }
.aktionen .status:empty { display: none; }
.aktionen .status.problem { color: var(--fehler); font-weight: 650; }
.knopf { display: inline-flex; align-items: center; gap: 10px; border: 0; border-radius: 999px; padding: 15px 24px; font-weight: 700; font-size: 16px; cursor: pointer; transition: transform 350ms var(--feder), background-color var(--feld) var(--kurve); }
.knopf.haupt { background: var(--navy); color: #f8f8ec; order: 2; }
.knopf.haupt span { display: inline-block; transition: transform 350ms var(--feder); }
@media (hover: hover) { .knopf.haupt:hover { background: var(--nacht); } .knopf.haupt:hover span { transform: translateX(3px); } }
.knopf.neben { background: none; color: var(--navy); padding-inline: 14px; order: 1; box-shadow: inset 0 0 0 1.5px var(--linie); }
@media (hover: hover) { .knopf.neben:hover { box-shadow: inset 0 0 0 1.5px var(--rahmen); } }
.knopf:active, .knopf.gedrueckt { transform: scale(.96); }
.knopf:focus-visible, .aendern:focus-visible { outline: 3px solid var(--navy); outline-offset: 3px; box-shadow: 0 0 0 7px var(--gelb); }
.knopf[hidden] { display: none; }
.aendern { display: inline-flex; align-items: center; gap: 6px; border: 0; background: none; color: var(--navy); font-weight: 700; font-size: 14.5px; text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1.5px; cursor: pointer; padding: 6px 2px; border-radius: 6px; }
.entwurf { font-size: 13px; color: var(--leise); margin-top: 12px; display: flex; gap: 6px; align-items: center; min-height: 1.3em; }
.entwurf::before { content: ''; width: 7px; height: 7px; border-radius: 50%; background: var(--gut); opacity: 0; transition: opacity var(--feld) var(--kurve); }
.entwurf.an::before { opacity: 1; }

/* Prüfen: Zusammenfassung */
.uebersicht { display: grid; gap: 10px; }
.block { border: 1.5px solid var(--linie); border-radius: 14px; padding: 8px 16px 12px; }
.block-kopf { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 4px; }
.block h3 { font: 700 16.5px/1.2 'Bricolage Grotesque', sans-serif; font-variation-settings: 'opsz' 24; }
.block dl { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 8px 16px; border-top: 1px solid var(--linie-weich); padding-top: 9px; }
.block dl > div { display: grid; gap: 1px; min-width: 0; align-content: start; }
.block dl > .lang { grid-column: span 2; }
.block dt { color: var(--leise); font-size: 13px; line-height: 1.3; }
.block dd { font-size: 15px; line-height: 1.35; font-weight: 650; overflow-wrap: anywhere; }
.block dd.leer { font-weight: 400; color: var(--leise); }
.block dd span { padding: 0 3px; margin: 0 -3px; border-radius: 3px; background: rgba(255, 214, 10, 0); transition: background-color 1200ms var(--kurve); }
.block dd.neu span { background: rgba(255, 214, 10, .85); transition-duration: 0ms; }
.zustimmung { margin-top: 4px; }
.zustimmung .wahl { padding-block: 12px; }
.zustimmung a { color: var(--navy); }

/* Erfolg */
.erfolg { flex: 1; display: flex; flex-direction: column; gap: 18px; view-transition-name: schritt; }
.erfolg[hidden] { display: none; }
.erfolg .stempel { align-self: flex-start; display: inline-flex; align-items: center; gap: 8px; background: var(--gelb); color: var(--nacht); border-radius: 999px; padding: 7px 14px 7px 9px; font-weight: 700; font-size: 14px; }
.erfolg .stempel svg { width: 20px; height: 20px; }
.erfolg .titel { font-size: clamp(28px, 2.8vw, 38px); }
.erfolg > p { font-size: 16.5px; line-height: 1.5; color: var(--leise); max-width: 50ch; }
.naechste { list-style: none; counter-reset: n; display: grid; gap: 0; border-top: 1px solid var(--linie-weich); }
.naechste li { counter-increment: n; display: grid; grid-template-columns: 40px 1fr; gap: 4px 12px; padding: 13px 0; border-bottom: 1px solid var(--linie-weich); font-size: 15.5px; line-height: 1.45; }
.naechste li::before { content: counter(n); grid-row: span 2; width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center; background: var(--navy); color: var(--gelb); font: 700 13px/1 'Bricolage Grotesque', sans-serif; }
.naechste b { font-weight: 700; }
.naechste span { color: var(--leise); }

/* ---------- View Transitions: Richtung über data-richtung am <html> ---------- */
::view-transition-group(*) { animation-duration: var(--rein); animation-timing-function: var(--kurve); }
::view-transition-old(schritt) { animation: vt-raus var(--raus) var(--kurve) both; }
::view-transition-new(schritt) { animation: vt-rein var(--rein) var(--kurve) both; }
:root[data-richtung="zurueck"]::view-transition-old(schritt) { animation-name: vt-raus-z; }
:root[data-richtung="zurueck"]::view-transition-new(schritt) { animation-name: vt-rein-z; }
@keyframes vt-raus { to { opacity: 0; transform: translateX(calc(-1 * var(--weg))); } }
@keyframes vt-rein { from { opacity: 0; transform: translateX(var(--weg)); } }
@keyframes vt-raus-z { to { opacity: 0; transform: translateX(var(--weg)); } }
@keyframes vt-rein-z { from { opacity: 0; transform: translateX(calc(-1 * var(--weg))); } }
/* Fallback ohne View Transitions: neuer Schritt blendet kurz ein */
.einblenden { animation: vt-rein var(--rein) var(--kurve) both; }
:root[data-richtung="zurueck"] .einblenden { animation-name: vt-rein-z; }

/* Gedachter Finger in der Vorführung */
.tipp { position: fixed; z-index: 50; width: 38px; height: 38px; margin: -19px 0 0 -19px; border-radius: 50%; background: rgba(63, 186, 223, .35); box-shadow: 0 0 0 2px rgba(20, 43, 80, .5); pointer-events: none; animation: tipp 450ms var(--expo) both; }
@keyframes tipp { from { scale: .4; opacity: 1; } to { scale: 1.25; opacity: 0; } }

/* Pult (im Fluss unter der Sektion) */
body { grid-template-rows: auto auto; gap: 18px; align-content: center; }
.pult { width: min(1120px, 100%); display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px 24px; font: 500 10.5px/1.5 'Geist Mono', ui-monospace, monospace; letter-spacing: .1em; text-transform: uppercase; color: var(--leise); }
.pult .modi { display: flex; flex-wrap: wrap; max-width: 100%; border: 1px solid var(--linie); border-radius: 8px; overflow: hidden; background: rgba(255, 255, 255, .5); }
@media (max-width: 400px) { .pult button { padding: 8px 6px; letter-spacing: .06em; } }
.vorschau .pult { display: none; }
.pult button { flex: 1; font: inherit; letter-spacing: inherit; text-transform: inherit; color: var(--leise); background: none; border: 0; padding: 8px 9px; cursor: pointer; transition: background-color var(--feld) var(--kurve), color var(--feld) var(--kurve); }
.pult button + button { border-left: 1px solid var(--linie); }
.pult button[aria-pressed="true"] { background: var(--navy); color: #f8f8ec; }
.pult button:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }
.pult b { color: var(--tinte); font-weight: 600; }

/* ---------- Reduzierte Bewegung ---------- */
:root.reduziert *, :root.reduziert *::before, :root.reduziert *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; }
.reduziert .tipp { display: none; }

/* ---------- Touch ---------- */
@media (hover: none) {
  .wahl { padding-block: 15px; }
  .chips .wahl { padding-block: 13px; }
  .aendern { padding: 10px 4px; }
}

/* ---------- Schmal ---------- */
@media (max-width: 860px) {
  .seite { grid-template-columns: 1fr; }
  .tafel { position: static; min-height: 0; gap: 12px; padding: 18px 20px 20px; }
  .tafel h1 { font-size: 32px; }
  .tafel .vorspann { font-size: 15.5px; }
  .strasse, .fuss { display: none; }
  .karte { min-height: 0; padding: 18px 16px 0; }
}
@media (max-width: 560px) {
  body { padding: 10px; }
  .karten, .zwei { grid-template-columns: 1fr; }
  .leiste .name { font-size: 12px; }
  .leiste li:not([aria-current="step"]) .name { color: transparent; height: 0; overflow: hidden; }
  .leiste { margin-bottom: 18px; }
  .leiste li { gap: 4px; }
  .titel { font-size: 25px; }
  /* Weiter/Zurück als feste Leiste unten in der Karte */
  .aktionen { position: sticky; bottom: 0; z-index: 5; background: var(--karte); margin: 18px -16px 0; padding: 12px 16px calc(12px + env(safe-area-inset-bottom)); box-shadow: 0 -10px 20px -16px rgba(20, 43, 80, .4); }
  .knopf.haupt { flex: 1; justify-content: center; }
  .block dl { grid-template-columns: 1fr 1fr; }
  [data-modus="senkrecht"] .schritt { padding: 14px; }
  [data-modus="senkrecht"] .schritt.aktuell { padding: 16px 14px 0; }
  [data-modus="senkrecht"] .aktionen { margin-inline: -14px; padding-inline: 14px; }
}


/* ---------- Variante PLZ zuerst (Pflegedienst am Isarbogen · Rezept pflege-dienst-naehe) ---------- */
[data-welt="pflege"][hidden], [data-welt="fahrschule"][hidden] { display: none !important; }
:root.welt-pflege { --grund: #faf7f2; background: #faf7f2; }
.pf {
  --pf-grund: #faf7f2; --pf-text: #1f1b16; --pf-leise: #4a443d; --pf-pflaume: #5b2a4a; --pf-gelb: #ffc845; --pf-hinweis: #fff1cc; --pf-linie: #d8d0c4; --pf-fehler: #a3241a;
  --pf-auf: cubic-bezier(0.16, 1, 0.3, 1); --pf-zu: cubic-bezier(0.7, 0, 0.84, 0);   /* token-toggle-asymmetrisch */
  --pf-oeffnen: 300ms; --pf-schliessen: 200ms;
  width: min(1120px, 100%); display: grid; grid-template-columns: minmax(0, .82fr) minmax(0, 1.18fr); gap: clamp(14px, 2vw, 24px); align-items: start;
  color: var(--pf-text); font-family: 'Hanken Grotesk', system-ui, sans-serif; font-size: 1.25rem; line-height: 1.6;
}
.pf h1, .pf h2 { font-family: 'Literata', serif; font-weight: 600; letter-spacing: -.01em; text-wrap: balance; }
.pf :focus-visible { outline: 3px solid var(--pf-pflaume); outline-offset: 3px; border-radius: 4px; }
.pf-tafel { background: var(--pf-pflaume); color: #fff; border-radius: 14px; padding: clamp(22px, 2.8vw, 36px); display: grid; gap: 22px; align-content: start; }
.pf-tafel :focus-visible { outline-color: var(--pf-gelb); }
.pf-marke { display: flex; align-items: center; gap: 12px; font-size: 1rem; line-height: 1.3; }
.pf-marke svg { width: 40px; height: 40px; flex: none; }
.pf-marke b { display: block; font-weight: 700; font-size: 1.125rem; }
.pf-tafel h1 { font-size: clamp(2.5rem, 1.8rem + 3vw, 4rem); line-height: 1.1; }
.pf-tafel > p { max-width: 30ch; }
.pf-person { display: grid; grid-template-columns: 72px 1fr; gap: 16px; align-items: center; padding-top: 22px; border-top: 1px solid rgba(255, 255, 255, .3); }
.pf-person svg { width: 72px; height: 72px; }
.pf-person p { display: grid; font-size: 1rem; line-height: 1.4; }
.pf-person b { font-size: 1.125rem; }
.pf-tel { font-size: 1.5rem; font-weight: 700; font-variant-numeric: tabular-nums; color: #fff; text-decoration: none; margin-top: 6px; }
.pf-tel:hover { text-decoration: underline; text-underline-offset: 4px; }
.pf-orte { font-size: 1rem; line-height: 1.5; max-width: 40ch !important; }
.pf-karte { background: #fff; border: 1px solid var(--pf-linie); border-radius: 14px; padding: clamp(22px, 3vw, 40px); display: grid; gap: 20px; min-height: 560px; align-content: start; }
.pf-stand { display: grid; gap: 8px; }
.pf-stand p { font-size: 1rem; color: var(--pf-leise); font-weight: 600; }
.pf-balken { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.pf-balken i { height: 6px; border-radius: 3px; background: var(--pf-linie); transition: background-color var(--pf-oeffnen) var(--pf-auf); }
.pf-balken i.an { background: var(--pf-pflaume); }
.pf-karte h2 { font-size: clamp(1.75rem, 1.4rem + 1.4vw, 2.25rem); line-height: 1.15; color: var(--pf-pflaume); outline: none; }
.pf-schritt { display: grid; gap: 20px; }
.pf-schritt[hidden] { display: none; }
.pf-schritt.rein { animation: pf-rein var(--pf-oeffnen) var(--pf-auf); }
@keyframes pf-rein { from { opacity: 0; translate: 0 8px; } }
.pf label, .pf legend { font-weight: 600; }
.pf-hilfe { font-size: 1rem; color: var(--pf-leise); }
.pf-zeile { display: flex; gap: 10px; flex-wrap: wrap; }
.pf input[type="text"], .pf input[type="tel"], .pf select { min-height: 56px; padding: 0 16px; border: 2px solid var(--pf-leise); border-radius: 8px; background: #fff; font-size: 1.25rem; color: var(--pf-text); }
.pf input[name="plz"] { width: 7.5em; font-variant-numeric: tabular-nums; letter-spacing: .08em; font-weight: 600; }
.pf input[aria-invalid="true"] { border-color: var(--pf-fehler); }
.pf-feld { display: grid; gap: 6px; }
.pf-fehler { color: var(--pf-fehler); font-weight: 600; font-size: 1rem; }
.pf-fehler::before { content: "! "; }
.pf-knopf { min-height: 56px; padding: 0 24px; border-radius: 8px; border: 2px solid var(--pf-text); background: var(--pf-gelb); color: var(--pf-text); font: 600 1.25rem/1.2 'Hanken Grotesk', sans-serif; cursor: pointer; text-decoration: none; display: inline-flex; align-items: center; justify-content: center; gap: 10px; transition: background-color 150ms var(--pf-auf); }
.pf-knopf:hover { background: #ffd673; }
.pf-knopf--neben { background: #fff; }
.pf-knopf--neben:hover { background: var(--pf-hinweis); }
.pf-aktionen { display: flex; flex-wrap: wrap; gap: 12px; }
.pf-antwort { display: grid; grid-template-columns: 44px 1fr; gap: 6px 16px; padding: 20px 22px; border-radius: 10px; border: 2px solid var(--pf-linie); background: var(--pf-grund); }
.pf-antwort[hidden] { display: none; }
.pf-antwort.rein { animation: pf-rein var(--pf-oeffnen) var(--pf-auf); }
.pf-antwort svg { width: 44px; height: 44px; grid-row: span 3; }
.pf-antwort h3 { font: 600 1.375rem/1.25 'Literata', serif; }
.pf-antwort p { max-width: 44ch; }
.pf-antwort .pf-aktionen { grid-column: 2; margin-top: 8px; }
.pf-antwort[data-art="ja"] { border-color: var(--pf-pflaume); }
.pf-antwort[data-art="anfrage"] { background: var(--pf-hinweis); border-color: #c99a1e; }
.pf-pauschale { font-weight: 600; padding-top: 8px; border-top: 1px solid var(--pf-linie); margin-top: 4px; font-variant-numeric: tabular-nums; }
.pf-pauschale small { display: block; font-weight: 400; font-size: 1rem; color: var(--pf-leise); }
.pf fieldset { border: 0; display: grid; gap: 10px; }
.pf legend { margin-bottom: 4px; }
.pf legend small { font-weight: 400; color: var(--pf-leise); font-size: 1rem; margin-left: 6px; }
.pf-wahl { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 15em), 1fr)); gap: 10px; }
.pf-wahl label { display: flex; gap: 12px; align-items: center; min-height: 56px; padding: 10px 14px; border: 2px solid var(--pf-linie); border-radius: 8px; font-weight: 500; cursor: pointer; transition: border-color 150ms var(--pf-auf), background-color 150ms var(--pf-auf); }
.pf-wahl label:has(:checked) { border-color: var(--pf-pflaume); background: #f6eef3; }
.pf-wahl input { width: 22px; height: 22px; accent-color: var(--pf-pflaume); flex: none; }
.pf-wahl small { display: block; font-size: 1rem; color: var(--pf-leise); line-height: 1.3; }
.pf-datenschutz { font-size: 1rem; color: var(--pf-leise); max-width: 52ch; }
.pf-fertig dl { display: grid; grid-template-columns: auto 1fr; gap: 6px 16px; padding: 16px 18px; background: var(--pf-grund); border-radius: 10px; font-size: 1.125rem; }
.pf-fertig dt { color: var(--pf-leise); }
.pf-notfall { font-size: 1rem; color: var(--pf-leise); padding-top: 16px; border-top: 1px solid var(--pf-linie); }
.pf-notfall b { color: var(--pf-text); }
@media (max-width: 860px) { .pf { grid-template-columns: minmax(0, 1fr); } .pf-karte { min-height: 0; } }
@media (max-width: 400px) { .pf { font-size: 1.125rem; } .pf-antwort { grid-template-columns: 1fr; } .pf-antwort svg { grid-row: auto; width: 36px; height: 36px; } .pf-antwort .pf-aktionen { grid-column: 1; } .pf-knopf { width: 100%; } .pf-person { grid-template-columns: 56px 1fr; } .pf-person svg { width: 56px; height: 56px; } }
.vorschau .pf-person, .vorschau .pf-orte, .vorschau .pf-notfall { display: none; }
.vorschau .pf-karte { min-height: 676px; }

/* ---------- Vorschau 960 × 720 ---------- */
.vorschau body { overflow: hidden; padding: 22px; align-items: start; }
.vorschau .tafel, .vorschau .karte { min-height: 676px; }
.vorschau .fuss, .vorschau .entwurf, .vorschau .hinweis-schritt { display: none; }
.vorschau .leiste { margin-bottom: 18px; }
.vorschau .zustimmung small { display: none; }
.vorschau .schritt-kopf { margin-bottom: 14px; }
.vorschau .tafel h1 { font-size: 40px; }
@media (forced-colors: active) { button, [role=button], [role=tab], [role=switch] { border: 1px solid ButtonText !important; } }
</style>
</head>
<body>
<main class="seite" data-welt="fahrschule">
  <aside class="tafel" aria-label="Fahrschule Lahnblick">
    <div class="marke">
      <span class="schild" aria-hidden="true"><i>D</i><b>GI·LB 17</b></span>
      <p><strong>Fahrschule Lahnblick</strong><span>Gießen · Innenstadt, Wieseck, Heuchelheim</span></p>
    </div>
    <h1>In vier Schritten zur <mark>ersten Fahrstunde.</mark></h1>
    <p class="vorspann">Dauert etwa drei Minuten. Kosten entstehen erst, wenn du im Büro den Ausbildungsvertrag unterschreibst.</p>
    <div class="strasse" aria-hidden="true">
      <svg viewBox="0 0 360 230" preserveAspectRatio="xMidYMid meet" data-strasse>
        <path class="rand" d="M-400 194 L-20 196 C 70 200, 110 150, 150 128 S 250 112, 270 74 S 330 26, 390 30 L 800 36"/>
        <path class="belag" d="M-400 194 L-20 196 C 70 200, 110 150, 150 128 S 250 112, 270 74 S 330 26, 390 30 L 800 36"/>
        <path class="mitte" d="M-400 194 L-20 196 C 70 200, 110 150, 150 128 S 250 112, 270 74 S 330 26, 390 30 L 800 36"/>
        <path data-weg d="M-20 196 C 70 200, 110 150, 150 128 S 250 112, 270 74 S 330 26, 390 30" fill="none"/>
        <g data-halte></g>
        <g data-auto>
          <rect x="-15" y="-8.5" width="30" height="17" rx="6" fill="#3fbadf"/>
          <rect x="-3" y="-6.5" width="9" height="13" rx="2.5" fill="#0e1d38" opacity=".75"/>
          <rect x="-12" y="-6" width="5" height="12" rx="2" fill="#0e1d38" opacity=".45"/>
          <rect x="11.5" y="-7" width="3" height="3.5" rx="1" fill="#ffd60a"/>
          <rect x="11.5" y="3.5" width="3" height="3.5" rx="1" fill="#ffd60a"/>
          <rect x="-7" y="-13" width="14" height="5" rx="2" fill="#ffd60a"/>
          <text x="0" y="-9.4" font-size="4.2" font-weight="800" text-anchor="middle" fill="#0e1d38" font-family="Bricolage Grotesque, sans-serif">FAHRSCHULE</text>
        </g>
      </svg>
    </div>
    <div class="fuss">
      <p><b>Lieber persönlich?</b> Büro am Lahnufer, Mo bis Fr 14 bis 18 Uhr. Anruf oder WhatsApp.</p>
      <p>Fahrstunden auf Deutsch, Englisch, Türkisch und Arabisch.</p>
    </div>
  </aside>

  <section class="karte" aria-label="Anmeldung" data-karte data-modus="leiste">
    <ol class="leiste" data-leiste aria-label="Fortschritt der Anmeldung"></ol>
    <div class="ring-kopf" data-ring aria-hidden="true">
      <div class="ring"><svg viewBox="0 0 58 58"><circle class="spur" cx="29" cy="29" r="23.13"/><circle class="bogen" cx="29" cy="29" r="23.13"/></svg><b data-ring-zahl>1/4</b></div>
      <p><strong data-ring-jetzt>Klasse und Standort</strong><span data-ring-naechst>Als Nächstes: Über dich</span></p>
    </div>

    <form class="schritte" data-form novalidate>
      <!-- Schritt 1 -->
      <section class="schritt" data-schritt="1" aria-labelledby="t1">
        <header class="schritt-kopf">
          <span class="nr" aria-hidden="true">1</span>
          <h2 class="titel" id="t1" tabindex="-1"><span class="zaehler">Schritt 1 von 4</span><span class="text">Klasse und Standort</span></h2>
          <p class="kurz" data-kurz></p>
          <button type="button" class="aendern" data-gehe="1">Ändern<span class="sr">: Klasse und Standort</span></button>
        </header>
        <div class="schritt-inhalt">
          <div class="felder">
            <fieldset class="gruppe">
              <legend>Welchen Führerschein möchtest du machen?</legend>
              <div class="karten">
                <label class="wahl"><input type="radio" name="klasse" value="B" required><b><em>B</em> Auto</b><small>Ab 18, begleitet ab 17 (BF17)</small></label>
                <label class="wahl"><input type="radio" name="klasse" value="B197" required><b><em>B197</em> Auto</b><small>Prüfung mit Automatik, fahren darfst du beides</small></label>
                <label class="wahl"><input type="radio" name="klasse" value="A2" required><b><em>A2</em> Motorrad</b><small>Bis 35 kW, ab 18</small></label>
                <label class="wahl"><input type="radio" name="klasse" value="BE" required><b><em>BE</em> Anhänger</b><small>Für Wohnwagen und Pferdeanhänger</small></label>
              </div>
              <p class="meldung" id="m-klasse">Bitte wähle eine Klasse. Unsicher? Nimm B, wir beraten dich im Büro.</p>
            </fieldset>
            <fieldset class="gruppe">
              <legend>Wo möchtest du lernen?</legend>
              <div class="chips">
                <label class="wahl"><input type="radio" name="standort" value="Innenstadt" required><b>Innenstadt</b></label>
                <label class="wahl"><input type="radio" name="standort" value="Wieseck" required><b>Wieseck</b></label>
                <label class="wahl"><input type="radio" name="standort" value="Heuchelheim" required><b>Heuchelheim</b></label>
              </div>
              <p class="meldung" id="m-standort">Bitte wähle einen Standort für Theorie und Treffpunkt.</p>
            </fieldset>
          </div>
          <div class="aktionen">
            <p class="status" role="status"></p>
            <button type="button" class="knopf haupt" data-weiter>Weiter <span aria-hidden="true">→</span></button>
            <button type="button" class="knopf haupt" data-zur-uebersicht hidden>Zur Übersicht <span aria-hidden="true">→</span></button>
          </div>
        </div>
      </section>

      <!-- Schritt 2 -->
      <section class="schritt" data-schritt="2" aria-labelledby="t2" hidden>
        <header class="schritt-kopf">
          <span class="nr" aria-hidden="true">2</span>
          <h2 class="titel" id="t2" tabindex="-1"><span class="zaehler">Schritt 2 von 4</span><span class="text">Über dich</span></h2>
          <p class="kurz" data-kurz></p>
          <button type="button" class="aendern" data-gehe="2">Ändern<span class="sr">: Über dich</span></button>
        </header>
        <div class="schritt-inhalt">
          <div class="felder">
            <div class="zwei">
              <div class="feld">
                <label for="f-vorname">Vorname</label>
                <input id="f-vorname" name="vorname" required autocomplete="given-name" aria-describedby="m-vorname" placeholder=" ">
                <p class="meldung" id="m-vorname">Wie heißt du mit Vornamen?</p>
              </div>
              <div class="feld">
                <label for="f-nachname">Nachname</label>
                <input id="f-nachname" name="nachname" required autocomplete="family-name" aria-describedby="m-nachname" placeholder=" ">
                <p class="meldung" id="m-nachname">Bitte gib deinen Nachnamen an, so wie im Ausweis.</p>
              </div>
            </div>
            <fieldset class="gruppe gruppe-datum" aria-describedby="h-geb m-geb">
              <legend>Geburtsdatum</legend>
              <p class="hilfe" id="h-geb">Zum Beispiel 14 03 2009. Anmelden kannst du dich ein halbes Jahr vor dem Mindestalter.</p>
              <div class="datum">
                <label>Tag<input class="tt" name="tag" inputmode="numeric" pattern="0?[1-9]|[12][0-9]|3[01]" maxlength="2" required autocomplete="bday-day"></label>
                <label>Monat<input class="tt" name="monat" inputmode="numeric" pattern="0?[1-9]|1[0-2]" maxlength="2" required autocomplete="bday-month"></label>
                <label>Jahr<input class="jj" name="jahr" inputmode="numeric" pattern="(19[4-9][0-9]|20[01][0-9])" maxlength="4" required autocomplete="bday-year"></label>
              </div>
              <p class="meldung" id="m-geb">Bitte prüfe das Datum: Tag, Monat und vierstelliges Jahr.</p>
            </fieldset>
            <div class="zwei">
              <div class="feld">
                <label for="f-mail">E-Mail</label>
                <input id="f-mail" name="email" type="email" required autocomplete="email" spellcheck="false" aria-describedby="m-mail" placeholder=" ">
                <p class="meldung" id="m-mail">So sieht es richtig aus: name@anbieter.de</p>
              </div>
              <div class="feld">
                <label for="f-tel">Telefon <small>optional</small></label>
                <input id="f-tel" name="telefon" type="tel" autocomplete="tel" inputmode="tel" placeholder=" ">
              </div>
            </div>
          </div>
          <div class="aktionen">
            <p class="status" role="status"></p>
            <button type="button" class="knopf neben" data-zurueck><span aria-hidden="true">←</span> Zurück</button>
            <button type="button" class="knopf haupt" data-weiter>Weiter <span aria-hidden="true">→</span></button>
            <button type="button" class="knopf haupt" data-zur-uebersicht hidden>Zur Übersicht <span aria-hidden="true">→</span></button>
          </div>
        </div>
      </section>

      <!-- Schritt 3 -->
      <section class="schritt" data-schritt="3" aria-labelledby="t3" hidden>
        <header class="schritt-kopf">
          <span class="nr" aria-hidden="true">3</span>
          <h2 class="titel" id="t3" tabindex="-1"><span class="zaehler">Schritt 3 von 4</span><span class="text">Termine</span></h2>
          <p class="kurz" data-kurz></p>
          <button type="button" class="aendern" data-gehe="3">Ändern<span class="sr">: Termine</span></button>
        </header>
        <div class="schritt-inhalt">
          <div class="felder">
            <fieldset class="gruppe">
              <legend>Wann passen dir Fahrstunden? <small>mehrere möglich</small></legend>
              <div class="chips" data-zeiten>
                <label class="wahl eckig"><input type="checkbox" name="zeiten" value="Vormittags"><b>Vormittags</b></label>
                <label class="wahl eckig"><input type="checkbox" name="zeiten" value="Nachmittags"><b>Nachmittags</b></label>
                <label class="wahl eckig"><input type="checkbox" name="zeiten" value="Abends"><b>Abends</b></label>
                <label class="wahl eckig"><input type="checkbox" name="zeiten" value="Samstags"><b>Samstags</b></label>
              </div>
              <p class="meldung" id="m-zeiten">Kreuze mindestens eine Zeit an, dann planen wir passend.</p>
            </fieldset>
            <fieldset class="gruppe">
              <legend>Theorieunterricht</legend>
              <div class="karten">
                <label class="wahl"><input type="radio" name="theorie" value="Abendkurs" required checked><b>Abendkurs</b><small>Di und Do, 18:30 Uhr, laufender Einstieg</small></label>
                <label class="wahl"><input type="radio" name="theorie" value="Ferienkurs" required><b>Ferienkurs</b><small>Zwei Wochen kompakt, nächster ab 4. Januar</small></label>
              </div>
            </fieldset>
            <div class="feld">
              <label for="f-sprache">Sprache der Fahrstunden</label>
              <select id="f-sprache" name="sprache" autocomplete="language">
                <option>Deutsch</option><option>Englisch</option><option>Türkisch</option><option>Arabisch</option>
              </select>
            </div>
          </div>
          <div class="aktionen">
            <p class="status" role="status"></p>
            <button type="button" class="knopf neben" data-zurueck><span aria-hidden="true">←</span> Zurück</button>
            <button type="button" class="knopf haupt" data-weiter>Weiter <span aria-hidden="true">→</span></button>
            <button type="button" class="knopf haupt" data-zur-uebersicht hidden>Zur Übersicht <span aria-hidden="true">→</span></button>
          </div>
        </div>
      </section>

      <!-- Schritt 4: Prüfen -->
      <section class="schritt" data-schritt="4" aria-labelledby="t4" hidden>
        <header class="schritt-kopf">
          <span class="nr" aria-hidden="true">4</span>
          <h2 class="titel" id="t4" tabindex="-1"><span class="zaehler">Schritt 4 von 4</span><span class="text">Prüfen und senden</span></h2>
          <p class="hinweis-schritt">Stimmt alles? Mit „Ändern“ springst du in den Schritt und kommst direkt hierher zurück.</p>
        </header>
        <div class="schritt-inhalt">
          <div class="felder">
            <div class="uebersicht" data-uebersicht></div>
            <fieldset class="gruppe zustimmung">
              <legend class="sr">Einwilligung</legend>
              <label class="wahl eckig"><input type="checkbox" name="einwilligung" value="ja" required><b>Ihr dürft meine Angaben für die Anmeldung speichern.</b><small>Details in den Datenschutzhinweisen. Du bekommst eine Kopie per E-Mail.</small></label>
              <p class="meldung" id="m-einw">Ohne dein Okay dürfen wir die Anmeldung nicht speichern.</p>
            </fieldset>
          </div>
          <div class="aktionen">
            <p class="status" role="status"></p>
            <button type="button" class="knopf neben" data-zurueck><span aria-hidden="true">←</span> Zurück</button>
            <button type="submit" class="knopf haupt" data-senden>Anmeldung senden <span aria-hidden="true">→</span></button>
          </div>
        </div>
      </section>
    </form>

    <section class="erfolg" data-erfolg hidden aria-labelledby="t-erfolg">
      <span class="stempel"><svg viewBox="0 0 20 20" aria-hidden="true"><circle cx="10" cy="10" r="9" fill="#142b50"/><path d="M5.8 10.4 8.6 13.1 14.2 7.3" fill="none" stroke="#ffd60a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg><span class="ziffern" data-nummer>Anmeldung GI-2611-0417</span></span>
      <h2 class="titel" id="t-erfolg" tabindex="-1"><span class="text" data-danke>Danke! Deine Anmeldung ist da.</span></h2>
      <p data-erfolg-text>Wir haben dir eine Bestätigung geschickt. So geht es weiter:</p>
      <ol class="naechste">
        <li><b>Wir rufen dich an</b><span>Innerhalb von zwei Werktagen, für einen Termin im Büro.</span></li>
        <li><b>Im Büro unterschreiben</b><span>Mitbringen: Ausweis, biometrisches Passbild, Sehtest und Nachweis über den Erste-Hilfe-Kurs.</span></li>
        <li><b>Antrag bei der Führerscheinstelle</b><span>Den Antrag stellst du selbst, wir sagen dir, wie.</span></li>
      </ol>
      <div><button type="button" class="knopf neben" data-nochmal>Neue Anmeldung</button></div>
    </section>
    <p class="entwurf" data-entwurf aria-live="off"></p>
  </section>
</main>

<!-- Variante PLZ zuerst: erst klären, ob der Dienst kommt, dann die Anfrage -->
<main class="pf" data-welt="pflege" hidden>
  <aside class="pf-tafel" aria-label="Pflegedienst am Isarbogen">
    <p class="pf-marke"><svg viewBox="0 0 40 40" aria-hidden="true"><circle cx="20" cy="20" r="20" fill="#ffc845"/><path d="M9 24c4-1 7 1 11 1s7-3 11-3" fill="none" stroke="#5b2a4a" stroke-width="2.6" stroke-linecap="round"/><path d="M20 25c0-6 3-11 8-13-1 6-3 10-8 13Z" fill="#5b2a4a"/></svg><span><b>Pflegedienst am Isarbogen</b>Ambulante Pflege · Landshut und Umgebung</span></p>
    <h1>Kommen wir zu Ihnen?</h1>
    <p>Geben Sie die Postleitzahl ein. Sie sehen sofort, ob wir in Ihrem Ort pflegen.</p>
    <div class="pf-person">
      <svg viewBox="0 0 72 72" aria-hidden="true"><circle cx="36" cy="36" r="36" fill="#f3e3ec"/><circle cx="36" cy="29" r="12" fill="#5b2a4a"/><path d="M14 64c3-13 12-19 22-19s19 6 22 19" fill="#5b2a4a"/><path d="M24 26c2-9 9-12 15-11 6 1 10 6 9 13-3-4-8-6-13-5-5 0-8 2-11 3Z" fill="#3b1a30"/></svg>
      <p><b>Sabine Hartl</b><span>Pflegedienstleitung</span><a class="pf-tel" href="tel:+498719651420">0871 965 14 20</a><span>Mo–Fr 8–16 Uhr, sonst Rückruf</span></p>
    </div>
    <p class="pf-orte"><b>Unsere Touren:</b> Landshut, Tiefenbach, Essenbach, Geisenhausen und Bruckberg. Ergoldsbach und Wörth an der Isar auf Anfrage.</p>
  </aside>

  <section class="pf-karte" aria-labelledby="pf-t1">
    <div class="pf-stand"><p data-pf-stand>Schritt 1 von 3 · Wohnort</p><div class="pf-balken" aria-hidden="true"><i class="an"></i><i></i><i></i></div></div>

    <div class="pf-schritt" data-pf="1">
      <h2 id="pf-t1" tabindex="-1">Wo wohnt die Person, die Hilfe braucht?</h2>
      <form class="pf-feld" data-pf-plz novalidate>
        <label for="pf-plz">Postleitzahl</label>
        <p class="pf-hilfe" id="pf-plz-hilfe">Fünf Ziffern, zum Beispiel 84034</p>
        <div class="pf-zeile">
          <input type="text" id="pf-plz" name="plz" inputmode="numeric" autocomplete="postal-code" maxlength="5" aria-describedby="pf-plz-hilfe pf-plz-fehler" aria-invalid="false">
          <button type="submit" class="pf-knopf pf-knopf--neben">Prüfen</button>
        </div>
        <p class="pf-fehler" id="pf-plz-fehler" data-pf-fehler hidden></p>
      </form>
      <div class="pf-antwort" data-pf-antwort role="status" hidden></div>
    </div>

    <form class="pf-schritt" data-pf="2" novalidate hidden>
      <h2 id="pf-t2" tabindex="-1">Wobei brauchen Sie Hilfe?</h2>
      <fieldset aria-describedby="pf-hilfe-fehler">
        <legend>Was wird gebraucht? <small>mehrere möglich</small></legend>
        <div class="pf-wahl">
          <label><input type="checkbox" name="hilfe" value="Waschen und Anziehen"><span>Waschen und Anziehen<small>(Grundpflege)</small></span></label>
          <label><input type="checkbox" name="hilfe" value="Tabletten, Spritzen, Verbände"><span>Tabletten, Spritzen, Verbände<small>(Behandlungspflege)</small></span></label>
          <label><input type="checkbox" name="hilfe" value="Haushalt und Einkauf"><span>Haushalt und Einkauf<small>(Alltagshilfe)</small></span></label>
          <label><input type="checkbox" name="hilfe" value="Entlastung für Angehörige"><span>Entlastung für Angehörige<small>(Verhinderungspflege)</small></span></label>
        </div>
        <p class="pf-fehler" id="pf-hilfe-fehler" data-pf-fehler hidden></p>
      </fieldset>
      <fieldset>
        <legend>Ab wann?</legend>
        <div class="pf-wahl">
          <label><input type="radio" name="ab" value="so bald wie möglich" checked><span>So bald wie möglich</span></label>
          <label><input type="radio" name="ab" value="in den nächsten Wochen"><span>In den nächsten Wochen</span></label>
        </div>
      </fieldset>
      <div class="pf-feld">
        <label for="pf-grad">Pflegegrad <small class="pf-hilfe">(freiwillig)</small></label>
        <select id="pf-grad" name="grad"><option value="">Keine Angabe</option><option>Noch keiner</option><option>Beantragt</option><option>Pflegegrad 1</option><option>Pflegegrad 2</option><option>Pflegegrad 3</option><option>Pflegegrad 4</option><option>Pflegegrad 5</option></select>
      </div>
      <div class="pf-aktionen"><button type="button" class="pf-knopf pf-knopf--neben" data-pf-zurueck>Zurück</button><button type="submit" class="pf-knopf">Weiter</button></div>
    </form>

    <form class="pf-schritt" data-pf="3" novalidate hidden>
      <h2 id="pf-t3" tabindex="-1">Wie erreichen wir Sie?</h2>
      <div class="pf-feld">
        <label for="pf-name">Ihr Name</label>
        <input type="text" id="pf-name" name="name" autocomplete="name" required aria-describedby="pf-name-fehler" aria-invalid="false">
        <p class="pf-fehler" id="pf-name-fehler" data-pf-fehler hidden></p>
      </div>
      <div class="pf-feld">
        <label for="pf-telefon">Telefon</label>
        <input type="tel" id="pf-telefon" name="telefon" autocomplete="tel" required aria-describedby="pf-telefon-fehler" aria-invalid="false">
        <p class="pf-fehler" id="pf-telefon-fehler" data-pf-fehler hidden></p>
      </div>
      <fieldset>
        <legend>Wann sollen wir anrufen?</legend>
        <div class="pf-wahl">
          <label><input type="radio" name="wann" value="vormittags" checked><span>Vormittags</span></label>
          <label><input type="radio" name="wann" value="nachmittags"><span>Nachmittags</span></label>
        </div>
      </fieldset>
      <p class="pf-datenschutz">Wir fragen nicht nach Krankheiten. Ihre Angaben nutzen wir nur für den Rückruf und löschen sie, wenn kein Auftrag entsteht.</p>
      <div class="pf-aktionen"><button type="button" class="pf-knopf pf-knopf--neben" data-pf-zurueck>Zurück</button><button type="submit" class="pf-knopf">Rückruf anfordern</button></div>
    </form>

    <div class="pf-schritt pf-fertig" data-pf="4" hidden>
      <h2 id="pf-t4" tabindex="-1">Danke. Wir rufen Sie an.</h2>
      <p data-pf-danke>Sabine Hartl meldet sich bis morgen Mittag.</p>
      <dl data-pf-zusammen></dl>
      <div class="pf-aktionen"><button type="button" class="pf-knopf pf-knopf--neben" data-pf-neu>Neue Anfrage</button></div>
    </div>

    <p class="pf-notfall"><b>Bei Lebensgefahr: 112.</b> Ärztlicher Bereitschaftsdienst: 116 117.</p>
  </section>
</main>

<div class="pult">
  <div class="modi" role="group" aria-label="Darstellung des Fortschritts">
    <button type="button" data-v="leiste" aria-pressed="true">Schrittleiste</button>
    <button type="button" data-v="ring" aria-pressed="false">Ring</button>
    <button type="button" data-v="senkrecht" aria-pressed="false">Senkrecht</button>
    <button type="button" data-v="plz" aria-pressed="false">PLZ zuerst</button>
  </div>
  <p data-werte></p>
</div>

<script>
(() => {
  const html = document.documentElement;
  const reduziert = () => html.classList.contains('reduziert');
  const vorfuehrung = () => html.classList.contains('vorfuehrung');
  const VT = typeof document.startViewTransition === 'function';
  const N = 4;
  const TITEL = ['Klasse und Standort', 'Über dich', 'Termine', 'Prüfen und senden'];
  const KLASSEN = { B: 'B · Auto', B197: 'B197 · Auto (Automatik)', A2: 'A2 · Motorrad', BE: 'BE · Anhänger' };
  const SPEICHER = 'lahnblick-anmeldung';

  const karte = document.querySelector('[data-karte]');
  const form = document.querySelector('[data-form]');
  const schritte = [...form.querySelectorAll('[data-schritt]')];
  const leiste = document.querySelector('[data-leiste]');
  const fuellung = leiste;
  const erfolg = document.querySelector('[data-erfolg]');
  const entwurf = document.querySelector('[data-entwurf]');
  const uebersicht = document.querySelector('[data-uebersicht]');
  const werte = document.querySelector('[data-werte]');
  const schalter = [...document.querySelectorAll('[data-v]')];
  const VARIANTEN = ['leiste', 'ring', 'senkrecht', 'plz'];

  let aktuell = 1, hoechster = 1, rueckZurUebersicht = false, modus = 'leiste', laufendeVT = null;
  let alteWerte = null;

  /* ---------- Schrittleiste aufbauen (ol mit aria-current="step") ---------- */
  TITEL.forEach((t, i) => {
    const li = document.createElement('li');
    li.innerHTML = `<span class="ziel"><span class="punkt"><span class="zahl">${i + 1}</span><svg class="haken" width="14" height="14" viewBox="0 0 14 14" aria-hidden="true"><path d="M3 7.3 5.8 10 11 4.4" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="name">${t}</span></span>`;
    leiste.appendChild(li);
  });
  const leistenPunkte = [...leiste.querySelectorAll('li')];

  /* ---------- Straße: Halte-Punkte und Auto (deko) ---------- */
  const weg = document.querySelector('[data-weg]');
  const auto = document.querySelector('[data-auto]');
  const halteG = document.querySelector('[data-halte]');
  const LAENGE = weg.getTotalLength();
  const ANTEIL = [.16, .4, .63, .86];
  const halte = ANTEIL.map((a, i) => {
    const p = weg.getPointAtLength(LAENGE * a);
    const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
    g.setAttribute('class', 'halt');
    g.innerHTML = `<circle cx="${p.x}" cy="${p.y - 30}" r="12"/><text x="${p.x}" y="${p.y - 30}">${i + 1}</text><line x1="${p.x}" x2="${p.x}" y1="${p.y - 18}" y2="${p.y - 12}" stroke="rgba(248,248,236,.4)" stroke-width="2"/>`;
    halteG.appendChild(g);
    return g;
  });
  let autoPos = ANTEIL[0], autoRaf = 0;
  function setzeAuto(a) {
    const l = LAENGE * a, p = weg.getPointAtLength(l), q = weg.getPointAtLength(Math.min(LAENGE, l + 2));
    const w = Math.atan2(q.y - p.y, q.x - p.x) * 180 / Math.PI;
    auto.setAttribute('transform', `translate(${p.x} ${p.y}) rotate(${w})`);
  }
  function fahre(ziel) {
    cancelAnimationFrame(autoRaf);
    const von = autoPos, t0 = performance.now(), dauer = 800;
    if (reduziert()) { autoPos = ziel; setzeAuto(ziel); return; }
    const expo = t => t >= 1 ? 1 : 1 - Math.pow(2, -10 * t);   /* expo.out ≈ cubic-bezier(0.16, 1, 0.3, 1) */
    (function schritt(t) {
      const k = Math.min(1, (t - t0) / dauer);
      autoPos = von + (ziel - von) * expo(k); setzeAuto(autoPos);
      if (k < 1) autoRaf = requestAnimationFrame(schritt);
    })(t0);
  }
  setzeAuto(autoPos);

  /* ---------- Werte, Zusammenfassung ---------- */
  const wert = name => (form.elements[name] && 'value' in form.elements[name]) ? form.elements[name].value.trim() : '';
  const zeiten = () => [...form.querySelectorAll('[name="zeiten"]:checked')].map(i => i.value);
  function geburtsdatum() {
    const t = wert('tag'), m = wert('monat'), j = wert('jahr');
    return (t && m && j) ? `${t.padStart(2, '0')}.${m.padStart(2, '0')}.${j}` : '';
  }
  function daten() {
    return [
      { schritt: 1, titel: TITEL[0], zeilen: [['Klasse', KLASSEN[wert('klasse')] || ''], ['Standort', wert('standort')]] },
      { schritt: 2, titel: TITEL[1], zeilen: [['Name', [wert('vorname'), wert('nachname')].filter(Boolean).join(' ')], ['Geburtsdatum', geburtsdatum()], ['E-Mail', wert('email')], ['Telefon', wert('telefon')]] },
      { schritt: 3, titel: TITEL[2], zeilen: [['Fahrstunden', zeiten().join(', ')], ['Theorie', wert('theorie')], ['Sprache', wert('sprache')]] }
    ];
  }
  const esc = s => s.replace(/[&<>"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' })[c]);
  function baueUebersicht() {
    const d = daten();
    uebersicht.innerHTML = d.map(g => `
      <section class="block" aria-labelledby="u${g.schritt}">
        <div class="block-kopf"><h3 id="u${g.schritt}">${g.titel}</h3>
          <button type="button" class="aendern" data-aendern="${g.schritt}">Ändern<span class="sr">: ${g.titel}</span></button></div>
        <dl>${g.zeilen.map(([k, v]) => {
          const neu = alteWerte && alteWerte[k] !== undefined && alteWerte[k] !== v;
          return `<div${k === 'E-Mail' ? ' class="lang"' : ''}><dt>${k}</dt><dd class="${v ? '' : 'leer'}${neu ? ' neu' : ''}"><span>${v ? esc(v) : 'nicht angegeben'}</span></dd></div>`;
        }).join('')}</dl>
      </section>`).join('');
    alteWerte = null;
    requestAnimationFrame(() => requestAnimationFrame(() => uebersicht.querySelectorAll('dd.neu').forEach(dd => dd.classList.remove('neu'))));
  }
  function merkeWerte() { alteWerte = {}; daten().forEach(g => g.zeilen.forEach(([k, v]) => { alteWerte[k] = v; })); }
  function kurzfassungen() {
    const d = daten();
    schritte.forEach((s, i) => {
      const k = s.querySelector('[data-kurz]');
      if (k && d[i]) k.textContent = d[i].zeilen.map(z => z[1]).filter(Boolean).slice(0, 3).join(' · ');
    });
  }

  /* ---------- Validierung: erst nach Interaktion oder nach „Weiter“ ---------- */
  function zeitenRegel() {
    const boxen = [...form.querySelectorAll('[name="zeiten"]')];
    boxen[0].setCustomValidity(zeiten().length ? '' : 'Mindestens eine Zeit');
  }
  const felderIn = s => [...s.querySelectorAll('input, select, textarea')];
  function syncAria(s) {
    const versucht = s.classList.contains('versucht');
    felderIn(s).forEach(el => {
      const falsch = !el.validity.valid && (versucht || el.matches(':user-invalid'));
      el.setAttribute('aria-invalid', falsch ? 'true' : 'false');
      const gruppe = el.closest('.gruppe, .feld');
      const m = gruppe && gruppe.querySelector('.meldung');
      if (m && el.type !== 'radio' && el.type !== 'checkbox' && !el.closest('.datum')) el.setAttribute('aria-describedby', m.id);
    });
    const offen = offeneGruppen(s).length;
    const st = s.querySelector('.status');
    if (versucht) {
      st.textContent = offen ? (offen === 1 ? 'Eine Angabe fehlt noch.' : `${offen} Angaben fehlen noch.`) : (s.dataset.schritt == N ? 'Alles bereit zum Senden.' : 'Alles da. Weiter geht’s.');
      st.classList.toggle('problem', offen > 0);
    }
  }
  function offeneGruppen(s) {
    const gruppen = new Set();
    felderIn(s).forEach(el => { if (!el.validity.valid) gruppen.add(el.closest('.gruppe, .feld') || el); });
    return [...gruppen];
  }
  function pruefe(s) {
    zeitenRegel();
    s.classList.add('versucht');
    syncAria(s);
    const offen = offeneGruppen(s);
    if (offen.length) {
      const erstes = offen[0].querySelector('input:invalid, select:invalid') || offen[0];
      erstes.focus({ preventScroll: vorfuehrung() });
      return false;
    }
    return true;
  }
  form.addEventListener('input', e => {
    zeitenRegel();
    const s = e.target.closest('[data-schritt]');
    if (s) syncAria(s);
    speichern();
  });
  form.addEventListener('change', e => { const s = e.target.closest('[data-schritt]'); if (s) syncAria(s); });
  form.addEventListener('focusout', e => { const s = e.target.closest('[data-schritt]'); if (s) setTimeout(() => syncAria(s)); });

  /* ---------- Entwurf in sessionStorage (Zurück und Neuladen ohne Datenverlust) ---------- */
  let speicherZeit = 0;
  function speichern() {
    if (vorfuehrung()) return;
    try {
      const d = {};
      new FormData(form).forEach((v, k) => { (d[k] = d[k] || []).push(v); });
      sessionStorage.setItem(SPEICHER, JSON.stringify({ d, aktuell, hoechster }));
      entwurf.textContent = 'Entwurf in diesem Tab gespeichert';
      entwurf.classList.add('an');
      clearTimeout(speicherZeit); speicherZeit = setTimeout(() => entwurf.classList.remove('an'), 1200);
    } catch (_) {}
  }
  function laden() {
    try {
      const roh = sessionStorage.getItem(SPEICHER);
      if (!roh) return;
      const { d, aktuell: a, hoechster: h } = JSON.parse(roh);
      felderIn(form).forEach(el => {
        const v = d[el.name];
        if (!v) { if (el.type === 'checkbox') el.checked = false; return; }
        if (el.type === 'radio' || el.type === 'checkbox') el.checked = v.includes(el.value);
        else el.value = v[0];
      });
      hoechster = Math.min(N, h || 1);
      zeige(Math.min(hoechster, a || 1));
      entwurf.textContent = 'Entwurf wiederhergestellt';
    } catch (_) {}
  }

  /* ---------- Anzeige eines Schritts ---------- */
  function zeige(n) {
    aktuell = n; hoechster = Math.max(hoechster, n);
    const senkrecht = modus === 'senkrecht';
    schritte.forEach((s, i) => {
      const nr = i + 1;
      s.hidden = senkrecht ? false : nr !== n;
      s.querySelector('.schritt-inhalt').hidden = nr !== n;
      s.classList.toggle('aktuell', nr === n);
      s.classList.toggle('erledigt', nr < n || (nr <= hoechster && nr !== n && nr < N));
      s.classList.toggle('kommt', nr > n && nr > hoechster);
      const ae = s.querySelector('.schritt-kopf .aendern');
      if (ae) ae.hidden = !(senkrecht && s.classList.contains('erledigt'));
      s.querySelector('[data-weiter]') && (s.querySelector('[data-weiter]').hidden = rueckZurUebersicht && nr < N);
      const zu = s.querySelector('[data-zur-uebersicht]');
      if (zu) zu.hidden = !(rueckZurUebersicht && nr < N);
      const zr = s.querySelector('[data-zurueck]');
      if (zr) zr.hidden = rueckZurUebersicht && nr < N;
    });
    // Schrittleiste: erledigte Schritte sind Knöpfe, der aktuelle trägt aria-current="step"
    leistenPunkte.forEach((li, i) => {
      const nr = i + 1, erledigt = nr < n || (nr <= hoechster && nr !== n && nr < N);
      li.classList.toggle('erledigt', erledigt);
      if (nr === n) li.setAttribute('aria-current', 'step'); else li.removeAttribute('aria-current');
      const innen = li.firstElementChild;
      const sollKnopf = erledigt;
      if (sollKnopf !== (innen.tagName === 'BUTTON')) {
        const neu = document.createElement(sollKnopf ? 'button' : 'span');
        if (sollKnopf) { neu.type = 'button'; neu.dataset.gehe = nr; } else neu.className = 'ziel';
        neu.innerHTML = innen.innerHTML; li.replaceChild(neu, innen);
      }
      const name = li.querySelector('.name');
      name.innerHTML = TITEL[i] + (erledigt ? '<span class="sr"> (erledigt, ändern)</span>' : nr === n ? '<span class="sr"> (aktuell)</span>' : '');
    });
    const anteil = (n - 1) / (N - 1);
    fuellung.style.setProperty('--anteil', anteil);
    karte.style.setProperty('--anteil-ring', n / N);
    document.querySelector('[data-ring-zahl]').textContent = `${n}/${N}`;
    document.querySelector('[data-ring-jetzt]').textContent = n < N ? `Noch ${N - n} ${N - n === 1 ? 'Schritt' : 'Schritte'} nach diesem` : 'Letzter Schritt';
    document.querySelector('[data-ring-naechst]').textContent = n < N ? `Als Nächstes: ${TITEL[n]}` : 'Danach: Bestätigung per E-Mail';
    halte.forEach((g, i) => { g.classList.toggle('erledigt', i + 1 < n); g.classList.toggle('aktuell', i + 1 === n); });
    fahre(ANTEIL[n - 1]);
    if (n === N) baueUebersicht();
    kurzfassungen();
    document.title = `Schritt ${n} von ${N}: ${TITEL[n - 1]} · Fahrschule Lahnblick`;
  }

  /* ---------- Wechsel mit View Transition (Fallback: kurze CSS-Einblendung) ---------- */
  function wechsel(update, richtung, fokusZiel) {
    html.dataset.richtung = richtung;
    const danach = () => {
      const ziel = fokusZiel();
      if (ziel) ziel.focus({ preventScroll: true });
      const oben = karte.getBoundingClientRect().top;
      if (oben < 22 && scrollY > 0) scrollTo({ top: Math.max(0, scrollY + oben - 22), behavior: reduziert() ? 'auto' : 'smooth' });
    };
    if (laufendeVT) laufendeVT.skipTransition();
    if (VT && !reduziert()) {
      laufendeVT = document.startViewTransition(update);
      laufendeVT.ready.catch(() => {});
      laufendeVT.updateCallbackDone.then(danach, danach);
      laufendeVT.finished.finally(() => { laufendeVT = null; });
    } else {
      update();
      if (!reduziert()) {
        const z = form.hidden ? erfolg : (modus === 'senkrecht' ? form.querySelector('.schritt.aktuell') : form);
        z.classList.remove('einblenden'); void z.offsetWidth; z.classList.add('einblenden');
      }
      danach();
    }
  }
  const titelVon = n => () => schritte[n - 1].querySelector('.titel');
  function gehe(n, richtung) {
    if (n === aktuell) return;
    wechsel(() => zeige(n), richtung || (n > aktuell ? 'vor' : 'zurueck'), titelVon(n));
  }

  form.addEventListener('click', e => {
    const b = e.target.closest('button');
    if (!b) return;
    const s = b.closest('[data-schritt]');
    if (b.hasAttribute('data-weiter')) { if (pruefe(s)) gehe(aktuell + 1, 'vor'); }
    else if (b.hasAttribute('data-zurueck')) gehe(aktuell - 1, 'zurueck');
    else if (b.hasAttribute('data-zur-uebersicht')) { if (pruefe(s)) { rueckZurUebersicht = false; gehe(N, 'vor'); } }
    else if (b.dataset.aendern) { merkeWerte(); rueckZurUebersicht = true; gehe(+b.dataset.aendern, 'zurueck'); }
    else if (b.dataset.gehe) { rueckZurUebersicht = false; gehe(+b.dataset.gehe); }
  });
  leiste.addEventListener('click', e => {
    const b = e.target.closest('button[data-gehe]');
    if (b) { rueckZurUebersicht = false; gehe(+b.dataset.gehe, 'zurueck'); }
  });

  form.addEventListener('submit', e => {
    e.preventDefault();
    if (aktuell !== N) { const s = schritte[aktuell - 1]; if (pruefe(s)) gehe(aktuell + 1, 'vor'); return; }
    if (!pruefe(schritte[N - 1])) return;
    const vorname = wert('vorname') || 'dir';
    document.querySelector('[data-danke]').textContent = `Danke, ${vorname}. Deine Anmeldung ist da.`;
    document.querySelector('[data-erfolg-text]').textContent = `Die Bestätigung geht an ${wert('email')}. So geht es weiter:`;
    document.querySelector('[data-nummer]').textContent = `Anmeldung GI-2611-${String(400 + Math.floor(Math.random() * 99)).padStart(4, '0')}`;
    wechsel(() => {
      form.hidden = true; erfolg.hidden = false;
      leistenPunkte.forEach(li => { li.classList.add('erledigt'); li.removeAttribute('aria-current'); });
      fuellung.style.setProperty('--anteil', 1); karte.style.setProperty('--anteil-ring', 1);
      document.querySelector('[data-ring-jetzt]').textContent = 'Angemeldet';
      document.querySelector('[data-ring-naechst]').textContent = 'Wir melden uns in zwei Werktagen';
      halte.forEach(g => { g.classList.add('erledigt'); g.classList.remove('aktuell'); });
      fahre(.9);
      document.title = 'Anmeldung gesendet · Fahrschule Lahnblick';
    }, 'vor', () => erfolg.querySelector('.titel'));
    try { sessionStorage.removeItem(SPEICHER); } catch (_) {}
    entwurf.textContent = '';
  });

  function zuruecksetzen() {
    if (laufendeVT) laufendeVT.skipTransition();
    form.reset();
    schritte.forEach(s => { s.classList.remove('versucht'); const st = s.querySelector('.status'); st.textContent = ''; st.classList.remove('problem'); felderIn(s).forEach(el => el.setAttribute('aria-invalid', 'false')); });
    form.querySelectorAll('.fokus').forEach(f => f.classList.remove('fokus'));
    form.hidden = false; erfolg.hidden = true;
    hoechster = 1; rueckZurUebersicht = false; alteWerte = null;
    zeitenRegel(); zeige(1);
    entwurf.textContent = '';
  }
  document.querySelector('[data-nochmal]').addEventListener('click', () => {
    wechsel(() => { zuruecksetzen(); }, 'zurueck', titelVon(1));
  });


  /* ---------- Variante PLZ zuerst: Kommen wir zu Ihnen? Dann die Anfrage ---------- */
  // Einzugsgebiet: [Ort, Antwort, Anfahrtspauschale je Einsatz in Euro]. Beispielwerte; echte Werte kommen vom Kunden.
  const ORTE = {
    84028: ['Landshut', 'ja', 0], 84030: ['Landshut', 'ja', 0], 84032: ['Landshut', 'ja', 0], 84034: ['Landshut', 'ja', 0], 84036: ['Landshut', 'ja', 0],
    84184: ['Tiefenbach', 'ja', 0], 84051: ['Essenbach', 'ja', 0],
    84144: ['Geisenhausen', 'ja', 4.9], 84079: ['Bruckberg', 'ja', 4.9],
    84061: ['Ergoldsbach', 'anfrage', 7.5], 84109: ['Wörth an der Isar', 'anfrage', 7.5],
  };
  const pf = document.querySelector('[data-welt="pflege"]');
  const pfSchritte = [...pf.querySelectorAll('[data-pf]')];
  const pfPlz = pf.querySelector('[data-pf-plz]'), pfFeld = pf.querySelector('#pf-plz'), pfAntwort = pf.querySelector('[data-pf-antwort]');
  const pfStand = pf.querySelector('[data-pf-stand]'), pfBalken = [...pf.querySelectorAll('.pf-balken i')];
  const STAND = ['Wohnort', 'Hilfe', 'Rückruf', 'Fertig'];
  const euro = n => n.toLocaleString('de-DE', { minimumFractionDigits: 2 }) + ' €';
  const ICON = {
    ja: '<svg viewBox="0 0 44 44" aria-hidden="true"><circle cx="22" cy="22" r="21" fill="#5b2a4a"/><path d="m13 22.5 6 6 12-13" fill="none" stroke="#fff" stroke-width="3.4" stroke-linecap="round" stroke-linejoin="round"/></svg>',
    anfrage: '<svg viewBox="0 0 44 44" aria-hidden="true"><circle cx="22" cy="22" r="21" fill="#ffc845" stroke="#1f1b16" stroke-width="2"/><path d="M17 17.5a5 5 0 1 1 7 4.6c-1.4.6-2 1.5-2 3v1" fill="none" stroke="#1f1b16" stroke-width="3" stroke-linecap="round"/><circle cx="22" cy="31.5" r="2" fill="#1f1b16"/></svg>',
    nein: '<svg viewBox="0 0 44 44" aria-hidden="true"><circle cx="22" cy="22" r="20" fill="#fff" stroke="#4a443d" stroke-width="2.4"/><path d="m15.5 15.5 13 13m0-13-13 13" stroke="#4a443d" stroke-width="3" stroke-linecap="round"/></svg>',
  };
  let pfJetzt = 1, pfOrt = null;
  function pfWelt(an) {
    document.querySelector('[data-welt="fahrschule"]').hidden = an;
    pf.hidden = !an;
    html.classList.toggle('welt-pflege', an);
  }
  function pfFehler(feld, text) {
    const f = document.getElementById(feld.getAttribute('aria-describedby').split(' ').pop());
    f.textContent = text || ''; f.hidden = !text;
    if (feld.matches('input')) feld.setAttribute('aria-invalid', String(!!text));
  }
  function pfZeige(n, fokus = true) {
    pfJetzt = n;
    pfSchritte.forEach(sch => {
      const an = +sch.dataset.pf === n;
      if (an && sch.hidden) { sch.hidden = false; if (!reduziert()) { sch.classList.remove('rein'); void sch.offsetWidth; sch.classList.add('rein'); } }
      else if (!an) sch.hidden = true;
    });
    pfStand.textContent = n < 4 ? `Schritt ${n} von 3 · ${STAND[n - 1]}` : 'Anfrage gesendet';
    pfBalken.forEach((b, i) => b.classList.toggle('an', i < n));
    if (fokus) pf.querySelector(`#pf-t${n}`).focus({ preventScroll: vorfuehrung() });
  }
  function pfPruefe() {
    const plz = pfFeld.value.trim();
    if (!/^\d{5}$/.test(plz)) { pfFehler(pfFeld, plz ? 'Bitte genau fünf Ziffern eingeben.' : 'Bitte geben Sie eine Postleitzahl ein.'); pfAntwort.hidden = true; pfFeld.focus(); return; }
    pfFehler(pfFeld, '');
    const o = ORTE[plz];
    pfOrt = o ? { plz, ort: o[0], art: o[1], pauschale: o[2] } : { plz, ort: null, art: 'nein', pauschale: 0 };
    const { ort, art, pauschale } = pfOrt;
    let inhalt;
    if (art === 'ja') inhalt = `<h3>Ja, wir kommen nach ${ort}.</h3><p>${ort} liegt auf unseren festen Touren.</p>`
      + `<p class="pf-pauschale">${pauschale ? `Anfahrtspauschale ${euro(pauschale)} je Einsatz` : 'Keine Anfahrtspauschale'}<small>${pauschale ? 'Für Orte außerhalb von Landshut. Was die Pflegekasse übernimmt, klären wir im Beratungsgespräch.' : 'Ihr Ort liegt im Kerngebiet.'}</small></p>`
      + `<div class="pf-aktionen"><button type="button" class="pf-knopf" data-pf-weiter>Weiter zur Anfrage</button></div>`;
    else if (art === 'anfrage') inhalt = `<h3>Auf Anfrage: ${ort} liegt am Rand unserer Touren.</h3><p>Ob es passt, hängt von den Uhrzeiten ab. Rufen Sie an oder fragen Sie an. Wir melden uns noch heute.</p>`
      + `<p class="pf-pauschale">Anfahrtspauschale ${euro(pauschale)} je Einsatz, wenn es passt<small>Für Einsätze am Rand unseres Gebiets.</small></p>`
      + `<div class="pf-aktionen"><a class="pf-knopf pf-knopf--neben" href="tel:+498719651420">Anrufen</a><button type="button" class="pf-knopf" data-pf-weiter>Trotzdem anfragen</button></div>`;
    else inhalt = `<h3>Nein, nach ${plz} kommen wir leider nicht.</h3><p>Der Pflegestützpunkt in Ihrem Landkreis kennt die Pflegedienste vor Ort. Die Adresse nennt Ihnen Ihre Pflegekasse.</p>`
      + `<div class="pf-aktionen"><button type="button" class="pf-knopf pf-knopf--neben" data-pf-andere>Andere Postleitzahl</button></div>`;
    pfAntwort.dataset.art = art;
    pfAntwort.innerHTML = ICON[art] + '<div>' + inhalt + '</div>';
    pfAntwort.hidden = false;
    if (!reduziert()) { pfAntwort.classList.remove('rein'); void pfAntwort.offsetWidth; pfAntwort.classList.add('rein'); }
  }
  pfPlz.addEventListener('submit', e => { e.preventDefault(); pfPruefe(); });
  pfFeld.addEventListener('input', () => { pfFeld.value = pfFeld.value.replace(/\D/g, '').slice(0, 5); if (pfFeld.getAttribute('aria-invalid') === 'true' && /^\d{5}$/.test(pfFeld.value)) pfFehler(pfFeld, ''); });
  pfAntwort.addEventListener('click', e => {
    if (e.target.closest('[data-pf-weiter]')) pfZeige(2);
    if (e.target.closest('[data-pf-andere]')) { pfAntwort.hidden = true; pfFeld.select(); pfFeld.focus(); }
  });
  pf.querySelectorAll('[data-pf-zurueck]').forEach(b => b.addEventListener('click', () => pfZeige(pfJetzt - 1)));
  const pf2 = pf.querySelector('[data-pf="2"]'), pf3 = pf.querySelector('[data-pf="3"]');
  pf2.addEventListener('submit', e => {
    e.preventDefault();
    const fs = pf2.querySelector('fieldset');
    if (!pf2.querySelector('[name="hilfe"]:checked')) { pfFehler(fs, 'Bitte wählen Sie mindestens einen Punkt.'); pf2.querySelector('[name="hilfe"]').focus(); return; }
    pfFehler(fs, ''); pfZeige(3);
  });
  pf2.addEventListener('change', () => { if (pf2.querySelector('[name="hilfe"]:checked')) pfFehler(pf2.querySelector('fieldset'), ''); });
  pf3.addEventListener('submit', e => {
    e.preventDefault();
    const leer = [...pf3.querySelectorAll('[required]')].filter(f => { const l = !f.value.trim(); pfFehler(f, l ? (f.name === 'name' ? 'Bitte geben Sie Ihren Namen ein.' : 'Bitte geben Sie eine Telefonnummer ein.') : ''); return l; });
    if (leer.length) { leer[0].focus(); return; }
    const hilfe = [...pf2.querySelectorAll('[name="hilfe"]:checked')].map(c => c.value).join(', ');
    const zeilen = [['Ort', `${pfOrt.plz} ${pfOrt.ort || ''}`], ['Hilfe', hilfe], ['Ab', pf2.elements.ab.value], ['Rückruf', `${pf3.elements.wann.value}, ${pf3.elements.telefon.value}`]];
    if (pf2.elements.grad.value) zeilen.splice(3, 0, ['Pflegegrad', pf2.elements.grad.value]);
    pf.querySelector('[data-pf-zusammen]').innerHTML = zeilen.map(([k, v]) => `<dt>${k}</dt><dd>${v.replace(/</g, '&lt;')}</dd>`).join('');
    pf.querySelector('[data-pf-danke]').textContent = `Sabine Hartl ruft Sie ${pf3.elements.wann.value === 'vormittags' ? 'morgen Vormittag' : 'heute oder morgen Nachmittag'} an.`;
    pfZeige(4);
  });
  pf3.addEventListener('input', e => { if (e.target.matches('[required]') && e.target.value.trim()) pfFehler(e.target, ''); });
  function pfReset() {
    pf.querySelectorAll('form').forEach(f => f.reset());
    pf.querySelectorAll('[data-pf-fehler]').forEach(f => { f.hidden = true; f.textContent = ''; });
    pf.querySelectorAll('[aria-invalid]').forEach(f => f.setAttribute('aria-invalid', 'false'));
    pfAntwort.hidden = true; pfOrt = null; pfZeige(1, false);
  }
  pf.querySelector('[data-pf-neu]').addEventListener('click', () => { pfReset(); pf.querySelector('#pf-t1').focus(); });

  /* ---------- Varianten (?v=leiste|ring|senkrecht|plz) ---------- */
  const WERTE = {
    leiste: 'Schrittleiste · <b>ol</b> + aria-current="step"',
    ring: 'Ring · stroke-dashoffset <b>220 ms</b>',
    senkrecht: 'Senkrecht · je Schritt view-transition-name, Größe morpht <b>220 ms</b>',
    plz: 'PLZ zuerst · Antwort und Schritt öffnen <b>300 ms</b> cubic-bezier(0.16, 1, 0.3, 1), <b>8 px</b> · schließen <b>200 ms</b> cubic-bezier(0.7, 0, 0.84, 0)'
  };
  function variante(v, inUrl) {
    if (!VARIANTEN.includes(v)) v = VARIANTEN[0];
    modus = v; if (v !== 'plz') karte.dataset.modus = v;
    pfWelt(v === 'plz');
    schalter.forEach(b => b.setAttribute('aria-pressed', String(b.dataset.v === v)));
    if (v === 'plz') werte.innerHTML = WERTE.plz; else werte.innerHTML = `Wechsel <b>${160}/${220} ms</b> · cubic-bezier(0.4, 0, 0.2, 1) · <b>24 px</b> · Auto <b>800 ms</b> expo.out · View Transitions: <b>${VT ? 'ja' : 'Fallback'}</b> · ${WERTE[v]}`;
    if (inUrl) { const q = new URLSearchParams(location.search); q.set('v', v); history.replaceState(null, '', location.pathname + '?' + q.toString() + location.hash); }
    zeige(aktuell);
  }
  schalter.forEach(b => b.addEventListener('click', () => {
    const lief = vorfuehrung();
    if (lief) pause();
    const vt = VT && !reduziert() ? document.startViewTransition(() => variante(b.dataset.v, true)) : (variante(b.dataset.v, true), null);
    if (lief) (vt ? vt.finished : Promise.resolve()).then(vorfuehren);
  }));

  /* ---------- Vorführung: Lena meldet sich an (ca. 24 s je Runde) ---------- */
  let zeitgeber = [], laeuft = false;
  const nach = (ms, f) => zeitgeber.push(setTimeout(f, ms));
  function tippAuf(el) {
    if (!el || reduziert()) return;
    const r = el.getBoundingClientRect();
    const t = document.createElement('span'); t.className = 'tipp';
    t.style.left = (r.left + Math.min(r.width / 2, 60)) + 'px'; t.style.top = (r.top + r.height / 2) + 'px';
    document.body.appendChild(t); setTimeout(() => t.remove(), 500);
  }
  function waehle(name, wertV, typ = 'radio') {
    const el = form.querySelector(`[name="${name}"][value="${wertV}"]`);
    tippAuf(el.closest('label'));
    el.checked = typ === 'checkbox' ? !el.checked : true;
    el.dispatchEvent(new Event('input', { bubbles: true })); el.dispatchEvent(new Event('change', { bubbles: true }));
  }
  function druecke(sel) {
    const s = schritte[aktuell - 1];
    const b = (form.hidden ? erfolg : (sel.startsWith('[data-aendern') ? uebersicht : s)).querySelector(sel);
    if (!b) return;
    tippAuf(b); b.classList.add('gedrueckt');
    setTimeout(() => { b.classList.remove('gedrueckt'); b.click(); }, 150);
  }
  function tippe(t0, name, text, ms = 60) {
    const el = form.elements[name];
    nach(t0, () => { form.querySelectorAll('.fokus').forEach(f => f.classList.remove('fokus')); (el.closest('.feld') || el.parentElement).classList.add('fokus'); });
    [...text].forEach((z, k) => nach(t0 + 80 + k * ms, () => { el.value += z; el.dispatchEvent(new Event('input', { bubbles: true })); }));
    const ende = t0 + 80 + text.length * ms;
    nach(ende + 60, () => { (el.closest('.feld') || el.parentElement).classList.remove('fokus'); });
    return ende + 160;
  }
  function pfTippe(t0, el, text, ms = 80) {
    nach(t0, () => { tippAuf(el); el.value = ''; });
    [...text].forEach((z, k) => nach(t0 + 80 + k * ms, () => { el.value += z; el.dispatchEvent(new Event('input', { bubbles: true })); }));
    return t0 + 160 + text.length * ms;
  }
  function pfDruecke(f) { const b = f(); if (!b) return; tippAuf(b); b.classList.add('gedrueckt'); setTimeout(() => { b.classList.remove('gedrueckt'); b.click(); }, 150); }
  function pfWaehle(sel) { const el = pf.querySelector(sel); tippAuf(el.closest('label')); el.checked = true; el.dispatchEvent(new Event('change', { bubbles: true })); }
  function rundePlz() {
    pfReset(); scrollTo({ top: 0, behavior: 'auto' });
    let t = 600;
    t = pfTippe(t, pfFeld, '84478'); nach(t + 150, () => pfDruecke(() => pfPlz.querySelector('[type="submit"]'))); t += 2600;   // Nein
    nach(t, () => pfDruecke(() => pfAntwort.querySelector('[data-pf-andere]'))); t += 500;
    t = pfTippe(t, pfFeld, '84061'); nach(t + 150, () => pfDruecke(() => pfPlz.querySelector('[type="submit"]'))); t += 2800;   // auf Anfrage
    nach(t, () => { pfAntwort.hidden = true; }); t += 300;
    t = pfTippe(t, pfFeld, '84144'); nach(t + 150, () => pfDruecke(() => pfPlz.querySelector('[type="submit"]'))); t += 2600;   // Ja, mit Pauschale
    nach(t, () => pfDruecke(() => pfAntwort.querySelector('[data-pf-weiter]'))); t += 1000;
    nach(t, () => pfWaehle('[name="hilfe"][value="Waschen und Anziehen"]')); t += 600;
    nach(t, () => pfWaehle('[name="hilfe"][value="Haushalt und Einkauf"]')); t += 600;
    nach(t, () => pfDruecke(() => pf2.querySelector('[type="submit"]'))); t += 1000;
    t = pfTippe(t, pf3.elements.name, 'Petra Lindner', 60);
    t = pfTippe(t, pf3.elements.telefon, '0871 4402 18', 60);
    nach(t + 200, () => pfWaehle('[name="wann"][value="nachmittags"]')); t += 800;
    nach(t, () => pfDruecke(() => pf3.querySelector('[type="submit"]'))); t += 3800;
    nach(t, eineRunde);
  }
  function eineRunde() {
    if (modus === 'plz') { rundePlz(); return; }
    zuruecksetzen();
    scrollTo({ top: 0, behavior: 'auto' });
    let t = 700;
    nach(t, () => waehle('klasse', 'B')); t += 650;
    nach(t, () => waehle('standort', 'Innenstadt')); t += 750;
    nach(t, () => druecke('[data-weiter]')); t += 900;
    t = tippe(t, 'vorname', 'Lena', 70);
    t = tippe(t, 'nachname', 'Brück', 70);
    t = tippe(t, 'tag', '14', 90); t = tippe(t, 'monat', '03', 90); t = tippe(t, 'jahr', '2009', 90);
    nach(t + 200, () => druecke('[data-weiter]')); t += 1900;           // E-Mail fehlt: Fehler erst jetzt
    t = tippe(t, 'email', 'lena.brueck@beispiel.de', 45); t += 500;      // sofort grün, sobald gültig
    nach(t, () => druecke('[data-weiter]')); t += 900;
    nach(t, () => waehle('zeiten', 'Nachmittags', 'checkbox')); t += 450;
    nach(t, () => waehle('zeiten', 'Samstags', 'checkbox')); t += 450;
    nach(t, () => waehle('theorie', 'Ferienkurs')); t += 700;
    nach(t, () => druecke('[data-weiter]')); t += 2200;                 // Übersicht lesen
    nach(t, () => druecke('[data-aendern="1"]')); t += 1100;            // zurück in Schritt 1, B ist noch gewählt
    nach(t, () => waehle('klasse', 'B197')); t += 700;
    nach(t, () => druecke('[data-zur-uebersicht]')); t += 1900;         // geänderte Zeile leuchtet kurz
    nach(t - 500, () => { const a = schritte[N - 1].querySelector('.aktionen'); if (a.getBoundingClientRect().bottom > innerHeight) a.scrollIntoView({ block: 'end', behavior: reduziert() ? 'auto' : 'smooth' }); });
    nach(t, () => waehle('einwilligung', 'ja', 'checkbox')); t += 650;
    nach(t, () => druecke('[data-senden]')); t += 4200;
    nach(t, eineRunde);
  }
  function pause() { zeitgeber.forEach(clearTimeout); zeitgeber = []; laeuft = false; }
  function neustart() { pause(); html.classList.remove('vorfuehrung'); zuruecksetzen(); pfReset(); }
  function vorfuehren() {
    pause(); html.classList.add('vorfuehrung'); laeuft = true;
    try { sessionStorage.removeItem(SPEICHER); } catch (_) {}
    eineRunde();
  }
  function weiter() { if (!laeuft) { if (html.classList.contains('vorfuehrung')) { laeuft = true; eineRunde(); } else vorfuehren(); } }

  // Start
  zeitenRegel();
  variante(new URLSearchParams(location.search).get('v'), false);
  zeige(1);
  laden();

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