Zum Inhalt
← Hero & Section-Layouts

Kontakt- und Anfrageformular als eigene Sektion

Die Anfrage bekommt eine eigene Sektion: direkte Wege links, ein kurzes Formular rechts, Fehler erst nach dem Verlassen eines Feldes und eine Bestätigung mit nächsten Schritten.

Makler, Handwerk, Kanzlei, Fotografie: eigene Kontakt-Sektion mit direkten Wegen links und Formular rechts, umschaltbar zwischen Bewertung (mit Foto-Upload), Rückruf und Kurzformular.

Eine eigene Sektion vor dem Footer, auch als Seite /kontakt. Links stehen ein Einleitungssatz und die direkten Wege: Telefon als tel:-Link, E-Mail mit Kopier-Feedback, Adresse. Rechts steht ein kurzes Formular mit Feldern passend zur Branche, etwa Anliegen als Auswahl, PLZ, Wunschzeitraum, Nachricht, optional Foto-Upload und Rückruf-Wunsch. Pflichtfelder sind minimal. Fehler erscheinen erst nach dem Verlassen eines Feldes, direkt am Feld. Nach dem Senden ersetzt eine Bestätigung das Formular, mit Antwortzeit und nächsten Schritten.

Für Handwerk, Kanzlei, Architektur, Fotografie, Makler oder Freelancer ist die Anfrage das eigentliche Ziel der Website. Als eigene Sektion mit klarer Erwartung (Antwortzeit, Ansprechperson, Rückruf) senkt sie die Hürde. Im Footer geht sie unter.

Wann einsetzen

Dienstleister, die Anfragen statt Bestellungen wollen: Handwerk, Kanzlei und Steuerberatung, Architektur, Immobilien, Fotografie, Freelancer, Agenturen.

Wann nicht

Wenn ein Buchungssystem die Anfrage ersetzt (dann termin-buchung-zwei-klick). Nicht als langes Formular mit mehr als 6 bis 7 Feldern auf der Startseite.

Varianten

  • Kurzformular: Name, E-Mail, Nachricht
  • Anfrage mit Leistungsauswahl, PLZ, Zeitraum und Foto-Upload (Handwerk, Bewertung im Immobilienbereich)
  • Erstgespräch oder Rückruf mit Wunschtag und Zeitfenster (Kanzlei, Steuerberatung, Makler)
  • Projektanfrage mit Budget-Spanne und Termin (Agentur, Freelancer, Fotografie)
  • Satz-Formular (Lückentext): die Anfrage als ein Satz mit höchstens 5 Lücken, Felder wachsen mit field-sizing: content (Kreativstudio, Freelancer, Eventlocation)

Umsetzung

Natives <form novalidate> mit sichtbaren <label>, autocomplete (name, email, tel, postal-code) und inputmode. Auswahl als Radio-Gruppe in einem <fieldset> mit <legend>. Fehler per CSS :user-invalid oder, wie in der Demo, per JS: beim focusout prüfen (leer verlassene Felder noch nicht), danach bei jeder Eingabe neu prüfen, beim Absenden alles prüfen und das erste Fehlerfeld fokussieren. Meldungstexte je validity-Fall (valueMissing, typeMismatch, patternMismatch, tooShort). Upload per <input type="file" accept="image/*" multiple> mit Grössenhinweis. Versand serverseitig, kein mailto:. Spam-Schutz per Honeypot-Feld und Zeitstempel statt Captcha. Bestätigung in einem Bereich mit role="status". Hinweis zum Datenschutz unter dem Button, keine vorab angehakte Einwilligung.

Robust umsetzen

Telefon. Einspaltig, Felder mindestens 48 px hoch, 16 px Schrift oder mehr gegen Zoom unter iOS. Passende Tastatur über type und inputmode (tel, email, numeric für die PLZ). Das Formular steht direkt unter der Einleitung, die direkten Wege darunter.

Ohne Bewegung. Keine Bewegung nötig. Zustandswechsel (Fehler, Bestätigung) erscheinen ohne Animation.

Barrierefrei. Sichtbare Labels, Platzhalter ersetzen kein Label. Optionale Felder als „(optional)“ markieren statt Pflichtfelder mit Sternchen. Fehler per aria-invalid und aria-describedby am Feld, nicht nur über Farbe (Symbol plus Text). Nach dem Senden Fokus auf die Überschrift der Bestätigung. Kontrast mindestens 4,5 : 1 für Text, 3 : 1 für Feldrahmen. Kopier-Feedback zusätzlich über eine role="status"-Meldung.

Code

