Zum Inhalt
← Sektionen & Funktionen

Wunschtermin mit freien Terminen und Anfrage-Übergabe

Wunschdatum, Anlass und Gäste in einer Leiste, darunter ehrlich frei, angefragt oder vergeben als Monatstabelle mit Stand-Datum; ein Klick auf einen freien Tag übergibt alles an die Anfrage.

Startseite jeder Location oder jedes Festsaals: Datum, Anlass und Gäste direkt unter dem Foto, Antwort sofort darunter, Anfrage klappt unter der Leiste auf.

Für Locations, Caterer und Festsäle ist das Datum die erste Frage: Ist unser Samstag noch frei? Eine Leiste fragt Wunschdatum (oder Monat mit „flexibel“), Anlass und Gäste ab und antwortet sofort darunter. Eine Monatsansicht als echte Tabelle zeigt, welche Tage frei, angefragt (Option mit Haltefrist) oder vergeben sind, als Text und Zeichen, mit Stand-Datum. Ein Klick auf einen freien Tag übernimmt das Datum in die Leiste und öffnet die Anfrage mit allen Angaben. Ein Tag, kein Zeitraum.

Paare und Firmen klären zuerst das Datum, alles andere später. Wer sofort sieht, dass der Wunschsamstag vergeben ist, schreibt keine vergebliche Anfrage und findet den nächsten freien Tag selbst. Der Status „angefragt“ erklärt die Option offen und lädt zum Vormerken ein, statt Knappheit vorzutäuschen. Die Anfrage kommt mit Datum, Anlass und Gästezahl an; das spart dem Haus die erste Rückfrage.

Wann einsetzen

Hochzeitslocation, Gutshof, Festsaal, Tagungshaus, Eventcatering, Bootsvermietung für Feiern, Fotostudio zur Tagesmiete: überall, wo ein ganzer Tag vergeben wird und das Haus seinen Kalender selbst pflegt.

Wann nicht

Wenn niemand den Kalender pflegt: Ein veralteter Kalender ist schlimmer als keiner, dann nur die Leiste mit Ausweichdatum. Für Zimmer mit An- und Abreise gilt buchungsleiste-datumsbereich, für Termine im Stundentakt termin-buchung-zwei-klick. Nicht für verbindliche Online-Buchung mit Anzahlung: Hier wird angefragt, nicht gebucht.

Varianten

  • Leiste unter dem Hero: Datum oder Monat, Anlass, Gäste, Antwort sofort darunter, mobil untereinander
  • Monatskalender mit frei, angefragt und vergeben, Erklärung der Option und Übergabe an die Anfrage
  • Catering: mehrere Feiern am Tag, Status „noch Kapazität“ mit Gästezahl statt frei
  • Ohne gepflegten Kalender: nur Leiste mit Ausweichdatum

Umsetzung

Ein form mit Datum als Texteingabe (TT.MM.JJJJ, auch „12.6.27“), Schalter „Datum flexibel“ mit Monatsauswahl, Anlass als select, Erwachsene und Kinder als Zahlenfelder. Die Belegung kommt aus einer JSON-Datei, die das Haus pflegt ({stand, tage: {"2027-06-19": {status: "angefragt", bis: "2026-11-09"}}}); ist der Stand älter als 21 Tage, fehlt der Kalender und die Leiste fragt ein Ausweichdatum ab. Die Monatsansicht ist eine table role=grid mit Spaltenköpfen (Wochentage) und Zeilenköpfen (Kalenderwoche); in jeder Zelle ein Knopf mit vollem Namen („Samstag, 19. Juni 2027, angefragt, Option bis 9. November“) und Roving tabindex. Freie Tage übernehmen das Datum und öffnen die Anfrage; angefragte erklären die Option und bieten „Als Nächste vormerken“; vergebene sind aria-disabled, nennen beim Aktivieren den Grund und schlagen die nächsten freien Tage vor (gleicher Wochentag zuerst). Feiertage rechnet ein kleines Oster-Rechenstück je Bundesland aus. Die Anfrage übernimmt alle Angaben als Zusammenfassung mit „Angaben ändern“, verlangt bei vergebenem Tag ein Ausweichdatum und sagt ehrlich, dass der Tag damit noch nicht reserviert ist. Catering-Variante: je Tag eine Restkapazität in Gästen; die eingegebene Gästezahl rechnet den Kalender live um.

Robust umsetzen

Telefon. Die Leiste stapelt sich: Datum, Anlass, Gäste, Knopf in voller Breite. Im Kalender zeigen die Zellen Ziffer und Zeichen, der Status steht im Namen und in der Legende; jede Zelle mindestens 50 px hoch. Die Anfrage folgt unter dem Kalender, der Fokus springt auf ihre Überschrift.

Ohne Bewegung. Monatswechsel und Anfrage-Tafel ohne Weg, nur kurzes Einblenden in 200 ms; der vorgeführte Zeiger springt.

Barrierefrei. Kalender als table role=grid mit Spalten- und Zeilenköpfen, Roving tabindex, Pfeiltasten, Bild auf/ab für Monate, Pos1/Ende für die Woche; Monatstitel als aria-live. Status nie nur über Farbe: jedes Zeichen hat eine eigene Form (Punkt, Halbkreis, Kreuz mit Schraffur), der Status steht im Namen. Vergebene Tage sind aria-disabled, nicht versteckt. Gewählter Tag mit aria-selected, heute mit aria-current="date". Datum immer auch als Texteingabe mit Format im Label. Antworten der Leiste in einer Statusregion; Fehler im Formular stehen am Feld (aria-invalid, aria-describedby), der Fokus springt aufs erste. Nach der Übergabe bekommt die Überschrift der Anfrage den Fokus. Datenschutz-Hinweis als markierter Platzhalter, vom Kunden prüfen lassen.

Code

<!DOCTYPE html>
<!--
  Wunschtermin mit freien Terminen und Anfrage-Übergabe
  Fundus · Sektionen & Funktionen · https://cecodesign.de/lab/fundus/sektionen/termin-verfuegbarkeit-anfrage/
  Eigenständige Datei. Bilder liegen hier unter bilder/ – eigene einsetzen.
-->
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Wunschtermin mit freien Terminen und Anfrage-Übergabe · 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: 'Imbue'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/imbue:vf@latest/latin-opsz-normal.woff') format('woff'); font-weight: 100 900; font-display: swap; }
@font-face { font-family: 'Archivo'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/archivo:vf@latest/latin-full-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-stretch: 62% 125%; font-display: swap; }

:root {
  /* Rezept eventlocation-hochzeit: Kalk für den Tag, Sandstein, Nachtblau, Mohnrot als einziges Signal, Messing nur als Linie */
  --kalk: #F5F0E6; --kalk-hell: #FBF8F2; --sand: #E9DCC8; --sand-hell: #F0E6D6; --nacht: #15171F; --nacht-text: #F2ECE2;
  --tinte: #1D1A17; --zweit: #5C574E; --linie: #D8CFC0; --feldrand: #857C70;
  --mohn: #C43B2B; --mohn-tief: #A3301F; --messing: #8C6A3C; --messing-hell: #C9A66B;
  /* token-studio-signatur: eine Kurve für alles, fünf Dauern */
  --kurve: cubic-bezier(0.625, 0.05, 0, 1);
  --t1: 250ms; --t2: 400ms; --t3: 475ms; --t4: 525ms; --t5: 725ms;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { background: var(--kalk); color: var(--tinte); font-family: 'Archivo', system-ui, sans-serif; font-size: 100%; -webkit-font-smoothing: antialiased; }
:root:has(:focus-visible) { scroll-behavior: auto; }
body { min-height: 100svh; font-size: 1.0625rem; line-height: 1.6; }
button, input, select, textarea { font: inherit; color: inherit; }
a { color: inherit; }
:focus-visible { outline: 2px solid var(--tinte); outline-offset: 3px; }
.nur-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.rubrik { font-stretch: 112%; font-weight: 600; font-size: .8125rem; line-height: 1.3; letter-spacing: .08em; text-transform: uppercase; }
.ziffern { font-variant-numeric: tabular-nums; }
.seite { max-width: 1240px; margin: 0 auto; padding: 0 clamp(16px, 4vw, 48px); }
[hidden] { display: none !important; }

/* Kopf */
.kopf { border-bottom: 1px solid var(--linie); }
.kopf .seite { display: flex; align-items: center; justify-content: space-between; gap: 12px 28px; min-height: 72px; flex-wrap: wrap; }
.marke { display: flex; align-items: baseline; gap: 12px; text-decoration: none; }
.marke b { font: 400 2rem/1 'Imbue', serif; letter-spacing: -.01em; }
.marke span { font-size: .8125rem; color: var(--zweit); font-stretch: 112%; letter-spacing: .08em; text-transform: uppercase; font-weight: 600; }
.kopf nav { display: flex; gap: 26px; font-size: .9375rem; }
.kopf nav a { text-decoration: none; padding: 10px 0; background: linear-gradient(var(--tinte), var(--tinte)) left bottom / 0 1px no-repeat; transition: background-size var(--t2) var(--kurve); }
.kopf nav a:hover, .kopf nav a[aria-current] { background-size: 100% 1px; }
.kopf .tel { font-variant-numeric: tabular-nums; font-weight: 600; text-decoration: none; padding: 10px 0; }

/* Knöpfe: Mohnrot mit Füllung vom Eintrittspunkt (Haupthandlung), Messing-Doppelkontur (Nebenweg) */
.knopf { position: relative; isolation: isolate; display: inline-flex; align-items: center; justify-content: center; gap: 10px; min-height: 48px; padding: 0 24px; border: 0; border-radius: 2px; font-weight: 600; font-size: 1rem; line-height: 1.2; text-decoration: none; cursor: pointer; white-space: nowrap; }
.knopf-haupt { background: var(--mohn); color: #fff; overflow: hidden; }
.knopf-haupt::before { content: ''; position: absolute; inset: 0; z-index: -1; background: var(--nacht); clip-path: circle(var(--r, 0px) at var(--x, 50%) var(--y, 50%)); transition: clip-path var(--t3) var(--kurve); }
.knopf-haupt.ohne-weg::before { transition: none; }
.knopf-haupt:focus-visible::before { clip-path: circle(150% at 50% 50%); }
.knopf-haupt:focus-visible { outline: 2px solid var(--tinte); outline-offset: 3px; }
.knopf-neben { background: transparent; color: var(--tinte); border: 1px solid var(--tinte); }
.knopf-neben::after { content: ''; position: absolute; inset: -1px; border: 1px solid var(--messing); translate: 3px 3px; transition: translate var(--t3) var(--kurve); pointer-events: none; }
.knopf-neben:hover::after, .knopf-neben:focus-visible::after { translate: 0 0; }
.knopf-neben:active { background: rgba(29, 26, 23, .06); }
.knopf .pfeil { width: 16px; height: 16px; flex: none; }

/* Hero (Variante leiste) */
.hero { padding-top: clamp(28px, 4vw, 48px); }
.hero-text { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 19rem); gap: 16px 48px; align-items: end; margin-bottom: clamp(20px, 3vw, 32px); }
.hero-text .rubrik { color: var(--mohn-tief); margin-bottom: 14px; }
.hero h1 { font: 300 clamp(3rem, 1.6rem + 6vw, 7.5rem)/.95 'Imbue', serif; letter-spacing: -.01em; text-wrap: balance; }
.hero-text p { color: var(--zweit); max-width: 40ch; text-wrap: pretty; }
.hero-bild { position: relative; aspect-ratio: 16 / 5.2; overflow: hidden; background: var(--sand); }
.hero-bild svg { display: block; width: 100%; height: 100%; }
.hero-bild figcaption { position: absolute; right: 12px; top: 10px; font-size: .75rem; color: var(--tinte); background: rgba(251, 248, 242, .86); padding: 2px 8px; }

/* Einstieg (Varianten kalender und catering) */
.einstieg { padding-top: clamp(28px, 4vw, 48px); display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 26rem); gap: 16px 48px; align-items: end; }
.einstieg .rubrik { color: var(--mohn-tief); margin-bottom: 12px; }
.einstieg h2 { font: 300 clamp(2.5rem, 1.6rem + 3.4vw, 4.5rem)/.98 'Imbue', serif; letter-spacing: -.01em; text-wrap: balance; }
.einstieg p { color: var(--zweit); text-wrap: pretty; }

