Räume mit Kapazitäten je Bestuhlung
Räume × Bestuhlung als echte Tabelle mit Fläche, Mindestgästen und Saisonmiete; ein Filter nach Gästezahl und Form markiert, was passt, und je Raum zeigt ein SVG-Plan mit Text, wie gestellt wird.
Locations, Tagungshäuser und Säle mit drei bis acht Räumen: alle Zahlen auf einen Blick, Filter nach Gästezahl und Form, Plan je Zeile.
Wer 120 Gäste mit Tanzfläche plant, will sofort wissen, welcher Raum passt. Eine echte Tabelle zeigt je Raum die Fläche und die Plätze je Bestuhlung (Tafel, Rundtische, Reihe, parlamentarisch, U-Form, Stehempfang), dazu Mindestgäste und Raummiete nach Saison, gruppiert nach Gebäude. Ein Filter nach Gästezahl und Form markiert passende Räume und kennzeichnet die anderen als „zu klein“ oder „zu groß“, ohne sie zu löschen. Je Raum gibt es einen Plan als SVG für die gewählte Bestuhlung, immer mit Textbeschreibung.
Die Gästezahl ist nach dem Datum die zweite harte Frage. Zahlen je Bestuhlung beantworten sie ohne Rückfrage, und der Plan zeigt, wie es sich anfühlt: Wo ist die Tanzfläche, wo das Buffet? Eine Location mit nur einer Kapazitätsangabe lässt genau das offen. Mindestgäste und Saisonmiete daneben ersparen enttäuschte Anfragen.
Wann einsetzen
Hochzeitslocation, Gutshof, Tagungshotel, Festsaal, Stadthalle, Kulturhaus mit vermietbaren Sälen, Restaurant mit Nebenräumen: überall mit zwei bis etwa acht Räumen, die je nach Anlass anders gestellt werden.
Wann nicht
Bei einem einzigen Raum mit einer Bestuhlung genügt ein Satz mit der Höchstzahl. Ab etwa 15 Räumen (Messe, Kongresszentrum) passt facettenfilter-ergebnisliste besser. Zahlen ohne Genehmigung oder Bestuhlungsplan nicht veröffentlichen: Höchstzahlen kommen vom Kunden und werden vom Kunden geprüft.
Varianten
- Vergleichstabelle aller Räume mit Filter, fester erster Spalte und Plan je Zeile
- Ein Raum mit Plan-Umschalter je Bestuhlung, Reiter nennen den Raum
- Gelände als Skizze, Räume darin anklickbar, mit Wegen ohne Stufen
- Telefon: Karten je Raum statt Tabelle
Umsetzung
Die Räume liegen als Daten vor ({gebaeude, name, l, b, plaetze: {tafel, rund, reihe, parlament, u, steh}, mindest, miete: [sommer, winter], zugang, stufenlos, gruende}); daraus entstehen Tabelle, Karten, Raumansicht, Gelände und Pläne. Die Tabelle hat zwei Kopfzeilen (Plätze je Bestuhlung und Raummiete als colgroup), je Gebäude ein tbody mit Gruppenkopf und eine feste erste Spalte (position: sticky; left: 0) im waagerecht scrollbaren Rahmen mit tabindex=0. Unmögliche Bestuhlungen stehen als „–“ mit Fußnote und Grund. Der Filter setzt je Raum passt, zu klein (Plätze unter der Gästezahl) oder zu groß (Mindestgäste über der Gästezahl), als Text und Zeichen in der Zeile, und meldet die Trefferzahl in einer Live-Region. Passt kein Raum, schlägt er zwei Räume im selben Gebäude vor oder Zelt und Telefon. Pläne entstehen als SVG aus Raummaßen und Bestuhlung (Tische, Stühle, Tanzfläche, Bühne, Tore, Maßstab) und tragen role=img mit aria-labelledby auf eine sichtbare Beschreibung.
Robust umsetzen
Telefon. Unter 700 px wird aus der Tabelle eine Karte je Raum: Gebäude und Ebene, Name, Fläche, Maße und Zugang, darunter die Plätze je Bestuhlung mit Tabellenziffern, Mindestgäste, Miete und der Plan-Knopf. Der Filter bleibt oben, die Trefferzahl direkt darunter. Auf dem Gelände ersetzt eine Knopfreihe die Schilder in der Skizze.
Ohne Bewegung. Plan öffnet und wechselt ohne Weg, nur Überblendung in 200 ms; Filterzustände wechseln sofort.
Barrierefrei. Echte Tabelle mit caption, Spaltenköpfen (scope=col und colgroup), Zeilenköpfen (scope=row) und Gruppenköpfen je Gebäude (scope=rowgroup); erste Spalte fest, der Scrollrahmen ist per Tastatur erreichbar (tabindex=0, role=region mit Namen). Zahlen in Tabellenziffern. „–“ hat eine verlinkte Fußnote und einen Text für Screenreader. Filter als fieldset mit legend, Bestuhlung als Radiogruppe, Trefferzahl in einer Live-Region. Zustände stehen als Text in der Zeile, nicht nur als Farbe. Pläne als SVG mit role=img und aria-labelledby auf die sichtbare Beschreibung. Plan-Knöpfe mit aria-expanded und aria-controls. Raumreiter als tablist mit Pfeiltasten, Pos1 und Ende; unmögliche Bestuhlungen sind deaktiviert und nennen den Grund im Text. Die Geländeskizze hat eine Beschreibung, die Räume darin sind Knöpfe mit vollem Namen.
Code
<!DOCTYPE html>
<!--
Räume mit Kapazitäten je Bestuhlung
Fundus · Sektionen & Funktionen · https://cecodesign.de/lab/fundus/sektionen/raumkapazitaeten-bestuhlung/
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>Räume mit Kapazitäten je Bestuhlung · 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: Räume, Preise und Pläne stehen immer auf Kalk; Mohnrot nur als 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;
--kurve: cubic-bezier(0.625, 0.05, 0, 1);
--t1: 250ms; --t2: 400ms; --t3: 475ms; --t4: 525ms; --t5: 725ms;
--spalte1: 13.5rem;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { background: var(--kalk); color: var(--tinte); font-family: 'Archivo', system-ui, sans-serif; -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 { font: inherit; color: inherit; }
a { color: inherit; }
:focus-visible { outline: 2px solid var(--tinte); outline-offset: 3px; }
[hidden] { display: none !important; }
.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; }
.seite { max-width: 1240px; margin: 0 auto; padding: 0 clamp(16px, 4vw, 48px); }
/* 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; }
.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; border-bottom: 1px solid transparent; transition: border-color var(--t2) var(--kurve); }
.kopf nav a:hover, .kopf nav a[aria-current] { border-bottom-color: var(--tinte); }
.kopf .tel { font-variant-numeric: tabular-nums; font-weight: 600; text-decoration: none; padding: 10px 0; }
/* Knöpfe */
.knopf { position: relative; isolation: isolate; display: inline-flex; align-items: center; justify-content: center; gap: 10px; min-height: 44px; padding: 0 20px; 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-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, .knopf-neben[aria-expanded="true"]::after { translate: 0 0; }
.knopf-neben[aria-expanded="true"] { background: var(--tinte); color: var(--kalk-hell); }
.knopf .pfeil { width: 14px; height: 14px; flex: none; transition: rotate var(--t3) var(--kurve); }
.knopf[aria-expanded="true"] .pfeil { rotate: 180deg; }
/* Einstieg */
.einstieg { padding-top: clamp(28px, 4vw, 48px); display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 27rem); 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; }
/* Filter */
.filter { margin-top: 26px; padding: 16px 20px 18px; background: var(--kalk-hell); border: 1px solid var(--linie); }
.filter-fs { border: 0; min-width: 0; display: grid; grid-template-columns: 9.5rem minmax(0, 1fr); gap: 10px 24px; align-items: start; }
.filter-fs > legend { padding: 0; font-stretch: 112%; font-weight: 600; font-size: .8125rem; letter-spacing: .08em; text-transform: uppercase; margin-bottom: 10px; }
.f-form { border: 0; min-width: 0; }
.f-form > legend { padding: 0; margin-bottom: 6px; }
.gruende { font-size: .875rem; color: var(--zweit); }
.plan .knopf-haupt { justify-self: start; margin-top: 4px; }
.f-gaeste { display: grid; gap: 6px; }
.f-gaeste label, .f-form-titel { font-size: .875rem; color: var(--zweit); line-height: 1.3; min-height: 20px; }
.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); -moz-appearance: textfield; appearance: textfield; }
.eingabe::-webkit-inner-spin-button, .eingabe::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.eingabe::placeholder { color: var(--zweit); opacity: 1; }
.eingabe:hover { border-color: var(--tinte); }
.eingabe:focus-visible { outline: 2px solid var(--tinte); outline-offset: 1px; }
.f-form { display: grid; gap: 6px; min-width: 0; }
.pillen { display: flex; flex-wrap: wrap; gap: 6px; }
.pille { position: relative; display: inline-flex; align-items: center; gap: 8px; min-height: 48px; padding: 0 14px; border: 1px solid var(--feldrand); background: #fff; border-radius: 2px; cursor: pointer; font-size: .9375rem; transition: background-color var(--t1) var(--kurve), border-color var(--t1) var(--kurve), color var(--t1) var(--kurve); }
.pille:hover { border-color: var(--tinte); }
.pille input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.pille:has(input:checked) { background: var(--tinte); color: var(--kalk-hell); border-color: var(--tinte); }
.pille:has(input:focus-visible) { outline: 2px solid var(--tinte); outline-offset: 2px; }
.pille .ik { width: 22px; height: 16px; flex: none; }
.pille .zahl { font-variant-numeric: tabular-nums; font-weight: 600; }
.pille[aria-disabled="true"], .pille:has(input[aria-disabled="true"]) { color: var(--zweit); background: transparent; border-style: dashed; cursor: not-allowed; }
.treffer { margin-top: 14px; min-height: 28px; font-size: 1rem; }
.treffer b { font-variant-numeric: tabular-nums; }
.kein-raum { margin-top: 12px; padding: 16px 20px; border: 1px solid var(--mohn-tief); border-left-width: 4px; background: var(--kalk-hell); display: grid; gap: 6px; }
.kein-raum strong { font-weight: 600; }
.kein-raum p { max-width: 70ch; }
/* Tabelle */
.tabelle-teil { margin-top: 18px; padding-bottom: 64px; }
.rahmen { position: relative; overflow-x: auto; background: var(--kalk-hell); border: 1px solid var(--linie); border-top: 3px double var(--messing); scrollbar-width: thin; scrollbar-color: var(--linie) transparent; }
.rahmen:focus-visible { outline: 2px solid var(--tinte); outline-offset: 3px; }
table.raeume { width: 100%; min-width: 1122px; table-layout: fixed; border-collapse: separate; border-spacing: 0; font-stretch: 87%; font-size: 1rem; }
.raeume col.w-fl { width: 76px; } .raeume col.w-f { width: 78px; } .raeume col.w-min { width: 84px; } .raeume col.w-m { width: 86px; } .raeume col.w-plan { width: 112px; } .raeume col.w-raum { width: var(--spalte1); }
.raeume caption { text-align: left; padding: 14px 18px 4px; font-stretch: 100%; font-size: .9375rem; color: var(--zweit); caption-side: top; }
.raeume th, .raeume td { padding: 12px 10px; border-bottom: 1px solid var(--linie); text-align: left; vertical-align: middle; background: var(--kalk-hell); transition: background-color var(--t1) var(--kurve), color var(--t2) var(--kurve); }
.raeume thead th { font-stretch: 87%; font-weight: 650; font-size: .75rem; letter-spacing: .05em; text-transform: uppercase; line-height: 1.25; vertical-align: bottom; padding-top: 10px; padding-bottom: 10px; }
.raeume thead th.ober { text-align: center; border-bottom-color: var(--messing); color: var(--zweit); }
.raeume .num { text-align: right; font-variant-numeric: tabular-nums; }
.raeume thead .form { text-align: right; }
.raeume thead .form .ik { display: block; width: 26px; height: 18px; margin: 0 0 6px auto; }
.raeume th[scope="row"], .raeume thead th.c-raum { position: sticky; left: 0; z-index: 1; width: var(--spalte1); min-width: var(--spalte1); padding-left: 18px; box-shadow: inset -1px 0 0 var(--linie); }
.raeume thead th.c-raum { z-index: 2; }
.raeume th[scope="row"] { font-stretch: 100%; font-weight: 600; font-size: 1.0625rem; line-height: 1.3; }
.raeume th[scope="row"] small { display: block; font-weight: 400; font-size: .8125rem; color: var(--zweit); margin-top: 2px; }
.raeume th[scope="row"] .zst { display: none; margin-top: 6px; font-stretch: 112%; font-size: .6875rem; letter-spacing: .06em; text-transform: uppercase; font-weight: 600; align-items: center; gap: 6px; }
.gefiltert .raeume th[scope="row"] .zst { display: inline-flex; }
.raeume tr.gruppe th { background: var(--sand-hell); padding: 8px 18px; font-stretch: 112%; font-weight: 600; font-size: .75rem; letter-spacing: .08em; text-transform: uppercase; }
.raeume tr.gruppe th span { position: sticky; left: 18px; display: inline-flex; gap: 10px; align-items: baseline; }
.raeume tr.gruppe th small { font-stretch: 100%; font-weight: 400; letter-spacing: 0; text-transform: none; font-size: .8125rem; color: var(--zweit); }
.raeume td.aktiv, .raeume th.aktiv { background: var(--sand-hell); }
.raeume thead th.aktiv { box-shadow: inset 0 -3px 0 var(--mohn); color: var(--tinte); }
.raeume td.nicht { color: var(--zweit); }
.raeume td.nicht sup a { font-size: .6875rem; text-decoration: none; padding: 0 2px; font-weight: 600; color: var(--messing); }
.raeume td.nicht sup a:hover { text-decoration: underline; }
.raeume .planknopf, .karte .planknopf { min-height: 44px; padding: 0 12px; font-size: .875rem; line-height: 1; }
.raeume td.c-plan { width: 108px; text-align: right; padding-right: 16px; }
/* Filterzustände: nichts verschwindet, unpassende Zeilen treten zurück */
.zeichen { display: inline-block; width: 9px; height: 9px; border-radius: 50%; flex: none; }
.z-passt { background: var(--mohn); }
.z-klein { border: 1.5px solid var(--zweit); }
.z-gross { border: 1.5px solid var(--zweit); background: linear-gradient(var(--zweit), var(--zweit)) center / 100% 1.5px no-repeat; }
.z-nein { border-radius: 0; background: linear-gradient(45deg, transparent 42%, var(--zweit) 42% 58%, transparent 58%), linear-gradient(-45deg, transparent 42%, var(--zweit) 42% 58%, transparent 58%); }
tr.passt th[scope="row"] { box-shadow: inset 4px 0 0 var(--mohn), inset -1px 0 0 var(--linie); }
tr.passt .zst { color: var(--mohn-tief); }
tr.weg th[scope="row"], tr.weg td { color: var(--zweit); }
tr.weg th[scope="row"] .name { font-weight: 500; }
tr.weg td.num:not(.aktiv) { color: #7C766C; }
.hinweise { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 12px 40px; margin-top: 14px; font-size: .875rem; color: var(--zweit); }
.fussnoten { list-style: none; display: grid; gap: 4px; }
.fussnoten li { display: grid; grid-template-columns: 1.4em minmax(0, 1fr); }
.fussnoten li:target { color: var(--tinte); background: var(--sand-hell); }
.platzhalter { display: inline-block; font-stretch: 112%; font-size: .6875rem; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--mohn-tief); border: 1px dashed var(--feldrand); padding: 1px 6px; margin-top: 4px; }
/* Plan */
.planzeile > td { padding: 0 !important; background: var(--kalk) !important; }
.plan-huelle { overflow: hidden; }
.plan { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); gap: 20px 32px; align-items: start; padding: 20px 18px 22px; }
.planzeile .plan { position: sticky; left: 0; width: min(100%, var(--sichtbar, 100%)); }
.plan figure { min-width: 0; }
.plan svg { display: block; width: 100%; height: auto; max-height: 420px; }
.plan .beschreibung { display: grid; gap: 10px; font-stretch: 100%; font-size: .9375rem; }
.plan .beschreibung h4 { font: 400 2rem/1.05 'Imbue', serif; }
.plan .beschreibung .gross { font: 300 3.25rem/1 'Imbue', serif; font-variant-numeric: tabular-nums; }
.plan .beschreibung .gross small { font: 600 .8125rem/1 'Archivo', sans-serif; font-stretch: 112%; letter-spacing: .08em; text-transform: uppercase; margin-left: 8px; }
.plan .formen-mini { display: flex; flex-wrap: wrap; gap: 6px; }
.plan .formen-mini button { min-height: 36px; padding: 0 10px; font-size: .8125rem; border: 1px solid var(--feldrand); background: #fff; border-radius: 2px; cursor: pointer; font-variant-numeric: tabular-nums; }
.plan .formen-mini button[aria-pressed="true"] { background: var(--tinte); color: var(--kalk-hell); border-color: var(--tinte); }
.plan .formen-mini button[aria-disabled="true"] { border-style: dashed; color: var(--zweit); background: transparent; cursor: not-allowed; }
.plan-wechsel { animation: planwechsel var(--t4) var(--kurve); }
@keyframes planwechsel { from { opacity: 0; } }
/* Karten (Telefon) */
.karten { list-style: none; display: none; gap: 14px; margin-top: 4px; }
.karte { background: var(--kalk-hell); border: 1px solid var(--linie); border-top: 3px double var(--messing); padding: 16px; display: grid; gap: 10px; }
.karte.passt { box-shadow: inset 4px 0 0 var(--mohn); }
.karte.weg { color: var(--zweit); }
.karte h3 { font: 400 2rem/1.05 'Imbue', serif; }
.karte .meta { font-size: .875rem; color: var(--zweit); }
.karte .zst { display: none; font-stretch: 112%; font-size: .75rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; align-items: center; gap: 6px; }
.gefiltert .karte .zst { display: inline-flex; }
.karte dl { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 0 16px; font-stretch: 87%; }
.karte dl div { display: flex; justify-content: space-between; gap: 8px; padding: 6px 0; border-bottom: 1px solid var(--linie); font-size: .9375rem; }
.karte dl div.aktiv { background: var(--sand-hell); font-weight: 600; }
.karte dd { font-variant-numeric: tabular-nums; text-align: right; }
.karte .plan { grid-template-columns: minmax(0, 1fr); padding: 6px 0 0; }
/* Variante raum */
.raum-teil { margin-top: 26px; padding-bottom: 64px; }
.raumreiter { display: flex; flex-wrap: wrap; gap: 0 22px; border-bottom: 1px solid var(--linie); }
.raumreiter button { position: relative; min-height: 48px; padding: 0 2px; background: none; border: 0; font-size: 1rem; color: var(--zweit); cursor: pointer; transition: color var(--t1) var(--kurve); }
.raumreiter button::after { content: ''; position: absolute; left: 0; right: 0; bottom: -1px; height: 3px; background: var(--mohn); transform: scaleX(0); transform-origin: left; transition: transform var(--t4) var(--kurve); }
.raumreiter button[aria-selected="true"] { color: var(--tinte); font-weight: 600; }
.raumreiter button[aria-selected="true"]::after { transform: scaleX(1); }
.raumreiter button:hover { color: var(--tinte); }
.raum-panel { display: grid; grid-template-columns: minmax(0, 21rem) minmax(0, 1fr); gap: 28px 48px; padding-top: 28px; }
.raum-panel:focus-visible { outline-offset: 6px; }
.raum-kopf .rubrik { color: var(--zweit); }
.raum-kopf h2 { font: 300 clamp(2.5rem, 1.6rem + 2.6vw, 4rem)/1 'Imbue', serif; letter-spacing: -.01em; margin: 8px 0 18px; text-wrap: balance; }
.fakten { display: grid; border-top: 1px solid var(--linie); }
.fakten div { display: grid; grid-template-columns: 8.5rem minmax(0, 1fr); gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--linie); font-size: .9375rem; }
.fakten dt { font-stretch: 112%; font-weight: 600; font-size: .75rem; letter-spacing: .08em; text-transform: uppercase; color: var(--zweit); padding-top: 3px; }
.fakten dd { font-variant-numeric: tabular-nums; }
.raum-plan { min-width: 0; display: grid; gap: 14px; align-content: start; }
.raum-plan .plan { padding: 0; grid-template-columns: minmax(0, 1fr); gap: 12px; }
.raum-plan .beschreibung { order: -1; grid-template-columns: auto minmax(0, 1fr); align-items: start; gap: 4px 24px; }
.raum-plan .beschreibung .rubrik { grid-column: 1 / -1; }
.raum-plan .beschreibung .gross { grid-row: span 3; }
.raum-plan .beschreibung .knopf-haupt, .raum-plan .beschreibung [data-anfrage-status] { grid-column: 2; }
.raum-plan .formwahl { border: 0; min-width: 0; }
.raum-plan .formwahl legend { font-stretch: 112%; font-weight: 600; font-size: .8125rem; letter-spacing: .08em; text-transform: uppercase; margin-bottom: 8px; }
/* Variante gelaende */
.gelaende-teil { margin-top: 18px; padding-bottom: 64px; }
.gelaende { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 320px); gap: 28px; align-items: start; }
.skizze { position: relative; background: var(--kalk-hell); border: 1px solid var(--linie); border-top: 3px double var(--messing); }
.skizze svg { display: block; width: 100%; height: auto; }
.ort { position: absolute; translate: -50% -50%; display: inline-flex; align-items: center; gap: 8px; min-height: 40px; padding: 0 12px; background: var(--kalk-hell); border: 1px solid var(--tinte); border-radius: 2px; font-size: .875rem; font-weight: 600; cursor: pointer; white-space: nowrap; font-variant-numeric: tabular-nums; box-shadow: 0 6px 16px -10px rgba(29, 26, 23, .6); transition: background-color var(--t1) var(--kurve), color var(--t1) var(--kurve); }
.ort small { font-weight: 500; font-size: .8125rem; }
.ort:hover { background: #fff; }
.ort[aria-pressed="true"] { background: var(--tinte); color: var(--kalk-hell); }
.ort.weg { border-style: dashed; color: var(--zweit); }
.ort.weg[aria-pressed="true"] { color: var(--kalk-hell); }
.ort .zeichen { display: none; }
.gefiltert .ort .zeichen { display: inline-block; }
.ort[aria-pressed="true"] .zeichen { border-color: currentColor; }
.raum-info { background: var(--kalk-hell); border: 1px solid var(--linie); border-top: 3px double var(--messing); padding: 20px 22px 22px; display: grid; gap: 12px; }
.raum-info h3 { font: 400 2rem/1.05 'Imbue', serif; }
.raum-info h3:focus { outline: none; }
.raum-info h3:focus-visible { outline: 2px solid var(--tinte); outline-offset: 4px; }
.raum-info .zugang { display: flex; gap: 10px; align-items: flex-start; font-size: .9375rem; }
.raum-info .zugang svg { width: 22px; height: 22px; flex: none; margin-top: 1px; }
.raum-info table { width: 100%; border-collapse: collapse; font-stretch: 87%; font-size: .9375rem; }
.raum-info th, .raum-info td { padding: 6px 0; border-bottom: 1px solid var(--linie); text-align: left; font-weight: 400; }
.raum-info td { text-align: right; font-variant-numeric: tabular-nums; }
.raum-info tr.aktiv th, .raum-info tr.aktiv td { font-weight: 700; }
.raum-info .zst { font-stretch: 112%; font-size: .75rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; display: inline-flex; align-items: center; gap: 6px; }
.ortliste { display: none; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.ortliste .ort { position: static; translate: none; min-height: 44px; font-size: .875rem; }
.legende-g { display: flex; flex-wrap: wrap; gap: 6px 20px; list-style: none; margin-top: 12px; font-size: .875rem; color: var(--zweit); }
.legende-g li { display: inline-flex; align-items: center; gap: 8px; }
.legende-g svg { width: 30px; height: 12px; }
/* Zeiger der Vorführung */
.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 */
.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 - 24px); 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; }
/* Varianten */
[data-v="tabelle"] .raum-teil, [data-v="tabelle"] .gelaende-teil { display: none; }
[data-v="raum"] .filter, [data-v="raum"] .treffer, [data-v="raum"] .kein-raum, [data-v="raum"] .tabelle-teil, [data-v="raum"] .karten, [data-v="raum"] .gelaende-teil { display: none !important; }
[data-v="gelaende"] .tabelle-teil, [data-v="gelaende"] .karten, [data-v="gelaende"] .raum-teil { display: none !important; }
/* Vorschau 960 × 720 */
.vorschau body { overflow: hidden; }
.vorschau .kopf, .vorschau .einstieg { display: none; }
.vorschau .filter { margin-top: 20px; }
.vorschau .raum-teil { margin-top: 12px; }
.vorschau .raum-plan svg { max-height: 300px; }
.vorschau .raum-kopf h2 { margin-bottom: 12px; }
: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 .plan-wechsel { 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 { opacity: 1; }
.knopf-haupt:focus-visible { outline: 2px solid var(--tinte); outline-offset: 3px; }
:root.reduziert .knopf-neben::after { translate: 0 0; }
:root.reduziert .raumreiter button::after { transition: none !important; }
@media (max-width: 1100px) {
.kopf nav { display: none; }
.gelaende { grid-template-columns: minmax(0, 1fr) minmax(0, 290px); gap: 20px; }
}
@media (max-width: 900px) {
.gelaende { grid-template-columns: minmax(0, 1fr); }
.einstieg, .raum-panel { grid-template-columns: minmax(0, 1fr); }
.plan { grid-template-columns: minmax(0, 1fr); }
.hinweise { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 700px) {
.tabelle-teil .rahmen { display: none; }
.karten { display: grid; }
.tabelle-teil { padding-bottom: 0; }
.filter { padding: 14px; }
.filter-fs { grid-template-columns: minmax(0, 1fr); }
.marke span { display: none; }
.pult { top: auto; bottom: 12px; }
.skizze .ort { display: none; }
.ortliste { display: flex; }
.pille { padding: 0 11px; }
}
@media (max-width: 360px) {
.raum-plan .beschreibung .knopf-haupt, .raum-plan .beschreibung [data-anfrage-status] { grid-column: 1 / -1; }
.karte dl { grid-template-columns: minmax(0, 1fr); }
.fakten div { grid-template-columns: minmax(0, 1fr); gap: 2px; }
}
@media (hover: none) {
.knopf-neben::after { translate: 0 0; }
.ort:hover { background: var(--kalk-hell); }
}
@media (forced-colors: active) {
button, [role=button], [role=tab], [role=switch], input, select, textarea, .pille { border: 1px solid ButtonText !important; }
.pille:has(input:checked), .ort[aria-pressed="true"], .raumreiter [aria-selected="true"] { outline: 3px solid Highlight; }
.zeichen, .plan svg { forced-color-adjust: none; }
}
</style>
</head>
<body data-v="tabelle">
<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="#raeume" aria-current="page">Räume</a><a href="#">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="raeume">
<section class="einstieg" aria-labelledby="titel">
<div>
<p class="rubrik" id="rubrik">Räume und Kapazitäten</p>
<h2 id="titel">Sechs Räume, ein Fest.</h2>
</div>
<p id="einstieg-text">Scheune, Gutshaus und Garten lassen sich für jeden Anlass anders stellen. Tragen Sie Ihre Gästezahl ein, dann zeigen wir, welcher Raum passt.</p>
</section>
<form class="filter" id="filter" novalidate>
<fieldset class="filter-fs">
<legend>Passenden Raum finden</legend>
<div class="f-gaeste">
<label for="f-gaeste">Gäste</label>
<input class="eingabe" type="number" id="f-gaeste" min="1" max="999" inputmode="numeric" placeholder="z. B. 120" aria-describedby="treffer">
</div>
<fieldset class="f-form">
<legend class="f-form-titel">Bestuhlung</legend>
<div class="pillen" id="pillen"></div>
</fieldset>
</fieldset>
</form>
<p class="treffer" id="treffer" role="status"></p>
<div class="kein-raum" id="kein-raum" hidden></div>
<section class="tabelle-teil" aria-label="Vergleich aller Räume">
<div class="rahmen" id="rahmen" tabindex="0" role="region" aria-labelledby="tab-cap"></div>
<ul class="karten" id="karten" aria-label="Räume als Karten"></ul>
<div class="hinweise">
<p>Höchstzahlen laut Genehmigung und Bestuhlungsplan, Stand 1. März 2026. Raummiete pro Tag inklusive Mehrwertsteuer, ohne Speisen, Getränke und Technik.<br><span class="platzhalter">Platzhalter · Zahlen und Preise vom Kunden prüfen lassen</span></p>
<ol class="fussnoten" id="fussnoten"></ol>
</div>
</section>
<section class="raum-teil" id="raum-teil" aria-label="Ein Raum mit Plan">
<div class="raumreiter" role="tablist" aria-label="Räume" id="raumreiter"></div>
<div class="raum-panel" role="tabpanel" id="raum-panel" tabindex="0"></div>
</section>
<section class="gelaende-teil" id="gelaende-teil" aria-label="Gelände mit Räumen">
<div class="gelaende">
<div>
<div class="skizze" id="skizze"></div>
<div class="ortliste" id="ortliste" role="group" aria-label="Räume auf dem Gelände"></div>
<ul class="legende-g">
<li><svg viewBox="0 0 30 12" aria-hidden="true"><path d="M1 6h28" stroke="#1D1A17" stroke-width="2.4" stroke-dasharray="1 4" stroke-linecap="round"/></svg>Weg ohne Stufen</li>
<li><svg viewBox="0 0 30 12" aria-hidden="true"><path d="M3 11h6V7h6V3h6" fill="none" stroke="#1D1A17" stroke-width="1.6"/></svg>Treppe</li>
<li><svg viewBox="0 0 30 12" aria-hidden="true"><rect x="9" y="0" width="12" height="12" fill="#1D1A17"/><text x="15" y="9.5" font-size="9" text-anchor="middle" fill="#FBF8F2" font-family="Archivo, sans-serif" font-weight="700">P</text></svg>Parkplatz, zwei breite Plätze am Hof</li>
<li><span class="zeichen z-passt" aria-hidden="true"></span>passt zu Ihrer Auswahl</li>
</ul>
</div>
<aside class="raum-info" id="raum-info" aria-live="polite"></aside>
</div>
</section>
</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="tabelle" aria-pressed="true">Tabelle</button>
<button type="button" data-variante="raum" aria-pressed="false">Raum</button>
<button type="button" data-variante="gelaende" aria-pressed="false">Gelände</button>
</span>
<span class="werte">Kurve 0.625, 0.05, 0, 1 · 250 / 400 / 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)';
const TEL = '09331 0 47 11 0', TEL_HREF = 'tel:+4993310471100';
const euro = new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR', maximumFractionDigits: 0 });
const zahl = new Intl.NumberFormat('de-DE', { maximumFractionDigits: 1 });
/* ---------- Daten, wie das Haus sie pflegt (Beispielwerte) ---------- */
const FORMEN = [
{ id: 'tafel', name: 'Tafel', ik: '<rect x="3" y="7.5" width="20" height="3" rx=".5"/><path d="M5 5h2M10 5h2M15 5h2M20 5h1M5 13h2M10 13h2M15 13h2M20 13h1"/>' },
{ id: 'rund', name: 'Rundtische', ik: '<circle cx="13" cy="9" r="4"/><path d="M13 2.5v1.2M13 14.3v1.2M6.5 9h1.2M18.3 9h1.2M8.4 4.4l.8.8M16.8 12.8l.8.8M17.6 4.4l-.8.8M9.2 12.8l-.8.8"/>' },
{ id: 'reihe', name: 'Reihe', ik: '<path d="M3 3h2M8 3h2M16 3h2M21 3h2M3 9h2M8 9h2M16 9h2M21 9h2M3 15h2M8 15h2M16 15h2M21 15h2"/>' },
{ id: 'parlament', name: 'Parlamentarisch', ik: '<path d="M3 3.5h8M15 3.5h8M3 10.5h8M15 10.5h8"/><path d="M4 6.5h1.5M8 6.5h1.5M16 6.5h1.5M20 6.5h1.5M4 13.5h1.5M8 13.5h1.5M16 13.5h1.5M20 13.5h1.5"/>' },
{ id: 'u', name: 'U-Form', ik: '<path d="M6 3v10h14V3"/><path d="M3 5v1.5M3 10v1.5M23 5v1.5M23 10v1.5M10 16h1.5M15 16h1.5"/>' },
{ id: 'steh', name: 'Stehempfang', ik: '<circle cx="6" cy="5" r="2"/><circle cx="18" cy="5" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="22" cy="14" r="1.6"/><circle cx="4" cy="14" r="1.6"/>' }
];
const FORM = Object.fromEntries(FORMEN.map(f => [f.id, f]));
const GEBAEUDE = { Scheune: 'Fachwerk von 1874, ebenerdig mit Heuboden', Gutshaus: 'Barockbau, Gartenseite mit Terrasse', Garten: 'unter der Linde, Mai bis September' };
const RAEUME = [
{ id: 'scheune', geb: 'Scheune', name: 'Große Scheune', ebene: 'Erdgeschoss', l: 28, b: 15, hoehe: '11 m bis zum First', p: { tafel: 160, rund: 180, reihe: 260, parlament: 120, u: 60, steh: 300 }, mindest: 80, miete: [3900, 2400], stufenlos: true, zugang: 'ebenerdig über zwei Tore, WC ohne Stufen im Anbau', tanz: [8, 6], buehne: true, tore: [['s', .5, 3.4], ['n', .5, 3.4], ['o', .5, 1.2]] },
{ id: 'heuboden', geb: 'Scheune', name: 'Heuboden', ebene: 'Obergeschoss', l: 16, b: 10, hoehe: '4,20 m unter dem Gebälk', p: { tafel: 60, rund: 50, reihe: 90, parlament: 48, u: 30, steh: 110 }, mindest: 30, miete: [1600, 1100], stufenlos: false, zugang: 'Holztreppe mit 16 Stufen, kein Aufzug', tore: [['w', .3, 1.2], ['o', .7, 1]] },
{ id: 'gartensaal', geb: 'Gutshaus', name: 'Gartensaal', ebene: 'Erdgeschoss', l: 14, b: 10, hoehe: '4,60 m, Stuckdecke', p: { tafel: 70, rund: 60, reihe: 100, parlament: 50, u: 32, steh: 120 }, mindest: 40, miete: [1900, 1300], stufenlos: true, zugang: 'Rampe vom Hof, Terrassentüren zum Garten', tore: [['s', .3, 1.6], ['s', .7, 1.6], ['n', .5, 1.2]] },
{ id: 'kamin', geb: 'Gutshaus', name: 'Kaminzimmer', ebene: 'Erdgeschoss', l: 9, b: 6.5, hoehe: '3,40 m', p: { tafel: 24, rund: null, reihe: 36, parlament: 18, u: 14, steh: 40 }, mindest: 12, miete: [650, 450], stufenlos: false, zugang: 'eine Stufe (12 cm) vom Gartensaal, mobile Rampe auf Wunsch', gruende: { rund: 'Raum zu schmal für Rundtische mit Fluchtweg' }, tore: [['o', .5, 1.1]] },
{ id: 'bibliothek', geb: 'Gutshaus', name: 'Bibliothek', ebene: '1. Stock', l: 8.4, b: 5.5, hoehe: '3,10 m', p: { tafel: 16, rund: null, reihe: 24, parlament: 12, u: 12, steh: 30 }, mindest: 8, miete: [480, 350], stufenlos: true, zugang: '1. Stock, Aufzug für einen Rollstuhl', gruende: { rund: 'Regale an drei Wänden, kein Platz für Rundtische' }, tore: [['o', .5, 1]] },
{ id: 'linde', geb: 'Garten', name: 'Trauwiese', ebene: 'unter der Linde', l: 30, b: 20, hoehe: 'im Freien, Linde etwa 24 m', p: { tafel: null, rund: null, reihe: 180, parlament: null, u: null, steh: 250 }, mindest: null, miete: [900, null], stufenlos: true, zugang: 'Kiesweg vom Hof, mit Rollstuhl befahrbar', aussen: true,
gruende: { tafel: 'Essen nur in den Sälen (Auflage der Genehmigung)', rund: 'Essen nur in den Sälen (Auflage der Genehmigung)', parlament: 'nur für Trauung und Empfang', u: 'nur für Trauung und Empfang', winter: 'nur Mai bis September', mindest: 'keine Mindestzahl, nur zusammen mit einem Saal' } }
];
const RAUM = Object.fromEntries(RAEUME.map(r => [r.id, r]));
const maxPlaetze = r => Math.max(...Object.values(r.p).filter(x => x != null));
/* Fußnoten aus den Gründen */
const FN = [];
function fussnote(text) { let i = FN.indexOf(text); if (i < 0) { FN.push(text); i = FN.length - 1; } return i + 1; }
RAEUME.forEach(r => { if (r.gruende) Object.values(r.gruende).forEach(fussnote); });
/* ---------- Zustand ---------- */
const Z = { v: 'tabelle', g: 0, f: 'alle', offen: null, planForm: {}, raum: 'scheune', raumForm: 'rund', ort: 'scheune', ortPlan: false, vorf: false, ohneAnim: false };
const pillen = $('#pillen'), fG = $('#f-gaeste'), treffer = $('#treffer'), keinRaum = $('#kein-raum');
function bewerte(r) {
const G = Z.g, f = Z.f;
if (!G) return { s: 'alle', text: '' };
if (f !== 'alle') {
const c = r.p[f];
if (c == null) return { s: 'nein', text: FORM[f].name + ' nicht möglich' };
if (c < G) return { s: 'klein', text: 'zu klein · bis ' + c };
if (r.mindest && r.mindest > G) return { s: 'gross', text: 'zu groß · ab ' + r.mindest + ' Gäste' };
return { s: 'passt', text: 'passt · bis ' + c };
}
const m = maxPlaetze(r);
if (m < G) return { s: 'klein', text: 'zu klein · bis ' + m };
if (r.mindest && r.mindest > G) return { s: 'gross', text: 'zu groß · ab ' + r.mindest + ' Gäste' };
const n = FORMEN.filter(x => r.p[x.id] != null && r.p[x.id] >= G).length;
return { s: 'passt', text: 'passt · ' + n + (n === 1 ? ' Form' : ' Formen') };
}
const zeichenKlasse = s => ({ passt: 'z-passt', klein: 'z-klein', gross: 'z-gross', nein: 'z-nein' }[s] || '');
const formFuerPlan = r => {
const w = Z.planForm[r.id] || (Z.f !== 'alle' ? Z.f : null);
if (w && r.p[w] != null) return w;
return ['rund', 'reihe', 'tafel', 'steh', 'parlament', 'u'].find(x => r.p[x] != null);
};
/* ---------- Plan als SVG aus Raummaßen ---------- */
let planNr = 0;
function plan(r, f) {
const L = r.l, B = r.b, M = Math.min(1, B * .12), cap = r.p[f], sw = Math.max(L, B) / 380, fs = Math.max(L, B) / 36;
const T = '#1D1A17', S = '#E9DCC8', W = '#FBF8F2', MS = '#8C6A3C';
const out = [], x0 = M, y0 = M, x1 = L - M, y1 = B - M;
let text = '';
const stuhl = (x, y) => out.push('<rect x="' + (x - .21).toFixed(2) + '" y="' + (y - .21).toFixed(2) + '" width=".42" height=".42" rx=".08" fill="' + S + '" stroke="' + T + '" stroke-width="' + (sw * .7).toFixed(3) + '"/>');
const label = (x, y, t, anker = 'middle', gross = 1) => out.push('<text x="' + x.toFixed(2) + '" y="' + y.toFixed(2) + '" font-size="' + (fs * gross).toFixed(2) + '" text-anchor="' + anker + '" fill="' + T + '" font-family="Archivo, sans-serif">' + t + '</text>');
const senkrecht = (x, t) => out.push('<text transform="translate(' + x.toFixed(2) + ' ' + (B / 2).toFixed(2) + ') rotate(-90)" font-size="' + (fs * .8).toFixed(2) + '" text-anchor="middle" fill="' + T + '" font-family="Archivo, sans-serif">' + t + '</text>');
// Raum
if (r.aussen) {
out.push('<rect x="0" y="0" width="' + L + '" height="' + B + '" fill="#F2EBDD" stroke="' + MS + '" stroke-width="' + (sw * 2) + '" stroke-dasharray="' + (sw * 6) + ' ' + (sw * 5) + '"/>');
out.push('<circle cx="' + (x0 + 1.4) + '" cy="' + (B / 2) + '" r="2.6" fill="#3B3A2E" opacity=".85"/>');
label(x0 + 1.4, B / 2 + 3.9, 'Linde');
} else {
out.push('<rect x="0" y="0" width="' + L + '" height="' + B + '" fill="' + W + '" stroke="' + T + '" stroke-width="' + (sw * 3) + '"/>');
}
(r.tore || []).forEach(([s, p, w]) => {
const h = s === 'n' || s === 's', x = h ? L * p - w / 2 : (s === 'w' ? -sw * 2 : L - sw * 2), y = h ? (s === 'n' ? -sw * 2 : B - sw * 2) : B * p - w / 2;
out.push('<rect x="' + x.toFixed(2) + '" y="' + y.toFixed(2) + '" width="' + (h ? w : sw * 4).toFixed(2) + '" height="' + (h ? sw * 4 : w).toFixed(2) + '" fill="' + MS + '"/>');
});
let anzahl = 0;
if (f === 'rund') {
const n = Math.round(cap / 10), D = 1.8, R = 1.25;
const sperr = [];
let xs = x0;
if (r.buehne) { out.push('<rect x="0" y="' + (B / 2 - 3.5) + '" width="2" height="7" fill="' + S + '" stroke="' + T + '" stroke-width="' + sw + '"/>'); out.push('<text transform="translate(1.25 ' + (B / 2) + ') rotate(-90)" font-size="' + (fs * .9).toFixed(2) + '" text-anchor="middle" fill="' + T + '" font-family="Archivo, sans-serif">Bühne</text>'); sperr.push([0, B / 2 - 3.5, 2.6, B / 2 + 3.5]); }
if (r.tanz) {
const [tl, tb] = r.tanz, tx = 2.6, ty = B / 2 - tb / 2;
out.push('<rect x="' + tx + '" y="' + ty + '" width="' + tl + '" height="' + tb + '" fill="url(#schraffur-' + planNr + ')" stroke="' + MS + '" stroke-width="' + sw * 1.5 + '"/>');
label(tx + tl / 2, ty + tb / 2 - fs * .25, 'Tanzfläche'); label(tx + tl / 2, ty + tb / 2 + fs * .95, tl + ' × ' + tb + ' m');
sperr.push([tx - .9, ty - .9, tx + tl + .9, ty + tb + .9]);
}
let pitch = 3.1, pos = [];
for (; pitch >= 2.5; pitch -= .1) {
pos = [];
const cols = Math.floor((x1 - xs) / pitch), rows = Math.floor((y1 - y0) / pitch);
const ox = xs + (x1 - xs - cols * pitch) / 2 + pitch / 2, oy = y0 + (y1 - y0 - rows * pitch) / 2 + pitch / 2;
for (let i = cols - 1; i >= 0; i--) for (let j = 0; j < rows; j++) {
const cx = ox + i * pitch, cy = oy + j * pitch;
if (sperr.some(([a, b, c, d]) => cx + D / 2 > a && cx - D / 2 < c && cy + D / 2 > b && cy - D / 2 < d)) continue;
pos.push([cx, cy]);
}
if (pos.length >= n) break;
}
pos.slice(0, n).forEach(([cx, cy]) => {
out.push('<circle cx="' + cx.toFixed(2) + '" cy="' + cy.toFixed(2) + '" r="' + D / 2 + '" fill="#fff" stroke="' + T + '" stroke-width="' + sw + '"/>');
for (let k = 0; k < 10; k++) { const a = k / 10 * Math.PI * 2; stuhl(cx + Math.cos(a) * R, cy + Math.sin(a) * R); }
anzahl += 10;
});
text = n + ' runde Tische (Ø 1,80 m) für je 10 Personen' + (r.tanz ? ', Tanzfläche ' + r.tanz[0] + ' × ' + r.tanz[1] + ' m vor der Bühne' : '') + '.';
} else if (f === 'tafel') {
if (r.buehne) { out.push('<rect x="0" y="' + (B / 2 - 3.5) + '" width="2" height="7" fill="' + S + '" stroke="' + T + '" stroke-width="' + sw + '"/>'); out.push('<text transform="translate(1.25 ' + (B / 2) + ') rotate(-90)" font-size="' + (fs * .9).toFixed(2) + '" text-anchor="middle" fill="' + T + '" font-family="Archivo, sans-serif">Bühne</text>'); }
const xs = x0 + (r.buehne ? 2.6 : 1.2), frei = x1 - xs - .4;
let k = 1;
while (Math.ceil(cap / k / 2) * .65 + .3 > frei && k < 8) k++;
const pitch = Math.min(3, (y1 - y0) / k), rest = cap % k;
for (let i = 0; i < k; i++) {
const sitze = Math.floor(cap / k) + (i < rest ? 1 : 0), oben = Math.ceil(sitze / 2), unten = sitze - oben, len = oben * .65 + .3;
const ty = y0 + pitch * (i + .5) - .45, tx = xs + (frei - len) / 2;
out.push('<rect x="' + tx.toFixed(2) + '" y="' + ty.toFixed(2) + '" width="' + len.toFixed(2) + '" height=".9" fill="#fff" stroke="' + T + '" stroke-width="' + sw + '"/>');
for (let s = 0; s < oben; s++) stuhl(tx + .475 + s * .65, ty - .35);
for (let s = 0; s < unten; s++) stuhl(tx + .475 + s * .65, ty + 1.25);
anzahl += sitze;
}
text = k + (k === 1 ? ' lange Tafel' : ' lange Tafeln') + ' (90 cm breit), 65 cm je Platz, zusammen ' + cap + ' Plätze' + (r.buehne ? ', Bühne an der Stirnwand' : '') + '.';
} else if (f === 'reihe' || f === 'parlament') {
const parl = f === 'parlament', w = parl ? .7 : .55, gang = (B - 2 * M) >= 7 ? (parl ? 1.2 : 1.4) : 0;
const fz = r.aussen ? 4.6 : (B >= 8 ? (parl ? 2.2 : 3) : 2);
const proSeite = Math.floor(((y1 - y0) - gang) / (gang ? 2 : 1) / w), proReihe = gang ? proSeite * 2 : proSeite;
const reihen = Math.ceil(cap / proReihe), pitch = Math.min(parl ? 1.6 : 1, (x1 - x0 - fz) / reihen);
const breite = proSeite * w, ys = gang ? [B / 2 - gang / 2 - breite, B / 2 + gang / 2] : [B / 2 - breite / 2];
if (r.aussen) { out.push('<rect x="' + (x0 + 2.8) + '" y="' + (B / 2 - .5) + '" width=".9" height="1" fill="#fff" stroke="' + T + '" stroke-width="' + sw + '"/>'); label(x0 + 3.25, B / 2 - 1.1, 'Trautisch', 'middle', .9); }
else if (parl) { out.push('<rect x="' + (sw * 3) + '" y="' + (B / 2 - Math.min(2.4, B / 3)) + '" width=".25" height="' + Math.min(4.8, B / 1.5) + '" fill="' + T + '"/>'); senkrecht(.75, 'Leinwand'); }
else { out.push('<rect x="' + (x0 + .2) + '" y="' + (B / 2 - .9) + '" width=".7" height="1.8" fill="#fff" stroke="' + T + '" stroke-width="' + sw + '"/>'); senkrecht(x0 - .25, 'Pult'); }
let gesetzt = 0;
for (let i = 0; i < reihen; i++) {
const x = x0 + fz + i * pitch + (parl ? 0 : .25);
ys.forEach(ya => {
if (parl) out.push('<rect x="' + x.toFixed(2) + '" y="' + ya.toFixed(2) + '" width=".45" height="' + breite.toFixed(2) + '" fill="#fff" stroke="' + T + '" stroke-width="' + sw * .8 + '"/>');
for (let s = 0; s < proSeite && gesetzt < cap; s++, gesetzt++) stuhl(x + (parl ? .75 : 0), ya + w * (s + .5));
});
}
anzahl = gesetzt;
text = parl ? reihen + ' Tischreihen mit je bis zu ' + proReihe + ' Plätzen' + (gang ? ', Mittelgang 1,20 m' : '') + ', alle mit Blick zur Leinwand an der Stirnwand.'
: reihen + ' Stuhlreihen mit je bis zu ' + proReihe + ' Plätzen' + (gang ? ', Mittelgang 1,40 m' : '') + (r.aussen ? ', vorn der Trautisch unter der Linde.' : ', vorn Platz für Trautisch oder Rednerpult.');
} else if (f === 'u') {
const t = .6, w = .7, lb = Math.min((y1 - y0) - 1.6, cap * w * .4), nb = Math.max(2, Math.round(lb / w)), nl = Math.ceil((cap - nb) / 2), ll = nl * w;
const xe = x1 - .8, xa = xe - ll, ya = B / 2 - lb / 2 - t, yb = B / 2 + lb / 2;
out.push('<path d="M' + xa.toFixed(2) + ' ' + ya.toFixed(2) + 'H' + (xe + t).toFixed(2) + 'V' + (yb + t).toFixed(2) + 'H' + xa.toFixed(2) + 'V' + yb.toFixed(2) + 'H' + xe.toFixed(2) + 'V' + (ya + t).toFixed(2) + 'H' + xa.toFixed(2) + 'Z" fill="#fff" stroke="' + T + '" stroke-width="' + sw + '"/>');
let gesetzt = 0;
for (let s = 0; s < nb && gesetzt < cap; s++, gesetzt++) stuhl(xe + t + .35, B / 2 - lb / 2 + w * (s + .5) - (nb * w - lb) / 2);
for (let s = 0; s < nl && gesetzt < cap; s++, gesetzt++) stuhl(xa + w * (s + .5), ya - .35);
for (let s = 0; s < nl && gesetzt < cap; s++, gesetzt++) stuhl(xa + w * (s + .5), yb + t + .35);
out.push('<rect x="' + (sw * 3) + '" y="' + (B / 2 - Math.min(2, B / 3)) + '" width=".25" height="' + Math.min(4, B / 1.5) + '" fill="' + T + '"/>'); senkrecht(.75, 'Leinwand');
anzahl = gesetzt;
text = 'Tische in U-Form, ' + cap + ' Plätze an der Außenseite; die offene Seite zeigt zur Leinwand an der Stirnwand.';
} else if (f === 'steh') {
const n = Math.ceil(cap / 8), bl = Math.min(6, L / 3);
out.push('<rect x="' + (L / 2 - bl / 2) + '" y="' + (y0 - .2) + '" width="' + bl + '" height=".8" fill="' + S + '" stroke="' + T + '" stroke-width="' + sw + '"/>');
label(L / 2, y0 + 1.75, 'Bar', 'middle', .9);
let pitch = 2.6, pos = [];
for (; pitch >= 1.8; pitch -= .1) {
pos = [];
const cols = Math.floor((x1 - x0 - (r.aussen ? 4 : 0)) / pitch), rows = Math.floor((y1 - y0 - 2) / pitch);
const ox = x0 + (r.aussen ? 4 : 0) + (x1 - x0 - (r.aussen ? 4 : 0) - cols * pitch) / 2 + pitch / 2, oy = y0 + 2 + (y1 - y0 - 2 - rows * pitch) / 2 + pitch / 2;
for (let j = 0; j < rows; j++) for (let i = 0; i < cols; i++) pos.push([ox + i * pitch + (j % 2 ? pitch / 4 : -pitch / 4), oy + j * pitch]);
if (pos.length >= n) break;
}
const schritt = pos.length / n;
for (let i = 0; i < n; i++) { const [cx, cy] = pos[Math.floor(i * schritt)]; out.push('<circle cx="' + cx.toFixed(2) + '" cy="' + cy.toFixed(2) + '" r=".4" fill="#fff" stroke="' + T + '" stroke-width="' + sw + '"/>'); }
anzahl = cap;
text = n + ' Stehtische für ' + cap + ' Gäste im Stehen, Bar an der Längswand.';
}
// Maßstab und Norden
out.push('<path d="M0 ' + (B + 1.3) + 'h5M0 ' + (B + 1) + 'v.6M5 ' + (B + 1) + 'v.6" stroke="' + T + '" stroke-width="' + sw * 1.4 + '" fill="none"/>');
label(5.5, B + 1.3 + fs * .35, '5 m', 'start', .85);
label(L, -0.5, zahl.format(r.l) + ' × ' + zahl.format(r.b) + ' m · ' + flaeche(r) + ' m²', 'end', .85);
const id = 'plan-' + (++planNr);
const ganz = r.name + ', ' + FORM[f].name + ': ' + text + ' ' + (r.aussen ? 'Zugang: ' : 'Zugang: ') + r.zugang + '.';
const defs = '<defs><pattern id="schraffur-' + planNr + '" width=".6" height=".6" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><rect width=".6" height=".6" fill="#F0E6D6"/><path d="M0 0v.6" stroke="' + MS + '" stroke-width=".07" opacity=".55"/></pattern></defs>';
const svg = '<svg viewBox="' + (-1.2) + ' ' + (-1.6) + ' ' + (L + 2.4) + ' ' + (B + 3.4) + '" role="img" aria-labelledby="' + id + '-t">' + defs + out.join('').replace(/schraffur-(\d+)/g, 'schraffur-' + planNr) + '</svg>';
return { svg, text, ganz, id, anzahl };
}
const flaeche = r => Math.round(r.l * r.b);
function planHtml(r, f, mitWahl) {
const p = plan(r, f);
return '<figure>' + p.svg + '</figure><div class="beschreibung"><p class="rubrik">Plan · ' + FORM[f].name + '</p>'
+ '<p class="gross">' + r.p[f] + '<small>Plätze</small></p>'
+ '<p id="' + p.id + '-t">' + p.ganz + '</p>'
+ (mitWahl ? '<div class="formen-mini" role="group" aria-label="Plan für Bestuhlung">' + FORMEN.map(x => r.p[x.id] == null
? '<button type="button" aria-disabled="true" title="' + r.gruende[x.id] + '" aria-label="' + x.name + ': nicht möglich, ' + r.gruende[x.id] + '">' + x.name + ' –</button>'
: '<button type="button" data-planform="' + x.id + '" data-raum="' + r.id + '" aria-pressed="' + (x.id === f) + '">' + x.name + ' ' + r.p[x.id] + '</button>').join('') + '</div>' : '')
+ '<a class="knopf knopf-haupt" href="#anfrage" data-anfrage="' + r.id + '">Mit diesem Raum anfragen</a><p class="gruende" role="status" data-anfrage-status></p></div>';
}
/* ---------- Tabelle ---------- */
function zelle(r, f) {
const c = r.p[f];
if (c == null) { const n = fussnote(r.gruende[f]); return '<td class="num nicht" data-form="' + f + '"><span aria-hidden="true">–</span><sup><a href="#fn-' + n + '" aria-label="Fußnote ' + n + '">' + n + '</a></sup><span class="nur-sr">nicht möglich: ' + r.gruende[f] + '</span></td>'; }
return '<td class="num" data-form="' + f + '">' + c + '</td>';
}
function geldZelle(r, i) {
if (r.miete[i] == null) { const n = fussnote(r.gruende.winter); return '<td class="num nicht"><span aria-hidden="true">–</span><sup><a href="#fn-' + n + '" aria-label="Fußnote ' + n + '">' + n + '</a></sup><span class="nur-sr">nicht vermietet: ' + r.gruende.winter + '</span></td>'; }
return '<td class="num">' + euro.format(r.miete[i]) + '</td>';
}
function tabelle() {
let h = '<table class="raeume" id="tab"><caption id="tab-cap">Räume von Gut Weidenhain: Fläche, Plätze je Bestuhlung, Mindestgäste und Raummiete pro Tag</caption>'
+ '<colgroup><col class="w-raum"><col class="w-fl"></colgroup><colgroup>' + '<col class="w-f">'.repeat(6) + '</colgroup><colgroup><col class="w-min"></colgroup><colgroup><col class="w-m"><col class="w-m"></colgroup><colgroup><col class="w-plan"></colgroup>'
+ '<thead><tr><th scope="col" rowspan="2" class="c-raum">Raum</th><th scope="col" rowspan="2" class="num">Fläche</th><th scope="colgroup" colspan="6" class="ober">Plätze je Bestuhlung</th><th scope="col" rowspan="2" class="num">Mindest­gäste</th><th scope="colgroup" colspan="2" class="ober">Raummiete pro Tag</th><th scope="col" rowspan="2"><span class="nur-sr">Plan</span></th></tr><tr>'
+ FORMEN.map(f => '<th scope="col" class="num form" data-form="' + f.id + '"><svg class="ik" viewBox="0 0 26 18" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true">' + f.ik + '</svg>' + ({ parlament: 'Parlamen­tarisch', steh: 'Steh­empfang', rund: 'Rund­tische', u: '<span style="white-space:nowrap">U-Form</span>' }[f.id] || f.name) + '</th>').join('')
+ '<th scope="col" class="num">Mai–Okt</th><th scope="col" class="num">Nov–Apr</th></tr></thead>';
Object.keys(GEBAEUDE).forEach(g => {
h += '<tbody><tr class="gruppe"><th colspan="12" scope="rowgroup"><span>' + g + ' <small>' + GEBAEUDE[g] + '</small></span></th></tr>';
RAEUME.filter(r => r.geb === g).forEach(r => {
h += '<tr data-raum="' + r.id + '"><th scope="row"><span class="name">' + r.name + '</span><small>' + r.ebene + ' · ' + (r.stufenlos ? 'ohne Stufen' : 'mit Stufen') + '</small><span class="zst"></span></th>'
+ '<td class="num">' + flaeche(r) + ' m²</td>' + FORMEN.map(f => zelle(r, f.id)).join('')
+ (r.mindest == null ? '<td class="num nicht"><span aria-hidden="true">–</span><sup><a href="#fn-' + fussnote(r.gruende.mindest) + '" aria-label="Fußnote ' + fussnote(r.gruende.mindest) + '">' + fussnote(r.gruende.mindest) + '</a></sup><span class="nur-sr">' + r.gruende.mindest + '</span></td>' : '<td class="num">' + r.mindest + '</td>')
+ geldZelle(r, 0) + geldZelle(r, 1)
+ '<td class="c-plan"><button type="button" class="knopf knopf-neben planknopf" data-plan="' + r.id + '" aria-expanded="false" aria-controls="pz-' + r.id + '">Plan <svg class="pfeil" viewBox="0 0 14 14" aria-hidden="true"><path d="M3 5l4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.5"/></svg><span class="nur-sr"> für ' + r.name + '</span></button></td></tr>'
+ '<tr class="planzeile" id="pz-' + r.id + '" hidden><td colspan="12"><div class="plan-huelle"><div class="plan" data-plan-inhalt="' + r.id + '"></div></div></td></tr>';
});
h += '</tbody>';
});
h += '</table>';
$('#rahmen').innerHTML = h;
$('#fussnoten').innerHTML = FN.map((t, i) => '<li id="fn-' + (i + 1) + '"><span>' + (i + 1) + '</span><span>' + t + '</span></li>').join('');
}
function karten() {
$('#karten').innerHTML = RAEUME.map(r => '<li class="karte" data-raum="' + r.id + '"><p class="rubrik">' + r.geb + ' · ' + r.ebene + '</p><h3>' + r.name + '</h3>'
+ '<p class="meta">' + flaeche(r) + ' m² · ' + zahl.format(r.l) + ' × ' + zahl.format(r.b) + ' m · ' + r.zugang + '</p><span class="zst"></span>'
+ '<dl>' + FORMEN.map(f => '<div data-form="' + f.id + '"><dt>' + f.name + '</dt><dd>' + (r.p[f.id] == null ? '–<span class="nur-sr"> nicht möglich: ' + r.gruende[f.id] + '</span>' : r.p[f.id]) + '</dd></div>').join('')
+ '<div><dt>Mindestgäste</dt><dd>' + (r.mindest || '–') + '</dd></div><div><dt>Miete Mai–Okt</dt><dd>' + euro.format(r.miete[0]) + '</dd></div><div><dt>Miete Nov–Apr</dt><dd>' + (r.miete[1] == null ? '–' : euro.format(r.miete[1])) + '</dd></div></dl>'
+ '<div><button type="button" class="knopf knopf-neben planknopf" data-plan="' + r.id + '" data-karte aria-expanded="false" aria-controls="pk-' + r.id + '">Plan <svg class="pfeil" viewBox="0 0 14 14" aria-hidden="true"><path d="M3 5l4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.5"/></svg><span class="nur-sr"> für ' + r.name + '</span></button></div>'
+ '<div class="plan-huelle" id="pk-' + r.id + '" hidden><div class="plan" data-plan-karte="' + r.id + '"></div></div></li>').join('');
}
/* ---------- Filter anwenden ---------- */
function anwenden(ansagen = true) {
const gef = !!Z.g;
body.classList.toggle('gefiltert', gef);
const res = RAEUME.map(r => [r, bewerte(r)]);
res.forEach(([r, b]) => {
const zeile = document.querySelector('tr[data-raum="' + r.id + '"]'), karte = document.querySelector('.karte[data-raum="' + r.id + '"]');
[zeile, karte].forEach(el => {
if (!el) return;
el.classList.toggle('passt', b.s === 'passt'); el.classList.toggle('weg', gef && b.s !== 'passt');
const z = el.querySelector('.zst'); if (z) z.innerHTML = gef ? '<span class="zeichen ' + zeichenKlasse(b.s) + '" aria-hidden="true"></span>' + b.text : '';
});
});
document.querySelectorAll('[data-form]').forEach(el => el.classList.toggle('aktiv', el.dataset.form === Z.f));
// Orte auf dem Gelände
document.querySelectorAll('.ort').forEach(o => {
const r = RAUM[o.dataset.ort], b = bewerte(r);
o.classList.toggle('weg', gef && b.s !== 'passt');
o.querySelector('.zeichen').className = 'zeichen ' + zeichenKlasse(b.s);
o.querySelector('small').textContent = Z.f !== 'alle' ? (r.p[Z.f] == null ? '–' : 'bis ' + r.p[Z.f]) : 'bis ' + maxPlaetze(r);
o.setAttribute('aria-label', r.name + ', ' + r.geb + ', ' + (Z.f !== 'alle' ? FORM[Z.f].name + ' ' + (r.p[Z.f] == null ? 'nicht möglich' : 'bis ' + r.p[Z.f] + ' Plätze') : 'bis ' + maxPlaetze(r) + ' Plätze') + (gef ? ', ' + b.text.replace(' · ', ', ') : ''));
});
const passt = res.filter(([, b]) => b.s === 'passt').length, form = Z.f !== 'alle' ? ' ' + ({ tafel: 'an Tafeln', rund: 'an Rundtischen', reihe: 'in Reihe', parlament: 'parlamentarisch', u: 'in U-Form', steh: 'im Stehen' }[Z.f]) : '';
let t;
if (!gef) t = Z.f === 'alle' ? 'Alle ' + RAEUME.length + ' Räume. Tragen Sie Ihre Gästezahl ein, dann markieren wir, was passt.' : FORM[Z.f].name + ': Plätze in der markierten Spalte. Mit Gästezahl markieren wir, was passt.';
else if (passt) t = '<b>' + passt + ' von ' + RAEUME.length + ' Räumen</b> ' + (passt === 1 ? 'passt' : 'passen') + ' für ' + Z.g + ' Gäste' + form + '.';
else t = '<b>Kein Raum</b> passt für ' + Z.g + ' Gäste' + form + '.';
if (ansagen || treffer.innerHTML !== t) treffer.innerHTML = t;
// keine passende Fläche
if (gef && !passt) {
const f = Z.f !== 'alle' ? Z.f : null, wert = r => f ? (r.p[f] || 0) : maxPlaetze(r);
let best = null;
Object.keys(GEBAEUDE).forEach(g => {
const l = RAEUME.filter(r => r.geb === g && wert(r) > 0).sort((a, b) => wert(b) - wert(a));
if (l.length > 1 && (!best || wert(l[0]) + wert(l[1]) > best[2])) best = [l[0], l[1], wert(l[0]) + wert(l[1])];
});
const kombi = best && best[2] >= Z.g ? '<p>Zwei Räume zusammen: <b>' + best[0].name + '</b> (' + wert(best[0]) + ') und <b>' + best[1].name + '</b> (' + wert(best[1]) + ') bieten ' + best[2] + ' Plätze' + form + ', in zwei Räumen desselben Hauses.</p>' : '';
keinRaum.innerHTML = '<strong>Für ' + Z.g + ' Gäste' + form + ' ist kein Raum groß genug.</strong>' + kombi + '<p>Mehr Plätze gibt es nur mit Zelt auf der Trauwiese. Rufen Sie an, wir rechnen es durch: <a href="' + TEL_HREF + '">' + TEL + '</a>.</p>';
keinRaum.hidden = false;
} else keinRaum.hidden = true;
if (Z.v === 'gelaende') info(false);
}
/* ---------- Plan auf- und zuklappen ---------- */
function setzePlan(r, ziel, mitWahl) {
const f = formFuerPlan(r);
ziel.innerHTML = planHtml(r, f, mitWahl);
}
function klappe(huelle, auf, fertig) {
if (reduziert() || Z.ohneAnim) { fertig && fertig(); return; }
const h = huelle.scrollHeight;
huelle.animate(auf ? [{ height: '0px', opacity: 0 }, { height: h + 'px', opacity: 1 }] : [{ height: h + 'px', opacity: 1 }, { height: '0px', opacity: 0 }], { duration: 525, easing: KURVE }).onfinish = () => fertig && fertig();
}
function planUmschalten(id, karte) {
const knopf = document.querySelector('.planknopf[data-plan="' + id + '"]' + (karte ? '[data-karte]' : ':not([data-karte])'));
const zeile = karte ? $('#pk-' + id) : $('#pz-' + id);
const auf = knopf.getAttribute('aria-expanded') !== 'true';
if (!karte && auf && Z.offen && Z.offen !== id) planUmschalten(Z.offen, false);
const huelle = karte ? zeile : zeile.querySelector('.plan-huelle');
if (auf) {
setzePlan(RAUM[id], karte ? zeile.querySelector('.plan') : zeile.querySelector('.plan'), true);
zeile.hidden = false; knopf.setAttribute('aria-expanded', 'true');
if (!karte) { Z.offen = id; breiteMerken(); }
klappe(huelle, true);
} else {
knopf.setAttribute('aria-expanded', 'false');
if (!karte) Z.offen = null;
klappe(huelle, false, () => { zeile.hidden = true; });
}
}
function breiteMerken() { const rr = $('#rahmen'); rr.style.setProperty('--sichtbar', rr.clientWidth + 'px'); }
addEventListener('resize', breiteMerken);
function planForm(id, f) {
Z.planForm[id] = f;
document.querySelectorAll('[data-plan-inhalt="' + id + '"], [data-plan-karte="' + id + '"], [data-ort-plan="' + id + '"]').forEach(el => {
const fokus = document.activeElement && el.contains(document.activeElement) ? f : null;
setzePlan(RAUM[id], el, true);
el.querySelector('figure').classList.add('plan-wechsel');
if (fokus) { const b = el.querySelector('[data-planform="' + fokus + '"]'); if (b) b.focus(); }
});
}
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.pointerType !== 'mouse' || (e.relatedTarget && k.contains(e.relatedTarget)))) 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-anfrage]'); if (a) { e.preventDefault(); const st = a.parentElement.querySelector('[data-anfrage-status]'); if (st) st.textContent = 'Demo: Auf der echten Seite öffnet sich hier die Anfrage mit ' + RAUM[a.dataset.anfrage].name + ' (' + FORM[formFuerPlan(RAUM[a.dataset.anfrage])].name + ') vorausgewählt.'; } });
/* ---------- Variante raum ---------- */
function raumReiter() {
$('#raumreiter').innerHTML = RAEUME.map(r => '<button type="button" role="tab" id="tab-' + r.id + '" aria-controls="raum-panel" aria-selected="' + (r.id === Z.raum) + '" tabindex="' + (r.id === Z.raum ? 0 : -1) + '" data-raumtab="' + r.id + '">' + r.name + '</button>').join('');
}
function raumPanel(wechsel) {
const r = RAUM[Z.raum];
if (r.p[Z.raumForm] == null) Z.raumForm = ['rund', 'reihe', 'tafel', 'steh'].find(x => r.p[x] != null);
const panel = $('#raum-panel');
panel.setAttribute('aria-labelledby', 'tab-' + r.id);
const unm = FORMEN.filter(f => r.p[f.id] == null);
panel.innerHTML = '<div class="raum-kopf"><p class="rubrik">' + r.geb + ' · ' + r.ebene + '</p><h2>' + r.name + '</h2><dl class="fakten">'
+ '<div><dt>Fläche</dt><dd>' + flaeche(r) + ' m² · ' + zahl.format(r.l) + ' × ' + zahl.format(r.b) + ' m</dd></div>'
+ '<div><dt>Höhe</dt><dd>' + r.hoehe + '</dd></div>'
+ '<div><dt>Mindestgäste</dt><dd>' + (r.mindest || 'keine, nur zusammen mit einem Saal') + '</dd></div>'
+ '<div><dt>Miete Mai–Okt</dt><dd>' + euro.format(r.miete[0]) + ' pro Tag</dd></div>'
+ '<div><dt>Miete Nov–Apr</dt><dd>' + (r.miete[1] == null ? 'nicht vermietet (' + r.gruende.winter + ')' : euro.format(r.miete[1]) + ' pro Tag') + '</dd></div>'
+ '<div><dt>Zugang</dt><dd>' + r.zugang + '</dd></div></dl>'
+ '<p class="platzhalter">Platzhalter · Zahlen vom Kunden prüfen lassen</p></div>'
+ '<div class="raum-plan"><fieldset class="formwahl"><legend>Bestuhlung</legend><div class="pillen">'
+ FORMEN.map(f => '<label class="pille"' + (r.p[f.id] == null ? ' aria-disabled="true"' : '') + '><input type="radio" name="raumform" value="' + f.id + '"' + (f.id === Z.raumForm ? ' checked' : '') + (r.p[f.id] == null ? ' disabled' : '') + '><svg class="ik" viewBox="0 0 26 18" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true">' + f.ik + '</svg>' + f.name + ' <span class="zahl">' + (r.p[f.id] == null ? '–' : r.p[f.id]) + '</span>' + (r.p[f.id] == null ? '<span class="nur-sr">, nicht möglich: ' + r.gruende[f.id] + '</span>' : '') + '</label>').join('')
+ '</div></fieldset>' + (unm.length ? '<p class="gruende">' + unm.map(f => f.name + ': ' + r.gruende[f.id]).filter((t, i, a) => a.indexOf(t) === i).join(' · ') + '</p>' : '')
+ '<div class="plan" id="raum-plan">' + planHtml(r, Z.raumForm, false) + '</div></div>';
if (wechsel && !Z.ohneAnim) panel.animate(reduziert() ? [{ opacity: 0 }, { opacity: 1 }] : [{ opacity: 0 }, { opacity: 1 }], { duration: reduziert() ? 200 : 525, easing: KURVE });
}
function raumForm(f) {
Z.raumForm = f;
const r = RAUM[Z.raum], ziel = $('#raum-plan');
ziel.innerHTML = planHtml(r, f, false);
if (!Z.ohneAnim) ziel.querySelector('figure').classList.add('plan-wechsel'), ziel.querySelector('.gross').classList.add('plan-wechsel');
document.querySelectorAll('input[name="raumform"]').forEach(i => { i.checked = i.value === f; });
}
function waehleRaum(id, fokus) {
Z.raum = id;
document.querySelectorAll('[data-raumtab]').forEach(b => { const ja = b.dataset.raumtab === id; b.setAttribute('aria-selected', String(ja)); b.tabIndex = ja ? 0 : -1; if (ja && fokus) b.focus(); });
raumPanel(true);
}
/* ---------- Variante gelaende ---------- */
const ORTE = { scheune: [27, 52], heuboden: [27, 66], bibliothek: [73, 15], gartensaal: [73, 31], kamin: [73, 45], linde: [76, 80] };
function skizze() {
const T = '#1D1A17', S = '#E9DCC8', SH = '#F0E6D6', MS = '#8C6A3C';
const svg = '<svg viewBox="0 0 1000 600" role="img" aria-label="Lageplan von Gut Weidenhain: oben die Zufahrt mit Parkplatz, links die Scheune mit Großer Scheune und Heuboden, in der Mitte der Hof, rechts das Gutshaus mit Gartensaal, Kaminzimmer und Bibliothek, unten rechts die Trauwiese unter der Linde, ganz unten der Main. Ein Weg ohne Stufen führt vom Parkplatz über den Hof zu Scheune, Gutshaus (Rampe) und Trauwiese.">'
+ '<defs><pattern id="kies" width="10" height="10" patternUnits="userSpaceOnUse"><rect width="10" height="10" fill="' + SH + '"/><circle cx="3" cy="3" r=".9" fill="#CDBB9C"/><circle cx="8" cy="7" r=".7" fill="#CDBB9C"/></pattern>'
+ '<pattern id="gras" width="14" height="14" patternUnits="userSpaceOnUse"><rect width="14" height="14" fill="#E4DDC9"/><path d="M3 9l1-3M9 4l1-3" stroke="#B9AE8E" stroke-width="1"/></pattern></defs>'
+ '<rect width="1000" height="600" fill="#FBF8F2"/>'
+ '<rect x="0" y="0" width="1000" height="44" fill="' + S + '"/><text x="24" y="28" font-size="15" fill="' + T + '" font-family="Archivo, sans-serif">Weidenhainer Weg</text>'
+ '<path d="M0 548 C200 530 420 562 640 544 C800 532 900 552 1000 540 V600 H0Z" fill="#DCD3C2"/><path d="M40 572 C200 560 320 584 480 572 M560 576 C700 566 820 584 960 574" stroke="' + MS + '" stroke-width="1.4" fill="none" opacity=".6"/><text x="880" y="590" font-size="15" font-style="italic" fill="' + T + '" font-family="Archivo, sans-serif">Main</text>'
+ '<rect x="40" y="62" width="300" height="118" fill="' + SH + '" stroke="' + T + '" stroke-width="1.2"/>'
+ Array.from({ length: 11 }, (_, i) => '<path d="M' + (66 + i * 25) + ' 62v42" stroke="' + T + '" stroke-width=".8" opacity=".5"/>').join('')
+ '<rect x="266" y="120" width="70" height="56" fill="' + S + '" stroke="' + T + '" stroke-width="1"/><text x="301" y="153" font-size="13" text-anchor="middle" fill="' + T + '" font-family="Archivo, sans-serif" font-weight="700">P · breit</text>'
+ '<text x="56" y="160" font-size="15" fill="' + T + '" font-family="Archivo, sans-serif">Parkplatz · 60 Plätze</text>'
+ '<rect x="360" y="190" width="250" height="250" fill="url(#kies)"/><text x="485" y="330" font-size="15" text-anchor="middle" fill="' + T + '" font-family="Archivo, sans-serif">Hof</text>'
+ '<rect x="60" y="220" width="300" height="200" fill="' + S + '" stroke="' + T + '" stroke-width="2"/><path d="M60 320h300" stroke="' + T + '" stroke-width="1" stroke-dasharray="6 5"/>'
+ '<rect x="356" y="296" width="8" height="48" fill="' + MS + '"/>'
+ '<path d="M70 404h14v-10h14v-10h14" fill="none" stroke="' + T + '" stroke-width="1.6"/>'
+ '<rect x="610" y="90" width="300" height="250" fill="#EDE3D3" stroke="' + T + '" stroke-width="2"/><path d="M610 90l150 -0" stroke="' + T + '"/>'
+ '<path d="M610 280h-40" stroke="' + T + '" stroke-width="2"/><path d="M572 272l38 0" stroke="' + T + '" stroke-width="10" opacity=".12"/>'
+ '<text x="590" y="300" font-size="12" text-anchor="middle" fill="' + T + '" font-family="Archivo, sans-serif">Rampe</text>'
+ '<rect x="560" y="380" width="380" height="150" fill="url(#gras)" stroke="' + MS + '" stroke-width="1.4" stroke-dasharray="7 5"/>'
+ '<circle cx="640" cy="455" r="52" fill="#3B3A2E" opacity=".9"/><circle cx="626" cy="440" r="22" fill="#4A4838"/>'
+ '<path d="M300 180 V200 H470 V370 H560 V420 M470 260 H360 M470 230 H610 M520 230 V270 H572" fill="none" stroke="' + T + '" stroke-width="3" stroke-dasharray="1 7" stroke-linecap="round"/>'
+ '<text x="955" y="80" font-size="14" text-anchor="middle" fill="' + T + '" font-family="Archivo, sans-serif">N</text><path d="M955 86v26M949 94l6-8 6 8" fill="none" stroke="' + T + '" stroke-width="1.4"/>'
+ '<path d="M40 520h100M40 514v12M140 514v12" stroke="' + T + '" stroke-width="1.4"/><text x="148" y="525" font-size="13" fill="' + T + '" font-family="Archivo, sans-serif">25 m</text>'
+ '</svg>';
const knoepfe = RAEUME.map(r => '<button type="button" class="ort" data-ort="' + r.id + '" style="left:' + ORTE[r.id][0] + '%;top:' + ORTE[r.id][1] + '%" aria-pressed="' + (r.id === Z.ort) + '"><span class="zeichen" aria-hidden="true"></span>' + r.name + ' <small></small></button>').join('');
$('#skizze').innerHTML = svg + knoepfe;
$('#ortliste').innerHTML = RAEUME.map(r => '<button type="button" class="ort" data-ort="' + r.id + '" aria-pressed="' + (r.id === Z.ort) + '"><span class="zeichen" aria-hidden="true"></span>' + r.name + ' <small></small></button>').join('');
}
function info(fokus) {
const r = RAUM[Z.ort], b = bewerte(r), el = $('#raum-info');
el.innerHTML = '<p class="rubrik">' + r.geb + ' · ' + r.ebene + '</p><h3 tabindex="-1" id="info-titel">' + r.name + '</h3>'
+ (Z.g ? '<p class="zst"><span class="zeichen ' + zeichenKlasse(b.s) + '" aria-hidden="true"></span>' + b.text + '</p>' : '')
+ '<p class="zugang"><svg viewBox="0 0 22 22" aria-hidden="true">' + (r.stufenlos ? '<circle cx="11" cy="11" r="10" fill="none" stroke="#1D1A17" stroke-width="1.4"/><path d="M6.5 11.5l3 3 6-7" fill="none" stroke="#1D1A17" stroke-width="1.8"/>' : '<path d="M3 18h5v-5h5V8h5V3" fill="none" stroke="#1D1A17" stroke-width="1.8"/>') + '</svg><span><b>' + (r.stufenlos ? 'Ohne Stufen erreichbar.' : 'Mit Stufen.') + '</b> ' + r.zugang + '.</span></p>'
+ '<table><caption class="nur-sr">Plätze im Raum ' + r.name + ' je Bestuhlung</caption><tbody>' + FORMEN.map(f => '<tr' + (f.id === Z.f ? ' class="aktiv"' : '') + '><th scope="row">' + f.name + '</th><td>' + (r.p[f.id] == null ? '–<span class="nur-sr"> nicht möglich</span>' : r.p[f.id]) + '</td></tr>').join('')
+ '<tr><th scope="row">Fläche</th><td>' + flaeche(r) + ' m²</td></tr><tr><th scope="row">Miete Mai–Okt</th><td>' + euro.format(r.miete[0]) + '</td></tr><tr><th scope="row">Miete Nov–Apr</th><td>' + (r.miete[1] == null ? '–' : euro.format(r.miete[1])) + '</td></tr></tbody></table>'
+ (r.gruende ? '<p class="gruende">' + Object.entries(r.gruende).filter(([k]) => FORM[k]).map(([k, t]) => FORM[k].name + ': ' + t).join(' · ') + '</p>' : '')
+ '<div><button type="button" class="knopf knopf-neben planknopf" id="ort-plan-knopf" aria-expanded="' + Z.ortPlan + '" aria-controls="ort-plan">Plan zeigen <svg class="pfeil" viewBox="0 0 14 14" aria-hidden="true"><path d="M3 5l4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.5"/></svg></button></div>'
+ '<div class="plan-huelle" id="ort-plan"' + (Z.ortPlan ? '' : ' hidden') + '><div class="plan" data-ort-plan="' + r.id + '" style="padding:0;grid-template-columns:minmax(0,1fr)"></div></div>';
if (Z.ortPlan) setzePlan(r, el.querySelector('[data-ort-plan]'), true);
if (fokus && !Z.vorf) $('#info-titel').focus({ preventScroll: false });
}
function waehleOrt(id, fokus) {
Z.ort = id;
document.querySelectorAll('.ort').forEach(o => o.setAttribute('aria-pressed', String(o.dataset.ort === id)));
info(fokus);
if (!Z.ohneAnim) $('#raum-info').animate([{ opacity: 0 }, { opacity: 1 }], { duration: reduziert() ? 200 : 400, easing: KURVE });
}
/* ---------- Ereignisse ---------- */
pillen.innerHTML = [{ id: 'alle', name: 'Alle Formen', ik: '' }].concat(FORMEN).map(f => '<label class="pille"><input type="radio" name="form" value="' + f.id + '"' + (f.id === 'alle' ? ' checked' : '') + '>' + (f.ik ? '<svg class="ik" viewBox="0 0 26 18" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true">' + f.ik + '</svg>' : '') + f.name + '</label>').join('');
fG.addEventListener('input', () => { Z.g = Math.max(0, parseInt(fG.value, 10) || 0); anwenden(); });
pillen.addEventListener('change', e => { Z.f = e.target.value; Z.planForm = {}; anwenden(); if (Z.offen) planForm(Z.offen, formFuerPlan(RAUM[Z.offen])); });
$('#filter').addEventListener('submit', e => e.preventDefault());
document.addEventListener('click', e => {
const pk = e.target.closest('.planknopf[data-plan]');
if (pk) { planUmschalten(pk.dataset.plan, pk.hasAttribute('data-karte')); return; }
const pf = e.target.closest('[data-planform]');
if (pf) { planForm(pf.dataset.raum, pf.dataset.planform); return; }
const rt = e.target.closest('[data-raumtab]');
if (rt) { waehleRaum(rt.dataset.raumtab, false); return; }
const o = e.target.closest('.ort');
if (o) { waehleOrt(o.dataset.ort, false); return; }
if (e.target.closest('#ort-plan-knopf')) {
Z.ortPlan = !Z.ortPlan;
const k = $('#ort-plan-knopf'), hl = $('#ort-plan');
k.setAttribute('aria-expanded', String(Z.ortPlan));
if (Z.ortPlan) { setzePlan(RAUM[Z.ort], hl.querySelector('.plan'), true); hl.hidden = false; klappe(hl, true); }
else klappe(hl, false, () => { hl.hidden = true; });
}
});
$('#raumreiter').addEventListener('keydown', e => {
const i = RAEUME.findIndex(r => r.id === Z.raum);
const n = { ArrowRight: i + 1, ArrowLeft: i - 1, Home: 0, End: RAEUME.length - 1 }[e.key];
if (n == null) return;
e.preventDefault();
waehleRaum(RAEUME[(n + RAEUME.length) % RAEUME.length].id, true);
});
document.addEventListener('change', e => { if (e.target.name === 'raumform') raumForm(e.target.value); });
/* ---------- Varianten ---------- */
const EINSTIEG = {
tabelle: ['Räume und Kapazitäten', 'Sechs Räume, ein Fest.', 'Scheune, Gutshaus und Garten lassen sich für jeden Anlass anders stellen. Tragen Sie Ihre Gästezahl ein, dann zeigen wir, welcher Raum passt.'],
raum: ['Räume und Kapazitäten', 'Ein Raum, sechs Arten zu feiern.', 'Wählen Sie den Raum und die Bestuhlung. Der Plan zeigt Tische, Tanzfläche und Ausgänge, darunter steht alles als Text.'],
gelaende: ['Das Gelände', 'Alles an einem Hof.', 'Scheune, Gutshaus und Trauwiese liegen um einen Hof. Alle Säle bis auf den Heuboden erreichen Sie ohne Stufen.']
};
function aufbauen() {
body.dataset.v = Z.v;
const e = EINSTIEG[Z.v];
$('#rubrik').textContent = e[0]; $('#titel').textContent = e[1]; $('#einstieg-text').textContent = e[2];
tabelle(); karten(); raumReiter(); raumPanel(false); skizze(); info(false);
anwenden();
breiteMerken();
document.querySelectorAll('[data-variante]').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.variante === Z.v)));
}
function zuruecksetzen() {
$('#filter').reset(); Z.g = 0; Z.f = 'alle'; Z.offen = null; Z.planForm = {}; Z.raum = 'scheune'; Z.raumForm = 'rund'; Z.ort = 'scheune'; Z.ortPlan = false;
}
function variante(v, schreiben) {
Z.v = ['tabelle', 'raum', 'gelaende'].includes(v) ? v : 'tabelle';
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); }));
/* ---------- Vorführung ---------- */
const zeiger = $('#zeiger');
let lauf = 0, pausiert = false, zx = 0, zy = 0;
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 * .3), 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'); await warte(160); 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 : 110); }
}
const pille = id => pillen.querySelector('input[value="' + id + '"]');
async function szene() {
neustart(true);
zeiger.classList.add('an');
setzeZeiger(innerWidth * .6, innerHeight * .92, true);
await warte(500);
if (Z.v === 'tabelle') {
await zu(fG, .3); await tippe(fG, '120'); await warte(1300);
await zu(pille('rund').parentElement); await tipp(pille('rund')); await warte(1500);
const k = [...document.querySelectorAll('.planknopf[data-plan="scheune"]')].find(el => el.offsetParent);
await zu(k); await tipp(k); await warte(900);
await zu(document.querySelector('[data-plan-inhalt="scheune"] figure, [data-plan-karte="scheune"] figure'), .5, .4); await warte(2600);
window.scrollTo({ top: 0, behavior: reduziert() ? 'auto' : 'smooth' }); await warte(700);
await zu(fG, .3); await tippe(fG, '240'); await warte(3200);
} else if (Z.v === 'raum') {
for (const f of ['tafel', 'reihe', 'steh', 'rund']) { const i = document.querySelector('input[name="raumform"][value="' + f + '"]'); await zu(i.parentElement); await tipp(i); await warte(1500); }
await zu($('#tab-gartensaal')); await tipp($('#tab-gartensaal')); await warte(1700);
const i = document.querySelector('input[name="raumform"][value="tafel"]'); await zu(i.parentElement); await tipp(i); await warte(1800);
} else {
await zu(fG, .3); await tippe(fG, '120'); await warte(900);
await zu(pille('rund').parentElement); await tipp(pille('rund')); await warte(1300);
for (const id of ['scheune', 'kamin', 'linde']) { const o = [...document.querySelectorAll('.ort[data-ort="' + id + '"]')].find(el => el.offsetParent); await zu(o); await tipp(o); await warte(1700); }
}
}
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>