<!DOCTYPE html>
<!--
  Kontakt- und Anfrageformular als eigene Sektion
  Fundus · Hero & Section-Layouts · https://cecodesign.de/lab/fundus/hero-layouts/kontakt-anfrage-formular/
  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>Kontakt- und Anfrageformular als eigene Sektion · 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: 'Libre Caslon Display'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/libre-caslon-display@latest/latin-400-normal.woff2') format('woff2'); font-weight: 400; 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 „Immobilien: diskret und hochwertig“ */
  --grund: #fcfaf5;        /* warmes Fast-Weiss */
  --sand: #efe8df;         /* Sandfläche */
  --marke: #02385c;        /* Markenton: Headlines, Auswahl, Footer */
  --text: #313131;
  --leise: #66615b;        /* Meta-Text, 5,6 : 1 auf --grund */
  --linie: #d9d3ca;        /* Trennlinien */
  --rand: #8d8780;         /* Feldrahmen, 3,4 : 1 (Nicht-Text-Kontrast) */
  --cta: #ed7f1a;          /* einziger Akzent: Absenden */
  --cta-tief: #d96f0f;
  --fehler: #b3261e;
  --feld: #ffffff;
  --ui: cubic-bezier(0.165, 0.84, 0.44, 1);   /* easeOutQuart, Rezept */
  --zu: cubic-bezier(0.4, 0, 1, 1);           /* Schliessen/Ausblenden: ease-in */
  --t-ui: 400ms; --t-farbe: 200ms; --t-aus: 200ms;
  --ziel: 48px;            /* Touch-Ziel (Messwert: mind. 44 px) */
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { background: var(--grund); color: var(--text); font-family: 'Hanken Grotesk', system-ui, sans-serif; -webkit-font-smoothing: antialiased; font-size: 100%; }
body { min-height: 100svh; background: var(--grund); display: flex; flex-direction: column; }
button, input, select, textarea { font: inherit; color: inherit; }
.versal { font-size: .75rem; font-weight: 600; letter-spacing: .09em; text-transform: uppercase; line-height: 1.3; }
.mono { font: 500 10.5px/1.35 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; color: var(--leise); }
.ziffern { font-variant-numeric: tabular-nums lining-nums; }
.antiqua { font-family: 'Libre Caslon Display', 'Times New Roman', serif; font-weight: 400; letter-spacing: -.005em; color: var(--marke); }
.unsichtbar { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* Demo-Leiste (Varianten + Werte) */
.demo { display: flex; justify-content: space-between; align-items: center; gap: 10px 24px; flex-wrap: wrap; padding: 8px clamp(16px, 3vw, 40px); border-bottom: 1px solid var(--linie); }
.demo .gruppe { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; }
.varianten { display: flex; flex-wrap: wrap; gap: 2px; }
.varianten button { font: inherit; letter-spacing: inherit; text-transform: inherit; color: var(--leise); background: none; border: 1px solid transparent; border-radius: 99px; padding: 5px 10px; cursor: pointer; transition: color var(--t-farbe) var(--ui), border-color var(--t-farbe) var(--ui); }
.varianten button:hover { color: var(--marke); }
.varianten button[aria-pressed="true"] { color: var(--marke); border-color: var(--marke); }
.varianten button:focus-visible { outline: 2px solid var(--marke); outline-offset: 2px; }
.werte { display: flex; flex-wrap: wrap; gap: 4px 16px; }
.werte b { color: var(--text); font-weight: 500; }

/* Kopf der Website */
.kopf { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 14px clamp(16px, 3vw, 40px); }
.marke { display: flex; align-items: baseline; gap: 10px; color: var(--marke); text-decoration: none; }
.marke b { font-weight: 600; font-size: 15px; letter-spacing: .3em; }
.marke span { font-size: 12px; color: var(--leise); letter-spacing: .02em; }
.kopf nav { display: flex; align-items: center; gap: clamp(14px, 2.4vw, 32px); font-size: 14px; }
.kopf nav a { color: var(--text); text-decoration: none; }
.kopf nav a[aria-current] { color: var(--marke); text-decoration: underline; text-underline-offset: 6px; text-decoration-thickness: 1px; }
.kopf nav .tel { font-weight: 600; color: var(--marke); }

/* Sektion */
.kontakt {
  flex: 1; display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  grid-template-areas: "intro form" "wege form"; grid-template-rows: auto 1fr;
  margin: 0 clamp(16px, 3vw, 40px); border-top: 1px solid var(--linie);
}
.intro, .wege { background: var(--sand); padding-inline: clamp(24px, 3.4vw, 52px); }
.intro { grid-area: intro; padding-top: clamp(26px, 4vw, 56px); }
.wege { grid-area: wege; padding-bottom: clamp(20px, 2.6vw, 36px); display: flex; flex-direction: column; }
.intro .versal { color: var(--leise); margin-bottom: 18px; display: flex; gap: 10px; align-items: center; }
.intro .versal::before { content: ''; width: 22px; height: 1px; background: var(--marke); }
.intro h2 { font-size: clamp(2.25rem, 1.4rem + 2vw, 3.75rem); line-height: 1.06; max-width: 13ch; text-wrap: balance; }
.intro p:not(.versal) { margin-top: 18px; font-size: 1.0625rem; line-height: 1.55; max-width: 40ch; text-wrap: pretty; }
.tauschen { transition: opacity var(--t-ui) var(--ui), translate var(--t-ui) var(--ui); }
.wechsel .tauschen { opacity: 0; translate: 0 8px; transition: opacity var(--t-aus) var(--zu), translate var(--t-aus) var(--zu); }

.wege dl { margin-top: clamp(22px, 3vw, 40px); border-top: 1px solid rgba(2, 56, 92, .16); }
.wege .zeile { display: grid; grid-template-columns: 5.5em minmax(0, 1fr) auto; align-items: center; gap: 12px; min-height: 52px; border-bottom: 1px solid rgba(2, 56, 92, .16); }
.wege dt { color: var(--leise); font-size: .75rem; font-weight: 600; letter-spacing: .09em; text-transform: uppercase; }
.wege dd { font-size: 1rem; overflow-wrap: anywhere; }
.wege dd a { color: var(--marke); text-decoration: none; font-weight: 500; }
.wege dd a:hover { text-decoration: underline; text-underline-offset: 4px; }
.kopieren { font-size: .75rem; font-weight: 600; letter-spacing: .09em; text-transform: uppercase; color: var(--marke); background: none; border: 1px solid rgba(2, 56, 92, .3); border-radius: 99px; height: 32px; padding: 0 12px; cursor: pointer; position: relative; transition: background-color var(--t-farbe) var(--ui), color var(--t-farbe) var(--ui); }
.kopieren::after { content: ''; position: absolute; inset: -8px -4px; }   /* Trefferfläche 48 px */
.kopieren:hover { background: rgba(2, 56, 92, .07); }
.kopieren.ok { background: var(--marke); color: #fff; border-color: var(--marke); }
.kopieren:focus-visible { outline: 2px solid var(--marke); outline-offset: 2px; }
.person { display: flex; gap: 14px; align-items: center; margin-top: auto; padding-top: 18px; border-top: 1px solid var(--linie); }
.form-flaeche > form, .form-flaeche > .bestaetigung { margin-bottom: 32px; }
.person svg { flex: none; width: 52px; height: 52px; }
.person b { display: block; font-weight: 600; font-size: 15px; color: var(--marke); }
.person span { font-size: 14px; color: var(--leise); line-height: 1.4; }
.ufer { margin-top: auto; padding-top: 22px; }
.ufer svg { display: block; width: 100%; height: auto; overflow: visible; }
.ufer figcaption { display: flex; justify-content: space-between; gap: 12px; margin-top: 8px; font-size: 12px; color: var(--leise); }

/* Formular */
.form-flaeche { grid-area: form; display: flex; flex-direction: column; padding: clamp(24px, 3.4vw, 52px) clamp(20px, 4vw, 64px) clamp(20px, 3vw, 40px); background: var(--grund); position: relative; }
.form-kopf { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding-bottom: 14px; margin-bottom: 22px; border-bottom: 1px solid var(--linie); }
.form-kopf h3 { font-size: 1.25rem; font-weight: 600; color: var(--marke); letter-spacing: -.01em; }
.form-kopf p { font-size: 13px; color: var(--leise); }
form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px 20px; }
.voll { grid-column: 1 / -1; }
.feld { display: grid; gap: 7px; align-content: start; position: relative; }
.feld > label:not(.ablage), .feld > legend, .feld .titel { font-size: .75rem; font-weight: 600; letter-spacing: .09em; text-transform: uppercase; color: var(--text); }
.feld .opt { font-weight: 500; letter-spacing: .02em; text-transform: none; color: var(--leise); font-size: 13px; }
fieldset { border: 0; min-width: 0; }
.eingabe { position: relative; }
.eingabe input, .eingabe select, .eingabe textarea {
  width: 100%; min-height: var(--ziel); padding: 12px 14px; font-size: 1rem; line-height: 1.4;
  background: var(--feld); border: 1px solid var(--rand); border-radius: 3px; outline: none; appearance: none;
  transition: border-color var(--t-farbe) var(--ui), box-shadow var(--t-farbe) var(--ui);
}
.eingabe textarea { min-height: 112px; resize: vertical; }
.eingabe select { padding-right: 40px; cursor: pointer; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%2302385c' stroke-width='1.5'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 16px center; }
.eingabe input::placeholder, .eingabe textarea::placeholder { color: #8a847d; }
.eingabe input:hover, .eingabe select:hover, .eingabe textarea:hover { border-color: var(--text); }
.eingabe input:focus-visible, .eingabe select:focus-visible, .eingabe textarea:focus-visible,
.feld.fokus .eingabe > :is(input, select, textarea) { border-color: var(--marke); box-shadow: inset 0 0 0 1px var(--marke); }
/* Gültig nach dem Verlassen: kleines Häkchen */
.eingabe .haken { position: absolute; right: 14px; top: 50%; translate: 0 -50%; width: 14px; height: 14px; opacity: 0; scale: .6; transition: opacity var(--t-farbe) var(--ui), scale var(--t-ui) var(--ui); pointer-events: none; }
.feld.gut .haken { opacity: 1; scale: 1; }
.feld.gut .eingabe input { padding-right: 38px; }
/* Fehler: erst nach blur oder Absenden (Klasse .fehler, gesetzt per JS) */
.feld.fehler .eingabe > :is(input, select, textarea) { border-color: var(--fehler); box-shadow: inset 0 0 0 1px var(--fehler); }
.meldung { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--t-ui) var(--ui); }
.meldung > span { overflow: hidden; display: flex; gap: 7px; align-items: flex-start; font-size: 13.5px; line-height: 1.35; color: var(--fehler); opacity: 0; transition: opacity var(--t-farbe) var(--ui); }
.meldung > span::before { content: '!'; flex: none; width: 15px; height: 15px; margin-top: 1px; border-radius: 50%; background: var(--fehler); color: #fff; font: 700 10.5px/15px 'Hanken Grotesk', sans-serif; text-align: center; }
.feld.fehler .meldung { grid-template-rows: 1fr; }
.feld.fehler .meldung > span { opacity: 1; }
.hinweis { font-size: 13px; color: var(--leise); line-height: 1.35; }

/* Auswahl als Pillen (Radio) */
.pillen { display: flex; flex-wrap: wrap; gap: 8px; }
.pille { position: relative; }
.pille input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.pille span { display: inline-flex; align-items: center; gap: 8px; min-height: var(--ziel); padding: 0 18px; border: 1px solid var(--rand); border-radius: 99px; background: var(--feld); font-size: 15px; transition: background-color var(--t-farbe) var(--ui), color var(--t-farbe) var(--ui), border-color var(--t-farbe) var(--ui); }
.pille span::before { content: ''; width: 8px; height: 8px; border-radius: 50%; border: 1px solid currentColor; transition: background-color var(--t-farbe) var(--ui); }
.pille:hover span { border-color: var(--marke); }
.pille input:checked + span { background: var(--marke); border-color: var(--marke); color: #fff; }
.pille input:checked + span::before { background: var(--cta); border-color: var(--cta); }
.pille input:focus-visible + span, .pille.fokus span { outline: 2px solid var(--marke); outline-offset: 2px; }
.feld.fehler .pille span { border-color: var(--fehler); }

/* Foto-Ablage */
.ablage { display: flex; align-items: center; gap: 12px; min-height: var(--ziel); padding: 6px 14px; border: 1px dashed var(--rand); border-radius: 3px; background: var(--feld); cursor: pointer; transition: border-color var(--t-farbe) var(--ui), background-color var(--t-farbe) var(--ui); position: relative; }
.ablage:hover, .ablage.fokus, .ablage:focus-within { border-color: var(--marke); background: #fff; }
.ablage:focus-within { outline: 2px solid var(--marke); outline-offset: 2px; }
.ablage input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.ablage svg { flex: none; }
.ablage b { font-weight: 600; font-size: 15px; color: var(--marke); }
.ablage small { display: block; font-size: 12.5px; color: var(--leise); }
.dateien { display: flex; flex-wrap: wrap; gap: 6px; list-style: none; }
.dateien:empty { display: none; }
.dateien li { font-size: 12.5px; padding: 4px 10px; border-radius: 99px; background: var(--sand); color: var(--marke); animation: rein var(--t-ui) var(--ui) both; }

/* Häkchen-Option */
.option { display: flex; align-items: center; gap: 12px; min-height: var(--ziel); cursor: pointer; font-size: 15px; width: fit-content; }
.option input { appearance: none; width: 22px; height: 22px; border: 1px solid var(--rand); border-radius: 3px; background: var(--feld) center / 14px no-repeat; cursor: pointer; transition: background-color var(--t-farbe) var(--ui), border-color var(--t-farbe) var(--ui); }
.option input:checked { background-color: var(--marke); border-color: var(--marke); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='m2.5 7.2 3 3 6-6.4' fill='none' stroke='%23fff' stroke-width='1.8'/%3E%3C/svg%3E"); }
.option input:focus-visible { outline: 2px solid var(--marke); outline-offset: 2px; }

/* Absenden */
.senden-zeile { display: flex; align-items: center; gap: 14px 22px; flex-wrap: wrap; padding-top: 4px; }
.absenden { position: relative; display: inline-flex; align-items: center; justify-content: center; gap: 12px; min-height: 52px; padding: 0 26px; border: 0; border-radius: 99px; background: var(--cta); color: #10263a; font-size: 16px; font-weight: 600; cursor: pointer; transition: background-color var(--t-farbe) var(--ui), scale var(--t-farbe) var(--ui); }
.absenden:hover { background: var(--cta-tief); }
.absenden:active, .absenden.druck { scale: .97; }
.absenden:focus-visible { outline: 2px solid var(--marke); outline-offset: 3px; }
.absenden .pfeil { transition: translate var(--t-ui) var(--ui); }
.absenden:hover .pfeil { translate: 4px 0; }
.absenden .kreis { display: none; width: 16px; height: 16px; border-radius: 50%; border: 2px solid rgba(16, 38, 58, .25); border-top-color: #10263a; animation: drehen .8s linear infinite; }
.absenden[aria-busy="true"] { pointer-events: none; }
.absenden[aria-busy="true"] .kreis { display: block; }
.absenden[aria-busy="true"] .pfeil { display: none; }
.datenschutz { font-size: 13px; line-height: 1.45; color: var(--leise); max-width: 36ch; }
.datenschutz a { color: var(--marke); }
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* Variantenfelder */
[data-nur][hidden] { display: none !important; }
form.wechsel > *, .form-kopf.wechsel > * { opacity: 0; translate: 0 8px; transition: opacity var(--t-aus) var(--zu), translate var(--t-aus) var(--zu); }
form > *, .form-kopf > * { transition: opacity var(--t-ui) var(--ui), translate var(--t-ui) var(--ui); }

/* Bestätigung */
.bestaetigung { display: none; }
.form-flaeche.gesendet form, .form-flaeche.gesendet .form-kopf { display: none; }
.form-flaeche.gesendet .bestaetigung { display: block; animation: rein var(--t-ui) var(--ui) both; }
.bestaetigung .versal { color: var(--leise); display: flex; gap: 10px; align-items: center; }
.bestaetigung .versal::before { content: ''; width: 22px; height: 1px; background: var(--cta); }
.bestaetigung h3 { font-size: clamp(2.25rem, 1.6rem + 1.6vw, 3.25rem); line-height: 1.08; margin: 16px 0 14px; outline: none; }
.bestaetigung > p { font-size: 1.0625rem; line-height: 1.55; max-width: 46ch; }
.schritte { list-style: none; margin: 26px 0 24px; border-top: 1px solid var(--linie); counter-reset: s; }
.schritte li { display: grid; grid-template-columns: 3em minmax(0, 1fr) auto; gap: 12px; align-items: baseline; padding: 14px 0; border-bottom: 1px solid var(--linie); counter-increment: s; animation: rein var(--t-ui) var(--ui) both; }
.schritte li:nth-child(2) { animation-delay: 80ms; } .schritte li:nth-child(3) { animation-delay: 160ms; }
.schritte li::before { content: '0' counter(s); font-size: .75rem; font-weight: 600; letter-spacing: .09em; color: var(--marke); }
.schritte b { font-weight: 600; display: block; }
.schritte span { font-size: 14px; color: var(--leise); }
.schritte em { font-style: normal; font-size: 13px; color: var(--leise); white-space: nowrap; }
.zurueck { font-size: 15px; font-weight: 600; color: var(--marke); background: none; border: 1px solid var(--marke); border-radius: 99px; min-height: var(--ziel); padding: 0 22px; cursor: pointer; transition: background-color var(--t-farbe) var(--ui), color var(--t-farbe) var(--ui); }
.zurueck:hover { background: var(--marke); color: #fff; }
.zurueck:focus-visible { outline: 2px solid var(--marke); outline-offset: 3px; }
.kopie { margin-top: 16px; font-size: 13px; color: var(--leise); }

/* Footer */
.fuss { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px 24px; padding: 16px clamp(16px, 3vw, 40px); background: var(--marke); color: rgba(255, 255, 255, .78); font-size: 13px; margin-top: 0; }
.fuss a { color: #fff; text-decoration: none; }
.fuss p > * + *::before { content: '·'; margin: 0 10px; opacity: .5; }

@keyframes rein { from { opacity: 0; translate: 0 8px; } }
@keyframes drehen { to { rotate: 360deg; } }

/* Vorführung: gezeichneter Zeiger */
.zeiger { position: fixed; left: 0; top: 0; z-index: 20; width: 20px; height: 20px; pointer-events: none; display: none; transition: transform 550ms var(--ui); }
.vorfuehrung .zeiger { display: block; }
.zeiger.klick circle { animation: tipp 450ms var(--ui); }
@keyframes tipp { from { r: 3; opacity: .5; } to { r: 16; opacity: 0; } }

/* Vorschau 960 × 720: nur die Bühne */
.vorschau body { overflow: hidden; height: 100svh; }
.vorschau .fuss, .vorschau .kopf nav a:not(.tel) { display: none; }
.vorschau .person, .vorschau .form-kopf { display: none; }
.vorschau .form-flaeche { padding-top: 22px; }
.vorschau .kopf { padding-block: 11px; }
.vorschau .ufer { padding-top: 14px; }

/* Breiten */
@media (max-width: 1100px) {
  .kopf nav a:not(.tel):not([aria-current]) { display: none; }
}
@media (max-width: 1100px) and (min-width: 821px) {
  .intro h2 { font-size: clamp(2.25rem, 1.2rem + 2.2vw, 3rem); }
  .intro p:not(.versal) { font-size: 1rem; margin-top: 14px; }
  .wege dl { margin-top: 20px; }
  .wege .zeile { min-height: 46px; grid-template-columns: 4.6em minmax(0, 1fr) auto; }
  .wege dd { font-size: 15px; }
  form { gap: 14px 16px; }
  .form-kopf { margin-bottom: 18px; }
  .kontakt { grid-template-columns: minmax(0, 4.3fr) minmax(0, 7.7fr); }
  .pille span { padding: 0 14px; font-size: 14.5px; gap: 7px; }
  .wege .zeile { grid-template-columns: 4.6em minmax(0, 1fr); row-gap: 0; padding: 6px 0; }
  .wege .kopieren { grid-column: 2; justify-self: start; margin-bottom: 4px; }
  .ablage small { font-size: 12px; }
  .werte span:nth-child(n+4) { display: none; }
  .ufer figcaption span + span { display: none; }
  .wege dd { overflow-wrap: normal; }
}
@media (max-width: 820px) {
  .kontakt { grid-template-columns: minmax(0, 1fr); grid-template-areas: "intro" "form" "wege"; grid-template-rows: auto; margin: 0; }
  .intro { padding-bottom: 28px; }
  .wege { padding-top: 6px; padding-bottom: 28px; }
  .wege dl { margin-top: 0; }
  .form-flaeche { padding: 28px 16px 36px; }
  .intro, .wege { padding-inline: 16px; }
  .kopf nav a[aria-current] { display: none; }
  .demo .werte { display: none; }
}
@media (max-width: 560px) {
  form { grid-template-columns: minmax(0, 1fr); gap: 18px; }
  .form-kopf { flex-direction: column; gap: 4px; }
  .marke span { display: none; }
  .absenden { width: 100%; }
  .wege .zeile { grid-template-columns: 4.6em minmax(0, 1fr) auto; }
  .schritte li { grid-template-columns: 2.4em minmax(0, 1fr); }
  .schritte em { grid-column: 2; }
}
/* Touch: keine Hover-Gesten, grössere Ziele, Tastaturen über type/inputmode */
@media (hover: none) {
  :root { --ziel: 52px; }
  .absenden:hover { background: var(--cta); }
  .absenden:hover .pfeil { translate: none; }
  .pille:hover span { border-color: var(--rand); }
  .eingabe input, .eingabe select, .eingabe textarea { font-size: 17px; }   /* kein Zoom unter iOS */
}
/* Reduzierte Bewegung: Zustände wechseln ohne Weg und ohne Dauer */
.reduziert *, .reduziert *::before, .reduziert *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; animation-delay: 0ms !important; }
.reduziert .wechsel .tauschen, .reduziert form.wechsel > *, .reduziert .form-kopf.wechsel > * { translate: none; }
.reduziert .zeiger { transition: none; }
.reduziert .absenden .kreis { animation: none; border-top-color: rgba(16, 38, 58, .25); border-right-color: #10263a; }

/* ===== Variante „satz“: Satz-Formular (Lückentext), Rezept freelancer-stille-visitenkarte ===== */
@font-face { font-family: 'Host Grotesk'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/host-grotesk:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 300 800; font-display: swap; }
@font-face { font-family: 'Newsreader'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/newsreader:vf@latest/latin-opsz-italic.woff2') format('woff2-variations'); font-weight: 200 800; font-style: italic; font-display: swap; }
.satz-welt {
  --s-grund: #fcfcfc; --s-text: #0b0b09; --s-text-2: #646464; --s-linie: #e8e8e8; --s-flaeche: #f0f0f0;
  --s-rand: #8a8a8a;      /* Unterstrich der Lücke, 3,4 : 1 */
  --s-blau: #2f5bea;      /* einzige Farbe: Fokus und Link-Hover */
  --s-fehler: #b3261e;
  --s-kurve: cubic-bezier(0.2, 0, 0.38, 0.9); --s-aus: cubic-bezier(0.2, 0, 1, 0.9);
  --s-t: 150ms; --s-t-ein: 320ms; --s-t-aus: 200ms;
  display: none; flex: 1; flex-direction: column; background: var(--s-grund); color: var(--s-text);
  font-family: 'Host Grotesk', system-ui, sans-serif; font-weight: 400; font-size: 1rem; line-height: 1.6;
  padding-inline: clamp(20px, 5vw, 48px);
}
html:has(body.ist-satz), body.ist-satz { background: #fcfcfc; }
.ist-satz .kopf, .ist-satz .kontakt, .ist-satz .fuss { display: none; }
.ist-satz .satz-welt { display: flex; }
.ist-satz .zeiger path { fill: #0b0b09; } .ist-satz .zeiger circle { fill: #2f5bea; }
.satz-welt .s-innen { width: 100%; max-width: 1072px; margin-inline: auto; display: flex; flex-direction: column; flex: 1; }
.s-kopf { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; padding-block: 22px; font-size: .875rem; }
.s-kopf b { font-weight: 500; }
.s-kopf nav { display: flex; gap: 20px; }
.s-kopf nav a { color: var(--s-text-2); text-decoration: none; transition: color var(--s-t) var(--s-kurve); }
.s-kopf nav a:hover { color: var(--s-blau); }
.s-kopf nav a[aria-current] { color: var(--s-text); }
.satz-welt a:focus-visible, .satz-welt button:focus-visible { outline: 2px solid var(--s-blau); outline-offset: 3px; border-radius: 4px; }
.s-haupt { width: 100%; max-width: 40rem; padding-block: clamp(40px, 9vh, 104px) clamp(32px, 6vh, 64px); flex: 1; }
.s-meta { font: 500 11px/1.4 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; color: var(--s-text-2); display: flex; gap: 10px; align-items: center; margin-bottom: 20px; }
.s-meta i { width: 7px; height: 7px; border-radius: 50%; background: #1f8a4c; box-shadow: 0 0 0 3px rgba(31, 138, 76, .16); }
.satz-welt form { display: block; }
.satz { border: 0; min-width: 0; margin: 0; padding: 0; }
.satz legend { float: left; width: 100%; padding: 0; margin-bottom: 14px; font-size: 1rem; font-weight: 500; color: var(--s-text-2); }
.satz legend + *, .satz-text { clear: both; }
.satz-text { font-size: clamp(1.5rem, 1.05rem + 1.7vw, 2.25rem); line-height: 1.62; letter-spacing: -.012em; text-wrap: pretty; }
.satz-text em, .s-danke em { font-family: 'Newsreader', Georgia, serif; font-style: italic; font-size: 1.05em; letter-spacing: 0; font-variation-settings: 'opsz' 36; }
/* Die Lücken: wachsen mit dem Inhalt (field-sizing: content), mindestens 3ch, nie breiter als die Zeile */
.satz-text :is(input, select) {
  font: inherit; letter-spacing: inherit; color: var(--s-text); background: transparent;
  border: 0; border-bottom: 1px solid var(--s-rand); border-radius: 0; margin: 0 .04em; padding: 0 .08em;
  min-width: 3ch; max-width: 100%; field-sizing: content; line-height: 1.25; vertical-align: baseline;
  appearance: none; outline: none; cursor: text;
  transition: border-color var(--s-t) var(--s-kurve), background-color var(--s-t) var(--s-kurve), box-shadow var(--s-t) var(--s-kurve);
}
.satz-text select { cursor: pointer; padding-right: .72em; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23646464' stroke-width='1.3'/%3E%3C/svg%3E") right .08em center / .32em no-repeat; }
.satz-text select option { font-size: 1rem; font-family: 'Host Grotesk', system-ui, sans-serif; }
.satz-text input::placeholder { color: var(--s-text-2); opacity: 1; font-family: 'Newsreader', Georgia, serif; font-style: italic; font-size: 1.02em; letter-spacing: 0; }
.satz-text :is(input, select):hover { background-color: var(--s-flaeche); }
.satz-text :is(input, select):focus-visible, .satz-text .fokus { background-color: var(--s-flaeche); border-bottom-color: var(--s-blau); box-shadow: 0 1px 0 var(--s-blau); outline: 2px solid var(--s-blau); outline-offset: 4px; border-radius: 3px 3px 0 0; }
.satz-text [aria-invalid="true"] { border-bottom-color: var(--s-fehler); box-shadow: 0 1px 0 var(--s-fehler); }
.satz-text [aria-invalid="true"]:focus-visible { outline-color: var(--s-fehler); }
/* Rückfall ohne field-sizing: feste Breiten in ch */
@supports not (field-sizing: content) {
  #s-name { width: 9ch; } #s-firma { width: 11ch; } #s-mail { width: 13ch; }
}
.s-meldungen { list-style: none; display: grid; gap: 4px; margin-top: 14px; }
.s-meldungen li { display: flex; gap: 8px; align-items: flex-start; font-size: .9375rem; line-height: 1.45; color: var(--s-fehler); animation: s-rein var(--s-t-ein) var(--s-kurve) both; }
.s-meldungen li::before { content: '!'; flex: none; width: 16px; height: 16px; margin-top: 3px; border-radius: 50%; background: var(--s-fehler); color: #fff; font: 600 11px/16px 'Host Grotesk', sans-serif; text-align: center; }
.s-meldungen li[hidden] { display: none; }
.s-senden { display: flex; flex-wrap: wrap; align-items: center; gap: 14px 24px; margin-top: 36px; padding-top: 24px; border-top: 1px solid var(--s-linie); }
.s-pille { display: inline-flex; align-items: center; gap: 10px; min-height: 44px; padding: 0 20px; border: 1px solid var(--s-text); border-radius: 999px; background: transparent; color: var(--s-text); font: 500 1rem/1 'Host Grotesk', system-ui, sans-serif; cursor: pointer; transition: background-color var(--s-t) var(--s-kurve), color var(--s-t) var(--s-kurve); }
.s-pille:hover { background: var(--s-flaeche); }
.s-pille svg { transition: translate 200ms var(--s-kurve); }
.s-pille:hover svg { translate: 4px 0; }
.s-pille .s-kreis { display: none; width: 14px; height: 14px; border-radius: 50%; border: 1.5px solid rgba(11, 11, 9, .2); border-top-color: var(--s-text); animation: drehen .8s linear infinite; }
.s-pille[aria-busy="true"] { pointer-events: none; }
.s-pille[aria-busy="true"] .s-kreis { display: block; } .s-pille[aria-busy="true"] svg { display: none; }
.s-pille.druck { background: var(--s-flaeche); }
.s-klein { font-size: .875rem; line-height: 1.5; color: var(--s-text-2); max-width: 34ch; }
.s-klein a, .s-direkt a { color: inherit; text-decoration: underline; text-decoration-color: var(--s-linie); text-underline-offset: 3px; transition: color var(--s-t) var(--s-kurve); }
.s-klein a:hover, .s-direkt a:hover { color: var(--s-blau); text-decoration-color: currentColor; }
.s-direkt { display: grid; grid-template-columns: minmax(0, 1fr) auto; margin-top: 44px; border-top: 1px solid var(--s-linie); font-size: .875rem; }
.s-direkt > * { display: flex; align-items: center; min-height: 44px; border-bottom: 1px solid var(--s-linie); }
.s-direkt dt { color: var(--s-text-2); grid-column: 1; }
.s-direkt dd { grid-column: 2; justify-content: flex-end; gap: 10px; font-variant-numeric: tabular-nums; }
.s-kopier { font: 500 .8125rem/1 'Host Grotesk', system-ui, sans-serif; color: var(--s-text-2); background: none; border: 1px solid var(--s-linie); border-radius: 6px; height: 28px; padding: 0 10px; cursor: pointer; position: relative; transition: background-color var(--s-t) var(--s-kurve), color var(--s-t) var(--s-kurve); }
.s-kopier::after { content: ''; position: absolute; inset: -8px -4px; }
.s-kopier:hover { background: var(--s-flaeche); color: var(--s-text); }
.s-kopier.ok { color: #1f6b3e; border-color: #b9d8c4; background: #eef6f0; }
/* Bestätigung ersetzt den Satz */
.s-danke { display: none; }
.s-haupt.gesendet .satz, .s-haupt.gesendet .s-senden, .s-haupt.gesendet .s-meldungen { display: none; }
.s-haupt.gesendet .s-danke { display: block; animation: s-rein var(--s-t-ein) var(--s-kurve) both; }
.s-danke h2 { font-size: clamp(1.5rem, 1.05rem + 1.7vw, 2.25rem); font-weight: 400; line-height: 1.5; letter-spacing: -.012em; outline: none; text-wrap: pretty; }
.s-danke ol { list-style: none; margin: 28px 0; border-top: 1px solid var(--s-linie); font-size: .9375rem; }
.s-danke li { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 16px; padding: 12px 0; border-bottom: 1px solid var(--s-linie); animation: s-rein var(--s-t-ein) var(--s-kurve) both; }
.s-danke li:nth-child(2) { animation-delay: 30ms; } .s-danke li:nth-child(3) { animation-delay: 60ms; }
.s-danke li span { color: var(--s-text-2); font-variant-numeric: tabular-nums; text-align: right; }
.s-danke p { font-size: .875rem; color: var(--s-text-2); margin-top: 14px; }
.s-fuss { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px 20px; padding-block: 20px 26px; border-top: 1px solid var(--s-linie); font-size: .8125rem; color: var(--s-text-2); }
.s-fuss a { color: inherit; text-decoration: none; } .s-fuss a:hover { color: var(--s-blau); }
.s-fuss nav { display: flex; gap: 18px; }
@keyframes s-rein { from { opacity: 0; translate: 0 6px; } }
.vorschau .s-fuss, .vorschau .s-direkt { display: none; }
.vorschau .s-haupt { padding-top: 56px; }
@media (max-width: 560px) {
  .s-kopf nav a:not([aria-current]) { display: none; }
  .s-senden { flex-direction: column; align-items: stretch; }
  .s-pille { justify-content: center; }
  .s-direkt dd { flex-wrap: wrap; }
}
@media (hover: none) {
  .s-pille:hover { background: transparent; } .s-pille:hover svg { translate: none; }
  .satz-text :is(input, select):hover { background-color: transparent; }
}
.reduziert .s-meldungen li, .reduziert .s-danke, .reduziert .s-danke li { animation-name: s-blende; }
@keyframes s-blende { from { opacity: 0; } }
.reduziert .s-pille .s-kreis { animation: none; border-right-color: var(--s-text); }
@media (forced-colors: active) { .satz-text :is(input, select) { border: 1px solid CanvasText; } .s-pille, .s-kopier { border: 1px solid ButtonText; } }
.varianten .trenner { width: 1px; align-self: stretch; margin: 4px 6px; background: var(--linie); }
</style>
</head>
<body>
<div class="demo mono">
  <div class="gruppe">
    <span>Variante</span>
    <div class="varianten" role="group" aria-label="Formular-Variante">
      <button type="button" data-variante="bewertung" aria-pressed="true">Bewertung</button>
      <button type="button" data-variante="rueckruf" aria-pressed="false">Rückruf</button>
      <button type="button" data-variante="kurz" aria-pressed="false">Kurz</button>
      <span class="trenner" aria-hidden="true"></span>
      <button type="button" data-welt="satz" aria-pressed="false">Satz</button>
    </div>
  </div>
  <p class="werte" data-werte aria-live="polite"></p>
</div>

<header class="kopf">
  <a class="marke" href="#"><b>RHYHALDE</b><span>Immobilien Basel</span></a>
  <nav aria-label="Hauptnavigation">
    <a href="#">Kaufen</a><a href="#">Verkaufen</a><a href="#">Bewerten</a><a href="#kontakt" aria-current="page">Kontakt</a>
    <a class="tel ziffern" href="tel:+41612714890">061 271 48 90</a>
  </nav>
</header>

<main class="kontakt" id="kontakt" aria-labelledby="k-titel">
  <div class="intro">
    <p class="versal tauschen" data-text="augenbraue">Für Eigentümerinnen und Eigentümer</p>
    <h2 class="antiqua tauschen" id="k-titel" data-text="titel">Was ist Ihre Liegenschaft heute wert?</h2>
    <p class="tauschen" data-text="vorspann">Wir bewerten Wohnungen, Häuser und Mehrfamilienhäuser in Basel-Stadt und im Baselbiet. Persönlich vor Ort, kostenlos und streng vertraulich.</p>
  </div>

  <div class="wege">
    <dl>
      <div class="zeile"><dt>Telefon</dt><dd><a class="ziffern" href="tel:+41612714890">061 271 48 90</a></dd><span></span></div>
      <div class="zeile"><dt>E-Mail</dt><dd><a href="mailto:anfrage@rhyhalde.ch" data-mail>anfrage@rhyhalde.ch</a></dd><button type="button" class="kopieren" data-kopieren aria-describedby="kopiert">Kopieren</button></div>
      <div class="zeile"><dt>Büro</dt><dd>St. Alban-Vorstadt 38, 4052 Basel</dd><span></span></div>
    </dl>
    <p class="unsichtbar" id="kopiert" role="status" data-kopiert-status></p>
    <figure class="ufer" aria-hidden="true">
      <svg viewBox="0 0 600 140" fill="none" stroke="#02385c" stroke-width="1" stroke-linejoin="round">
        <g opacity=".62"><path d="M0 112V68H30V112 M30 112V74L41 62L52 74V112 M52 112V50L67 34L82 50V112 M82 112V68L101 47L120 68V112 M120 112V56H142V112 M142 112V68L153 56L164 68V112 M164 112V56L183 35L202 56V112 M202 112V74L221 53L240 74V112 M240 112V74H278V112 M278 112V68L289 56L300 68V112 M300 112V68L310 55H328L338 68V112 M338 112V68L345 60L352 68V112 M448 112V62L467 41L486 62V112 M486 112V50L497 38L508 50V112 M508 112V74L523 58L538 74V112 M538 112V74L557 53L576 74V112 M576 112V64H600V112"/>
        <path d="M352 112V70L376 54H424L448 70V112 M368 112V34H390V112 M368 34L379 4L390 34 M376 44h6v10h-6z M410 112V34H432V112 M410 34L421 4L432 34 M418 44h6v10h-6z M386 92a12 12 0 0 1 24 0"/></g>
        <path opacity=".38" stroke-width=".8" d="M7.5 75h4v6h-4z M18.5 75h4v6h-4z M7.5 87h4v6h-4z M18.5 87h4v6h-4z M7.5 99h4v6h-4z M18.5 99h4v6h-4z M33.5 81h4v6h-4z M44.5 81h4v6h-4z M33.5 93h4v6h-4z M44.5 93h4v6h-4z M59.5 57h4v6h-4z M70.5 57h4v6h-4z M59.5 69h4v6h-4z M70.5 69h4v6h-4z M59.5 81h4v6h-4z M70.5 81h4v6h-4z M59.5 93h4v6h-4z M70.5 93h4v6h-4z M88 75h4v6h-4z M99 75h4v6h-4z M110 75h4v6h-4z M88 87h4v6h-4z M99 87h4v6h-4z M110 87h4v6h-4z M88 99h4v6h-4z M99 99h4v6h-4z M110 99h4v6h-4z M123.5 63h4v6h-4z M134.5 63h4v6h-4z M123.5 75h4v6h-4z M134.5 75h4v6h-4z M123.5 87h4v6h-4z M134.5 87h4v6h-4z M123.5 99h4v6h-4z M134.5 99h4v6h-4z M145.5 75h4v6h-4z M156.5 75h4v6h-4z M145.5 87h4v6h-4z M156.5 87h4v6h-4z M145.5 99h4v6h-4z M156.5 99h4v6h-4z M170 63h4v6h-4z M181 63h4v6h-4z M192 63h4v6h-4z M170 75h4v6h-4z M181 75h4v6h-4z M192 75h4v6h-4z M170 87h4v6h-4z M181 87h4v6h-4z M192 87h4v6h-4z M170 99h4v6h-4z M181 99h4v6h-4z M192 99h4v6h-4z M208 81h4v6h-4z M219 81h4v6h-4z M230 81h4v6h-4z M208 93h4v6h-4z M219 93h4v6h-4z M230 93h4v6h-4z M246 81h4v6h-4z M257 81h4v6h-4z M268 81h4v6h-4z M246 93h4v6h-4z M257 93h4v6h-4z M268 93h4v6h-4z M281.5 75h4v6h-4z M292.5 75h4v6h-4z M281.5 87h4v6h-4z M292.5 87h4v6h-4z M281.5 99h4v6h-4z M292.5 99h4v6h-4z M306 75h4v6h-4z M317 75h4v6h-4z M328 75h4v6h-4z M306 87h4v6h-4z M317 87h4v6h-4z M328 87h4v6h-4z M306 99h4v6h-4z M317 99h4v6h-4z M328 99h4v6h-4z M343 75h4v6h-4z M343 87h4v6h-4z M343 99h4v6h-4z M454 69h4v6h-4z M465 69h4v6h-4z M476 69h4v6h-4z M454 81h4v6h-4z M465 81h4v6h-4z M476 81h4v6h-4z M454 93h4v6h-4z M465 93h4v6h-4z M476 93h4v6h-4z M489.5 57h4v6h-4z M500.5 57h4v6h-4z M489.5 69h4v6h-4z M500.5 69h4v6h-4z M489.5 81h4v6h-4z M500.5 81h4v6h-4z M489.5 93h4v6h-4z M500.5 93h4v6h-4z M515.5 81h4v6h-4z M526.5 81h4v6h-4z M515.5 93h4v6h-4z M526.5 93h4v6h-4z M544 81h4v6h-4z M555 81h4v6h-4z M566 81h4v6h-4z M544 93h4v6h-4z M555 93h4v6h-4z M566 93h4v6h-4z M580.5 71h4v6h-4z M591.5 71h4v6h-4z M580.5 83h4v6h-4z M591.5 83h4v6h-4z M580.5 95h4v6h-4z M591.5 95h4v6h-4z"/>
        <path d="M0 112.5H600" opacity=".8"/>
        <path opacity=".35" stroke-dasharray="14 9" d="M8 122H596 M40 130H560 M90 138H510"/>
      </svg>
      <figcaption><span>Grossbasler Rheinufer, Aufriss</span><span class="ziffern">47.56° N · 7.59° O</span></figcaption>
    </figure>
  </div>

  <div class="form-flaeche" data-flaeche>
    <div class="form-kopf" data-kopf>
      <h3 data-text="formtitel">Bewertung anfragen</h3>
      <p data-text="formmeta">Unverbindlich · etwa 2 Minuten</p>
    </div>

    <form novalidate data-form aria-labelledby="k-titel" action="/anfrage" method="post">
      <!-- Spam-Schutz ohne Captcha: verstecktes Feld + Zeitstempel -->
      <div class="hp" aria-hidden="true"><label>Website <input type="text" name="website" tabindex="-1" autocomplete="off"></label></div>
      <input type="hidden" name="t0" data-t0>

      <fieldset class="feld voll" data-nur="bewertung" data-feld="objekt" aria-describedby="objekt-f">
        <legend>Was möchten Sie bewerten lassen?</legend>
        <div class="pillen" style="margin-top:9px">
          <label class="pille"><input type="radio" name="objekt" value="wohnung" required><span>Wohnung</span></label>
          <label class="pille"><input type="radio" name="objekt" value="haus"><span>Haus</span></label>
          <label class="pille"><input type="radio" name="objekt" value="mfh"><span>Mehrfamilienhaus</span></label>
          <label class="pille"><input type="radio" name="objekt" value="anderes"><span>Anderes</span></label>
        </div>
        <div class="meldung" id="objekt-f"><span></span></div>
      </fieldset>

      <div class="feld" data-nur="bewertung">
        <label for="plz">PLZ der Liegenschaft</label>
        <div class="eingabe"><input id="plz" name="plz" type="text" inputmode="numeric" autocomplete="postal-code" pattern="[1-9][0-9]{3}" maxlength="4" required placeholder="z. B. 4052" aria-describedby="plz-f"><svg class="haken" viewBox="0 0 14 14" aria-hidden="true"><path d="m2.5 7.2 3 3 6-6.4" fill="none" stroke="#02385c" stroke-width="1.8"/></svg></div>
        <div class="meldung" id="plz-f"><span></span></div>
      </div>
      <div class="feld" data-nur="bewertung">
        <label for="zeitraum">Verkauf geplant <span class="opt">(optional)</span></label>
        <div class="eingabe"><select id="zeitraum" name="zeitraum">
          <option value="offen">Noch offen</option>
          <option value="3">In den nächsten 3 Monaten</option>
          <option value="3-6">In 3 bis 6 Monaten</option>
          <option value="12">In etwa einem Jahr</option>
          <option value="nur">Nur Bewertung, kein Verkauf</option>
        </select></div>
      </div>

      <div class="feld" data-nur="bewertung rueckruf kurz">
        <label for="name">Vor- und Nachname</label>
        <div class="eingabe"><input id="name" name="name" type="text" autocomplete="name" minlength="2" required aria-describedby="name-f"><svg class="haken" viewBox="0 0 14 14" aria-hidden="true"><path d="m2.5 7.2 3 3 6-6.4" fill="none" stroke="#02385c" stroke-width="1.8"/></svg></div>
        <div class="meldung" id="name-f"><span></span></div>
      </div>
      <div class="feld" data-nur="bewertung kurz">
        <label for="mail">E-Mail</label>
        <div class="eingabe"><input id="mail" name="email" type="email" inputmode="email" autocomplete="email" required aria-describedby="mail-f"><svg class="haken" viewBox="0 0 14 14" aria-hidden="true"><path d="m2.5 7.2 3 3 6-6.4" fill="none" stroke="#02385c" stroke-width="1.8"/></svg></div>
        <div class="meldung" id="mail-f"><span></span></div>
      </div>
      <div class="feld" data-nur="bewertung rueckruf" data-pflicht="rueckruf">
        <label for="tel">Telefon <span class="opt" data-nur="bewertung">(optional)</span></label>
        <div class="eingabe"><input id="tel" name="tel" type="tel" inputmode="tel" autocomplete="tel" pattern="[+0-9 \(\)\/\-]{9,}" aria-describedby="tel-f"><svg class="haken" viewBox="0 0 14 14" aria-hidden="true"><path d="m2.5 7.2 3 3 6-6.4" fill="none" stroke="#02385c" stroke-width="1.8"/></svg></div>
        <div class="meldung" id="tel-f"><span></span></div>
      </div>
      <div class="feld" data-nur="bewertung">
        <span class="titel" id="fotos-t">Fotos <span class="opt">(optional)</span></span>
        <label class="ablage" data-ablage>
          <svg width="22" height="22" viewBox="0 0 22 22" fill="none" stroke="#02385c" stroke-width="1.3" aria-hidden="true"><rect x="2.5" y="4.5" width="17" height="13" rx="1.5"/><path d="m2.5 15 5-5 4 4 3-3 5 5"/><circle cx="15" cy="8.5" r="1.5"/></svg>
          <span><b>Bilder wählen</b><small>JPG oder HEIC, je bis 10 MB</small></span>
          <input type="file" name="fotos" accept="image/*" multiple aria-labelledby="fotos-t" aria-describedby="fotos-h" data-datei>
        </label>
        <ul class="dateien" id="fotos-h" data-dateien aria-live="polite"></ul>
      </div>

      <div class="feld" data-nur="rueckruf">
        <label for="thema">Worum geht es?</label>
        <div class="eingabe"><select id="thema" name="thema">
          <option>Verkaufen</option>
          <option>Bewerten lassen</option>
          <option>Vermieten und verwalten</option>
          <option>Ein Objekt suchen</option>
        </select></div>
      </div>
      <div class="feld" data-nur="rueckruf">
        <label for="tag">Wunschtag</label>
        <div class="eingabe"><select id="tag" name="tag" class="ziffern">
          <option>Montag, 2. November</option>
          <option>Dienstag, 3. November</option>
          <option>Mittwoch, 4. November</option>
          <option>Donnerstag, 5. November</option>
        </select></div>
      </div>
      <fieldset class="feld voll" data-nur="rueckruf" data-feld="fenster" aria-describedby="fenster-f">
        <legend>Zeitfenster für den Rückruf</legend>
        <div class="pillen ziffern" style="margin-top:9px">
          <label class="pille"><input type="radio" name="fenster" value="vormittag" required><span>8 – 12 Uhr</span></label>
          <label class="pille"><input type="radio" name="fenster" value="mittag"><span>12 – 14 Uhr</span></label>
          <label class="pille"><input type="radio" name="fenster" value="nachmittag"><span>14 – 18 Uhr</span></label>
        </div>
        <div class="meldung" id="fenster-f"><span></span></div>
      </fieldset>

      <div class="feld voll" data-nur="kurz">
        <label for="nachricht">Ihre Nachricht</label>
        <div class="eingabe"><textarea id="nachricht" name="nachricht" minlength="10" required rows="4" aria-describedby="nachricht-h nachricht-f"></textarea></div>
        <p class="hinweis" id="nachricht-h">Ein, zwei Sätze genügen. Wir melden uns mit allen Fragen zurück.</p>
        <div class="meldung" id="nachricht-f"><span></span></div>
      </div>

      <label class="option voll" data-nur="bewertung"><input type="checkbox" name="rueckruf"> Bitte rufen Sie mich zur Terminabsprache an</label>

      <div class="senden-zeile voll">
        <button class="absenden" type="submit" data-absenden><span class="kreis" aria-hidden="true"></span><span data-text="knopf">Bewertung anfragen</span><svg class="pfeil" width="16" height="12" viewBox="0 0 16 12" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><path d="M1 6h13M9.5 1.5 14 6l-4.5 4.5"/></svg></button>
        <p class="datenschutz">Wir verwenden Ihre Angaben nur für diese Anfrage. Mehr in der <a href="#">Datenschutzerklärung</a>.</p>
      </div>
    </form>

    <div class="bestaetigung" role="status" data-bestaetigung>
      <p class="versal ziffern" data-nr>Anfrage eingegangen · RH-26-1147</p>
      <h3 class="antiqua" tabindex="-1" data-danke>Vielen Dank.</h3>
      <p data-antwort>Lea Brändli meldet sich innert 24 Stunden bei Ihnen, werktags meist schon am selben Tag.</p>
      <ol class="schritte" data-schritte></ol>
      <button type="button" class="zurueck" data-neu>Weitere Anfrage</button>
      <p class="kopie" data-kopie></p>
    </div>

    <div class="person">
      <svg viewBox="0 0 52 52" aria-hidden="true"><circle cx="26" cy="26" r="25.5" fill="#02385c"/><text x="26" y="32.5" text-anchor="middle" fill="#fcfaf5" font-family="Libre Caslon Display, serif" font-size="19">LB</text></svg>
      <p><b>Lea Brändli</b><span>Partnerin, Bewertung und Verkauf. Sie liest jede Anfrage selbst und antwortet innert 24 Stunden (Mo–Fr).</span></p>
    </div>
  </div>
</main>

<footer class="fuss">
  <p><span>Rhyhalde Immobilien AG</span><span>St. Alban-Vorstadt 38, 4052 Basel</span></p>
  <p><a href="#">Datenschutz</a><a href="#">Impressum</a></p>
</footer>

<!-- Variante „satz“: eigene Welt (Rezept freelancer-stille-visitenkarte) -->
<div class="satz-welt" data-satz-welt>
  <div class="s-innen">
    <header class="s-kopf">
      <b>Thea Liebetrau</b>
      <nav aria-label="Hauptnavigation (Satz-Variante)"><a href="#">Arbeiten</a><a href="#">Notizen</a><a href="#s-anfrage" aria-current="page">Kontakt</a></nav>
    </header>
    <main class="s-haupt" id="s-anfrage" data-s-haupt>
      <p class="s-meta"><i aria-hidden="true"></i>Interface-Gestaltung · Erfurt · frei ab Januar 2027</p>
      <form novalidate data-satz-form action="/anfrage" method="post">
        <div class="hp" aria-hidden="true"><label>Website <input type="text" name="website" tabindex="-1" autocomplete="off"></label></div>
        <fieldset class="satz">
          <legend>Projektanfrage in einem Satz</legend>
          <div class="unsichtbar"><label class="unsichtbar" for="s-name">Ihr Name</label><label class="unsichtbar" for="s-firma">Firma oder Projekt (optional)</label><label class="unsichtbar" for="s-art">Art des Projekts</label><label class="unsichtbar" for="s-start">Gewünschter Start</label><label class="unsichtbar" for="s-mail">E-Mail-Adresse</label></div>
          <p class="satz-text"><em>Hallo Thea,</em> ich bin
            <input id="s-name" name="name" type="text" autocomplete="name" minlength="2" required placeholder="Ihr Name" aria-describedby="s-name-f">
            von <input id="s-firma" name="firma" type="text" autocomplete="organization" placeholder="Firma, optional">.
            Wir brauchen <select id="s-art" name="art">
              <option>eine neue Website</option><option>ein Redesign</option><option>ein Designsystem</option><option>eine App-Oberfläche</option><option>ein paar Tage Beratung</option><option>etwas anderes</option>
            </select>
            und möchten <select id="s-start" name="start">
              <option>so bald wie möglich</option><option>noch im Dezember</option><option selected>im Januar</option><option>im Februar</option><option>im Frühjahr</option><option>später im Jahr</option>
            </select>
            starten. Am besten erreichen Sie mich unter
            <input id="s-mail" name="email" type="email" inputmode="email" autocomplete="email" required placeholder="name@firma.de" aria-describedby="s-mail-f">.
          </p>
        </fieldset>
        <ul class="s-meldungen">
          <li id="s-name-f" hidden></li>
          <li id="s-mail-f" hidden></li>
        </ul>
        <div class="s-senden">
          <button class="s-pille" type="submit" data-s-senden><span class="s-kreis" aria-hidden="true"></span>Anfrage senden<svg width="14" height="10" viewBox="0 0 14 10" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true"><path d="M1 5h11.5M8.5 1 12.5 5l-4 4"/></svg></button>
          <p class="s-klein">Ich antworte selbst, meist am selben Werktag. Ihre Angaben nutze ich nur für diese Anfrage (<a href="#">Datenschutz</a>).</p>
        </div>
      </form>
      <div class="s-danke" role="status" data-s-danke>
        <h2 tabindex="-1" data-s-danke-titel></h2>
        <ol data-s-schritte></ol>
        <button type="button" class="s-pille" data-s-neu>Neue Anfrage</button>
        <p data-s-kopie></p>
      </div>
      <dl class="s-direkt">
        <dt>Lieber direkt</dt><dd><a href="mailto:post@thea-liebetrau.de" data-s-mail>post@thea-liebetrau.de</a><button type="button" class="s-kopier" data-s-kopieren>Kopieren</button></dd>
        <dt>Telefon, Di bis Do</dt><dd><a href="tel:+493612604417">0361 260 44 17</a></dd>
      </dl>
      <p class="unsichtbar" role="status" data-s-kopiert></p>
    </main>
    <footer class="s-fuss"><span>© 2026 Thea Liebetrau, Erfurt</span><nav aria-label="Rechtliches"><a href="#">Impressum</a><a href="#">Datenschutz</a></nav></footer>
  </div>
</div>

<svg class="zeiger" data-zeiger viewBox="0 0 20 20" aria-hidden="true" overflow="visible"><circle cx="3" cy="2" r="3" fill="#02385c" opacity="0"/><path d="M2 1.5 16.5 9.6l-6.4 1.4-3.1 6Z" fill="#02385c" stroke="#fcfaf5" stroke-width="1.3" stroke-linejoin="round"/></svg>

<script>
(() => {
  const html = document.documentElement;
  const reduziert = () => html.classList.contains('reduziert');
  const $ = (s, r = document) => r.querySelector(s);
  const $$ = (s, r = document) => [...r.querySelectorAll(s)];
  const form = $('[data-form]'), flaeche = $('[data-flaeche]'), kopf = $('[data-kopf]');
  const knopf = $('[data-absenden]'), zeiger = $('[data-zeiger]');
  const T = { ui: 400, aus: 200, senden: 900 };   // ms; Kurve cubic-bezier(0.165, 0.84, 0.44, 1)

  /* ---------- Varianten ---------- */
  const VARIANTEN = {
    bewertung: {
      augenbraue: 'Für Eigentümerinnen und Eigentümer',
      titel: 'Was ist Ihre Liegenschaft heute wert?',
      vorspann: 'Wir bewerten Wohnungen, Häuser und Mehrfamilienhäuser in Basel-Stadt und im Baselbiet. Persönlich vor Ort, kostenlos und streng vertraulich.',
      formtitel: 'Bewertung anfragen', formmeta: 'Unverbindlich · etwa 2 Minuten', knopf: 'Bewertung anfragen',
      antwort: 'Lea Brändli meldet sich innert 24 Stunden bei Ihnen, werktags meist am selben Tag.',
      schritte: [['Erste Einschätzung', 'Wir prüfen Lage, Objektart und Ihre Fotos.', 'heute'], ['Termin vor Ort', 'Rundgang von etwa einer Stunde, ohne Verpflichtung.', 'nach Absprache'], ['Schriftliche Bewertung', 'Marktwert mit Vergleichsobjekten aus Ihrem Quartier.', 'innert 10 Tagen']],
    },
    rueckruf: {
      augenbraue: 'Erstgespräch',
      titel: 'Lieber zuerst ein Gespräch?',
      vorspann: 'Sagen Sie uns, wann es Ihnen passt. Wir rufen im gewählten Zeitfenster an, ungefähr 15 Minuten, ohne Verkaufsdruck.',
      formtitel: 'Rückruf vereinbaren', formmeta: 'Unverbindlich · 4 Angaben', knopf: 'Rückruf vereinbaren',
      antwort: 'Wir rufen Sie im gewählten Zeitfenster an. Vorher erhalten Sie eine kurze SMS.',
      schritte: [['Bestätigung per SMS', 'Mit Name und Durchwahl Ihrer Ansprechperson.', 'sofort'], ['Rückruf', 'Etwa 15 Minuten zu Ihrem Anliegen.', 'im Zeitfenster'], ['Nächster Schritt', 'Nur wenn Sie möchten: Termin oder Unterlagen.', 'danach']],
    },
    kurz: {
      augenbraue: 'Kontakt',
      titel: 'Schreiben Sie uns.',
      vorspann: 'Eine Frage zu einem Objekt, zur Verwaltung oder zu Ihrem Haus? Drei Felder genügen. Wir antworten persönlich, nicht automatisch.',
      formtitel: 'Nachricht senden', formmeta: '3 Felder', knopf: 'Nachricht senden',
      antwort: 'Wir antworten innert 24 Stunden, werktags meist am selben Tag.',
      schritte: [['Eingang', 'Ihre Nachricht liegt bei der zuständigen Person.', 'jetzt'], ['Antwort', 'Persönlich per E-Mail oder Telefon.', 'innert 24 Std.']],
    },
  };
  let variante = 'bewertung';

  function felderSetzen(v) {
    $$('[data-nur]').forEach(el => {
      const an = el.dataset.nur.split(' ').includes(v);
      el.hidden = !an;
      $$('input, select, textarea', el).forEach(i => { if (i.name !== 'website') i.disabled = !an; });
    });
    $$('[data-pflicht]').forEach(el => { $('input', el).required = el.dataset.pflicht.split(' ').includes(v); });
    const d = VARIANTEN[v];
    $$('[data-text]').forEach(el => { if (typeof d[el.dataset.text] === 'string') el.textContent = d[el.dataset.text]; });
    knoepfe();
    werteZeigen();
  }
  function varianteWaehlen(v, sofort) {
    if (v === variante && !sofort) return Promise.resolve();
    variante = v;
    zuruecksetzen();
    const teile = [$('.kontakt'), form, kopf];
    if (sofort || reduziert()) { felderSetzen(v); return Promise.resolve(); }
    teile.forEach(t => t.classList.add('wechsel'));
    return new Promise(r => setTimeout(() => {
      felderSetzen(v);
      teile.forEach(t => t.classList.remove('wechsel'));
      setTimeout(r, T.ui);
    }, T.aus));
  }

  function werteZeigen() {
    if (welt === 'satz') {
      $('[data-werte]').innerHTML = `<span>Felder <b>5</b> · Pflicht <b>2</b></span><span>field-sizing <b>content</b></span><span>min <b>3ch</b> · max <b>100 %</b></span><span>UI <b>150 ms</b></span><span>cubic-bezier(.2,0,.38,.9)</span>`;
      return;
    }
    const felder = $$('[data-nur]:not([hidden])', form).filter(el => el.matches('.feld')).length;
    const pflicht = new Set($$('input, select, textarea', form).filter(i => !i.disabled && i.required).map(i => i.name)).size;
    $('[data-werte]').innerHTML =
      `<span>Felder <b>${felder}</b> · Pflicht <b>${pflicht}</b></span>` +
      `<span>Fehler <b>nach blur</b></span>` +
      `<span>UI <b>400 ms</b></span><span>cubic-bezier(.165,.84,.44,1)</span>` +
      `<span>Farbe <b>200 ms</b></span><span>Ziel <b>${matchMedia('(hover: none)').matches ? 52 : 48} px</b></span>`;
  }

  /* ---------- Prüfen: erst nach dem Verlassen, danach live ---------- */
  const TEXTE = {
    objekt: { valueMissing: 'Bitte wählen Sie die Art der Liegenschaft.' },
    fenster: { valueMissing: 'Bitte wählen Sie ein Zeitfenster.' },
    plz: { valueMissing: 'Bitte die Postleitzahl angeben.', patternMismatch: 'Die PLZ hat vier Ziffern, z. B. 4052.' },
    name: { valueMissing: 'Wie dürfen wir Sie ansprechen?', tooShort: 'Bitte den vollständigen Namen angeben.' },
    email: { valueMissing: 'Bitte eine E-Mail-Adresse angeben.', typeMismatch: 'Die Adresse scheint unvollständig, z. B. name@beispiel.ch.' },
    tel: { valueMissing: 'Für den Rückruf brauchen wir Ihre Nummer.', patternMismatch: 'Bitte eine vollständige Nummer, z. B. 061 271 48 90.' },
    nachricht: { valueMissing: 'Worum geht es? Ein Satz genügt.', tooShort: 'Ein paar Worte mehr helfen uns, z. B. die Adresse des Objekts.' },
  };
  const feldVon = i => i.closest('.feld');
  function fehlerArt(i) {
    if (i.type === 'radio') return form.querySelector(`[name="${i.name}"]:checked`) ? '' : (i.required ? 'valueMissing' : '');
    const v = i.validity;
    if (v.valueMissing) return 'valueMissing';
    if (v.typeMismatch) return 'typeMismatch';
    if (v.patternMismatch) return 'patternMismatch';
    if (i.minLength > 0 && i.value.trim().length && i.value.trim().length < i.minLength) return 'tooShort';
    return '';
  }
  function pruefen(i, zeigen) {
    const f = feldVon(i); if (!f || i.disabled) return true;
    const art = fehlerArt(i);
    if (zeigen) f.dataset.beruehrt = '1';
    if (!f.dataset.beruehrt) return !art;
    const span = $('.meldung > span', f);
    if (art) {
      if (span) span.textContent = TEXTE[i.name][art];
      f.classList.add('fehler'); f.classList.remove('gut');
      i.setAttribute('aria-invalid', 'true');
    } else {
      f.classList.remove('fehler');
      f.classList.toggle('gut', i.type !== 'radio' && i.value.trim() !== '');
      i.removeAttribute('aria-invalid');
    }
    return !art;
  }
  // blur: Fehler erst zeigen, wenn das Feld verlassen wurde (und etwas drinsteht oder es Pflicht ist)
  form.addEventListener('focusout', e => {
    const i = e.target; if (!i.matches('input:not([type=radio]):not([type=file]):not([type=checkbox]), textarea')) return;
    if (i.value.trim() === '' && !feldVon(i).dataset.beruehrt) return;   // leer verlassen: noch nicht schimpfen
    pruefen(i, true);
  });
  // input: ist ein Fehler einmal sichtbar, verschwindet er, sobald es stimmt
  form.addEventListener('input', e => { if (e.target.type === 'radio') pruefen(e.target, true); else pruefen(e.target, false); });
  form.addEventListener('change', e => { if (e.target.type === 'radio') pruefen(e.target, true); });

  function allesPruefen() {
    let erstes = null;
    const gesehen = new Set();
    $$('input, select, textarea', form).forEach(i => {
      if (i.disabled || i.type === 'hidden' || i.name === 'website' || gesehen.has(i.name)) return;
      gesehen.add(i.name);
      if (!pruefen(i, true) && !erstes) erstes = i;
    });
    return erstes;
  }

  /* ---------- Fotos ---------- */
  const datei = $('[data-datei]'), liste = $('[data-dateien]');
  function dateienZeigen() {
    liste.innerHTML = '';
    [...datei.files].forEach(f => {
      const li = document.createElement('li');
      li.textContent = `${f.name} · ${(f.size / 1048576).toFixed(1).replace('.', ',')} MB`;
      liste.append(li);
    });
  }
  datei.addEventListener('change', dateienZeigen);

  /* ---------- Absenden ---------- */
  let sendeUhr = null;
  function senden(echt) {
    const fehler = allesPruefen();
    if (fehler) { if (echt) fehler.focus(); return false; }
    knopf.setAttribute('aria-busy', 'true');
    clearTimeout(sendeUhr);
    sendeUhr = setTimeout(() => {
      knopf.removeAttribute('aria-busy');
      const d = VARIANTEN[variante];
      const name = form.elements.name.value.trim();
      const vorname = name.split(/\s+/)[0] || '';
      $('[data-danke]').textContent = vorname ? `Vielen Dank, ${name}.` : 'Vielen Dank.';
      $('[data-antwort]').textContent = d.antwort;
      $('[data-schritte]').innerHTML = d.schritte.map(([t, s, w]) => `<li><p><b>${t}</b><span>${s}</span></p><em>${w}</em></li>`).join('');
      const mail = form.elements.email && !form.elements.email.disabled ? form.elements.email.value : '';
      const tel = form.elements.tel && !form.elements.tel.disabled ? form.elements.tel.value : '';
      $('[data-kopie]').textContent = mail ? `Eine Kopie Ihrer Anfrage ging an ${mail}.` : (tel ? `Rückrufnummer: ${tel}` : '');
      flaeche.classList.add('gesendet');
      if (echt) $('[data-danke]').focus({ preventScroll: false });
    }, T.senden);
    return true;
  }
  form.addEventListener('submit', e => { e.preventDefault(); senden(true); });

  function zuruecksetzen() {
    clearTimeout(sendeUhr);
    form.reset(); liste.innerHTML = '';
    knopf.removeAttribute('aria-busy');
    flaeche.classList.remove('gesendet');
    $$('.feld', form).forEach(f => { f.classList.remove('fehler', 'gut', 'fokus'); delete f.dataset.beruehrt; });
    $$('[aria-invalid]', form).forEach(i => i.removeAttribute('aria-invalid'));
    $$('.fokus').forEach(el => el.classList.remove('fokus'));
    $('[data-t0]').value = Date.now();
  }
  $('[data-neu]').addEventListener('click', () => { zuruecksetzen(); form.elements.name.focus(); });

  /* ---------- E-Mail kopieren ---------- */
  const kopierKnopf = $('[data-kopieren]'); let kopierUhr = null;
  function kopiert() {
    kopierKnopf.textContent = 'Kopiert'; kopierKnopf.classList.add('ok');
    $('[data-kopiert-status]').textContent = 'E-Mail-Adresse kopiert';
    clearTimeout(kopierUhr);
    kopierUhr = setTimeout(() => { kopierKnopf.textContent = 'Kopieren'; kopierKnopf.classList.remove('ok'); $('[data-kopiert-status]').textContent = ''; }, 1600);
  }
  kopierKnopf.addEventListener('click', () => {
    const t = $('[data-mail]').textContent;
    try { navigator.clipboard.writeText(t).then(kopiert, kopiert); } catch (e) { kopiert(); }
  });

  $$('.varianten button[data-variante]').forEach(b => b.addEventListener('click', () => { pause(); html.classList.remove('vorfuehrung'); const war = welt; weltSetzen('basis'); varianteWaehlen(b.dataset.variante, war !== 'basis'); }));
  addEventListener('resize', werteZeigen);

  /* ---------- Vorführung: ein gedachter Besucher füllt aus ---------- */
  let lauf = 0;                          // Laufnummer; pause() macht laufende Skripte ungültig
  const warte = (ms, n) => new Promise((ok, nein) => setTimeout(() => n === lauf ? ok() : nein('stop'), ms));
  let fokusEl = null;
  function zeigerAuf(el, n, dx = 0.3, dy = 0.55) {
    const r = el.getBoundingClientRect();
    zeiger.style.transform = `translate(${r.left + r.width * dx}px, ${r.top + Math.min(r.height * dy, 30)}px)`;
    return warte(reduziert() ? 120 : 600, n);
  }
  function tippen() { zeiger.classList.remove('klick'); void zeiger.offsetWidth; zeiger.classList.add('klick'); }
  function fokus(i) {
    if (fokusEl && fokusEl !== i) {      // verlassen = blur
      feldVon(fokusEl)?.classList.remove('fokus');
      if (fokusEl.value.trim() !== '' || feldVon(fokusEl)?.dataset.beruehrt) pruefen(fokusEl, true);
    }
    fokusEl = i;
    if (i) feldVon(i)?.classList.add('fokus');
  }
  async function schreiben(i, text, n) {
    for (const z of text) { i.value += z; pruefen(i, false); await warte(reduziert() ? 10 : 62, n); }
  }
  const SKRIPT = {
    async bewertung(n) {
      await zeigerAuf($('[name=objekt][value=wohnung]').parentNode, n); tippen();
      $('[name=objekt][value=wohnung]').checked = true; pruefen($('[name=objekt]'), true);
      await warte(150, n);
      await zeigerAuf($('#plz'), n); tippen(); fokus($('#plz'));
      await schreiben($('#plz'), '405', n); await warte(150, n);
      await zeigerAuf($('#name'), n); tippen(); fokus($('#name'));      // PLZ verlassen: Fehler erscheint
      await warte(900, n);
      await schreiben($('#name'), 'Andrea Keller', n); await warte(400, n);
      await zeigerAuf($('#plz'), n); tippen(); fokus($('#plz'));
      await warte(300, n); await schreiben($('#plz'), '2', n);            // korrigiert: Fehler geht sofort
      await warte(500, n);
      await zeigerAuf($('#mail'), n); tippen(); fokus($('#mail'));
      await schreiben($('#mail'), 'andrea.keller@mail.ch', n);
      await zeigerAuf($('#zeitraum'), n); tippen(); fokus(null); $('#zeitraum').value = '3-6';
      await warte(400, n);
      await zeigerAuf($('[data-ablage]'), n, 0.2); tippen();
      try { const dt = new DataTransfer(); dt.items.add(new File([new Uint8Array(2516582)], 'wohnzimmer.jpg', { type: 'image/jpeg' })); dt.items.add(new File([new Uint8Array(1887437)], 'kueche.jpg', { type: 'image/jpeg' })); datei.files = dt.files; } catch (e) {}
      dateienZeigen(); await warte(600, n);
      await zeigerAuf(knopf, n, 0.5); tippen(); knopf.classList.add('druck'); await warte(150, n); knopf.classList.remove('druck');
      senden(false); await warte(T.senden + 3600, n);
      await zeigerAuf($('[data-neu]'), n, 0.5); tippen(); await warte(300, n);
    },
    async rueckruf(n) {
      await zeigerAuf($('#name'), n); tippen(); fokus($('#name'));
      await schreiben($('#name'), 'Marc Sutter', n);
      await zeigerAuf($('[name=fenster][value=mittag]').parentNode, n); tippen(); fokus(null);
      $('[name=fenster][value=mittag]').checked = true; pruefen($('[name=fenster]'), true);
      await warte(400, n);
      await zeigerAuf(knopf, n, 0.5); tippen(); senden(false);           // Telefon fehlt: Meldung am Feld
      await warte(1300, n);
      await zeigerAuf($('#tel'), n); tippen(); fokus($('#tel'));
      await schreiben($('#tel'), '079 418 62 05', n); await warte(500, n);
      await zeigerAuf(knopf, n, 0.5); tippen(); fokus(null); senden(false);
      await warte(T.senden + 3400, n);
      await zeigerAuf($('[data-neu]'), n, 0.5); tippen(); await warte(300, n);
    },
    async kurz(n) {
      await zeigerAuf($('#name'), n); tippen(); fokus($('#name'));
      await schreiben($('#name'), 'Nora Gysin', n);
      await zeigerAuf($('#mail'), n); tippen(); fokus($('#mail'));
      await schreiben($('#mail'), 'nora.gysin@', n); await warte(200, n);
      await zeigerAuf($('#nachricht'), n); tippen(); fokus($('#nachricht'));   // Mail unvollständig verlassen
      await warte(900, n);
      await zeigerAuf($('#mail'), n, 0.6); tippen(); fokus($('#mail'));
      await schreiben($('#mail'), 'mail.ch', n); await warte(300, n);
      await zeigerAuf($('#nachricht'), n); tippen(); fokus($('#nachricht'));
      await schreiben($('#nachricht'), 'Ist die 4½-Zimmer-Wohnung an der Feldbergstrasse noch frei?', n);
      await zeigerAuf(knopf, n, 0.5); tippen(); fokus(null); senden(false);
      await warte(T.senden + 3400, n);
      await zeigerAuf($('[data-neu]'), n, 0.5); tippen(); await warte(300, n);
    },
  };

  /* ---------- Variante „satz“: Satz-Formular (eigene Welt) ---------- */
  const sForm = $('[data-satz-form]'), sHaupt = $('[data-s-haupt]'), sKnopf = $('[data-s-senden]');
  const S_TEXTE = {
    name: { valueMissing: 'Bitte tragen Sie Ihren Namen ein.', tooShort: 'Bitte den vollständigen Namen.' },
    email: { valueMissing: 'Wohin darf ich antworten? Bitte eine E-Mail-Adresse.', typeMismatch: 'Die E-Mail-Adresse scheint unvollständig, etwa name@firma.de.' },
  };
  const S_MELDUNG = { name: $('#s-name-f'), email: $('#s-mail-f') };
  const sBeruehrt = new Set();
  function sPruefen(i, zeigen) {
    if (!S_TEXTE[i.name]) return true;
    const art = fehlerArt(i);
    if (zeigen) sBeruehrt.add(i.name);
    if (!sBeruehrt.has(i.name)) return !art;
    const li = S_MELDUNG[i.name];
    if (art) { li.textContent = S_TEXTE[i.name][art]; li.hidden = false; i.setAttribute('aria-invalid', 'true'); }
    else { li.textContent = ''; li.hidden = true; i.removeAttribute('aria-invalid'); }
    return !art;
  }
  sForm.addEventListener('focusout', e => {
    const i = e.target; if (!S_TEXTE[i.name]) return;
    if (i.value.trim() === '' && !sBeruehrt.has(i.name)) return;   // leer verlassen: noch nicht schimpfen
    sPruefen(i, true);
  });
  sForm.addEventListener('input', e => sPruefen(e.target, false));
  let sUhr = null;
  const S_SCHRITTE = {
    'eine neue Website': 'Website', 'ein Redesign': 'Redesign', 'ein Designsystem': 'Designsystem',
    'eine App-Oberfläche': 'App-Oberfläche', 'ein paar Tage Beratung': 'Beratung', 'etwas anderes': 'Anfrage',
  };
  const S_DATIV = {
    'eine neue Website': 'einer neuen Website', 'ein Redesign': 'einem Redesign', 'ein Designsystem': 'einem Designsystem',
    'eine App-Oberfläche': 'einer App-Oberfläche', 'ein paar Tage Beratung': 'ein paar Tagen Beratung', 'etwas anderes': 'Ihrem Vorhaben',
  };
  function naechsterWerktag() {
    const d = new Date(); do d.setDate(d.getDate() + 1); while (d.getDay() === 0 || d.getDay() === 6);
    return new Intl.DateTimeFormat('de-DE', { weekday: 'short', day: 'numeric', month: 'numeric' }).format(d).replace('.,', ',');
  }
  function sSenden(echt) {
    let erstes = null;
    ['name', 'email'].forEach(n => { const i = sForm.elements[n]; if (!sPruefen(i, true) && !erstes) erstes = i; });
    if (erstes) { if (echt) erstes.focus(); return false; }
    sKnopf.setAttribute('aria-busy', 'true');
    clearTimeout(sUhr);
    sUhr = setTimeout(() => {
      sKnopf.removeAttribute('aria-busy');
      const f = sForm.elements, vor = f.name.value.trim().split(/\s+/)[0];
      const t = $('[data-s-danke-titel]');
      t.innerHTML = '';
      const em = document.createElement('em'); em.textContent = `Danke, ${vor}.`;
      t.append(em, ` Ihre Anfrage zu ${S_DATIV[f.art.value]} ist angekommen. Ich antworte bis morgen und schlage zwei Termine für ein erstes Gespräch vor.`);
      $('[data-s-schritte]').innerHTML = [
        ['Antwort von Thea, persönlich', 'bis ' + naechsterWerktag()],
        ['Gespräch, 30 Minuten per Video', 'diese Woche'],
        [`Angebot für ${S_SCHRITTE[f.art.value]}, Start ${f.start.value.replace(/^im |^noch im /, '')}`, 'nach dem Gespräch'],
      ].map(([a, b]) => `<li>${a}<span>${b}</span></li>`).join('');
      $('[data-s-kopie]').textContent = `Eine Kopie ging an ${f.email.value}.`;
      sHaupt.classList.add('gesendet');
      if (echt) t.focus();
    }, 700);
    return true;
  }
  sForm.addEventListener('submit', e => { e.preventDefault(); sSenden(true); });
  function sZuruecksetzen() {
    clearTimeout(sUhr); sForm.reset(); sBeruehrt.clear();
    sKnopf.removeAttribute('aria-busy'); sKnopf.classList.remove('druck');
    sHaupt.classList.remove('gesendet');
    Object.values(S_MELDUNG).forEach(li => { li.hidden = true; li.textContent = ''; });
    $$('[aria-invalid]', sForm).forEach(i => i.removeAttribute('aria-invalid'));
    $$('.fokus', sForm).forEach(i => i.classList.remove('fokus'));
  }
  $('[data-s-neu]').addEventListener('click', () => { sZuruecksetzen(); $('#s-name').focus(); });
  const sKopier = $('[data-s-kopieren]'); let sKopierUhr = null;
  function sKopiert() {
    sKopier.textContent = 'Kopiert'; sKopier.classList.add('ok'); $('[data-s-kopiert]').textContent = 'E-Mail-Adresse kopiert';
    clearTimeout(sKopierUhr);
    sKopierUhr = setTimeout(() => { sKopier.textContent = 'Kopieren'; sKopier.classList.remove('ok'); $('[data-s-kopiert]').textContent = ''; }, 1600);
  }
  sKopier.addEventListener('click', () => { try { navigator.clipboard.writeText($('[data-s-mail]').textContent).then(sKopiert, sKopiert); } catch (e) { sKopiert(); } });

  /* Welt wählen: ?v=basis (Rhyhalde, drei Formulare) oder ?v=satz */
  const WELTEN = ['basis', 'satz'];
  let welt = WELTEN.includes(new URLSearchParams(location.search).get('v')) ? new URLSearchParams(location.search).get('v') : 'basis';
  function knoepfe() {
    $$('.varianten button').forEach(b => b.setAttribute('aria-pressed', welt === 'satz' ? b.dataset.welt === 'satz' : b.dataset.variante === variante));
  }
  function weltSetzen(w, start) {
    welt = w;
    document.body.classList.toggle('ist-satz', w === 'satz');
    if (!start) { const q = new URLSearchParams(location.search); q.set('v', w); history.replaceState(null, '', location.pathname + '?' + q.toString() + location.hash); }
    knoepfe(); werteZeigen();
  }
  $('[data-welt="satz"]').addEventListener('click', () => { pause(); html.classList.remove('vorfuehrung'); sZuruecksetzen(); weltSetzen('satz'); });

  /* Vorführung Satz: die Lücken wachsen beim Tippen, die Auswahl schrumpft auf das Gewählte */
  function sFokus(i) {
    if (fokusEl && fokusEl !== i) { fokusEl.classList.remove('fokus'); if (fokusEl.value.trim() !== '' || sBeruehrt.has(fokusEl.name)) sPruefen(fokusEl, true); }
    fokusEl = i; if (i) i.classList.add('fokus');
  }
  async function sSchreiben(i, text, n) { for (const z of text) { i.value += z; sPruefen(i, false); await warte(reduziert() ? 10 : 70, n); } }
  async function sWaehlen(s, wert, n) { await zeigerAuf(s, n, 0.5); tippen(); sFokus(s); await warte(260, n); s.value = wert; await warte(500, n); }
  async function satzSkript(n) {
    const name = $('#s-name'), firma = $('#s-firma'), mail = $('#s-mail');
    await zeigerAuf(name, n, 0.4); tippen(); sFokus(name);
    await sSchreiben(name, 'Jana Pfisterer', n); await warte(250, n);
    await zeigerAuf(firma, n, 0.4); tippen(); sFokus(firma);
    await sSchreiben(firma, 'Buchbinderei Arndt', n); await warte(250, n);
    await sWaehlen($('#s-art'), 'ein Redesign', n);
    await sWaehlen($('#s-start'), 'im Februar', n);
    await zeigerAuf(mail, n, 0.4); tippen(); sFokus(mail);
    await sSchreiben(mail, 'jana@', n); await warte(200, n);
    await zeigerAuf(sKnopf, n, 0.5); tippen(); sFokus(null);           // unvollständig verlassen: Meldung
    sKnopf.classList.add('druck'); await warte(150, n); sKnopf.classList.remove('druck'); sSenden(false);
    await warte(1300, n);
    await zeigerAuf(mail, n, 0.7); tippen(); sFokus(mail);
    await sSchreiben(mail, 'buchbinderei-arndt.de', n); await warte(400, n);
    await zeigerAuf(sKnopf, n, 0.5); tippen(); sFokus(null);
    sKnopf.classList.add('druck'); await warte(150, n); sKnopf.classList.remove('druck'); sSenden(false);
    await warte(700 + 3800, n);
    await zeigerAuf($('[data-s-neu]'), n, 0.5); tippen(); await warte(300, n);
    sZuruecksetzen(); await warte(400, n);
  }

  const FOLGE = ['bewertung', 'rueckruf', 'kurz'];
  async function vorfuehren() {
    pause();
    const n = lauf;
    html.classList.add('vorfuehrung');
    zeiger.style.transition = 'none';
    zeiger.style.transform = `translate(${innerWidth * 0.62}px, ${innerHeight * 0.9}px)`;
    void zeiger.offsetWidth; zeiger.style.transition = '';
    let k = FOLGE.indexOf(variante);
    try {
      if (welt === 'satz') { sZuruecksetzen(); fokusEl = null; for (;;) { await warte(300, n); await satzSkript(n); fokusEl = null; } }
      for (;;) {
        await varianteWaehlen(FOLGE[k], true); fokusEl = null;
        await warte(150, n);
        await SKRIPT[FOLGE[k]](n);
        k = (k + 1) % FOLGE.length;
        if (!reduziert()) await warte(T.aus, n);
      }
    } catch (e) { if (e !== 'stop') throw e; }
  }
  function pause() { lauf++; }
  function weiter() { if (html.classList.contains('vorfuehrung')) vorfuehren(); }
  function neustart() { pause(); html.classList.remove('vorfuehrung'); fokusEl = null; if (welt === 'satz') sZuruecksetzen(); else varianteWaehlen(variante, true); }

  felderSetzen(variante); zuruecksetzen(); sZuruecksetzen(); weltSetzen(welt, true);

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