Eingabehilfen und Autofill
Terminanfrage einer Kfz-Werkstatt, bei der jedes Feld dem Browser sagt, was es will: ein Inspektor zeigt Token und Tastatur, Kennzeichen und Kilometer werden deutsch gesetzt, die PLZ schlägt den Ort vor, Autofill bekommt Gelb mit „bitte prüfen“.
Jedes Feld sagt dem Browser, was es will: autocomplete-Token für Name, Telefon, E-Mail, Straße, PLZ und Ort, type und inputmode für die passende Telefon-Tastatur, enterkeyhint für die Eingabetaste. Deutsche Formate werden angenommen, wie Menschen sie tippen (Telefon mit Leerzeichen oder Schrägstrich, Kennzeichen mit oder ohne Bindestrich, Kilometer mit Punkt) und erst beim Verlassen ordentlich gesetzt. Die PLZ schlägt den Ort vor, überschreibbar. Freiwillige Felder sind mit „freiwillig“ beschriftet. Fehlertexte nennen, was fehlt, mit Beispiel. Vom Browser ausgefüllte Felder bekommen über :autofill einen eigenen Stil in der Markenfarbe statt Gelb oder Blau, mit dem Hinweis „bitte prüfen“.
Auf dem Telefon kostet jedes getippte Feld Zeit. Mit richtigen Token füllt der Browser Name, Adresse und Telefon in einem Tipp; die passende Tastatur spart Wechsel. Wer „0521/12 34 56“ tippen darf, statt an einem starren Muster zu scheitern, bricht seltener ab. Ein eigener Autofill-Stil wirkt gestaltet und erinnert daran, die Angaben zu prüfen.
Wann einsetzen
Jedes Formular mit persönlichen Angaben: Kontakt, Termin, Rückruf, Bewerbung, Bestellung, Hol- und Bringdienst. Pflicht im Generator für alle Formulare.
Wann nicht
Kein autocomplete für Angaben über Dritte (Empfänger eines Geschenks) ohne section- oder shipping-Präfix. PLZ-Ortsvorschlag nur mit echter, gepflegter Liste oder Dienst; nie den Ort gegen die Eingabe erzwingen. Keine Formatprüfung, die echte Eingaben abweist (Doppelnamen, ausländische Nummern).
Varianten
- Termin: Name, Telefon, E-Mail (freiwillig), Kennzeichen, Kilometerstand (freiwillig), Wunschtag
- Abholung: Abholadresse mit section- und shipping-Token, PLZ schlägt Ort vor, abweichende Rechnungsadresse mit billing-Token
- Übersicht: Spickzettel aller Felder mit type, inputmode, autocomplete, weiteren Attributen und Fehlertext
Umsetzung
Labels über jedem Feld, nie nur Platzhalter. Token: name, tel, email, street-address oder address-line1, postal-code, address-level2, bei zwei Adressen section-abholung bzw. billing davor. Telefon type=tel autocomplete=tel; E-Mail type=email spellcheck=false autocapitalize=off; PLZ inputmode=numeric maxlength=5 autocomplete=postal-code; Kennzeichen autocapitalize=characters; Kilometer inputmode=numeric, beim Verlassen mit Intl.NumberFormat('de-DE') gesetzt. enterkeyhint=next bzw. send. Prüfen erst beim Verlassen und Absenden (siehe validierung-nach-interaktion), Fehler über aria-describedby und aria-invalid, Text mit Beispiel. PLZ-Vorschlag: nach fünf Ziffern Ort aus Liste vorschlagen, nur wenn das Ortsfeld leer oder selbst vorgeschlagen ist; mehrere Orte als Auswahl. Autofill-Stil: input:autofill bzw. input:-webkit-autofill { box-shadow: inset 0 0 0 100px <Hausfarbe hell>; -webkit-text-fill-color: <Text> } und ein Hinweis, der über animationstart oder matches(':autofill') eingeblendet wird.
Robust umsetzen
Telefon. Eine Spalte, Felder mindestens 48 px hoch, Schrift mindestens 16 px gegen den Zoom in iOS. Tastatur passend zum Feld, Eingabetaste springt zum nächsten Feld. PLZ und Ort nebeneinander erst ab 360 px.
Ohne Bewegung. Hinweise und Fehler erscheinen ohne Gleiten.
Barrierefrei. Erfüllt WCAG 1.3.5 (Zweck der Eingabe bestimmen) über autocomplete-Token. Freiwillige Felder im Label benannt, nicht nur per Stern. Fehler am Feld mit aria-describedby und aria-invalid, Beispiel im Hinweistext. Der Autofill-Stil hält 4,5 : 1. Keine Zeitgrenzen, kein automatisches Springen ins nächste Feld.
Code
<!DOCTYPE html>
<!--
Eingabehilfen und Autofill
Fundus · Zugang & Robustheit · https://cecodesign.de/lab/fundus/zugang/formular-eingabehilfen-autofill/
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>Eingabehilfen und Autofill · Fundus-Demo</title>
<script>document.documentElement.classList.add('js');if(matchMedia('(prefers-reduced-motion: reduce)').matches)document.documentElement.classList.add('reduziert')</script>
<style>
@font-face { font-family: 'Mona Sans'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/mona-sans:vf@latest/latin-wdth-normal.woff2') format('woff2-variations'); font-weight: 200 900; font-stretch: 75% 125%; font-display: swap; }
@font-face { font-family: 'Google Sans Flex'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/google-sans-flex:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 100 1000; font-display: swap; }
/* Welt: Rezept kfz-werkstatt-autohaus · Werkstatt Brandt & Sezer, Rheda-Wiedenbrück (erfunden) */
:root {
--grund: #F2F1ED; --text: #121314; --leise: #595E63; --asphalt: #232629; --asphalt-leise: #B9BEC4; --gelb: #F5B700; --gruen: #1F7A3E; --rot: #B3261E;
--feld: #FFFFFF; --linie: rgba(89, 94, 99, .35); --autofill: #FFF4CC;
--ein: cubic-bezier(0.16, 1, 0.3, 1); --aus: cubic-bezier(0.7, 0, 0.84, 0);
--t-farbe: 120ms; --t-auf: 250ms; --t-zu: 170ms;
--headline: 'Mona Sans', system-ui, sans-serif; --schrift: 'Google Sans Flex', system-ui, sans-serif;
}
* { box-sizing: border-box; margin: 0; }
html { background: var(--grund); color: var(--text); font-family: var(--schrift); -webkit-text-size-adjust: 100%; }
:root:has(:focus-visible) { scroll-behavior: auto; }
body { min-height: 100svh; font-size: 1.0625rem; line-height: 1.55; }
:focus-visible { outline: 2px solid var(--text); outline-offset: 3px; }
.dunkel :focus-visible { outline-color: #fff; }
a { color: inherit; }
button, input, select, textarea { font: inherit; color: inherit; }
.label { font: 600 .8125rem/1.2 var(--schrift); letter-spacing: .06em; text-transform: uppercase; }
.nur-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
code, .code { font-family: ui-monospace, 'SFMono-Regular', Menlo, monospace; font-size: .875em; }
.kopf { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px 24px; padding: 16px clamp(20px, 4vw, 48px); background: var(--asphalt); color: #fff; }
.marke { font: 800 1.25rem/1 var(--headline); font-stretch: 80%; letter-spacing: -.01em; }
.kopf a { font-weight: 700; font-size: 1.25rem; font-variant-numeric: tabular-nums; text-decoration: none; }
.buehne { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: clamp(24px, 4vw, 64px); max-width: 1320px; margin: 0 auto; padding: clamp(28px, 4vw, 56px) clamp(20px, 4vw, 48px) 110px; align-items: start; }
.seite h1 { font: 800 clamp(2.25rem, 1.6rem + 3vw, 4rem)/.98 var(--headline); font-stretch: 80%; letter-spacing: -.02em; margin: 10px 0 16px; text-wrap: balance; }
.seite > p { max-width: 38ch; color: var(--leise); }
.seite .label { color: var(--leise); }
/* Feld-Inspektor: zeigt, was das Feld dem Browser sagt */
.inspektor { position: sticky; top: 20px; margin-top: 28px; background: var(--asphalt); color: #fff; border-radius: 8px; padding: 20px 20px 22px; }
.inspektor .label { color: var(--asphalt-leise); }
.inspektor h2 { font: 750 1.375rem/1.15 var(--headline); font-stretch: 85%; margin: 6px 0 14px; min-height: 1.2em; }
.attr { display: grid; grid-template-columns: 8.5rem minmax(0, 1fr); gap: 4px 12px; font-size: .9375rem; }
.attr dt { color: var(--asphalt-leise); }
.attr dd { font-family: ui-monospace, Menlo, monospace; font-size: .875rem; overflow-wrap: anywhere; }
.attr dd.leer { color: var(--asphalt-leise); font-family: var(--schrift); }
.tastatur { margin-top: 18px; padding: 10px; border-radius: 6px; background: #17191B; display: grid; gap: 5px; }
.tastatur .reihe { display: grid; gap: 5px; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); }
.tastatur span { display: grid; place-items: center; height: 30px; border-radius: 4px; background: #3A3E42; font-size: .8125rem; font-weight: 600; }
.tastatur span.hell { background: var(--gelb); color: var(--text); }
.tastatur span.enter { background: #5A6066; }
.tastatur.ziffern span { height: 34px; font-size: .9375rem; }
.tastatur-name { font-size: .875rem; color: var(--asphalt-leise); margin-top: 10px; }
.inspektor .wechsel { animation: rein var(--t-auf) var(--ein) both; }
@keyframes rein { from { opacity: 0; transform: translateY(4px); } }
/* Formular */
.formular { background: var(--feld); border: 1px solid var(--linie); border-radius: 8px; padding: clamp(20px, 3vw, 36px); }
.formular h2 { font: 750 clamp(1.5rem, 1.2rem + 1.2vw, 2.125rem)/1.1 var(--headline); font-stretch: 85%; }
.pflicht { margin: 8px 0 24px; color: var(--leise); }
fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }
legend { font: 750 1.125rem/1.2 var(--headline); font-stretch: 90%; margin-bottom: 14px; padding: 0; }
fieldset + fieldset { margin-top: 26px; padding-top: 22px; border-top: 1px solid var(--linie); }
.zeile { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 18px; }
.zeile.plz { grid-template-columns: minmax(0, 9rem) minmax(0, 1fr); }
.feld { margin-bottom: 18px; position: relative; }
.feld > label { display: block; font-weight: 600; margin-bottom: 6px; }
.frei { font-weight: 400; color: var(--leise); }
.feld input:not([type=checkbox]):not([type=radio]), .feld textarea { width: 100%; min-height: 50px; padding: 0 14px; background: var(--feld); border: 1px solid #8A8F94; border-radius: 4px; font-size: 1.0625rem; transition: border-color var(--t-farbe) linear, background-color var(--t-farbe) linear, box-shadow var(--t-farbe) linear; }
.feld textarea { padding: 12px 14px; min-height: 96px; resize: vertical; }
.feld input:hover, .feld textarea:hover { border-color: var(--text); }
.feld input:focus-visible, .feld textarea:focus-visible, .feld input[type=date]:focus, .feld input[type=date]:focus-within { outline: 2px solid var(--text); outline-offset: 1px; }
.feld input[aria-invalid=true] { border: 2px solid var(--rot); }
.feld .einheit { position: absolute; right: 14px; top: 43px; color: var(--leise); pointer-events: none; }
.kennz input { text-transform: uppercase; font-variant-numeric: tabular-nums; letter-spacing: .04em; }
.kennz .schild { position: absolute; left: 1px; top: 31px; bottom: auto; height: 48px; width: 28px; border-radius: 3px 0 0 3px; background: #1F4FA6; color: #fff; font-size: .6875rem; font-weight: 700; display: grid; place-items: end center; padding-bottom: 5px; pointer-events: none; }
.kennz input { padding-left: 40px !important; }
.hilfe { font-size: .9375rem; color: var(--leise); margin-top: 6px; }
.fehler { display: flex; gap: 8px; align-items: flex-start; color: var(--rot); font-weight: 600; font-size: .9375rem; margin-top: 6px; animation: rein var(--t-auf) var(--ein) both; }
.fehler:empty { display: none; }
.fehler::before { content: '!'; flex: none; width: 18px; height: 18px; border-radius: 50%; background: var(--rot); color: #fff; display: grid; place-items: center; font-size: .75rem; margin-top: 2px; }
.vorschlag { display: flex; flex-wrap: wrap; gap: 6px 10px; align-items: center; margin-top: 6px; font-size: .9375rem; color: var(--leise); }
.vorschlag:empty { display: none; }
.vorschlag button { min-height: 36px; padding: 0 12px; border: 1px solid var(--text); border-radius: 4px; background: var(--grund); cursor: pointer; font-weight: 600; }
/* Autofill: eigener Stil statt Browser-Gelb oder -Blau, dazu der Hinweis „bitte prüfen“ */
@keyframes autofill-an { from { opacity: 1; } to { opacity: 1; } }
.feld input:-webkit-autofill, .feld input:-webkit-autofill:hover, .feld input:-webkit-autofill:focus { -webkit-box-shadow: 0 0 0 100px var(--autofill) inset; box-shadow: 0 0 0 100px var(--autofill) inset; -webkit-text-fill-color: var(--text); caret-color: var(--text); border-color: #9A7400; animation: autofill-an 1ms; }
.feld input:autofill { box-shadow: 0 0 0 100px var(--autofill) inset; border-color: #9A7400; }
.feld input.sim-autofill { box-shadow: 0 0 0 100px var(--autofill) inset; border-color: #9A7400; }
.af-hinweis { display: none; position: absolute; right: 8px; top: 0; font-size: .8125rem; font-weight: 600; color: #6B5000; background: var(--autofill); border: 1px solid #C9A200; border-radius: 999px; padding: 1px 10px; }
.feld.af .af-hinweis { display: block; animation: rein var(--t-auf) var(--ein) both; }
.wahl { display: flex; flex-wrap: wrap; gap: 8px; }
.wahl label { font-weight: 500; }
.wahl input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.wahl span { display: inline-flex; align-items: center; min-height: 44px; padding: 0 14px; border: 1px solid #8A8F94; border-radius: 4px; cursor: pointer; }
.wahl input:checked + span { background: var(--asphalt); color: #fff; border-color: var(--asphalt); }
.wahl input:focus-visible + span { outline: 2px solid var(--text); outline-offset: 2px; }
.check { display: flex; gap: 12px; align-items: center; min-height: 44px; font-weight: 600; cursor: pointer; }
.check input { width: 22px; height: 22px; accent-color: var(--asphalt); }
.aufklapp { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--t-zu) var(--aus); }
.aufklapp.auf { grid-template-rows: 1fr; transition: grid-template-rows var(--t-auf) var(--ein); }
.aufklapp > div { overflow: hidden; }
.aufklapp > div > fieldset { padding-top: 14px; }
.senden { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 20px; margin-top: 26px; }
.termin { min-height: 52px; padding: 0 24px; border: 2px solid var(--asphalt); border-radius: 4px; background: var(--gelb); font-weight: 700; font-size: 1.0625rem; cursor: pointer; box-shadow: 0 4px 0 var(--asphalt); transition: transform var(--t-farbe) var(--ein), box-shadow var(--t-farbe) var(--ein); }
.termin:active { transform: translateY(3px); box-shadow: 0 1px 0 var(--asphalt); }
.danke { display: flex; gap: 12px; align-items: center; color: var(--gruen); font-weight: 700; }
.danke:empty { display: none; }
.danke:focus { outline: none; }
/* Variante Übersicht: Spickzettel */
.v-uebersicht { display: none; }
[data-variante=uebersicht] .buehne { display: none; }
[data-variante=uebersicht] .v-uebersicht { display: block; }
[data-variante=abholung] .nur-termin, [data-variante=termin] .nur-abholung { display: none; }
.v-uebersicht { max-width: 1320px; margin: 0 auto; padding: clamp(28px, 4vw, 56px) clamp(20px, 4vw, 48px) 110px; }
.v-uebersicht h1 { font: 800 clamp(2rem, 1.5rem + 2.4vw, 3.25rem)/1 var(--headline); font-stretch: 80%; letter-spacing: -.02em; margin: 10px 0 14px; }
.v-uebersicht > p { color: var(--leise); max-width: 62ch; margin-bottom: 26px; }
.spick { width: 100%; border-collapse: collapse; background: var(--feld); border: 1px solid var(--linie); font-size: .9375rem; }
.spick caption { text-align: left; caption-side: bottom; padding-top: 12px; color: var(--leise); font-size: .875rem; }
.spick th, .spick td { text-align: left; vertical-align: top; padding: 11px 12px; border-bottom: 1px solid rgba(89, 94, 99, .22); }
.spick thead th { background: var(--asphalt); color: #fff; font-weight: 600; font-size: .8125rem; letter-spacing: .04em; text-transform: uppercase; position: sticky; top: 0; }
.spick tbody th { font-weight: 700; white-space: nowrap; }
.spick td code { background: var(--grund); padding: 1px 5px; border-radius: 3px; white-space: nowrap; }
.spick td.text { color: var(--leise); min-width: 16rem; }
.spick-wrap { overflow-x: auto; }
/* Pult */
.pult { position: fixed; right: 16px; bottom: 16px; z-index: 50; display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; max-width: calc(100vw - 32px); padding: 10px 12px; background: rgba(18, 19, 20, .93); color: #f3f2ee; font: 600 11px/1 var(--schrift); letter-spacing: .1em; text-transform: uppercase; border-radius: 8px; }
.pult .gruppe { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.pult .gruppe > span { color: rgba(243, 242, 238, .72); margin-right: 4px; }
.pult button { background: transparent; color: inherit; border: 1px solid rgba(243, 242, 238, .3); border-radius: 4px; height: 28px; padding: 0 9px; font: inherit; letter-spacing: inherit; text-transform: inherit; cursor: pointer; }
.pult button[aria-pressed=true] { background: #f3f2ee; color: #121314; border-color: #f3f2ee; }
.pult button:focus-visible { outline: 2px solid #f3f2ee; outline-offset: 2px; }
.vorschau body { overflow: hidden; }
@media (max-width: 900px) {
.buehne { grid-template-columns: minmax(0, 1fr); }
.inspektor { position: static; }
.tastatur { display: none; } .tastatur-name { display: none; }
}
@media (max-width: 560px) {
.zeile { grid-template-columns: minmax(0, 1fr); }
.inspektor { display: none; }
.spick thead { display: none; }
.spick, .spick tbody, .spick tr, .spick th, .spick td { display: block; }
.spick tr { padding: 10px 12px; border-bottom: 1px solid var(--linie); }
.spick th, .spick td { border: 0; padding: 3px 0; }
.spick td[data-k]::before { content: attr(data-k) ': '; color: var(--leise); font-size: .8125rem; }
.spick td.text { min-width: 0; }
.spick td code { white-space: normal; }
}
@media (max-width: 360px) { .zeile.plz { grid-template-columns: minmax(0, 1fr); } }
:root.reduziert *, :root.reduziert *::before, :root.reduziert *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; }
@media (forced-colors: active) {
button, [role=button], input, textarea, .wahl span { border: 1px solid ButtonText !important; }
.feld input.sim-autofill, .feld input:autofill { border: 3px solid Highlight !important; }
}
</style>
</head>
<body>
<header class="kopf dunkel">
<p class="marke">Werkstatt Brandt & Sezer</p>
<a href="tel:+495242408700">05242 40 87-0</a>
</header>
<main>
<section class="buehne" aria-labelledby="h1">
<div class="seite">
<p class="label">Rheda-Wiedenbrück · Termin</p>
<h1 id="h1"><span class="nur-termin">Termin anfragen. In einer Minute.</span><span class="nur-abholung">Wir holen Ihr Auto ab.</span></h1>
<p class="nur-termin">Ihr Browser kann fast alles selbst ausfüllen. Wir rufen Sie zurück und bestätigen den Tag.</p>
<p class="nur-abholung">Hol- und Bringdienst im Umkreis von 15 km. Abholung ab 7:30 Uhr, zurück bis 18 Uhr.</p>
<aside class="inspektor dunkel" aria-label="Was das Feld dem Browser sagt">
<p class="label">Das sagt das Feld dem Browser</p>
<h2 data-i-name>Feld antippen</h2>
<dl class="attr" data-i-attr><dt>type</dt><dd class="leer">noch kein Feld gewählt</dd></dl>
<div class="tastatur" data-i-tastatur aria-hidden="true"></div>
<p class="tastatur-name" data-i-tastatur-name></p>
</aside>
</div>
<form class="formular" id="form" novalidate action="danke.html" method="post">
<h2 class="nur-termin">Ihre Angaben</h2><h2 class="nur-abholung">Abholung</h2>
<p class="pflicht">Alle Felder brauchen wir, außer sie sind mit „freiwillig“ markiert.</p>
<fieldset class="nur-termin" data-gruppe="termin">
<legend>Kontakt</legend>
<div class="zeile">
<div class="feld"><label for="t-name">Name</label><span class="af-hinweis">Vom Browser, bitte prüfen</span>
<input id="t-name" name="name" autocomplete="name" autocapitalize="words" enterkeyhint="next" required data-fehler="Bitte geben Sie Ihren Namen ein." aria-describedby="t-name-f"><p class="fehler" id="t-name-f"></p></div>
<div class="feld"><label for="t-tel">Telefon</label><span class="af-hinweis">Vom Browser, bitte prüfen</span>
<input id="t-tel" name="telefon" type="tel" autocomplete="tel" enterkeyhint="next" required data-pruef="tel" aria-describedby="t-tel-h t-tel-f"><p class="hilfe" id="t-tel-h">Mit Leerzeichen, Schrägstrich oder +49, wie Sie mögen.</p><p class="fehler" id="t-tel-f"></p></div>
</div>
<div class="feld"><label for="t-mail">E-Mail <span class="frei">(freiwillig)</span></label><span class="af-hinweis">Vom Browser, bitte prüfen</span>
<input id="t-mail" name="email" type="email" autocomplete="email" autocapitalize="off" spellcheck="false" enterkeyhint="next" data-pruef="mail" aria-describedby="t-mail-h t-mail-f"><p class="hilfe" id="t-mail-h">Nur für die Terminbestätigung.</p><p class="fehler" id="t-mail-f"></p></div>
</fieldset>
<fieldset class="nur-termin">
<legend>Fahrzeug</legend>
<div class="zeile">
<div class="feld kennz"><label for="t-kz">Kennzeichen</label><span class="schild" aria-hidden="true">D</span>
<input id="t-kz" name="kennzeichen" autocapitalize="characters" autocomplete="off" spellcheck="false" enterkeyhint="next" required data-pruef="kz" maxlength="12" aria-describedby="t-kz-h t-kz-f"><p class="hilfe" id="t-kz-h">z. B. GT-AB 123, mit oder ohne Bindestrich</p><p class="fehler" id="t-kz-f"></p></div>
<div class="feld"><label for="t-km">Kilometerstand <span class="frei">(freiwillig)</span></label>
<input id="t-km" name="km" inputmode="numeric" enterkeyhint="next" data-pruef="km" aria-describedby="t-km-h t-km-f" style="padding-right: 44px"><span class="einheit">km</span><p class="hilfe" id="t-km-h">ungefähr reicht</p><p class="fehler" id="t-km-f"></p></div>
</div>
<div class="feld"><label for="t-tag">Wunschtag</label>
<input id="t-tag" name="wunschtag" type="date" required enterkeyhint="send" data-fehler="Bitte wählen Sie einen Tag. Wir bestätigen telefonisch." aria-describedby="t-tag-h t-tag-f"><p class="hilfe" id="t-tag-h">Montag bis Freitag. Wir bestätigen den Termin telefonisch.</p><p class="fehler" id="t-tag-f"></p></div>
</fieldset>
<fieldset class="nur-abholung" data-gruppe="abholung">
<legend>Wo steht das Auto?</legend>
<div class="feld"><label for="a-name">Name</label><span class="af-hinweis">Vom Browser, bitte prüfen</span>
<input id="a-name" name="abholung-name" autocomplete="section-abholung shipping name" enterkeyhint="next" required data-fehler="Bitte geben Sie Ihren Namen ein." aria-describedby="a-name-f"><p class="fehler" id="a-name-f"></p></div>
<div class="feld"><label for="a-str">Straße und Hausnummer</label><span class="af-hinweis">Vom Browser, bitte prüfen</span>
<input id="a-str" name="abholung-strasse" autocomplete="section-abholung shipping address-line1" enterkeyhint="next" required data-fehler="Bitte geben Sie Straße und Hausnummer ein, z. B. Lange Straße 12." aria-describedby="a-str-f"><p class="fehler" id="a-str-f"></p></div>
<div class="zeile plz">
<div class="feld"><label for="a-plz">PLZ</label><span class="af-hinweis">Browser</span>
<input id="a-plz" name="abholung-plz" inputmode="numeric" autocomplete="section-abholung shipping postal-code" maxlength="5" enterkeyhint="next" required data-pruef="plz" data-ort="a-ort" aria-describedby="a-plz-f"><p class="fehler" id="a-plz-f"></p></div>
<div class="feld"><label for="a-ort">Ort</label><span class="af-hinweis">Vom Browser, bitte prüfen</span>
<input id="a-ort" name="abholung-ort" autocomplete="section-abholung shipping address-level2" enterkeyhint="next" required data-fehler="Bitte geben Sie den Ort ein." aria-describedby="a-ort-v a-ort-f"><p class="vorschlag" id="a-ort-v" aria-live="polite"></p><p class="fehler" id="a-ort-f"></p></div>
</div>
<div class="feld"><label for="a-tel">Telefon für den Fahrer</label><span class="af-hinweis">Vom Browser, bitte prüfen</span>
<input id="a-tel" name="telefon" type="tel" autocomplete="tel" enterkeyhint="next" required data-pruef="tel" aria-describedby="a-tel-f"><p class="fehler" id="a-tel-f"></p></div>
<div class="feld"><span class="label" id="a-zeit-l" style="display:block;margin-bottom:8px;text-transform:none;letter-spacing:0;font-size:1.0625rem">Abholen</span>
<div class="wahl" role="radiogroup" aria-labelledby="a-zeit-l">
<label><input type="radio" name="abholzeit" value="7:30" checked><span>7:30 bis 9 Uhr</span></label>
<label><input type="radio" name="abholzeit" value="9"><span>9 bis 11 Uhr</span></label>
</div></div>
<label class="check"><input type="checkbox" id="a-rechnung"> Rechnung an eine andere Adresse</label>
<div class="aufklapp" id="a-rechnung-box"><div inert>
<fieldset>
<legend>Rechnungsadresse</legend>
<div class="feld"><label for="r-name">Name</label><span class="af-hinweis">Vom Browser, bitte prüfen</span>
<input id="r-name" name="rechnung-name" autocomplete="billing name" enterkeyhint="next" data-fehler="Bitte geben Sie den Namen ein." aria-describedby="r-name-f"><p class="fehler" id="r-name-f"></p></div>
<div class="feld"><label for="r-str">Straße und Hausnummer</label><span class="af-hinweis">Vom Browser, bitte prüfen</span>
<input id="r-str" name="rechnung-strasse" autocomplete="billing address-line1" enterkeyhint="next" data-fehler="Bitte geben Sie Straße und Hausnummer ein." aria-describedby="r-str-f"><p class="fehler" id="r-str-f"></p></div>
<div class="zeile plz">
<div class="feld"><label for="r-plz">PLZ</label><input id="r-plz" name="rechnung-plz" inputmode="numeric" autocomplete="billing postal-code" maxlength="5" enterkeyhint="next" data-pruef="plz" data-ort="r-ort" aria-describedby="r-plz-f"><p class="fehler" id="r-plz-f"></p></div>
<div class="feld"><label for="r-ort">Ort</label><input id="r-ort" name="rechnung-ort" autocomplete="billing address-level2" enterkeyhint="send" data-fehler="Bitte geben Sie den Ort ein." aria-describedby="r-ort-v r-ort-f"><p class="vorschlag" id="r-ort-v" aria-live="polite"></p><p class="fehler" id="r-ort-f"></p></div>
</div>
</fieldset>
</div></div>
</fieldset>
<div class="senden">
<button class="termin" type="submit"><span class="nur-termin">Termin anfragen</span><span class="nur-abholung">Abholung anfragen</span></button>
<p class="danke" id="danke" tabindex="-1" role="status"></p>
</div>
</form>
</section>
<section class="v-uebersicht" aria-labelledby="u-titel">
<p class="label">Spickzettel</p>
<h1 id="u-titel">Jedes Feld sagt, was es will.</h1>
<p>Diese Tabelle ist die Vorlage für jedes Formular. Fehlertexte nennen, was fehlt, mit einem Beispiel. Freiwillige Felder heißen „freiwillig“, Pflicht ist der Normalfall.</p>
<div class="spick-wrap" tabindex="0" role="region" aria-label="Spickzettel, waagerecht scrollbar">
<table class="spick">
<caption>Token nach HTML Standard (Autofill). Formate und Fehlertexte für deutsche Kundenseiten; Kennzeichen-Regel vom Kunden prüfen lassen.</caption>
<thead><tr><th scope="col">Feld</th><th scope="col">type</th><th scope="col">inputmode</th><th scope="col">autocomplete</th><th scope="col">sonst</th><th scope="col">Fehlertext</th></tr></thead>
<tbody>
<tr><th scope="row">Name</th><td data-k="type"><code>text</code></td><td data-k="inputmode">–</td><td data-k="autocomplete"><code>name</code></td><td data-k="sonst"><code>autocapitalize="words"</code></td><td class="text" data-k="Fehler">Bitte geben Sie Ihren Namen ein.</td></tr>
<tr><th scope="row">Telefon</th><td data-k="type"><code>tel</code></td><td data-k="inputmode">– (kommt mit tel)</td><td data-k="autocomplete"><code>tel</code></td><td data-k="sonst">kein <code>pattern</code>; mindestens 6 Ziffern</td><td class="text" data-k="Fehler">Bitte geben Sie eine Telefonnummer ein, z. B. 05242 40 87-0.</td></tr>
<tr><th scope="row">E-Mail</th><td data-k="type"><code>email</code></td><td data-k="inputmode">–</td><td data-k="autocomplete"><code>email</code></td><td data-k="sonst"><code>spellcheck="false"</code> <code>autocapitalize="off"</code></td><td class="text" data-k="Fehler">Die E-Mail-Adresse braucht ein @, z. B. name@beispiel.de.</td></tr>
<tr><th scope="row">Straße und Nr.</th><td data-k="type"><code>text</code></td><td data-k="inputmode">–</td><td data-k="autocomplete"><code>address-line1</code></td><td data-k="sonst">ein Feld, nicht trennen</td><td class="text" data-k="Fehler">Bitte geben Sie Straße und Hausnummer ein, z. B. Lange Straße 12.</td></tr>
<tr><th scope="row">PLZ</th><td data-k="type"><code>text</code></td><td data-k="inputmode"><code>numeric</code></td><td data-k="autocomplete"><code>postal-code</code></td><td data-k="sonst"><code>maxlength="5"</code>; schlägt Ort vor</td><td class="text" data-k="Fehler">Die Postleitzahl hat fünf Ziffern, z. B. 33378.</td></tr>
<tr><th scope="row">Ort</th><td data-k="type"><code>text</code></td><td data-k="inputmode">–</td><td data-k="autocomplete"><code>address-level2</code></td><td data-k="sonst">Vorschlag nur, wenn leer</td><td class="text" data-k="Fehler">Bitte geben Sie den Ort ein.</td></tr>
<tr><th scope="row">Kennzeichen</th><td data-k="type"><code>text</code></td><td data-k="inputmode">–</td><td data-k="autocomplete"><code>off</code></td><td data-k="sonst"><code>autocapitalize="characters"</code>; setzt GT-AB 123</td><td class="text" data-k="Fehler">Bitte so eingeben: GT-AB 123 (Ort, Buchstaben, Zahl).</td></tr>
<tr><th scope="row">Kilometer</th><td data-k="type"><code>text</code></td><td data-k="inputmode"><code>numeric</code></td><td data-k="autocomplete">–</td><td data-k="sonst">setzt 86.400 mit <code>Intl.NumberFormat('de-DE')</code></td><td class="text" data-k="Fehler">Bitte nur Ziffern, z. B. 86400.</td></tr>
<tr><th scope="row">Datum</th><td data-k="type"><code>date</code></td><td data-k="inputmode">–</td><td data-k="autocomplete">–</td><td data-k="sonst"><code>min</code> = morgen</td><td class="text" data-k="Fehler">Bitte wählen Sie einen Tag.</td></tr>
<tr><th scope="row">Zweite Adresse</th><td data-k="type"><code>text</code></td><td data-k="inputmode">–</td><td data-k="autocomplete"><code>billing …</code>, <code>shipping …</code>, <code>section-x …</code></td><td data-k="sonst">nur für eigene Angaben</td><td class="text" data-k="Fehler">wie oben</td></tr>
<tr><th scope="row">Einmal-Code</th><td data-k="type"><code>text</code></td><td data-k="inputmode"><code>numeric</code></td><td data-k="autocomplete"><code>one-time-code</code></td><td data-k="sonst"><code>enterkeyhint="done"</code></td><td class="text" data-k="Fehler">Der Code hat sechs Ziffern.</td></tr>
</tbody>
</table>
</div>
</section>
</main>
<div class="pult">
<div class="gruppe" role="group" aria-label="Variante"><span>Variante</span>
<button type="button" data-v="termin" aria-pressed="true">Termin</button>
<button type="button" data-v="abholung" aria-pressed="false">Abholung</button>
<button type="button" data-v="uebersicht" aria-pressed="false">Übersicht</button>
</div>
<button type="button" data-autofill>Autofill simulieren</button>
<button type="button" data-reset>Leeren</button>
</div>
<script>
(() => {
const h = document.documentElement, $ = s => document.querySelector(s), $$ = s => [...document.querySelectorAll(s)];
const reduziert = () => h.classList.contains('reduziert');
const VARIANTEN = ['termin', 'abholung', 'uebersicht'];
const q = new URLSearchParams(location.search);
let variante = VARIANTEN.includes(q.get('v')) ? q.get('v') : 'termin';
const form = $('#form'), danke = $('#danke');
const de = new Intl.NumberFormat('de-DE');
/* Beispielliste für die Demo. Im Projekt: gepflegte Liste oder Dienst, nie den Ort erzwingen. */
const ORTE = { '33378': 'Rheda-Wiedenbrück', '33330': 'Gütersloh', '33332': 'Gütersloh', '33333': 'Gütersloh', '33334': 'Gütersloh', '33335': 'Gütersloh', '33415': 'Verl', '33397': 'Rietberg', '33442': 'Herzebrock-Clarholz', '33428': 'Harsewinkel', '59302': 'Oelde' };
/* ---------- Feld-Inspektor ---------- */
const iName = $('[data-i-name]'), iAttr = $('[data-i-attr]'), iTast = $('[data-i-tastatur]'), iTastName = $('[data-i-tastatur-name]');
const ENTER = { next: 'Weiter', send: 'Senden', done: 'Fertig', go: 'Los', search: 'Suchen' };
function tastatur(el) {
const im = el.getAttribute('inputmode'), t = el.type, ek = ENTER[el.getAttribute('enterkeyhint')] || '↵';
const gross = el.getAttribute('autocapitalize') === 'characters';
let reihen, art, ziffern = false;
if (im === 'numeric') { art = 'Ziffernblock'; ziffern = true; reihen = [['1', '2', '3'], ['4', '5', '6'], ['7', '8', '9'], ['', '0', '⌫']]; }
else if (t === 'tel') { art = 'Telefon-Tastatur'; ziffern = true; reihen = [['1', '2', '3'], ['4', '5', '6'], ['7', '8', '9'], ['+ * #', '0', '⌫']]; }
else if (t === 'date') { art = 'Datumsauswahl des Systems'; reihen = [['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'], ['3', '4', '5', '6', '7', '8', '9'], ['10', '11', '12', '13', '14', '15', '16']]; }
else {
const a = gross ? 'QWERTZUIOP' : 'qwertzuiop', b = gross ? 'ASDFGHJKL' : 'asdfghjkl', c = gross ? 'YXCVBNM' : 'yxcvbnm';
reihen = [a.split(''), b.split(''), c.split('')];
if (t === 'email') { art = 'Tastatur mit @ und Punkt'; reihen.push(['123', '@', 'Leer', '.', ek]); }
else { art = gross ? 'Tastatur, Großbuchstaben' : 'Tastatur'; reihen.push(['123', 'Leer', ek]); }
}
if (ziffern) reihen[reihen.length - 1].push(ek);
iTast.className = 'tastatur' + (ziffern ? ' ziffern' : '');
iTast.innerHTML = reihen.map(r => '<div class="reihe">' + r.map(k => `<span class="${k === '@' || k === '.' && t === 'email' ? 'hell' : k === ek ? 'enter' : ''}">${k}</span>`).join('') + '</div>').join('');
iTastName.textContent = art + ', Eingabetaste „' + ek + '“';
}
function inspiziere(el) {
const lab = document.querySelector(`label[for="${el.id}"]`);
iName.textContent = lab ? lab.textContent.replace(/\s+/g, ' ').trim() : el.name;
const zeilen = [
['type', el.getAttribute('type') || 'text'],
['inputmode', el.getAttribute('inputmode') || (el.type === 'tel' ? '(aus type=tel)' : el.type === 'email' ? '(aus type=email)' : '–')],
['autocomplete', el.getAttribute('autocomplete') || '–'],
['enterkeyhint', el.getAttribute('enterkeyhint') || '–'],
['sonst', [el.required ? 'Pflicht' : 'freiwillig', el.getAttribute('autocapitalize') && 'autocapitalize=' + el.getAttribute('autocapitalize'), el.getAttribute('spellcheck') === 'false' && 'spellcheck=false', el.maxLength > 0 && 'maxlength=' + el.maxLength].filter(Boolean).join(' · ')]
];
iAttr.innerHTML = zeilen.map(([k, v]) => `<dt>${k}</dt><dd>${v}</dd>`).join('');
tastatur(el);
[iName, iAttr, iTast].forEach(x => { x.classList.remove('wechsel'); void x.offsetWidth; x.classList.add('wechsel'); });
}
form.addEventListener('focusin', e => { if (e.target.matches('input:not([type=radio]):not([type=checkbox])')) inspiziere(e.target); });
/* ---------- Prüfen und deutsch setzen (erst beim Verlassen, siehe validierung-nach-interaktion) ---------- */
const fehlerEl = el => document.getElementById(el.id + '-f');
function setzeFehler(el, text) { el.setAttribute('aria-invalid', text ? 'true' : 'false'); const f = fehlerEl(el); if (f) f.textContent = text || ''; return !text; }
function pruefe(el, streng) {
let v = el.value.trim();
const art = el.dataset.pruef;
if (!v) return setzeFehler(el, el.required && streng ? (el.dataset.fehler || { tel: 'Bitte geben Sie eine Telefonnummer ein, z. B. 05242 40 87-0.', kz: 'Bitte geben Sie das Kennzeichen ein, z. B. GT-AB 123.', plz: 'Bitte geben Sie die Postleitzahl ein, z. B. 33378.' }[art] || 'Bitte ausfüllen.') : '');
if (art === 'tel') return setzeFehler(el, (v.match(/\d/g) || []).length < 6 || /[^\d\s+\/()\-.]/.test(v) ? 'Bitte geben Sie eine Telefonnummer ein, z. B. 05242 40 87-0.' : '');
if (art === 'mail') return setzeFehler(el, !v.includes('@') ? 'Die E-Mail-Adresse braucht ein @, z. B. name@beispiel.de.' : !/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(v) ? 'Bitte prüfen Sie die E-Mail-Adresse, z. B. name@beispiel.de.' : '');
if (art === 'kz') {
const up = v.toUpperCase().replace(/\s+/g, ' ');
const m = up.match(/^([A-ZÄÖÜ]{1,3})[\s-]+([A-Z]{1,2})[\s-]*([1-9]\d{0,3})([EH])?$/);
if (m) { el.value = `${m[1]}-${m[2]} ${m[3]}${m[4] || ''}`; return setzeFehler(el, ''); }
if (/^[A-ZÄÖÜ]{2,5}[1-9]\d{0,3}[EH]?$/.test(up)) { el.value = up; return setzeFehler(el, ''); } /* ohne Trenner: nicht raten, nur groß setzen */
return setzeFehler(el, 'Bitte so eingeben: GT-AB 123 (Ort, Buchstaben, Zahl).');
}
if (art === 'km') { const z = v.replace(/[.\s]/g, ''); if (!/^\d+$/.test(z)) return setzeFehler(el, 'Bitte nur Ziffern, z. B. 86400.'); el.value = de.format(+z); return setzeFehler(el, ''); }
if (art === 'plz') return setzeFehler(el, /^\d{5}$/.test(v) ? '' : 'Die Postleitzahl hat fünf Ziffern, z. B. 33378.');
return setzeFehler(el, '');
}
const felder = () => $$('#form input:not([type=radio]):not([type=checkbox]), #form textarea').filter(el => el.offsetParent && !el.closest('[inert]'));
form.addEventListener('focusout', e => { const el = e.target; if (el.matches('input:not([type=radio]):not([type=checkbox])') && (el.value || el.dataset.beruehrt)) pruefe(el, !!el.dataset.beruehrt); });
form.addEventListener('input', e => {
const el = e.target; el.dataset.beruehrt = '1';
if (el.getAttribute('aria-invalid') === 'true' && el.dataset.pruef !== 'kz' && el.dataset.pruef !== 'km') pruefe(el, false);
const f = el.closest('.feld'); let echt = false; try { echt = el.matches(':autofill'); } catch (x) { try { echt = el.matches(':-webkit-autofill'); } catch (y) {} }
if (f && !echt) { el.classList.remove('sim-autofill'); f.classList.remove('af'); } /* wer selbst tippt, hat geprüft */
if (el.dataset.pruef === 'plz') ortVorschlag(el);
if (el.dataset.vorgeschlagen !== undefined && el.id.endsWith('ort') && e.isTrusted) { delete el.dataset.vorgeschlagen; document.getElementById(el.id + '-v').textContent = ''; }
});
/* PLZ schlägt den Ort vor, aber nur, wenn das Ortsfeld leer ist oder selbst vorgeschlagen wurde */
function ortVorschlag(plz) {
const ort = document.getElementById(plz.dataset.ort), hin = document.getElementById(ort.id + '-v');
const v = plz.value.replace(/\D/g, '');
if (v.length !== 5) return;
const name = ORTE[v];
if (!name) { hin.textContent = ''; return; }
if (!ort.value || ort.dataset.vorgeschlagen !== undefined) { ort.value = name; ort.dataset.vorgeschlagen = ''; setzeFehler(ort, ''); hin.textContent = 'Ort aus der PLZ ergänzt. Stimmt nicht? Einfach überschreiben.'; }
else if (ort.value !== name) { hin.innerHTML = ''; const b = document.createElement('button'); b.type = 'button'; b.textContent = name + ' übernehmen'; b.addEventListener('click', () => { ort.value = name; ort.dataset.vorgeschlagen = ''; hin.textContent = 'Ort übernommen.'; ort.focus(); }); hin.append('Zur PLZ passt eher: ', b); }
}
/* ---------- Autofill erkennen: echter Browser über animationstart, Demo über die Klasse sim-autofill ---------- */
form.addEventListener('animationstart', e => { if (e.animationName === 'autofill-an') { const f = e.target.closest('.feld'); if (f) f.classList.add('af'); } });
const DATEN = {
termin: { 't-name': 'Petra Kowalski', 't-tel': '05242 98 31 20', 't-mail': 'p.kowalski@beispiel.de' },
abholung: { 'a-name': 'Petra Kowalski', 'a-str': 'Bielefelder Straße 41', 'a-plz': '33378', 'a-ort': 'Rheda-Wiedenbrück', 'a-tel': '05242 98 31 20' }
};
function autofill() {
const d = DATEN[variante]; if (!d) return;
Object.entries(d).forEach(([id, v]) => { const el = document.getElementById(id); el.value = v; el.classList.add('sim-autofill'); setzeFehler(el, ''); const f = el.closest('.feld'); if (f) f.classList.add('af'); });
}
/* ---------- Rechnung abweichend ---------- */
const rb = $('#a-rechnung'), rbox = $('#a-rechnung-box');
function rechnung(an) { rb.checked = an; rbox.classList.toggle('auf', an); rbox.firstElementChild.inert = !an; ['r-name', 'r-str', 'r-plz', 'r-ort'].forEach(id => { document.getElementById(id).required = an; }); }
rb.addEventListener('change', () => rechnung(rb.checked));
/* ---------- Absenden ---------- */
form.addEventListener('submit', e => {
e.preventDefault();
const fs = felder(); let erstes = null, n = 0;
fs.forEach(el => { el.dataset.beruehrt = '1'; if (!pruefe(el, true)) { n++; erstes = erstes || el; } });
if (n) { danke.style.color = 'var(--rot)'; danke.textContent = n === 1 ? 'Eine Angabe fehlt noch.' : `${n} Angaben fehlen noch.`; erstes.focus(); return; }
danke.style.color = ''; danke.textContent = variante === 'abholung' ? '✓ Danke. Der Fahrer ruft Sie vor der Abholung an.' : '✓ Danke. Wir rufen Sie heute bis 18 Uhr zurück.';
danke.focus();
});
/* ---------- Wunschtag ab morgen ---------- */
const heute = new Intl.DateTimeFormat('en-CA', { timeZone: 'Europe/Berlin' }).format(new Date());
const morgen = new Date(heute + 'T12:00:00Z'); morgen.setUTCDate(morgen.getUTCDate() + 1);
$('#t-tag').min = morgen.toISOString().slice(0, 10);
/* ---------- Pult ---------- */
function setzeVariante(v, schreiben) {
variante = v; document.body.dataset.variante = v;
$$('[data-v]').forEach(b => b.setAttribute('aria-pressed', b.dataset.v === v ? 'true' : 'false'));
$('[data-autofill]').hidden = v === 'uebersicht';
if (schreiben) { const p = new URLSearchParams(location.search); p.set('v', v); history.replaceState(null, '', location.pathname + '?' + p + location.hash); }
}
$$('[data-v]').forEach(b => b.addEventListener('click', () => { pause(); setzeVariante(b.dataset.v, true); neustart(); }));
$('[data-autofill]').addEventListener('click', () => { pause(); autofill(); });
$('[data-reset]').addEventListener('click', () => { pause(); neustart(); });
function neustart() {
form.reset(); rechnung(false); danke.textContent = '';
$$('#form input').forEach(el => { el.classList.remove('sim-autofill'); delete el.dataset.beruehrt; delete el.dataset.vorgeschlagen; if (el.type !== 'radio' && el.type !== 'checkbox') setzeFehler(el, ''); });
$$('.feld.af').forEach(f => f.classList.remove('af')); $$('.vorschlag').forEach(v => { v.textContent = ''; });
iName.textContent = 'Feld antippen'; iAttr.innerHTML = '<dt>type</dt><dd class="leer">noch kein Feld gewählt</dd>'; iTast.innerHTML = ''; iTastName.textContent = '';
$$('.spick tbody tr').forEach(tr => tr.style.background = '');
}
/* ---------- Vorführung ---------- */
let lauf = 0;
const warte = (ms, id) => new Promise((ok, nein) => setTimeout(() => (id === lauf ? ok() : nein('stopp')), ms));
async function tippe(el, text, id) { el.focus({ preventScroll: true }); for (const c of text) { el.value += c; el.dispatchEvent(new Event('input', { bubbles: true })); await warte(reduziert() ? 0 : 55, id); } }
const verlasse = el => el.blur();
async function runde(id) {
neustart(); await warte(500, id);
if (variante === 'termin') {
const tel = $('#t-tel'), mail = $('#t-mail'), kz = $('#t-kz'), km = $('#t-km');
await tippe(tel, '05242/98 31 20', id); await warte(700, id); verlasse(tel);
await tippe(mail, 'p.kowalski', id); await warte(400, id); verlasse(mail); await warte(1400, id);
await tippe(mail, '@beispiel.de', id); await warte(500, id); verlasse(mail);
await tippe(kz, 'gt ab 123', id); await warte(700, id); verlasse(kz); await warte(900, id);
await tippe(km, '86400', id); await warte(500, id); verlasse(km); await warte(1000, id);
neustart(); await warte(300, id); autofill(); await warte(1800, id);
form.requestSubmit(); await warte(2400, id);
} else if (variante === 'abholung') {
const plz = $('#a-plz'), str = $('#a-str');
await tippe(str, 'Bielefelder Str. 41', id); await warte(400, id); verlasse(str);
await tippe(plz, '33378', id); await warte(1600, id); verlasse(plz);
rb.focus(); rechnung(true); await warte(900, id);
const rp = $('#r-plz'); await tippe(rp, '33330', id); await warte(1600, id); verlasse(rp);
neustart(); await warte(300, id); autofill(); await warte(1800, id);
form.requestSubmit(); await warte(2200, id);
} else {
const rows = $$('.spick tbody tr');
for (const tr of rows) { tr.style.background = 'var(--autofill)'; await warte(650, id); tr.style.background = ''; }
}
runde(id).catch(() => {});
}
function vorfuehren() { const id = ++lauf; h.classList.add('vorfuehrung'); runde(id).catch(() => {}); }
function pause() { lauf++; h.classList.remove('vorfuehrung'); }
function weiter() { vorfuehren(); }
setzeVariante(variante, false);
})();
</script>
</body>
</html>