/* Leiste */
.leiste { position: relative; z-index: 2; display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, .9fr) minmax(0, 1.3fr) auto; gap: 14px 18px; align-items: start; margin-top: 24px; padding: 18px 20px 20px; background: var(--kalk-hell); border: 1px solid var(--linie); box-shadow: 0 26px 50px -34px rgba(29, 26, 23, .45); }
[data-v="leiste"] .leiste { margin: -64px clamp(0px, 3vw, 40px) 0; }
.leiste.mit-ausweich { grid-template-columns: minmax(0, 1.1fr) minmax(0, 1.1fr) minmax(0, .9fr) minmax(0, 1.12fr) auto; }
[data-v="catering"] .leiste { grid-template-columns: minmax(0, 1.2fr) minmax(0, .9fr) minmax(0, 1fr) minmax(0, 1.12fr) auto; }
[data-v="catering"] .leiste.ausweich-c { grid-template-columns: repeat(3, minmax(0, 1fr)); }
[data-v="catering"] .leiste.ausweich-c .knopf-haupt { grid-column: auto; }
.feld { display: grid; grid-template-columns: minmax(0, 1fr); gap: 6px; min-width: 0; border: 0; }
.feld > label:not(.flex), .gaeste .lt { display: flex; align-items: center; gap: 0 .5em; min-height: 24px; white-space: nowrap; font-stretch: 112%; font-weight: 600; font-size: .8125rem; letter-spacing: .08em; text-transform: uppercase; line-height: 1.3; padding: 0; }
.feld-kopf { display: flex; justify-content: space-between; align-items: center; gap: 8px; min-height: 24px; }
.feld .format { font-weight: 500; letter-spacing: .02em; text-transform: none; color: var(--zweit); font-stretch: 100%; }
.flex { display: inline-flex; align-items: center; gap: 8px; font-size: .9375rem; cursor: pointer; min-height: 24px; justify-self: start; }
.flex input { width: 18px; height: 18px; accent-color: var(--mohn); margin: 0; }
.eingabe { width: 100%; height: 48px; padding: 0 12px; background: #fff; border: 1px solid var(--feldrand); border-radius: 2px; font-size: 1.0625rem; font-variant-numeric: tabular-nums; transition: border-color var(--t1) var(--kurve), box-shadow var(--t1) var(--kurve); }
.eingabe.blitz { animation: feldblitz var(--t5) var(--kurve); }
@keyframes feldblitz { from { background-color: var(--sand); } }
.eingabe::placeholder { color: var(--zweit); opacity: 1; }
.eingabe:hover { border-color: var(--tinte); }
.eingabe:focus-visible { outline: 2px solid var(--tinte); outline-offset: 1px; border-color: var(--tinte); }
.eingabe[aria-invalid="true"] { border-color: var(--mohn); box-shadow: inset 0 -2px 0 var(--mohn); }
select.eingabe { appearance: none; padding-right: 36px; background: #fff 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.5l5 5 5-5' fill='none' stroke='%231D1A17' stroke-width='1.5'/%3E%3C/svg%3E") no-repeat right 14px center; cursor: pointer; }
.gaeste { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: 10px; }
.gaeste label { display: grid; gap: 6px; min-width: 0; }
.gaeste .lt { font-stretch: 112%; font-weight: 600; font-size: .8125rem; letter-spacing: .08em; text-transform: uppercase; line-height: 1.3; }
input[type=number] { -moz-appearance: textfield; appearance: textfield; }
input[type=number]::-webkit-inner-spin-button, input[type=number]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.leiste .knopf-haupt { height: 48px; margin-top: 30px; }

/* Antwort unter der Leiste */
.antwort { margin: 14px clamp(0px, 3vw, 40px) 0; padding: 0 20px; min-height: 28px; display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; }
[data-v="kalender"] .antwort, [data-v="catering"] .antwort { margin-inline: 0; padding-inline: 0; }
.antwort p { max-width: 74ch; text-wrap: pretty; }
.antwort p a { font-weight: 600; font-variant-numeric: tabular-nums; }
.antwort .warn { color: var(--mohn-tief); }
.vorschlaege { display: flex; flex-wrap: wrap; gap: 8px; }
.chip { min-height: 44px; padding: 0 14px; border: 1px solid var(--feldrand); background: var(--kalk-hell); border-radius: 2px; cursor: pointer; font-size: .9375rem; font-variant-numeric: tabular-nums; display: inline-flex; align-items: center; gap: 8px; transition: border-color var(--t1) var(--kurve), background-color var(--t1) var(--kurve); }
.chip:hover { border-color: var(--tinte); background: #fff; }
.link { background: none; border: 0; padding: 0; text-decoration: underline; text-underline-offset: 3px; cursor: pointer; font-weight: 600; min-height: 24px; }

/* Zeichen für den Status: Form, nicht nur Farbe */
.z { display: inline-block; width: 10px; height: 10px; flex: none; border-radius: 50%; vertical-align: baseline; }
.z-frei { background: var(--mohn); }
.z-angefragt { border: 1.5px solid var(--tinte); background: linear-gradient(90deg, var(--tinte) 50%, transparent 50%); }
.z-vergeben { border-radius: 0; width: 11px; height: 11px; background: linear-gradient(45deg, transparent 43%, var(--zweit) 43% 57%, transparent 57%), linear-gradient(-45deg, transparent 43%, var(--zweit) 43% 57%, transparent 57%); }
.z-kapazitaet { background: var(--mohn); border-radius: 1px; width: 12px; height: 8px; }
.z-zuknapp { border: 1.5px solid var(--mohn-tief); background: transparent; }
.z-ausgebucht { border-radius: 0; width: 11px; height: 11px; background: linear-gradient(45deg, transparent 43%, var(--zweit) 43% 57%, transparent 57%), linear-gradient(-45deg, transparent 43%, var(--zweit) 43% 57%, transparent 57%); }
.z-samstag { border-radius: 0; background: var(--sand); border: 1px solid var(--linie); width: 14px; height: 12px; }
.z-feiertag { border-radius: 0; width: 0; height: 0; border-top: 9px solid var(--messing); border-left: 9px solid transparent; }

/* Bühne: Kalender und Tafel */
.buehne { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 340px); gap: 32px; align-items: start; margin-top: 22px; padding-bottom: 72px; }
[data-v="leiste"] .buehne { grid-template-columns: minmax(0, 1fr); margin-top: 28px; }
.kalender { min-width: 0; }
.kal-kopf { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px 24px; flex-wrap: wrap; margin-bottom: 12px; }
.kal-kopf h3 { font: 400 2.25rem/1 'Imbue', serif; letter-spacing: -.005em; }
.kal-zahlen { font-size: .9375rem; color: var(--zweit); font-variant-numeric: tabular-nums; margin-top: 6px; }
.kal-nav { display: flex; gap: 10px; }
.kal-nav .knopf { width: 48px; padding: 0; }
.kal-nav .knopf[aria-disabled="true"] { opacity: 1; color: var(--zweit); border-color: var(--linie); cursor: not-allowed; }
.kal-nav .knopf[aria-disabled="true"]::after { display: none; }
.stand { display: flex; flex-wrap: wrap; gap: 4px 12px; align-items: baseline; font-size: .875rem; color: var(--zweit); padding: 10px 0; border-top: 3px double var(--messing); }
.stand b { color: var(--tinte); font-weight: 600; }

.monat { overflow: hidden; }
table.kal { width: 100%; table-layout: fixed; border-collapse: separate; border-spacing: 3px; font-stretch: 87%; margin: 0 -3px; width: calc(100% + 6px); }
.kal th { font-weight: 600; font-size: .8125rem; font-stretch: 112%; letter-spacing: .06em; text-transform: uppercase; padding: 6px 0 4px; text-align: left; padding-left: 8px; }
.kal thead th.sa { color: var(--tinte); box-shadow: inset 0 -2px 0 var(--messing); }
.kal thead th.kw, .kal tbody th { width: 44px; padding-left: 0; text-align: center; }
.kal tbody th { font-size: .75rem; color: var(--zweit); font-weight: 500; letter-spacing: .02em; text-transform: none; font-variant-numeric: tabular-nums; }
.kal td { padding: 0; height: 58px; }
.tag { position: relative; width: 100%; height: 100%; display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-rows: auto 1fr; align-items: start; padding: 6px 7px 5px 8px; border: 0; border-radius: 2px; background: var(--kalk-hell); text-align: left; cursor: pointer; line-height: 1.15; transition: background-color var(--t1) var(--kurve), box-shadow var(--t2) var(--kurve); }
.tag .zf { font-size: 1.0625rem; font-weight: 500; font-variant-numeric: tabular-nums; }
.tag .z { margin-top: 5px; }
.tag .st { grid-column: 1 / -1; align-self: end; font-size: .8125rem; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: clip; }
.tag .ft { position: absolute; right: 0; bottom: 0; width: 0; height: 0; border-bottom: 10px solid var(--messing); border-left: 10px solid transparent; }
.tag.sa { background: var(--sand-hell); }
.tag[data-s="frei"]:hover, .tag[data-s="kapazitaet"]:hover { background: #fff; box-shadow: inset 0 0 0 1px var(--tinte); }
.tag[data-s="angefragt"]:hover { box-shadow: inset 0 0 0 1px var(--tinte); }
.tag[data-s="vergeben"], .tag[data-s="ausgebucht"], .tag[data-s="zuknapp"] { color: var(--zweit); background-image: repeating-linear-gradient(135deg, transparent 0 7px, rgba(140, 106, 60, .16) 7px 8px); cursor: not-allowed; }
.tag[data-s="zuknapp"] { cursor: pointer; }
.tag[data-s="vorbei"] { color: var(--zweit); background: transparent; cursor: default; }
.tag[data-s="vorbei"] .zf { text-decoration: line-through; text-decoration-thickness: 1px; }
.tag.heute::after { content: 'heute'; position: absolute; right: 6px; bottom: 5px; font-size: .6875rem; font-stretch: 112%; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--tinte); }
.tag.gewaehlt { box-shadow: inset 0 0 0 2px var(--mohn); background: #fff; }
.tag.blitz { animation: blitz var(--t5) var(--kurve); }
@keyframes blitz { from { background-color: var(--sand); } }
.tag:focus-visible { outline: 2px solid var(--tinte); outline-offset: 1px; z-index: 1; }
.tag .st { color: inherit; }
.tag[data-s="frei"] .st, .tag[data-s="kapazitaet"] .st { color: var(--tinte); }
.leer-zelle { background: transparent; }

.legende { display: flex; flex-wrap: wrap; gap: 6px 18px; list-style: none; margin-top: 12px; font-size: .875rem; color: var(--zweit); }
.legende li { display: inline-flex; align-items: center; gap: 8px; }

/* Tafel: Erklärung, Anfrage, Dank */
.tafel { background: var(--kalk-hell); border: 1px solid var(--linie); border-top: 3px double var(--messing); padding: 22px 24px 24px; min-width: 0; }
.tafel > * + * { margin-top: 14px; }
.tafel h3, .tafel h2 { font: 400 2rem/1.02 'Imbue', serif; letter-spacing: -.005em; text-wrap: balance; }
.tafel h3:focus, .tafel h2:focus { outline: none; }
.tafel h3:focus-visible, .tafel h2:focus-visible { outline: 2px solid var(--tinte); outline-offset: 4px; }
.tafel .rubrik { color: var(--zweit); }
.tafel p { font-size: 1rem; text-wrap: pretty; }
.tafel .leise { color: var(--zweit); font-size: .9375rem; }
.marke-status { display: inline-flex; align-items: center; gap: 8px; font-stretch: 112%; font-weight: 600; font-size: .8125rem; letter-spacing: .08em; text-transform: uppercase; padding: 4px 10px; border: 1px solid var(--linie); background: #fff; }
.zus { display: grid; gap: 0; border-top: 1px solid var(--linie); }
.zus > div { display: grid; grid-template-columns: 9rem minmax(0, 1fr); gap: 4px 12px; padding: 9px 0; border-bottom: 1px solid var(--linie); font-size: .9375rem; }
.zus dt { font-stretch: 112%; font-weight: 600; font-size: .75rem; letter-spacing: .08em; text-transform: uppercase; padding-top: 3px; color: var(--zweit); }
.zus dd { font-variant-numeric: tabular-nums; }
.anfrage { display: grid; gap: 12px; }
.anfrage .feld > label { font-size: .75rem; white-space: normal; flex-wrap: wrap; }
.anfrage .eingabe { height: 46px; }
.feld .fehler { color: var(--mohn-tief); font-size: .875rem; font-weight: 500; }
.platzhalter-recht { font-size: .875rem; line-height: 1.5; padding: 10px 12px; border: 1px dashed var(--feldrand); background: var(--kalk); color: var(--tinte); }
.platzhalter-recht b { font-stretch: 112%; font-size: .75rem; letter-spacing: .08em; text-transform: uppercase; color: var(--mohn-tief); display: block; }
.anfrage .knopf-haupt { width: 100%; }
.reihe { display: flex; flex-wrap: wrap; gap: 10px 12px; }
.schritte { list-style: none; counter-reset: s; display: grid; gap: 8px; font-size: .9375rem; }
.schritte li { counter-increment: s; display: grid; grid-template-columns: 28px minmax(0, 1fr); gap: 8px; }
.schritte li::before { content: counter(s); font: 400 1.25rem/1.2 'Imbue', serif; color: var(--messing); }
.tafel-rein { animation: tafel-rein var(--t4) var(--kurve) both; }
@keyframes tafel-rein { from { opacity: 0; translate: 0 16px; } }

/* Variante leiste: Anfrage breit unter der Leiste */
[data-v="leiste"] .kalender { display: none; }
[data-v="leiste"] .tafel { margin-inline: clamp(0px, 3vw, 40px); display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); gap: 18px 40px; align-items: start; }
[data-v="leiste"] .tafel > * + * { margin-top: 0; }
[data-v="leiste"] .tafel .spalte-a { display: grid; gap: 14px; align-content: start; }
[data-v="leiste"] .tafel[data-modus="start"], [data-v="leiste"] .tafel[data-modus="option"], [data-v="leiste"] .tafel[data-modus="vergeben"] { display: none; }
.spalte-a, .spalte-b { display: grid; gap: 14px; align-content: start; min-width: 0; }
.ohne-kalender .kalender { display: none; }
.ohne-kalender .buehne { grid-template-columns: minmax(0, 1fr); }
.ohne-kalender .tafel { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); gap: 18px 40px; }
.ohne-kalender .tafel > * + * { margin-top: 0; }
.hinweis-ohne { display: none; }
.ohne-kalender .hinweis-ohne { display: block; }

/* Vorführ-Zeiger */
.zeiger { position: fixed; left: 0; top: 0; z-index: 50; width: 26px; height: 26px; pointer-events: none; opacity: 0; transition: opacity var(--t2) var(--kurve); }
.zeiger.an { opacity: 1; }
.zeiger svg { width: 100%; height: 100%; filter: drop-shadow(0 2px 4px rgba(0, 0, 0, .25)); }
.zeiger i { position: absolute; left: -9px; top: -9px; width: 18px; height: 18px; border-radius: 50%; border: 2px solid var(--mohn); opacity: 0; }
.zeiger.klick i { animation: tipp var(--t3) var(--kurve); }
@keyframes tipp { from { opacity: 1; scale: .4; } to { opacity: 0; scale: 1.6; } }

/* Pult (Demo-Steuerung) */
.pult { position: fixed; right: 12px; top: 80px; z-index: 40; display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; max-width: calc(100vw - 28px); padding: 8px 10px 8px 14px; background: rgba(21, 23, 31, .95); color: var(--nacht-text); border-radius: 3px; box-shadow: 0 12px 34px rgba(0, 0, 0, .28); font-size: .75rem; }
.pult .pt { font-stretch: 112%; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: #C9BFAF; }
.pult .gr { display: flex; flex-wrap: wrap; gap: 4px; }
.pult button { min-height: 30px; padding: 0 10px; border: 1px solid rgba(242, 236, 226, .28); border-radius: 2px; background: transparent; color: inherit; cursor: pointer; font-size: .75rem; font-weight: 600; }
.pult button[aria-pressed="true"] { background: var(--nacht-text); color: var(--nacht); border-color: var(--nacht-text); }
.pult button:focus-visible { outline: 2px solid var(--messing-hell); outline-offset: 2px; }
.pult .werte { color: #C9BFAF; font-variant-numeric: tabular-nums; }

/* Vorschau 960 × 720: nur die Bühne */
.vorschau body { overflow: hidden; }
.vorschau .kopf { display: none; }
.vorschau [data-v="leiste"] .hero-bild { aspect-ratio: 16 / 4.6; }
.vorschau [data-v="kalender"] .einstieg p, .vorschau [data-v="catering"] .einstieg p { display: none; }
.vorschau .einstieg { display: none !important; }
.vorschau [data-v="kalender"] .leiste, .vorschau [data-v="catering"] .leiste { margin-top: 20px; }

/* Reduzierte Bewegung: Farben in 200 ms, alles andere ohne Weg */
:root.reduziert *, :root.reduziert *::before, :root.reduziert *::after { transition-duration: 200ms !important; transition-property: opacity, background-color, border-color, color !important; animation-duration: .01ms !important; }
:root.reduziert .tafel-rein { animation: none; }
:root.reduziert .knopf-haupt::before { clip-path: none; opacity: 0; transition: opacity 150ms linear !important; }
:root.reduziert .knopf-haupt:hover::before, :root.reduziert .knopf-haupt:focus-visible::before, :root.reduziert .knopf-haupt.voll::before { opacity: 1; }
:root.reduziert .knopf-neben::after { translate: 0 0; }

@media (max-width: 1100px) {
  .kopf nav { display: none; }
  [data-v="leiste"] .leiste { margin-inline: 0; }
  .antwort, [data-v="leiste"] .tafel { margin-inline: 0; }
  .leiste.mit-ausweich, [data-v="catering"] .leiste { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.12fr); }
  .leiste.mit-ausweich .knopf-haupt, [data-v="catering"] .leiste .knopf-haupt { grid-column: span 3; }
}
@media (max-width: 900px) {
  .leiste .knopf-haupt { margin-top: 4px; }
  .buehne { grid-template-columns: minmax(0, 1fr); }
  .leiste, .leiste.mit-ausweich, [data-v="catering"] .leiste { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .leiste .knopf-haupt, .leiste.mit-ausweich .knopf-haupt, [data-v="catering"] .leiste .knopf-haupt { grid-column: 1 / -1; }
  [data-v="leiste"] .tafel, .ohne-kalender .tafel { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 640px) {
  .pult { top: auto; bottom: 12px; }
  .hero-text, .einstieg { grid-template-columns: minmax(0, 1fr); }
  .hero-bild { aspect-ratio: 16 / 11; }
  .marke span { display: none; }
  .kopf .seite { flex-wrap: nowrap; }
  [data-v="leiste"] .leiste { margin: -36px 0 0; }
  .leiste, .leiste.mit-ausweich, [data-v="catering"] .leiste { grid-template-columns: minmax(0, 1fr); padding: 16px; }
  .antwort, [data-v="leiste"] .tafel { margin-inline: 0; }
  .antwort { padding-inline: 4px; }
  .kal td { height: 50px; }
  .kal thead th.kw, .kal tbody th { width: 30px; }
  .kal th { padding-left: 4px; font-size: .75rem; letter-spacing: .02em; }
  .tag { padding: 5px 4px 4px 5px; grid-template-columns: minmax(0, 1fr); }
  .tag .z { grid-row: 2; align-self: end; margin: 0; }
  .tag .st { display: none; }
  .tag.heute::after { content: '•'; font-size: 1rem; right: 4px; top: 2px; }
  .tafel { padding: 18px 16px 20px; }
  .zus > div { grid-template-columns: minmax(0, 1fr); }
  .kal-kopf h3 { font-size: 2rem; }
}
@media (max-width: 360px) {
  table.kal { border-spacing: 2px; margin: 0 -2px; width: calc(100% + 4px); }
  .kal thead th.kw, .kal tbody th { width: 24px; }
  .tag .zf { font-size: .9375rem; }
  .marke span { display: none; }
}
@media (hover: none) {
  .tag[data-s="frei"]:hover, .tag[data-s="kapazitaet"]:hover { background: var(--kalk-hell); box-shadow: none; }
  .tag.sa[data-s="frei"]:hover { background: var(--sand-hell); }
  .knopf-neben::after { translate: 0 0; }
  .eingabe { font-size: 1.0625rem; }
}
/* Windows-Kontrastmodus: Bedienelemente brauchen eine Kante */
@media (forced-colors: active) {
  button, [role=button], [role=tab], [role=switch], input, select, textarea { border: 1px solid ButtonText !important; }
  .tag[aria-disabled="true"] { border-style: dashed !important; }
  .tag.gewaehlt { outline: 3px solid Highlight; }
  .z, .tag .ft { forced-color-adjust: none; }
}
</style>
</head>
<body data-v="leiste">
<header class="kopf">
  <div class="seite">
    <a class="marke" href="#" aria-label="Gut Weidenhain, Startseite"><b>Gut Weidenhain</b><span>Ochsenfurt am Main</span></a>
    <nav aria-label="Hauptnavigation">
      <a href="#">Ein Tag bei uns</a><a href="#">Räume</a><a href="#termine" aria-current="page">Freie Termine</a><a href="#">Anfahrt</a>
    </nav>
    <a class="tel" href="tel:+4993310471100">09331 0 47 11 0</a>
  </div>
</header>

<main class="seite" id="seite">
  <section class="hero" id="hero" aria-labelledby="hero-titel">
    <div class="hero-text">
      <div>
        <p class="rubrik">Hochzeiten · Feiern · Tagungen</p>
        <h1 id="hero-titel">Ein Tag am Main, der&nbsp;in die Nacht geht.</h1>
      </div>
      <p>Trauung unter der Linde, Essen in der Scheune, getanzt wird bis zwei. Ein Fest am Tag, für bis zu 180 Gäste.</p>
    </div>
    <figure class="hero-bild">
      <svg viewBox="0 0 1600 600" preserveAspectRatio="xMidYMid slice" role="img" aria-labelledby="bild-alt">
        <title id="bild-alt">Illustration: der Hof von Gut Weidenhain am frühen Abend, links eine große Linde, in der Mitte die Scheune mit offenem, warm beleuchtetem Tor, rechts das Gutshaus mit hellen Fenstern, dahinter Weinberge über dem Maintal</title>
        <rect width="1600" height="600" fill="#E9DCC8"/>
        <circle cx="1290" cy="250" r="64" fill="#F4ECDD"/>
        <path d="M0 330 C180 280 330 300 520 270 C760 232 980 300 1220 262 C1380 238 1500 262 1600 250 V600 H0Z" fill="#D9C6A8"/>
        <g stroke="#C2A983" stroke-width="2" opacity=".8">
          <path d="M600 300 L820 262 M640 318 L880 276 M690 334 L940 290 M1060 300 L1250 266 M1100 318 L1320 282 M1150 334 L1400 296"/>
        </g>
        <path d="M0 400 C300 372 640 384 900 376 C1180 368 1420 384 1600 378 V600 H0Z" fill="#CBB38F"/>
        <rect y="470" width="1600" height="130" fill="#E4D5BD"/>
        <path d="M430 470 L250 600 H0 V560 Z" fill="#1D1A17" opacity=".08"/>
        <path d="M980 470 L800 600 H520 L700 470Z" fill="#1D1A17" opacity=".08"/>
        <!-- Scheune -->
        <rect x="430" y="300" width="560" height="172" fill="#B89D78"/>
        <path d="M402 304 L566 178 L854 178 L1018 304Z" fill="#2B2824"/>
        <path d="M566 178 L592 160 L828 160 L854 178Z" fill="#3A3631"/>
        <rect x="430" y="300" width="560" height="6" fill="#8C6A3C" opacity=".55"/>
        <path d="M634 472 V378 A76 76 0 0 1 786 378 V472Z" fill="#E7C489"/>
        <path d="M634 472 V378 A76 76 0 0 1 786 378 V472" fill="none" stroke="#2B2824" stroke-width="8"/>
        <g fill="#2B2824" opacity=".55"><rect x="660" y="430" width="100" height="6"/><rect x="668" y="436" width="4" height="36"/><rect x="748" y="436" width="4" height="36"/></g>
        <g fill="#2B2824"><rect x="486" y="350" width="44" height="58"/><rect x="890" y="350" width="44" height="58"/></g>
        <g fill="#E7C489" opacity=".9"><rect x="492" y="356" width="32" height="46"/><rect x="896" y="356" width="32" height="46"/></g>
        <!-- Gutshaus -->
        <rect x="1060" y="268" width="380" height="204" fill="#EDE3D3"/>
        <path d="M1040 272 L1084 206 L1416 206 L1460 272Z" fill="#35312C"/>
        <rect x="1130" y="186" width="16" height="30" fill="#35312C"/><rect x="1352" y="186" width="16" height="30" fill="#35312C"/>
        <g fill="#2B2824" opacity=".85">
          <rect x="1100" y="300" width="40" height="56"/><rect x="1180" y="300" width="40" height="56"/><rect x="1280" y="300" width="40" height="56"/><rect x="1360" y="300" width="40" height="56"/>
          <rect x="1100" y="392" width="40" height="56"/><rect x="1180" y="392" width="40" height="56"/><rect x="1360" y="392" width="40" height="56"/>
          <path d="M1226 472 V404 A24 24 0 0 1 1274 404 V472Z"/>
        </g>
        <g fill="#E7C489">
          <rect x="1104" y="304" width="32" height="48"/><rect x="1284" y="304" width="32" height="48"/><rect x="1184" y="396" width="32" height="48"/><rect x="1364" y="396" width="32" height="48"/>
          <path d="M1232 472 V406 A18 18 0 0 1 1268 406 V472Z"/>
        </g>
        <!-- Linde -->
        <rect x="236" y="330" width="22" height="146" fill="#2B2824"/>
        <g fill="#3B3A2E">
          <circle cx="250" cy="250" r="118"/><circle cx="160" cy="300" r="82"/><circle cx="340" cy="296" r="86"/><circle cx="210" cy="176" r="74"/><circle cx="306" cy="196" r="70"/>
        </g>
        <g fill="#4A4838" opacity=".8"><circle cx="300" cy="230" r="46"/><circle cx="196" cy="250" r="40"/></g>
        <!-- Lichterkette zwischen Linde und Scheune -->
        <path d="M330 330 Q480 390 640 318" fill="none" stroke="#2B2824" stroke-width="1.5" opacity=".6"/>
        <g fill="#F6E3B5"><circle cx="360" cy="344" r="4"/><circle cx="400" cy="356" r="4"/><circle cx="440" cy="362" r="4"/><circle cx="480" cy="362" r="4"/><circle cx="520" cy="356" r="4"/><circle cx="560" cy="344" r="4"/><circle cx="600" cy="330" r="4"/></g>
        <!-- Stuhlreihen der Trauung unter der Linde -->
        <g fill="#2B2824" opacity=".75">
          <rect x="120" y="500" width="14" height="22"/><rect x="146" y="500" width="14" height="22"/><rect x="172" y="500" width="14" height="22"/><rect x="290" y="500" width="14" height="22"/><rect x="316" y="500" width="14" height="22"/><rect x="342" y="500" width="14" height="22"/>
          <rect x="104" y="540" width="16" height="26"/><rect x="134" y="540" width="16" height="26"/><rect x="164" y="540" width="16" height="26"/><rect x="300" y="540" width="16" height="26"/><rect x="330" y="540" width="16" height="26"/><rect x="360" y="540" width="16" height="26"/>
        </g>
      </svg>
      <figcaption>Illustration, Platzhalter für ein eigenes Foto</figcaption>
    </figure>
  </section>

  <section class="einstieg" id="einstieg" aria-labelledby="einstieg-titel" hidden>
    <div>
      <p class="rubrik" id="einstieg-rubrik">Freie Termine 2026 und 2027</p>
      <h2 id="einstieg-titel">Ist Ihr Tag noch frei?</h2>
    </div>
    <p id="einstieg-text">Wir vergeben jeden Tag nur einmal. Hier sehen Sie, was frei ist, was gerade jemand für sich hält und was vergeben ist.</p>
  </section>

  <form class="leiste" id="leiste" novalidate aria-label="Wunschtermin prüfen und anfragen">
    <div class="feld feld-datum">
      <label for="f-datum" id="l-datum">Wunschdatum <span class="format">TT.MM.JJJJ</span></label>
      <input class="eingabe" id="f-datum" name="datum" inputmode="numeric" autocomplete="off" placeholder="z. B. 12.06.2027" aria-describedby="antwort-text">
      <select class="eingabe" id="f-monat" name="monat" hidden></select>
      <label class="flex"><input type="checkbox" id="f-flex"> Datum flexibel, nur Monat</label>
    </div>
    <div class="feld feld-ausweich" hidden>
      <label for="f-ausweich">Ausweichdatum <span class="format">optional</span></label>
      <input class="eingabe" id="f-ausweich" name="ausweich" inputmode="numeric" autocomplete="off" placeholder="z. B. 19.06.2027">
    </div>
    <div class="feld feld-anlass">
      <label for="f-anlass" id="l-anlass">Anlass</label>
      <select class="eingabe" id="f-anlass" name="anlass"></select>
    </div>
    <div class="feld feld-ort" hidden>
      <label for="f-ort">Ort der Feier</label>
      <input class="eingabe" id="f-ort" name="ort" autocomplete="off" placeholder="Ort oder Location">
    </div>
    <fieldset class="feld feld-gaeste">
      <legend class="nur-sr">Gäste</legend>
      <div class="gaeste">
        <label><span class="lt">Erwachsene</span><input class="eingabe" type="number" id="f-erw" name="erwachsene" min="1" max="999" inputmode="numeric" placeholder="80"></label>
        <label><span class="lt">Kinder</span><input class="eingabe" type="number" id="f-kind" name="kinder" min="0" max="99" inputmode="numeric" placeholder="0"></label>
      </div>
    </fieldset>
    <button class="knopf knopf-haupt" type="submit" id="anfragen">Termin anfragen</button>
  </form>
  <div class="antwort" id="antwort">
    <p id="antwort-text" role="status"></p>
    <div class="vorschlaege" id="vorschlaege"></div>
  </div>

  <div class="buehne" id="termine">
    <section class="kalender" id="kalender" aria-labelledby="kal-titel">
      <p class="stand" id="stand"></p>
      <div class="kal-kopf">
        <div>
          <h3 id="kal-titel" aria-live="polite">Juni 2027</h3>
          <p class="kal-zahlen" id="kal-zahlen"></p>
        </div>
        <div class="kal-nav">
          <button class="knopf knopf-neben" type="button" id="zurueck"><svg class="pfeil" viewBox="0 0 16 16" aria-hidden="true"><path d="M10 3L5 8l5 5" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></button>
          <button class="knopf knopf-neben" type="button" id="vor"><svg class="pfeil" viewBox="0 0 16 16" aria-hidden="true"><path d="M6 3l5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></button>
        </div>
      </div>
      <div class="monat" id="monat"></div>
      <ul class="legende" id="legende"></ul>
    </section>
    <aside class="tafel" id="tafel" aria-labelledby="tafel-titel" data-modus="start"></aside>
  </div>
</main>

<div class="zeiger" id="zeiger" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M4 2l15 9.5-6.6 1.4 3.9 7.3-2.6 1.3-3.8-7.3L4 19z" fill="#15171F" stroke="#fff" stroke-width="1.4"/></svg><i></i></div>

<div class="pult">
  <span class="pt">Variante</span>
  <span class="gr" role="group" aria-label="Variante">
    <button type="button" data-variante="leiste" aria-pressed="true">Leiste</button>
    <button type="button" data-variante="kalender" aria-pressed="false">Kalender</button>
    <button type="button" data-variante="catering" aria-pressed="false">Catering</button>
  </span>
  <span class="pt">Zustand</span>
  <button type="button" id="gepflegt" aria-pressed="true">Kalender gepflegt</button>
  <span class="werte">Kurve 0.625, 0.05, 0, 1 · 400 / 475 / 525 ms</span>
</div>
<script>
(() => {
  const html = document.documentElement, body = document.body;
  const reduziert = () => html.classList.contains('reduziert');
  const $ = (s, r = document) => r.querySelector(s);
  const KURVE = 'cubic-bezier(0.625, 0.05, 0, 1)';

  /* ---------- Daten, wie das Haus sie pflegt (hier erzeugt) ---------- */
  const HEUTE = new Date(2026, 10, 2);
  const MIN = new Date(2026, 10, 1), MAX = new Date(2027, 11, 1);
  const TEL = '09331 0 47 11 0', TEL_HREF = 'tel:+4993310471100';
  const PLATZ = { haus: 180, catering: 400 };
  const STAND = { ja: new Date(2026, 10, 2, 9, 0), nein: new Date(2026, 8, 14, 16, 30) };
  const FRIST_TAGE = 21;   // älter als drei Wochen: Kalender ausblenden

  const iso = d => d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
  const vonIso = s => { const [y, m, t] = s.split('-').map(Number); return new Date(y, m - 1, t); };
  const plus = (d, n) => new Date(d.getFullYear(), d.getMonth(), d.getDate() + n);
  const fLang = new Intl.DateTimeFormat('de-DE', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });
  const fOhneJahr = new Intl.DateTimeFormat('de-DE', { weekday: 'long', day: 'numeric', month: 'long' });
  const fMon = new Intl.DateTimeFormat('de-DE', { month: 'long', year: 'numeric' });
  const fMonatNur = new Intl.DateTimeFormat('de-DE', { month: 'long' });
  const fWt = new Intl.DateTimeFormat('de-DE', { weekday: 'short' });
  const fWtLang = new Intl.DateTimeFormat('de-DE', { weekday: 'long' });
  const fTM = new Intl.DateTimeFormat('de-DE', { day: 'numeric', month: 'long' });
  const kurz = d => fWt.format(d).replace('.', '') + ', ' + fTM.format(d);
  const lang = d => fLang.format(d);
  const feldwert = d => String(d.getDate()).padStart(2, '0') + '.' + String(d.getMonth() + 1).padStart(2, '0') + '.' + d.getFullYear();

  function ostern(y) {
    const a = y % 19, b = Math.floor(y / 100), c = y % 100, d = Math.floor(b / 4), e = b % 4, f = Math.floor((b + 8) / 25), g = Math.floor((b - f + 1) / 3);
    const h = (19 * a + b - d - g + 15) % 30, i = Math.floor(c / 4), k = c % 4, l = (32 + 2 * e + 2 * i - h - k) % 7, m = Math.floor((a + 11 * h + 22 * l) / 451);
    return new Date(y, Math.floor((h + l - 7 * m + 114) / 31) - 1, ((h + l - 7 * m + 114) % 31) + 1);
  }
  const FEIERTAG = {};   // Bayern (Ochsenfurt: mit Mariä Himmelfahrt)
  [2026, 2027].forEach(y => {
    const o = ostern(y), f = (d, n) => { FEIERTAG[iso(d)] = n; };
    f(new Date(y, 0, 1), 'Neujahr'); f(new Date(y, 0, 6), 'Heilige Drei Könige'); f(plus(o, -2), 'Karfreitag'); f(plus(o, 1), 'Ostermontag');
    f(new Date(y, 4, 1), 'Tag der Arbeit'); f(plus(o, 39), 'Christi Himmelfahrt'); f(plus(o, 50), 'Pfingstmontag'); f(plus(o, 60), 'Fronleichnam');
    f(new Date(y, 7, 15), 'Mariä Himmelfahrt'); f(new Date(y, 9, 3), 'Tag der Deutschen Einheit'); f(new Date(y, 10, 1), 'Allerheiligen');
    f(new Date(y, 11, 25), '1. Weihnachtstag'); f(new Date(y, 11, 26), '2. Weihnachtstag');
  });
  function zufall(s) { let h = 2166136261; for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 16777619); } return ((h >>> 0) % 10000) / 10000; }

  const FEST = {
    '2027-05-01': ['frei'], '2027-05-08': ['angefragt', '2026-11-05'], '2027-05-15': ['vergeben'], '2027-05-22': ['vergeben'], '2027-05-29': ['frei'],
    '2027-06-04': ['vergeben'], '2027-06-05': ['vergeben'], '2027-06-06': ['frei'], '2027-06-11': ['angefragt', '2026-11-06'], '2027-06-12': ['frei'], '2027-06-13': ['vergeben'],
    '2027-06-18': ['frei'], '2027-06-19': ['angefragt', '2026-11-09'], '2027-06-20': ['frei'], '2027-06-25': ['vergeben'], '2027-06-26': ['vergeben'], '2027-06-27': ['frei'],
    '2027-07-03': ['frei'], '2027-07-10': ['vergeben'], '2027-07-17': ['angefragt', '2026-11-07'], '2027-07-24': ['vergeben'], '2027-07-31': ['vergeben'], '2026-12-31': ['vergeben']
  };
  const FEST_C = { '2027-06-12': 220, '2027-06-19': 80, '2027-06-05': 0, '2027-06-26': 140, '2027-06-11': 260, '2027-06-18': 120, '2027-06-25': 0 };

  function hausTag(d) {
    const s = iso(d);
    if (d < HEUTE) return { s: 'vorbei' };
    if (FEST[s]) return { s: FEST[s][0], bis: FEST[s][1] ? vonIso(FEST[s][1]) : null };
    const wd = d.getDay(), m = d.getMonth(), y = d.getFullYear(), r = zufall(s);
    const saison = y === 2027 && m >= 4 && m <= 8, advent = y === 2026 && (m === 11 || (m === 10 && d.getDate() >= 20));
    let pv, pa;
    if (wd === 6) { pv = saison ? .6 : advent ? .7 : .28; pa = saison ? .18 : .1; }
    else if (wd === 5) { pv = saison ? .42 : advent ? .6 : .16; pa = .1; }
    else if (wd === 0) { pv = saison ? .2 : .05; pa = .05; }
    else { pv = saison ? .14 : .08; pa = .03; }
    if (r < pv) return { s: 'vergeben' };
    if (r < pv + pa) return { s: 'angefragt', bis: plus(HEUTE, 1 + Math.floor(zufall(s + 'b') * 7)) };
    return { s: 'frei' };
  }
  function cateringTag(d, g) {
    const s = iso(d);
    if (d < HEUTE) return { s: 'vorbei' };
    let rest;
    if (s in FEST_C) rest = FEST_C[s];
    else {
      const wd = d.getDay(), m = d.getMonth(), y = d.getFullYear(), r = zufall('c' + s);
      const saison = y === 2027 && m >= 4 && m <= 8, advent = y === 2026 && (m === 11 || (m === 10 && d.getDate() >= 20));
      const stufen = wd === 6 ? (saison ? [0, 0, 80, 140, 220, 260] : advent ? [0, 60, 120, 200, 260, 400] : [140, 220, 300, 400, 400, 400])
        : wd === 5 ? (saison || advent ? [0, 120, 200, 260, 320, 400] : [220, 300, 400, 400, 400, 400])
        : [260, 320, 400, 400, 400, 400];
      rest = stufen[Math.floor(r * stufen.length)];
    }
    if (rest <= 0) return { s: 'ausgebucht', rest: 0 };
    if (g && g > rest) return { s: 'zuknapp', rest };
    return { s: 'kapazitaet', rest };
  }

  /* ---------- Zustand ---------- */
  const Z = { v: 'leiste', gepflegt: true, monat: new Date(2027, 5, 1), gewaehlt: null, fokus: null, vorf: false };
  const istCatering = () => Z.v === 'catering';
  const tagInfo = d => istCatering() ? cateringTag(d, gaeste().summe) : hausTag(d);
  const waehlbar = s => s === 'frei' || s === 'angefragt' || s === 'kapazitaet';

  const leiste = $('#leiste'), fDatum = $('#f-datum'), fMonat = $('#f-monat'), fFlex = $('#f-flex'), fAnlass = $('#f-anlass');
  const fErw = $('#f-erw'), fKind = $('#f-kind'), fAusweich = $('#f-ausweich'), fOrt = $('#f-ort');
  const antwortText = $('#antwort-text'), vorschlaege = $('#vorschlaege'), tafel = $('#tafel'), monat = $('#monat');

  for (let m = new Date(MIN); m <= MAX; m = new Date(m.getFullYear(), m.getMonth() + 1, 1)) {
    const o = document.createElement('option'); o.value = iso(m); o.textContent = fMon.format(m); fMonat.appendChild(o);
  }
  const ANLASS = { haus: ['Hochzeit', 'Feier (Geburtstag, Jubiläum)', 'Tagung oder Firmenfeier'], catering: ['Menü am Tisch', 'Buffet', 'Flying Dinner', 'Food-Stationen'] };
  function anlassOptionen() {
    const l = ANLASS[istCatering() ? 'catering' : 'haus'];
    fAnlass.innerHTML = l.map(t => '<option>' + t + '</option>').join('');
    $('#l-anlass').textContent = istCatering() ? 'Form' : 'Anlass';
  }

  function gaeste() {
    const e = Math.max(0, parseInt(fErw.value, 10) || 0), k = Math.max(0, parseInt(fKind.value, 10) || 0);
    return { e, k, summe: e + k };
  }
  function gaesteText(g) {
    if (!g.summe) return 'noch offen';
    return g.e + (g.e === 1 ? ' Erwachsener' : ' Erwachsene') + (g.k ? ', ' + g.k + (g.k === 1 ? ' Kind' : ' Kinder') : '');
  }
  function liesDatum(t) {
    t = (t || '').trim();
    if (!t) return null;
    let m = t.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/), y, mo, d;
    if (m) { y = +m[1]; mo = +m[2]; d = +m[3]; }
    else {
      m = t.match(/^(\d{1,2})\s*[.\/]\s*(\d{1,2})\s*[.\/]?\s*(\d{2}|\d{4})?$/);
      if (!m) return 'ungueltig';
      d = +m[1]; mo = +m[2]; y = m[3] ? (m[3].length === 2 ? 2000 + +m[3] : +m[3]) : HEUTE.getFullYear();
      if (!m[3] && new Date(y, mo - 1, d) < HEUTE) y++;
    }
    const x = new Date(y, mo - 1, d);
    if (x.getMonth() !== mo - 1 || x.getDate() !== d) return 'ungueltig';
    return x;
  }
  const imBereich = d => d >= MIN && d < new Date(MAX.getFullYear(), MAX.getMonth() + 1, 1);

  function naheFreie(d, n = 3) {
    const c = [];
    for (let i = -28; i <= 42; i++) {
      if (!i) continue;
      const x = plus(d, i);
      if (x < HEUTE || !imBereich(x)) continue;
      const s = tagInfo(x).s;
      if (s === 'frei' || s === 'kapazitaet') c.push({ x, w: (x.getDay() === d.getDay() ? 0 : 100) + Math.abs(i) });
    }
    return c.sort((a, b) => a.w - b.w).slice(0, n).map(o => o.x).sort((a, b) => a - b);
  }
  function chips(liste, ziel) {
    ziel.innerHTML = liste.map(d => '<button type="button" class="chip" data-aktion="waehle" data-iso="' + iso(d) + '" aria-label="' + lang(d) + ' übernehmen"><span class="z ' + (istCatering() ? 'z-kapazitaet' : 'z-frei') + '" aria-hidden="true"></span>' + kurz(d) + '</button>').join('');
  }

  /* ---------- Antwort unter der Leiste ---------- */
  function sage(htmlText, warn) { antwortText.innerHTML = htmlText; antwortText.classList.toggle('warn', !!warn); }
  function datumAusLeiste() { return fFlex.checked ? null : liesDatum(fDatum.value); }

  let pruefe = function () {
    vorschlaege.innerHTML = '';
    fDatum.removeAttribute('aria-invalid');
    const g = gaeste(), max = PLATZ[istCatering() ? 'catering' : 'haus'];
    if (g.summe > max) {
      sage('Für mehr als ' + max + ' Gäste bitte anrufen: <a href="' + TEL_HREF + '">' + TEL + '</a>. ' + (istCatering() ? 'Dann planen wir mit einer zweiten Küche.' : 'Mehr als ' + max + ' Plätze gibt es bei uns nur mit Zelt im Garten.'), true);
      return;
    }
    if (!Z.gepflegt) {
      sage('Nennen Sie uns Wunsch- und Ausweichdatum. Wir prüfen beide Tage und antworten innerhalb von 48 Stunden.');
      return;
    }
    if (fFlex.checked) { monatsAntwort(vonIso(fMonat.value)); return; }
    const d = liesDatum(fDatum.value);
    if (d === null) {
      sage(Z.v === 'leiste' ? 'Tippen Sie Ihr Wunschdatum ein oder wählen Sie „Datum flexibel“ und einen Monat.' : 'Wählen Sie einen Tag im Kalender oder tippen Sie ihn oben ein.');
      return;
    }
    if (d === 'ungueltig') { fDatum.setAttribute('aria-invalid', 'true'); sage('Bitte das Datum als TT.MM.JJJJ eingeben, zum Beispiel 12.06.2027.', true); return; }
    if (!imBereich(d) && d >= HEUTE) { sage('Termine vergeben wir bis Dezember 2027. Für ' + d.getFullYear() + ' fragen Sie gern schon an.'); return; }
    const t = tagInfo(d), b = '<b>' + lang(d) + '</b>';
    const ft = FEIERTAG[iso(d)] ? ' (' + FEIERTAG[iso(d)] + ')' : '';
    if (t.s === 'vorbei') { sage(b + ' liegt in der Vergangenheit.', true); return; }
    if (t.s === 'frei') sage('<span class="z z-frei" aria-hidden="true"></span> ' + b + ft + ' ist frei.');
    else if (t.s === 'angefragt') { sage('<span class="z z-angefragt" aria-hidden="true"></span> ' + b + ft + ' ist angefragt. Ein Paar hält den Tag bis ' + fOhneJahr.format(t.bis) + '. Sie können sich als Nächste vormerken.'); vorschlaege.innerHTML = '<button type="button" class="knopf knopf-neben" data-aktion="vormerken">Als Nächste vormerken</button>'; }
    else if (t.s === 'vergeben') { sage('<span class="z z-vergeben" aria-hidden="true"></span> ' + b + ft + ' ist vergeben. Frei in der Nähe:'); chips(naheFreie(d), vorschlaege); }
    else if (t.s === 'kapazitaet') sage('<span class="z z-kapazitaet" aria-hidden="true"></span> ' + b + ': noch Kapazität für ' + t.rest + ' Gäste.');
    else if (t.s === 'zuknapp') { sage('<span class="z z-zuknapp" aria-hidden="true"></span> ' + b + ': Für ' + g.summe + ' Gäste reicht es nicht mehr, möglich sind noch ' + t.rest + '. Passend in der Nähe:'); chips(naheFreie(d), vorschlaege); }
    else if (t.s === 'ausgebucht') { sage('<span class="z z-ausgebucht" aria-hidden="true"></span> ' + b + ' ist ausgebucht. Passend in der Nähe:'); chips(naheFreie(d), vorschlaege); }
  }

  const pruefeKern = pruefe;
  pruefe = function () { pruefeKern(); zusammenfassungNeu(); };

  function tageImMonat(m) {
    const l = [], letzt = new Date(m.getFullYear(), m.getMonth() + 1, 0).getDate();
    for (let i = 1; i <= letzt; i++) l.push(new Date(m.getFullYear(), m.getMonth(), i));
    return l;
  }
  function monatsZahlen(m) {
    const sa = tageImMonat(m).filter(d => d.getDay() === 6 && d >= HEUTE);
    const z = { frei: 0, angefragt: 0, vergeben: 0, kapazitaet: 0, zuknapp: 0, ausgebucht: 0, alle: sa.length };
    sa.forEach(d => { z[tagInfo(d).s]++; });
    return z;
  }
  function monatsAntwort(m) {
    const name = fMon.format(m), z = monatsZahlen(m), g = gaeste().summe;
    const frei = tageImMonat(m).filter(d => d >= HEUTE && (tagInfo(d).s === 'frei' || tagInfo(d).s === 'kapazitaet'));
    const top = frei.filter(d => d.getDay() === 6).concat(frei.filter(d => d.getDay() === 5)).slice(0, 4).sort((a, b) => a - b);
    if (istCatering()) {
      sage('<span class="z z-kapazitaet" aria-hidden="true"></span> Im ' + name + ' haben wir an ' + frei.length + ' Tagen noch Kapazität' + (g ? ' für ' + g + ' Gäste' : '') + ', an ' + z.kapazitaet + ' von ' + z.alle + ' Samstagen.' + (top.length ? ' Zum Beispiel:' : ''));
    } else {
      const fs = z.frei === 1 ? '1 von ' + z.alle + ' Samstagen ist frei' : z.frei + ' von ' + z.alle + ' Samstagen sind frei';
      sage('<span class="z z-frei" aria-hidden="true"></span> Im ' + name + ' ' + fs + (z.angefragt ? ', ' + z.angefragt + ' ' + (z.angefragt === 1 ? 'ist' : 'sind') + ' angefragt' : '') + '.' + (top.length ? ' Freie Freitage und Samstage:' : ''));
    }
    chips(top, vorschlaege);
  }

  /* ---------- Kalender ---------- */
  const WT = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'];
  function kw(d) { const t = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())); const n = t.getUTCDay() || 7; t.setUTCDate(t.getUTCDate() + 4 - n); const j = new Date(Date.UTC(t.getUTCFullYear(), 0, 1)); return Math.ceil(((t - j) / 864e5 + 1) / 7); }
  function kurzStatus(t) {
    return { frei: 'frei', angefragt: 'Option', vergeben: 'vergeben', vorbei: '', kapazitaet: 'noch ' + t.rest, zuknapp: 'nur ' + t.rest, ausgebucht: 'voll' }[t.s];
  }
  function langStatus(t) {
    const g = gaeste().summe;
    return { frei: 'frei', angefragt: 'angefragt, Option bis ' + (t.bis ? fTM.format(t.bis) : ''), vergeben: 'vergeben, nicht wählbar', vorbei: 'vorbei',
      kapazitaet: 'noch Kapazität für ' + t.rest + ' Gäste', zuknapp: 'für ' + g + ' Gäste zu knapp, noch ' + t.rest + ' Gäste möglich', ausgebucht: 'ausgebucht, nicht wählbar' }[t.s];
  }
  function zellName(d, t) {
    let n = lang(d) + ', ' + langStatus(t);
    if (FEIERTAG[iso(d)]) n += ', Feiertag: ' + FEIERTAG[iso(d)];
    if (iso(d) === iso(HEUTE)) n += ', heute';
    return n;
  }
  function rovingZiel(m) {
    const imM = d => d && d.getMonth() === m.getMonth() && d.getFullYear() === m.getFullYear();
    if (imM(Z.fokus)) return iso(Z.fokus);
    if (imM(Z.gewaehlt)) return iso(Z.gewaehlt);
    const t = tageImMonat(m).find(d => d >= HEUTE && waehlbar(tagInfo(d).s)) || tageImMonat(m)[0];
    return iso(t);
  }
  function zeichne(richtung) {
    const m = Z.monat, erster = new Date(m.getFullYear(), m.getMonth(), 1), off = (erster.getDay() + 6) % 7;
    const letzt = new Date(m.getFullYear(), m.getMonth() + 1, 0), ziel = rovingZiel(m);
    $('#kal-titel').textContent = fMon.format(m);
    let h = '<table class="kal" role="grid" aria-labelledby="kal-titel" aria-describedby="kal-zahlen"><thead><tr role="row"><th role="columnheader" class="kw" scope="col"><span aria-hidden="true">KW</span><span class="nur-sr">Kalenderwoche</span></th>';
    WT.forEach((w, i) => { h += '<th role="columnheader" scope="col"' + (i === 5 ? ' class="sa"' : '') + ' aria-label="' + w + '"><span aria-hidden="true">' + w.slice(0, 2) + '</span></th>'; });
    h += '</tr></thead><tbody>';
    let d = plus(erster, -off);
    while (d <= letzt) {
      h += '<tr role="row"><th role="rowheader" scope="row"><span class="nur-sr">Kalenderwoche </span>' + kw(d) + '</th>';
      for (let i = 0; i < 7; i++, d = plus(d, 1)) {
        if (d.getMonth() !== m.getMonth()) { h += '<td role="gridcell" class="leer-zelle"></td>'; continue; }
        const t = tagInfo(d), s = iso(d), gew = Z.gewaehlt && iso(Z.gewaehlt) === s;
        const aus = !waehlbar(t.s) && t.s !== 'zuknapp';
        h += '<td role="gridcell"' + (gew ? ' aria-selected="true"' : '') + '><button type="button" class="tag' + (d.getDay() === 6 ? ' sa' : '') + (s === iso(HEUTE) ? ' heute' : '') + (gew ? ' gewaehlt' : '') + '" data-iso="' + s + '" data-s="' + t.s + '" tabindex="' + (s === ziel ? 0 : -1) + '"'
          + (s === iso(HEUTE) ? ' aria-current="date"' : '') + (aus ? ' aria-disabled="true"' : '') + ' aria-label="' + zellName(d, t) + (gew ? ', gewählt' : '') + '">'
          + '<span class="zf" aria-hidden="true">' + d.getDate() + '</span>'
          + (t.s !== 'vorbei' ? '<span class="z z-' + t.s + '" aria-hidden="true"></span>' : '')
          + '<span class="st" aria-hidden="true">' + kurzStatus(t) + '</span>'
          + (FEIERTAG[s] ? '<span class="ft" aria-hidden="true"></span>' : '') + '</button></td>';
      }
      h += '</tr>';
    }
    h += '</tbody></table>';
    monat.innerHTML = h;
    if (richtung && !Z.ohneAnim) {
      const t = monat.firstElementChild;
      t.animate(reduziert() ? [{ opacity: 0 }, { opacity: 1 }] : [{ opacity: 0, transform: 'translateX(' + (richtung * 24) + 'px)' }, { opacity: 1, transform: 'none' }], { duration: reduziert() ? 200 : 525, easing: KURVE });
    }
    const z = monatsZahlen(m), g = gaeste().summe;
    $('#kal-zahlen').textContent = istCatering()
      ? 'Samstage mit Kapazität' + (g ? ' für ' + g + ' Gäste' : '') + ': ' + z.kapazitaet + ' von ' + z.alle
      : 'Samstage: ' + z.frei + ' frei · ' + z.angefragt + ' angefragt · ' + z.vergeben + ' vergeben';
    const vorher = new Date(m.getFullYear(), m.getMonth() - 1, 1), nachher = new Date(m.getFullYear(), m.getMonth() + 1, 1);
    navKnopf($('#zurueck'), vorher < MIN, 'Vorheriger Monat: ' + fMon.format(vorher));
    navKnopf($('#vor'), nachher > MAX, 'Nächster Monat: ' + fMon.format(nachher));
  }
  function navKnopf(k, aus, name) { k.setAttribute('aria-label', aus ? name + ' (nicht verfügbar)' : name); if (aus) k.setAttribute('aria-disabled', 'true'); else k.removeAttribute('aria-disabled'); }
  function geheZuMonat(d, fokusIso) {
    const neu = new Date(d.getFullYear(), d.getMonth(), 1);
    if (neu < MIN || neu > MAX) return false;
    const r = Math.sign(neu - Z.monat);
    Z.monat = neu;
    zeichne(r);
    if (fokusIso) { const b = monat.querySelector('[data-iso="' + fokusIso + '"]'); if (b) b.focus(); }
    return true;
  }
  function markiere() {
    monat.querySelectorAll('.tag').forEach(b => {
      const gew = Z.gewaehlt && b.dataset.iso === iso(Z.gewaehlt), d = vonIso(b.dataset.iso);
      b.parentElement.toggleAttribute('aria-selected', false);
      if (gew) b.parentElement.setAttribute('aria-selected', 'true');
      b.classList.toggle('gewaehlt', !!gew);
      if (Z.fokus) b.tabIndex = b.dataset.iso === iso(Z.fokus) ? 0 : -1;
      b.setAttribute('aria-label', zellName(d, tagInfo(d)) + (gew ? ', gewählt' : ''));
    });
  }
  function legende() {
    const p = istCatering()
      ? [['z-kapazitaet', 'noch Kapazität, Zahl = Gäste'], ['z-zuknapp', 'zu knapp für Ihre Gästezahl'], ['z-ausgebucht', 'ausgebucht'], ['z-samstag', 'Samstag'], ['z-feiertag', 'Feiertag in Bayern']]
      : [['z-frei', 'frei'], ['z-angefragt', 'angefragt (Option, bis zu sieben Tage)'], ['z-vergeben', 'vergeben'], ['z-samstag', 'Samstag'], ['z-feiertag', 'Feiertag in Bayern']];
    $('#legende').innerHTML = p.map(([k, t]) => '<li><span class="z ' + k + '" aria-hidden="true"></span>' + t + '</li>').join('');
  }
  function stand() {
    const s = STAND[Z.gepflegt ? 'ja' : 'nein'];
    $('#stand').innerHTML = 'Stand: <b>' + lang(s) + ', ' + s.getHours() + ':' + String(s.getMinutes()).padStart(2, '0') + ' Uhr</b><span>gepflegt von Johanna Brünninger, werktags</span>';
  }

  /* ---------- Tafel: Erklärung, Anfrage, Dank ---------- */
  function werktagePlus(d, n) { let x = d; while (n > 0) { x = plus(x, 1); if (x.getDay() % 6 !== 0) n--; } return x; }
  function tafelZeigen(modus, daten) {
    tafel.dataset.modus = modus;
    tafel.dataset.art = (daten && daten.art) || 'anfrage';
    tafel.innerHTML = TAFEL[modus](daten || {});
    if (!Z.ohneAnim) { tafel.classList.remove('tafel-rein'); void tafel.offsetWidth; tafel.classList.add('tafel-rein'); }
  }
  const kontakt = '<p class="leise">Johanna Brünninger beantwortet Anfragen werktags, Telefon <a href="' + TEL_HREF + '">' + TEL + '</a>.</p>';
  const TAFEL = {
    start() {
      if (istCatering()) return '<p class="rubrik">So lesen Sie den Kalender</p><h3 id="tafel-titel" tabindex="-1">Zwei Feiern an einem Tag sind möglich.</h3>'
        + '<p>Unsere Küche kocht bis zu 400 Gäste am Tag. Die Zahl im Kalender sagt, für wie viele Gäste wir an diesem Tag noch Platz haben.</p>'
        + '<ol class="schritte"><li>Gästezahl eintragen, der Kalender rechnet mit.</li><li>Tag wählen, Anfrage mit Ort und Form senden.</li><li>Probeessen, dann Angebot mit Personal und Geschirr.</li></ol>' + kontakt;
      return '<p class="rubrik">So lesen Sie den Kalender</p><h3 id="tafel-titel" tabindex="-1">Jeden Tag gibt es bei uns nur einmal.</h3>'
        + '<p>Freie Tage übernehmen Sie mit einem Klick in die Anfrage. „Angefragt“ heißt: Ein Paar hält den Tag gerade als Option, nach der Besichtigung bis zu sieben Tage kostenlos.</p>'
        + '<ol class="schritte"><li>Tag wählen oder oben eintippen.</li><li>Anfrage mit Anlass und Gästezahl senden.</li><li>Besichtigung, danach Option für sieben Tage.</li></ol>' + kontakt;
    },
    frei({ d }) {
      return '<span class="marke-status"><span class="z z-frei" aria-hidden="true"></span>Frei</span><h3 id="tafel-titel" tabindex="-1">' + lang(d) + '</h3>'
        + (FEIERTAG[iso(d)] ? '<p class="leise">Feiertag: ' + FEIERTAG[iso(d)] + '. Gäste reisen oft schon am Vortag an.</p>' : '')
        + '<p>Der Tag ist frei. Mit Ihrer Anfrage ist er noch nicht reserviert; nach der Besichtigung halten wir ihn sieben Tage kostenlos für Sie.</p>'
        + '<div class="reihe"><button type="button" class="knopf knopf-haupt" data-aktion="anfragen">Datum übernehmen</button></div>';
    },
    option({ d, t }) {
      return '<span class="marke-status"><span class="z z-angefragt" aria-hidden="true"></span>Angefragt</span><h3 id="tafel-titel" tabindex="-1">' + lang(d) + '</h3>'
        + '<p>Ein Paar hält diesen Tag bis ' + fOhneJahr.format(t.bis) + '. Sagt es ab oder läuft die Option aus, fragen wir Sie als Nächste.</p>'
        + '<div class="reihe"><button type="button" class="knopf knopf-haupt" data-aktion="vormerken">Als Nächste vormerken</button></div>'
        + '<p class="leise">Frei in der Nähe:</p><div class="vorschlaege">' + chipHtml(naheFreie(d)) + '</div>';
    },
    vergeben({ d, t }) {
      const c = istCatering();
      const satz = t.s === 'zuknapp' ? 'An diesem Tag kochen wir schon für eine andere Feier. Für ' + gaeste().summe + ' Gäste reicht es nicht mehr, möglich sind noch ' + t.rest + ' Gäste.'
        : c ? 'An diesem Tag ist unsere Küche ausgebucht.' : 'An diesem Tag feiert schon jemand bei uns. Wir vergeben jeden Tag nur einmal.';
      const marke = t.s === 'zuknapp' ? '<span class="z z-zuknapp" aria-hidden="true"></span>Zu knapp' : '<span class="z z-' + t.s + '" aria-hidden="true"></span>' + (c ? 'Ausgebucht' : 'Vergeben');
      return '<span class="marke-status">' + marke + '</span><h3 id="tafel-titel" tabindex="-1">' + lang(d) + '</h3><p>' + satz + '</p>'
        + (t.s === 'zuknapp' ? '<p class="leise">Mit Personal aus unserem Netzwerk geht manchmal mehr. Rufen Sie an: <a href="' + TEL_HREF + '">' + TEL + '</a>.</p>' : '')
        + '<p class="leise">' + (c ? 'Passend in der Nähe:' : 'Frei in der Nähe:') + '</p><div class="vorschlaege">' + chipHtml(naheFreie(d)) + '</div>';
    },
    anfrage({ art }) {
      const d = datumAusLeiste(), t = Z.gepflegt && d instanceof Date ? tagInfo(d) : null;
      const ausweichPflicht = !!t && (t.s === 'vergeben' || t.s === 'ausgebucht');
      const mitAusweich = Z.gepflegt && (art === 'vormerkung' || ausweichPflicht || fFlex.checked);
      return '<div class="spalte-a">' + spalteA(art) + '</div>'
        + '<form class="anfrage spalte-b" id="anfrage-form" novalidate>'
        + (mitAusweich ? feld('a-ausweich', 'Ausweichdatum', ausweichPflicht ? 'TT.MM.JJJJ' : 'TT.MM.JJJJ, optional', 'inputmode="numeric" autocomplete="off"' + (ausweichPflicht ? ' required' : '')) : '')
        + feld('a-name', 'Ihr Name', '', 'autocomplete="name" required') + feld('a-mail', 'E-Mail', '', 'type="email" autocomplete="email" required') + feld('a-tel', 'Telefon', 'optional', 'type="tel" autocomplete="tel"')
        + '<p class="platzhalter-recht"><b>Platzhalter · vom Kunden prüfen lassen</b>Hinweis zum Datenschutz: wofür wir Ihre Angaben nutzen, wie lange wir sie speichern, Link zur Datenschutzerklärung.</p>'
        + '<button class="knopf knopf-haupt" type="submit">' + (art === 'vormerkung' ? 'Vormerkung senden' : 'Anfrage senden') + '</button>'
        + '<p class="leise">Wir antworten werktags innerhalb von 48 Stunden. Reserviert ist der Tag erst mit der Option nach der Besichtigung.</p></form>';
    },
    danke({ name, termin, art }) {
      const bis = werktagePlus(HEUTE, 2);
      return '<div class="spalte-a"><p class="rubrik">' + (art === 'vormerkung' ? 'Vormerkung gesendet' : 'Anfrage gesendet') + '</p><h3 id="tafel-titel" tabindex="-1">Danke' + (name ? ', ' + name : '') + '. Wir melden uns bis ' + fOhneJahr.format(bis) + '.</h3>'
        + '<p>Ihre ' + (art === 'vormerkung' ? 'Vormerkung' : 'Anfrage') + ' für ' + termin + ' ist bei uns. Der Tag ist damit noch nicht reserviert.</p></div>'
        + '<div class="spalte-b"><ol class="schritte"><li>Wir rufen an und vereinbaren eine Besichtigung' + (istCatering() ? ' oder ein Probeessen' : '') + '.</li><li>Passt alles, halten wir den Tag sieben Tage kostenlos als Option.</li><li>Mit dem Vertrag ist er Ihrer. Anzahlung und Storno stehen vorher im Angebot.</li></ol>'
        + '<div class="reihe"><button type="button" class="knopf knopf-neben" data-aktion="neu">Neue Anfrage</button></div></div>';
    }
  };
  function spalteA(art) {
    const g = gaeste(), flex = fFlex.checked, d = datumAusLeiste(), t = Z.gepflegt && d instanceof Date ? tagInfo(d) : null;
    const name = t ? { frei: 'frei', angefragt: 'angefragt', vergeben: 'vergeben', kapazitaet: 'noch ' + t.rest + ' Gäste', zuknapp: 'zu knapp', ausgebucht: 'ausgebucht', vorbei: 'vorbei' }[t.s] : '';
    const termin = flex ? fMonat.options[fMonat.selectedIndex].text + ', Tag flexibel'
      : d instanceof Date ? lang(d) + (t ? ' <span class="marke-status"><span class="z z-' + t.s + '" aria-hidden="true"></span>' + name + '</span>' : '') : 'noch offen';
    const ausweichPflicht = !!t && (t.s === 'vergeben' || t.s === 'ausgebucht');
    const aw = !Z.gepflegt && liesDatum(fAusweich.value) instanceof Date ? lang(liesDatum(fAusweich.value)) : '';
    return '<p class="rubrik">Übernommen aus Ihrer Auswahl</p><h3 id="tafel-titel" tabindex="-1">' + (art === 'vormerkung' ? 'Ihre Vormerkung' : 'Ihre Anfrage') + '</h3>'
      + '<dl class="zus"><div><dt>' + (istCatering() ? 'Form' : 'Anlass') + '</dt><dd>' + fAnlass.value + '</dd></div><div><dt>Wunschtermin</dt><dd>' + termin + '</dd></div>'
      + (aw ? '<div><dt>Ausweichdatum</dt><dd>' + aw + '</dd></div>' : '')
      + '<div><dt>Gäste</dt><dd>' + gaesteText(g) + '</dd></div>'
      + (istCatering() ? '<div><dt>Ort</dt><dd>' + (fOrt.value.trim() || 'noch offen') + '</dd></div>' : '') + '</dl>'
      + (art === 'vormerkung' ? '<p class="leise">Sie stehen als Nächste auf der Liste. Wird der Tag frei, rufen wir Sie an.</p>' : '')
      + (ausweichPflicht ? '<p class="leise">Der Wunschtag ist vergeben. Nennen Sie bitte ein Ausweichdatum.</p>' : '')
      + '<p><button type="button" class="link" data-aktion="aendern">Angaben ändern</button></p>';
  }
  function zusammenfassungNeu() {
    if (tafel.dataset.modus !== 'anfrage') return;
    const a = tafel.querySelector('.spalte-a');
    if (a) a.innerHTML = spalteA(tafel.dataset.art);
  }
  function feld(id, label, hinweis, attr) {
    return '<div class="feld"><label for="' + id + '">' + label + (hinweis ? ' <span class="format">' + hinweis + '</span>' : '') + '</label><input class="eingabe" id="' + id + '" ' + attr + '><span class="fehler" id="' + id + '-f" hidden></span></div>';
  }
  function chipHtml(l) {
    return l.map(d => '<button type="button" class="chip" data-aktion="waehle" data-iso="' + iso(d) + '" aria-label="' + lang(d) + ' übernehmen"><span class="z ' + (istCatering() ? 'z-kapazitaet' : 'z-frei') + '" aria-hidden="true"></span>' + kurz(d) + '</button>').join('');
  }
  function fokusTafel() {
    const h = $('#tafel-titel');
    if (!h) return;
    if (Z.vorf) return;
    h.focus({ preventScroll: true });
    const r = tafel.getBoundingClientRect();
    if (r.top < 0 || r.top > innerHeight * .6) tafel.scrollIntoView({ behavior: reduziert() ? 'auto' : 'smooth', block: 'start' });
  }

  /* ---------- Übergabe an die Anfrage ---------- */
  function uebergabe(art) {
    const d = datumAusLeiste();
    if (!fFlex.checked && (d === null || d === 'ungueltig')) {
      fDatum.setAttribute('aria-invalid', 'true');
      sage(d === null ? 'Bitte ein Wunschdatum eintragen oder „Datum flexibel“ wählen.' : 'Bitte das Datum als TT.MM.JJJJ eingeben, zum Beispiel 12.06.2027.', true);
      if (!Z.vorf) fDatum.focus();
      return;
    }
    if (d instanceof Date && d < HEUTE) { pruefe(); if (!Z.vorf) fDatum.focus(); return; }
    tafelZeigen('anfrage', { art: art || 'anfrage' });
    fokusTafel();
  }

  function waehleTag(d, quelle) {
    const t = tagInfo(d);
    Z.fokus = d;
    if (t.s === 'vorbei') { sage('<b>' + lang(d) + '</b> liegt in der Vergangenheit.', true); return; }
    if (waehlbar(t.s)) {
      Z.gewaehlt = d;
      fFlex.checked = false; flexAnzeigen();
      fDatum.value = feldwert(d);
      fDatum.classList.remove('blitz'); void fDatum.offsetWidth;
    }
    if (Z.v !== 'leiste' && Z.gepflegt) {
      if (d.getMonth() !== Z.monat.getMonth() || d.getFullYear() !== Z.monat.getFullYear()) geheZuMonat(d); else markiere();
    }
    pruefe();
    if (t.s === 'frei' || t.s === 'kapazitaet') {
      if (Z.v === 'leiste') { fDatum.classList.add('blitz'); }
      else if (quelle === 'kalender') uebergabe('anfrage');
      else tafelZeigen('frei', { d, t });
    }
    else if (t.s === 'angefragt') { if (Z.v !== 'leiste') tafelZeigen('option', { d, t }); }
    else if (Z.v !== 'leiste') tafelZeigen('vergeben', { d, t });
  }

  /* ---------- Ereignisse ---------- */
  function flexAnzeigen() {
    const f = fFlex.checked;
    fDatum.hidden = f; fMonat.hidden = !f;
    $('#l-datum').firstChild.textContent = f ? 'Wunschmonat ' : 'Wunschdatum ';
    $('#l-datum .format').hidden = f;
    $('#l-datum').htmlFor = f ? 'f-monat' : 'f-datum';
  }
  fFlex.addEventListener('change', () => {
    flexAnzeigen();
    if (fFlex.checked) { const d = liesDatum(fDatum.value); if (d instanceof Date && imBereich(d)) fMonat.value = iso(new Date(d.getFullYear(), d.getMonth(), 1)); else fMonat.value = iso(Z.monat); }
    Z.gewaehlt = null; if (Z.v !== 'leiste' && Z.gepflegt) { markiere(); geheZuMonat(vonIso(fMonat.value)); }
    pruefe();
  });
  fMonat.addEventListener('change', () => { if (Z.v !== 'leiste' && Z.gepflegt) geheZuMonat(vonIso(fMonat.value)); pruefe(); });
  function datumGeaendert(fertig) {
    const d = liesDatum(fDatum.value);
    if (d instanceof Date) {
      if (!fertig && !/\d{4}$|\d{1,2}\.\d{1,2}\.\d{2}$/.test(fDatum.value.trim())) return;
      if (imBereich(d) && d >= HEUTE) { waehleTag(d, 'feld'); return; }
    }
    Z.gewaehlt = null; if (Z.v !== 'leiste' && Z.gepflegt) markiere();
    if (fertig || d === null) pruefe();
  }
  fDatum.addEventListener('input', () => datumGeaendert(false));
  fDatum.addEventListener('change', () => datumGeaendert(true));
  [fErw, fKind].forEach(f => f.addEventListener('input', () => { if (istCatering() && Z.gepflegt) { zeichne(0); } pruefe(); }));
  [fAusweich, fOrt].forEach(f => f.addEventListener('input', zusammenfassungNeu));
  fAnlass.addEventListener('change', zusammenfassungNeu);
  leiste.addEventListener('submit', e => { e.preventDefault(); uebergabe('anfrage'); });

  const knopfFuell = e => {
    const k = e.target.closest && e.target.closest('.knopf-haupt');
    if (!k || reduziert()) return;
    const r = k.getBoundingClientRect(), x = e.clientX - r.left, y = e.clientY - r.top;
    const R = Math.hypot(Math.max(x, r.width - x), Math.max(y, r.height - y)) + 2;
    if (e.type === 'pointerover' || e.type === 'pointerdown') {
      if (e.type === 'pointerover' && e.relatedTarget && k.contains(e.relatedTarget)) return;
      if (e.type === 'pointerover' && e.pointerType !== 'mouse') return;
      k.classList.add('ohne-weg'); k.style.setProperty('--x', x + 'px'); k.style.setProperty('--y', y + 'px'); k.style.setProperty('--r', '0px');
      void k.offsetWidth; k.classList.remove('ohne-weg'); k.style.setProperty('--r', R + 'px');
      if (e.pointerType !== 'mouse') setTimeout(() => { k.style.setProperty('--r', '0px'); }, 725);
    } else if (e.type === 'pointerout') {
      if (e.relatedTarget && k.contains(e.relatedTarget)) return;
      k.style.setProperty('--x', x + 'px'); k.style.setProperty('--y', y + 'px'); k.style.setProperty('--r', '0px');
    }
  };
  ['pointerover', 'pointerout', 'pointerdown'].forEach(t => document.addEventListener(t, knopfFuell));

  document.addEventListener('click', e => {
    const a = e.target.closest('[data-aktion]');
    if (a) {
      const x = a.dataset.aktion;
      if (x === 'waehle') waehleTag(vonIso(a.dataset.iso), 'chip');
      else if (x === 'vormerken') uebergabe('vormerkung');
      else if (x === 'anfragen') uebergabe('anfrage');
      else if (x === 'aendern') { fDatum.hidden ? fMonat.focus() : fDatum.focus(); leiste.scrollIntoView({ behavior: reduziert() ? 'auto' : 'smooth', block: 'center' }); }
      else if (x === 'neu') { leiste.reset(); Z.gewaehlt = null; flexAnzeigen(); pruefe(); if (Z.v !== 'leiste' && Z.gepflegt) markiere(); tafelZeigen(Z.v === 'leiste' || Z.gepflegt ? 'start' : 'anfrage', { art: 'anfrage' }); fDatum.focus(); }
      return;
    }
    const tag = e.target.closest('.tag');
    if (tag) waehleTag(vonIso(tag.dataset.iso), 'kalender');
  });
  $('#zurueck').addEventListener('click', e => { if (e.currentTarget.getAttribute('aria-disabled')) return; Z.fokus = null; geheZuMonat(new Date(Z.monat.getFullYear(), Z.monat.getMonth() - 1, 1)); });
  $('#vor').addEventListener('click', e => { if (e.currentTarget.getAttribute('aria-disabled')) return; Z.fokus = null; geheZuMonat(new Date(Z.monat.getFullYear(), Z.monat.getMonth() + 1, 1)); });

  monat.addEventListener('keydown', e => {
    const b = e.target.closest('.tag');
    if (!b) return;
    const d = vonIso(b.dataset.iso), wd = (d.getDay() + 6) % 7;
    const z = { ArrowLeft: plus(d, -1), ArrowRight: plus(d, 1), ArrowUp: plus(d, -7), ArrowDown: plus(d, 7), Home: plus(d, -wd), End: plus(d, 6 - wd),
      PageUp: new Date(d.getFullYear(), d.getMonth() - 1, Math.min(d.getDate(), new Date(d.getFullYear(), d.getMonth(), 0).getDate())),
      PageDown: new Date(d.getFullYear(), d.getMonth() + 1, Math.min(d.getDate(), new Date(d.getFullYear(), d.getMonth() + 2, 0).getDate())) }[e.key];
    if (!z) return;
    e.preventDefault();
    if (!imBereich(z)) return;
    Z.fokus = z;
    if (z.getMonth() !== Z.monat.getMonth() || z.getFullYear() !== Z.monat.getFullYear()) { geheZuMonat(z, iso(z)); return; }
    b.tabIndex = -1;
    const n = monat.querySelector('[data-iso="' + iso(z) + '"]');
    n.tabIndex = 0; n.focus();
  });

  tafel.addEventListener('submit', e => {
    e.preventDefault();
    const f = e.target, felder = [...f.querySelectorAll('input')];
    let erster = null;
    felder.forEach(i => {
      const fe = $('#' + i.id + '-f');
      let msg = '';
      if (i.required && !i.value.trim()) msg = { 'a-name': 'Bitte Ihren Namen angeben.', 'a-mail': 'Bitte Ihre E-Mail-Adresse angeben.', 'a-ausweich': 'Bitte ein Ausweichdatum angeben.' }[i.id];
      else if (i.id === 'a-mail' && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(i.value.trim())) msg = 'Die E-Mail-Adresse braucht ein @ und eine Endung, zum Beispiel name@beispiel.de.';
      else if (i.id === 'a-ausweich' && i.value.trim() && !(liesDatum(i.value) instanceof Date)) msg = 'Bitte als TT.MM.JJJJ eingeben.';
      if (msg) { fe.textContent = msg; fe.hidden = false; i.setAttribute('aria-invalid', 'true'); i.setAttribute('aria-describedby', fe.id); erster = erster || i; }
      else { fe.hidden = true; i.removeAttribute('aria-invalid'); i.removeAttribute('aria-describedby'); }
    });
    if (erster) { erster.focus(); return; }
    const d = datumAusLeiste(), g = gaeste();
    const termin = (fFlex.checked ? fMonat.options[fMonat.selectedIndex].text : d instanceof Date ? lang(d) : 'Ihren Wunschtag') + ' (' + fAnlass.value + (g.summe ? ', ' + g.summe + ' Gäste' : '') + ')';
    tafelZeigen('danke', { name: ($('#a-name').value.trim().split(/\s+/)[0] || ''), termin, art: f.querySelector('[type=submit]').textContent.startsWith('Vormerkung') ? 'vormerkung' : 'anfrage' });
    fokusTafel();
  });

  /* ---------- Varianten und Zustand ---------- */
  const EINSTIEG = {
    kalender: ['Freie Termine 2026 und 2027', 'Ist Ihr Tag noch frei?', 'Wir vergeben jeden Tag nur einmal. Hier sehen Sie, was frei ist, was gerade jemand für sich hält und was vergeben ist.'],
    catering: ['Catering außer Haus · bis 400 Gäste', 'Wir kochen, wo Sie feiern.', 'Die Küche von Gut Weidenhain kocht auch an Ihrem Ort, an einem Tag oft für zwei Feiern. Der Kalender zeigt, für wie viele Gäste wir noch Platz haben.'],
    ohne: ['Anfrage', 'Ihr Wunschtermin', 'Nennen Sie uns Ihren Wunschtag und ein Ausweichdatum. Wir prüfen beide und melden uns innerhalb von 48 Stunden.']
  };
  function aufbauen() {
    const v = Z.v;
    body.dataset.v = v;
    body.classList.toggle('ohne-kalender', !Z.gepflegt);
    $('#hero').hidden = v !== 'leiste';
    $('#einstieg').hidden = v === 'leiste';
    const e = EINSTIEG[!Z.gepflegt && v !== 'leiste' ? 'ohne' : v] || EINSTIEG.kalender;
    $('#einstieg-rubrik').textContent = e[0]; $('#einstieg-titel').textContent = e[1]; $('#einstieg-text').textContent = e[2];
    $('.feld-ort').hidden = v !== 'catering';
    $('.feld-ausweich').hidden = Z.gepflegt;
    leiste.classList.toggle('mit-ausweich', !Z.gepflegt && v !== 'catering');
    leiste.classList.toggle('ausweich-c', !Z.gepflegt && v === 'catering');
    fErw.placeholder = v === 'catering' ? '120' : '80';
    anlassOptionen(); legende(); stand();
    if (Z.gepflegt) zeichne(0);
    tafelZeigen(!Z.gepflegt && v !== 'leiste' ? 'anfrage' : 'start', { art: 'anfrage' });
    pruefe();
    document.querySelectorAll('[data-variante]').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.variante === v)));
    $('#gepflegt').setAttribute('aria-pressed', String(Z.gepflegt));
    $('#gepflegt').textContent = Z.gepflegt ? 'Kalender gepflegt' : 'Stand veraltet';
  }
  function zuruecksetzen() {
    leiste.reset(); fDatum.removeAttribute('aria-invalid');
    Z.gewaehlt = null; Z.fokus = null; Z.monat = new Date(2027, 5, 1);
    flexAnzeigen();
  }
  function variante(v, schreiben) {
    Z.v = ['leiste', 'kalender', 'catering'].includes(v) ? v : 'leiste';
    if (schreiben) {
      const q = new URLSearchParams(location.search);
      if (q.get('v') !== Z.v) { q.set('v', Z.v); history.replaceState(null, '', '?' + q.toString().replace(/=(&|$)/g, '$1')); }
    }
    zuruecksetzen(); Z.ohneAnim = true; aufbauen(); Z.ohneAnim = false;
  }
  document.querySelectorAll('[data-variante]').forEach(b => b.addEventListener('click', () => { stop(); variante(b.dataset.variante, true); }));
  $('#gepflegt').addEventListener('click', () => { stop(); Z.gepflegt = !Z.gepflegt; zuruecksetzen(); Z.ohneAnim = true; aufbauen(); Z.ohneAnim = false; });
  // Ein Stand älter als drei Wochen blendet den Kalender aus (im echten Einsatz aus der JSON-Datei)
  Z.gepflegt = (HEUTE - STAND.ja) / 864e5 < FRIST_TAGE;

  /* ---------- Vorführung ---------- */
  const zeiger = $('#zeiger');
  let lauf = 0, pausiert = false, zx = innerWidth * .5, zy = innerHeight * .8;
  function stop() { lauf++; Z.vorf = false; zeiger.classList.remove('an'); }
  async function warte(ms) {
    const t = lauf;
    await new Promise(r => setTimeout(r, ms));
    while (pausiert && t === lauf) await new Promise(r => setTimeout(r, 150));
    if (t !== lauf) throw new Error('abbruch');
  }
  function setzeZeiger(x, y, sofort) {
    zx = x; zy = y;
    zeiger.style.transition = sofort || reduziert() ? 'opacity 400ms ' + KURVE : 'opacity 400ms ' + KURVE + ', transform 725ms ' + KURVE;
    zeiger.style.transform = 'translate(' + x + 'px,' + y + 'px)';
  }
  async function zu(el, dx = .5, dy = .55) {
    if (!el) return;
    const r = el.getBoundingClientRect();
    if (r.bottom > innerHeight - 20 || r.top < 0) {
      window.scrollTo({ top: Math.max(0, scrollY + r.top - innerHeight * .35), behavior: reduziert() ? 'auto' : 'smooth' });
      await warte(reduziert() ? 80 : 650);
    }
    const q = el.getBoundingClientRect();
    setzeZeiger(q.left + q.width * dx, q.top + q.height * dy);
    await warte(reduziert() ? 200 : 760);
  }
  async function tipp(el) {
    zeiger.classList.remove('klick'); void zeiger.offsetWidth; zeiger.classList.add('klick');
    const k = el.closest('.knopf-haupt');
    if (k && !reduziert()) { const r = k.getBoundingClientRect(); k.classList.add('ohne-weg'); k.style.setProperty('--x', (zx - r.left) + 'px'); k.style.setProperty('--y', (zy - r.top) + 'px'); k.style.setProperty('--r', '0px'); void k.offsetWidth; k.classList.remove('ohne-weg'); k.style.setProperty('--r', (r.width + 20) + 'px'); await warte(480); setTimeout(() => k.style.setProperty('--r', '0px'), 600); }
    await warte(120);
    el.click();
  }
  async function tippe(el, text) {
    el.value = '';
    for (const c of text) { el.value += c; el.dispatchEvent(new Event('input', { bubbles: true })); await warte(reduziert() ? 20 : 85); }
    el.dispatchEvent(new Event('change', { bubbles: true }));
  }
  const zelle = s => monat.querySelector('[data-iso="' + s + '"]');
  const chipFuer = s => document.querySelector('#vorschlaege [data-iso="' + s + '"]');
  async function szene() {
    neustart(true);
    zeiger.classList.add('an');
    setzeZeiger(innerWidth * .62, innerHeight * .9, true);
    await warte(500);
    if (!Z.gepflegt) {
      await zu(fDatum); await tippe(fDatum, '12.06.2027'); await warte(500);
      if (!fAusweich.closest('[hidden]')) { await zu(fAusweich); await tippe(fAusweich, '19.06.2027'); await warte(400); }
      await zu(fErw); await tippe(fErw, '96'); await warte(300);
      await zu($('#anfragen')); await tipp($('#anfragen')); await warte(800);
      await zu($('#tafel-titel'), .3, .5); await warte(2600);
      return;
    }
    if (Z.v === 'leiste') {
      await zu(fDatum, .35); await tippe(fDatum, '05.06.2027'); await warte(1700);
      const c = chipFuer('2027-06-12'); if (c) { await zu(c); await tipp(c); } await warte(1100);
      await zu(fErw, .4); await tippe(fErw, '196'); await warte(1700);
      await tippe(fErw, '96'); await zu(fKind, .4); await tippe(fKind, '8'); await warte(500);
      await zu($('#anfragen'), .6); await tipp($('#anfragen')); await warte(500);
      await zu($('#tafel-titel'), .3, .5); await warte(3000);
      window.scrollTo({ top: 0, behavior: reduziert() ? 'auto' : 'smooth' });
      await warte(700);
      return;
    }
    if (Z.v === 'catering') { await zu(fErw, .4); await tippe(fErw, '140'); await warte(1100); }
    await zu(fFlex.parentElement, .2); await tipp(fFlex); await warte(500);
    fMonat.value = '2027-05-01'; fMonat.dispatchEvent(new Event('change', { bubbles: true })); await warte(1500);
    await zu($('#vor')); await tipp($('#vor')); await warte(900);
    await zu(zelle('2027-06-19')); await tipp(zelle('2027-06-19')); await warte(2300);
    await zu(zelle('2027-06-12')); await tipp(zelle('2027-06-12')); await warte(700);
    await zu($('#tafel-titel'), .3, .5); await warte(2800);
  }
  async function vorfuehren() {
    stop();
    const t = lauf;
    Z.vorf = true; pausiert = false;
    try { while (t === lauf) await szene(); } catch (e) { /* abgebrochen */ }
  }
  function neustart(intern) {
    if (!intern) stop();
    zuruecksetzen(); Z.ohneAnim = true; aufbauen(); Z.ohneAnim = false;
    window.scrollTo({ top: 0, behavior: 'auto' });
  }
  function pause() { pausiert = true; }
  function weiter() { if (Z.vorf) pausiert = false; else vorfuehren(); }
  ['pointerdown', 'keydown'].forEach(t => addEventListener(t, e => { if (Z.vorf && e.isTrusted && !e.target.closest('[data-fundus-pult]')) stop(); }, true));

  variante(new URLSearchParams(location.search).get('v'), false);

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