Zum Inhalt
← Galerien & Slider

Natives CSS-Karussell (Scroll-Snap, Scroll-Buttons, Scroll-Marker)

Produktreihe einer Münsteraner Rösterei als echter Scroller: Scroll-Snap, Pfeile und Punkte vom Browser, die eingerastete Tüte hebt sich per scroll-state(), ganz ohne JS.

Shop, Rösterei, Mode, d2c: viele gleichrangige Produkte, die mittlere hebt sich hervor (Technik umschaltbar: Nativ, Durchlauf, Fallback).

Ein echter Scroller mit overflow-x auto und scroll-snap-type x mandatory. Die Kinder rasten mit scroll-snap-align center ein; scroll-snap-stop always sorgt für eine Karte pro Klick oder Geste. ::scroll-button(left/right) erzeugt die Pfeile mit Alt-Text, ::scroll-marker mit scroll-marker-group die Punkte als Tablist. Mit container-type scroll-state und @container scroll-state(snapped: x) hebt sich die eingerastete Karte hervor. Optional skaliert oder verschiebt eine view-timeline die Karten beim Durchlaufen.

Es ist echtes Scrollen: Trackpad, Mausrad, Wischen und Tastatur verhalten sich auf jedem Gerät so, wie man es erwartet. Es braucht kein JS, und Buttons und Marker bringen ihre Rollen für Tastatur und Screenreader mit. Für Claude ist es der robusteste Standard, auf den man aufbauen kann.

Wann einsetzen

Produktreihen, Shop-Teaser, Referenzen, Blog-Teaser und Karten-Slider auf Landingpages. Immer dann, wenn kein Endlos-Loop nötig ist.

Wann nicht

Wenn ein nahtloser Endlos-Loop, Trägheit mit eigener Physik oder eine 3D-Anordnung gefragt ist. Wenn Firefox- und Safari-Nutzer genau dieselbe Bedienung brauchen: Dort gibt es bisher nur Scroll-Snap und eigene Buttons.

Varianten

  • Nur Scroll-Snap mit eigenen Buttons (läuft überall)
  • Mit ::scroll-button und ::scroll-marker als Punkte oder Farbfelder
  • Mit scroll-state(snapped) für die aktive Karte
  • Mit view-timeline für Skalierung oder Parallaxe beim Durchlaufen
  • Mit interactivity: inert für nicht sichtbare Karten (laut Chrome-Configurator)
  • Vollbild-Bühne: ein Bild füllt die Breite, Marker als Reiter mit Titel im Modus tabs (scroll-marker-group: after tabs, Chrome 154)
  • Kartenreihe mit Peek: Start-Ausrichtung, die nächste Karte schaut an der Kante herein, Marker im Modus links
  • Variable Breiten: gemischte Hoch- und Querformate in fester Höhe (width auto), Snap auf start, Pfeile blättern um die Sichtbreite, Zähler zeigt den sichtbaren Bereich
  • Fenster: ein Bild in einem schmalen Fenster von etwa 4 Spalten, zentriert, Endlosschleife über Klone, keine Punkte, große 1-px-Haarlinien-Pfeile im Leerraum (nach Kereliott)

Umsetzung

Progressive Enhancement. Basis ist Scroll-Snap mit eigenen Buttons (scrollBy) und einem IntersectionObserver für den Aktivzustand. Mit @supports selector(::scroll-button(right)) die eigenen Buttons ausblenden und die nativen nutzen. Die nativen Buttons lassen sich mit position-anchor an jede Stelle der Seite setzen. Fallstricke: Ohne scroll-snap-stop always überspringen schnelle Gesten Karten. scrollbar-width none nur mit sichtbaren Alternativen. Die Marker-Gruppe braucht ein eigenes Layout (flex, gap). Damit die erste und letzte Karte mittig einrasten, braucht der Scroller seitliches Padding von 50 % minus halber Kartenbreite.

Robust umsetzen

Telefon. Ideal: natives Wischen mit Einrasten. Pfeile auf Touch ausblenden, Marker behalten.

Ohne Bewegung. scroll-behavior auto statt smooth. view-timeline-Effekte in der Media Query entfernen. Die Hervorhebung darf bleiben, aber ohne Übergang.

Barrierefrei. Der Browser vergibt Rollen, Tab-Reihenfolge und Zustand für Buttons und Marker. Trotzdem braucht der Scroller ein aria-label und jede Karte eine eindeutige Beschriftung (z. B. „Aasee, 2 von 8“). Im Fallback eigene Buttons mit aria-label und aria-controls.

Code

<!DOCTYPE html>
<!--
  Natives CSS-Karussell (Scroll-Snap, Scroll-Buttons, Scroll-Marker)
  Fundus · Galerien & Slider · https://cecodesign.de/lab/fundus/galerien-slider/css-native-karussell/
  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>Natives CSS-Karussell (Scroll-Snap, Scroll-Buttons, Scroll-Marker) · 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: 'Fraunces'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/fraunces:vf@latest/latin-full-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-style: normal; font-display: swap; }
@font-face { font-family: 'Fraunces'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/fraunces:vf@latest/latin-wght-italic.woff2') format('woff2-variations'); font-weight: 100 900; font-style: italic; font-display: swap; }
@font-face { font-family: 'Commissioner'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/commissioner:vf@latest/latin-full-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-display: swap; }
@font-face { font-family: 'Instrument Sans'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/instrument-sans:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 400 700; font-display: swap; }
@font-face { font-family: 'Instrument Serif'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/instrument-serif@latest/latin-400-italic.woff2') format('woff2'); font-weight: 400; font-style: italic; font-display: swap; }
@font-face { font-family: 'Newsreader'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/newsreader:vf@latest/latin-opsz-normal.woff2') format('woff2-variations'); font-weight: 200 800; font-display: swap; }
@font-face { font-family: 'Newsreader'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/newsreader:vf@latest/latin-opsz-italic.woff2') format('woff2-variations'); font-weight: 200 800; font-style: italic; font-display: swap; }
@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; }
@font-face { font-family: 'Geist Mono'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/geist-mono:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-display: swap; }
@font-face { font-family: 'Instrument Serif'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/instrument-serif@latest/latin-400-normal.woff2') format('woff2'); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: 'Fragment Mono'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/fragment-mono@latest/latin-400-normal.woff2') format('woff2'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'Mona Sans'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/mona-sans:vf@latest/latin-wdth-normal.woff2') format('woff2-variations'); font-weight: 200 900; font-stretch: 75% 125%; font-display: swap; }

:root {
  --papier: #f0ece0; --tinte: #353839; --leise: rgba(53, 56, 57, .75); --linie: rgba(53, 56, 57, .16);
  --salbei: #7f937a; --salbei-text: #586b54; --gruen: #293e3a; --senf: #eac16e; --rose: #e1a894;
  --kurve: cubic-bezier(0.33, 1, 0.68, 1);   /* Hervorhebung, gemessen (Chrome-Artikel) */
  --aktiv: 400ms;                            /* Aktivzustand 300–500 ms */
  --ui: 150ms;
  --rand: clamp(20px, 3.4vw, 48px);
  --breite: clamp(232px, min(25vw, 35svh), 340px);
  --luecke: clamp(16px, 2vw, 28px);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { background: var(--papier); color: var(--tinte); font-family: 'Commissioner', system-ui, sans-serif; font-variation-settings: 'FLAR' 0; -webkit-font-smoothing: antialiased; }
body { min-height: 100svh; overflow-x: hidden; display: grid; grid-template-rows: auto 1fr auto; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; }
.versal { font-size: 11.5px; font-weight: 560; letter-spacing: .15em; text-transform: uppercase; font-variation-settings: 'FLAR' 80; }
.mono { font: 500 10.5px/1.4 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; color: var(--leise); }

/* Kopf des Cafés */
.kopf { display: flex; align-items: center; justify-content: space-between; gap: 16px 28px; padding: 22px var(--rand); border-bottom: 1px solid var(--linie); }
.marke { display: flex; align-items: baseline; gap: 14px; }
.marke b { font: 600 25px/1 'Fraunces', Georgia, serif; font-variation-settings: 'SOFT' 100, 'opsz' 30; letter-spacing: -.01em; }
.marke b i { font-style: italic; font-weight: 450; color: #70836b; }
.marke span { color: var(--leise); }
.navi { display: flex; gap: 26px; font-size: 15px; }
.navi a { position: relative; }
.navi a[aria-current] { font-weight: 600; }
.navi a[aria-current]::after { content: ''; position: absolute; left: 0; right: 0; bottom: -6px; height: 2px; background: var(--senf); border-radius: 2px; }
.status { display: flex; align-items: center; gap: 16px; font-size: 14px; }
.status .offen { display: inline-flex; align-items: center; gap: 8px; }
.status .offen::before { content: ''; width: 8px; height: 8px; border-radius: 50%; background: var(--salbei); box-shadow: 0 0 0 4px rgba(127, 147, 122, .22); }
.status .zu::before { background: var(--rose); box-shadow: 0 0 0 4px rgba(225, 168, 148, .25); }
.status .reservieren { height: 38px; padding: 0 16px; display: inline-flex; align-items: center; border-radius: 999px; background: var(--gruen); color: var(--papier); font-weight: 560; transition: background-color var(--ui) var(--kurve); }
.status .reservieren:hover { background: #1d2d2a; }

/* Abschnitt Shop */
.shop { display: grid; align-content: center; gap: clamp(16px, 2.6svh, 30px); padding: clamp(18px, 3.4svh, 40px) 0 clamp(10px, 2svh, 24px); }
.titelzeile { display: flex; justify-content: space-between; align-items: flex-end; gap: 18px 40px; padding-inline: var(--rand); }
.titel .versal { color: var(--salbei-text); display: block; margin-bottom: 14px; }
.titel h2 { font: 520 clamp(2.4rem, 1.4rem + 3.4vw, 4.6rem)/1.02 'Fraunces', Georgia, serif; font-variation-settings: 'SOFT' 80; letter-spacing: -.01em; max-width: 15ch; text-wrap: balance; }
.titel h2 em { font-weight: 420; color: var(--gruen); }
.seitlich { display: grid; justify-items: end; gap: 14px; text-align: right; }
.seitlich p { font-size: 15px; line-height: 1.5; color: var(--leise); max-width: 30ch; }
.steuer { display: flex; align-items: center; gap: 16px; }
.zaehler { font-variant-numeric: tabular-nums; font-size: 15px; font-weight: 500; display: none; }
.zaehler span { color: var(--leise); }

/* Platz für die Pfeile: Anker für die nativen ::scroll-button, Heimat der eigenen Knöpfe */
.knopf-platz { anchor-name: --knoepfe; display: flex; gap: 8px; width: 104px; height: 48px; }
.knopf, .band::scroll-button(*) {
  width: 48px; height: 48px; border-radius: 50%; border: 1px solid rgba(53, 56, 57, .3); background-color: transparent; color: var(--tinte); cursor: pointer;
  transition: background-color var(--ui) var(--kurve), color var(--ui) var(--kurve), border-color var(--ui) var(--kurve), opacity var(--ui) var(--kurve), scale var(--ui) var(--kurve);
}
.knopf { display: none; place-items: center; }
.knopf svg { width: 18px; height: 18px; }
.knopf:hover:not(:disabled), .band::scroll-button(*):hover:not(:disabled), .knopf.druck { background-color: var(--gruen); border-color: var(--gruen); color: var(--papier); }
.knopf.druck, .band.druck-links::scroll-button(left), .band.druck-rechts::scroll-button(right) { scale: .92; background-color: var(--gruen); border-color: var(--gruen); }
.knopf:disabled, .band::scroll-button(*):disabled { opacity: .3; cursor: default; }
.knopf:focus-visible, .band::scroll-button(*):focus-visible, .modi button:focus-visible { outline: 2px solid var(--gruen); outline-offset: 3px; }

/* Das Band: echter Scroller mit Scroll-Snap */
.band {
  list-style: none; display: grid; grid-auto-flow: column; grid-auto-columns: var(--breite); gap: var(--luecke);
  overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none;
  scroll-snap-type: x mandatory; scroll-behavior: smooth;
  padding: 8px calc(50% - var(--breite) / 2) 4px; outline: none;
}
.band::-webkit-scrollbar { display: none; }
.band:focus-visible { box-shadow: inset 0 0 0 2px var(--gruen); }
.slide { scroll-snap-align: center; scroll-snap-stop: always; container-type: scroll-state; view-timeline-name: --karte; view-timeline-axis: inline; }
.karte { display: grid; gap: 14px; opacity: .46; transition: opacity var(--aktiv) var(--kurve); }
.kachel { position: relative; aspect-ratio: 1 / 1.04; border-radius: 10px; overflow: hidden; container-type: inline-size; background:
  radial-gradient(120% 80% at 22% 8%, rgba(255, 255, 255, .26), transparent 60%),
  linear-gradient(to bottom, transparent 74%, rgba(0, 0, 0, .09) 74%, rgba(0, 0, 0, .04)),
  var(--kachel); scale: .94; transition: scale var(--aktiv) var(--kurve); }
/* Eingerastet: native Abfrage scroll-state(snapped: x) – kein JS */
@container scroll-state(snapped: x) {
  :is([data-modus=nativ], [data-modus=durchlauf]) .karte { opacity: 1; }
  :is([data-modus=nativ], [data-modus=durchlauf]) .kachel { scale: 1; }
  :is([data-modus=nativ], [data-modus=durchlauf]) .tuete { translate: 0 -5%; box-shadow: var(--tuete-schatten), 0 34px 36px -18px rgba(30, 24, 18, .5); }
}
/* Fallback: dieselbe Hervorhebung über eine Klasse aus dem IntersectionObserver */
[data-modus=fallback] .ist-aktiv .karte { opacity: 1; }
[data-modus=fallback] .ist-aktiv .kachel { scale: 1; }
[data-modus=fallback] .ist-aktiv .tuete { translate: 0 -5%; box-shadow: var(--tuete-schatten), 0 34px 36px -18px rgba(30, 24, 18, .5); }

/* Tüte, Etikett, Bohnen: alles CSS */
.tuete {
  --tuete-schatten: inset -1.2cqi 0 2.4cqi rgba(0, 0, 0, .16), inset 1cqi 0 1.6cqi rgba(255, 255, 255, .1);
  position: absolute; left: 27%; bottom: 17%; width: 46%; aspect-ratio: .74; border-radius: 1.4cqi 1.4cqi 2.4cqi 2.4cqi;
  background: linear-gradient(90deg, rgba(0, 0, 0, .08), transparent 18%, transparent 82%, rgba(0, 0, 0, .12)), var(--tuete);
  box-shadow: var(--tuete-schatten), 0 22px 26px -16px rgba(30, 24, 18, .45);
  transition: translate var(--aktiv) var(--kurve), box-shadow var(--aktiv) var(--kurve);
}
.tuete::before { content: ''; position: absolute; inset: 0 0 auto; height: 10%; border-radius: inherit; border-bottom-left-radius: 0; border-bottom-right-radius: 0;
  background: repeating-linear-gradient(to bottom, rgba(0, 0, 0, .2) 0 .5cqi, transparent .5cqi 1.4cqi), var(--tuete); }
.ventil { position: absolute; right: 14%; top: 16%; width: 6cqi; aspect-ratio: 1; border-radius: 50%; background: radial-gradient(circle, rgba(0, 0, 0, .25) 0 22%, rgba(255, 255, 255, .16) 24% 60%, rgba(0, 0, 0, .14) 62%); }
.etikett { position: absolute; left: 13%; right: 13%; top: 27%; bottom: 13%; border-radius: 1cqi; background: var(--etikett, #f6f1e4); color: var(--etikett-text, var(--gruen));
  display: grid; align-content: start; justify-items: center; text-align: center; padding: 4cqi 2cqi 0; overflow: hidden; }
.etikett small { font: 650 2.6cqi/1 'Commissioner', sans-serif; font-variation-settings: 'FLAR' 100; letter-spacing: .2em; text-transform: uppercase; }
.etikett b { font: italic 480 6.6cqi/1 'Fraunces', Georgia, serif; margin-top: 3.4cqi; letter-spacing: -.01em; }
.etikett i { font: normal 560 2.5cqi/1 'Commissioner', sans-serif; letter-spacing: .16em; text-transform: uppercase; margin-top: 2.2cqi; opacity: .75; }
.etikett .muster { position: absolute; left: 0; right: 0; bottom: 0; height: 26%; background-color: var(--band); }
.m-wellen .muster { background-image: radial-gradient(circle at 50% 0, transparent 1.6cqi, rgba(255, 255, 255, .55) 1.6cqi 2.2cqi, transparent 2.2cqi); background-size: 4.4cqi 2.6cqi; }
.m-punkte .muster { background-image: radial-gradient(circle, rgba(255, 255, 255, .6) .7cqi, transparent .8cqi); background-size: 3cqi 3cqi; }
.m-streifen .muster { background-image: repeating-linear-gradient(90deg, rgba(255, 255, 255, .5) 0 1cqi, transparent 1cqi 2.6cqi); }
.m-karo .muster { background-image: linear-gradient(45deg, rgba(255, 255, 255, .4) 25%, transparent 25% 75%, rgba(255, 255, 255, .4) 75%), linear-gradient(45deg, rgba(255, 255, 255, .4) 25%, transparent 25% 75%, rgba(255, 255, 255, .4) 75%); background-size: 3cqi 3cqi; background-position: 0 0, 1.5cqi 1.5cqi; }
.bohne { position: absolute; left: var(--x); top: var(--y); width: 6.4cqi; aspect-ratio: .74; border-radius: 50%; rotate: var(--r);
  background: radial-gradient(70% 60% at 35% 30%, #8a5a3c, #4a2c1d 70%); box-shadow: 0 .6cqi .8cqi rgba(0, 0, 0, .22); }
.bohne::after { content: ''; position: absolute; left: 44%; top: 12%; bottom: 12%; width: 20%; border-left: .45cqi solid #2b170e; border-radius: 50%; }
/* Abo: zwei Tüten; Gutschein: Karte statt Tüte */
.tuete.hinten { left: 15%; bottom: 23%; width: 40%; rotate: -7deg; filter: brightness(.94); }
.tuete.vorn { left: 41%; width: 43%; }
.gutschein { position: absolute; left: 14%; right: 14%; top: 30%; aspect-ratio: 1.6; border-radius: 1.6cqi; rotate: -5deg; background: var(--senf); color: var(--gruen);
  display: grid; align-content: space-between; padding: 5cqi; box-shadow: 0 24px 30px -18px rgba(0, 0, 0, .6); transition: translate var(--aktiv) var(--kurve); }
.gutschein::before { content: ''; position: absolute; top: 0; bottom: 0; right: 27%; border-left: .4cqi dashed rgba(41, 62, 58, .45); }
.gutschein small { font: 650 2.6cqi/1 'Commissioner', sans-serif; font-variation-settings: 'FLAR' 100; letter-spacing: .2em; text-transform: uppercase; }
.gutschein b { font: italic 480 8cqi/1 'Fraunces', Georgia, serif; }
.gutschein span { font: 600 4cqi/1 'Commissioner', sans-serif; font-variant-numeric: tabular-nums; }
.gutschein.zwei { rotate: 6deg; top: 22%; left: 20%; right: 8%; background: var(--rose); filter: brightness(.96); }

.info { display: grid; grid-template-columns: 1fr auto; gap: 4px 12px; padding-inline: 2px; }
.info h3 { font: 520 22px/1.1 'Fraunces', Georgia, serif; font-variation-settings: 'SOFT' 60; }
.preis { font-size: 16px; font-weight: 560; font-variant-numeric: tabular-nums; align-self: baseline; }
.herkunft { grid-column: 1 / -1; font-size: 15px; color: var(--leise); line-height: 1.45; }
.noten { grid-column: 1 / -1; color: var(--salbei-text); font-size: 11px; margin-top: 2px; }

/* Variante „Nativ“ und „Durchlauf“: Buttons und Marker vom Browser */
:is([data-modus=nativ], [data-modus=durchlauf]) .band { scroll-marker-group: after; }
:is([data-modus=nativ], [data-modus=durchlauf]) .band::scroll-button(*) { position: fixed; position-anchor: --knoepfe; top: anchor(top); display: grid; place-items: center; font: 400 28px/1 'Fraunces', Georgia, serif; padding-bottom: 4px; }
:is([data-modus=nativ], [data-modus=durchlauf]) .band::scroll-button(left) { content: '\2039' / 'Vorherige Sorte'; left: anchor(left); }
:is([data-modus=nativ], [data-modus=durchlauf]) .band::scroll-button(right) { content: '\203A' / 'Nächste Sorte'; right: anchor(right); }
.band::scroll-marker-group { display: flex; justify-content: center; align-items: center; gap: 10px; padding-top: clamp(14px, 2.4svh, 24px); }
.slide::scroll-marker { content: ''; width: 8px; height: 8px; border-radius: 99px; background: rgba(53, 56, 57, .24); cursor: pointer;
  transition: width var(--aktiv) var(--kurve), background-color var(--aktiv) var(--kurve); }
.slide::scroll-marker:target-current { width: 30px; background: var(--tinte); }
.slide::scroll-marker:focus-visible { outline: 2px solid var(--gruen); outline-offset: 3px; }
/* Durchlauf: Marker als Farbfelder, Tüte wandert per view-timeline durch die Kachel */
[data-modus=durchlauf] .slide::scroll-marker { width: 22px; height: 10px; border-radius: 3px; background: var(--kachel); opacity: .5; transition: opacity var(--aktiv) var(--kurve), width var(--aktiv) var(--kurve); }
[data-modus=durchlauf] .slide::scroll-marker:target-current { width: 40px; opacity: 1; background: var(--kachel); box-shadow: inset 0 0 0 1.5px rgba(53, 56, 57, .5); }
@keyframes durchlauf {
  from { transform: translateX(34%) rotate(9deg); }
  50% { transform: none; }
  to { transform: translateX(-34%) rotate(-9deg); }
}
[data-modus=durchlauf] :is(.tuete, .gutschein, .bohne) { animation: durchlauf linear both; animation-timeline: --karte; }
[data-modus=durchlauf] .bohne { animation-range: cover 10% cover 90%; }

/* Fallback: eigene Knöpfe, Zähler */
[data-modus=fallback] .knopf { display: grid; }
[data-modus=fallback] .zaehler { display: block; }
.hinweis { display: none; }
@supports not selector(::scroll-button(right)) {
  .knopf { display: grid; }
  .zaehler { display: block; }
  .hinweis { display: block; }
}

/* Fuß: Varianten-Umschalter und verwendete Werte */
.fuss { display: flex; justify-content: space-between; align-items: center; gap: 12px 28px; flex-wrap: wrap; padding: 14px var(--rand) 18px; border-top: 1px solid var(--linie); }
.modi { display: flex; gap: 4px; flex-wrap: wrap; }
.modi button { font: inherit; letter-spacing: inherit; text-transform: inherit; color: var(--leise); background: none; border: 1px solid var(--linie); border-radius: 999px; height: 30px; padding: 0 12px; cursor: pointer; transition: color var(--ui) var(--kurve), background-color var(--ui) var(--kurve), border-color var(--ui) var(--kurve); }
.modi button:hover { color: var(--tinte); border-color: rgba(53, 56, 57, .4); }
.modi button[aria-pressed=true] { background: var(--tinte); color: var(--papier); border-color: var(--tinte); }
.werte { max-width: 760px; text-align: right; }
.werte b { color: var(--tinte); font-weight: 500; }

/* Vorführung: gezeichneter Zeiger */
.zeiger { position: fixed; left: 0; top: 0; z-index: 20; width: 22px; height: 22px; pointer-events: none; display: none; transition: transform 700ms var(--kurve); }
.vorfuehrung .zeiger { display: block; }

.vorschau body { overflow: hidden; }
.vorschau .fuss, .vorschau .navi, .vorschau .seitlich p, .vorschau .status .adresse { display: none; }

@media (max-width: 1100px) { .navi { display: none; } }
@media (max-width: 760px) {
  .kopf { padding-block: 16px; }
  .marke span, .status .adresse { display: none; }
  .status .reservieren { height: 34px; padding: 0 13px; font-size: 13px; }
  .titelzeile { flex-direction: column; align-items: flex-start; }
  .seitlich { justify-items: start; text-align: left; }
  .werte { text-align: left; }
  :root { --breite: 74vw; }
}
@media (max-width: 420px) { .status .offen { display: none; } }
/* Touch: Wischen ist die Bedienung, Pfeile fallen weg, Marker bleiben */
@media (hover: none) {
  .knopf-platz { display: none; }
  [data-modus] .band::scroll-button(*) { display: none; }
  .zeiger { display: none !important; }
}
/* Reduzierte Bewegung: kein weiches Scrollen, kein Durchlauf, Zustände ohne Übergang */
.reduziert .band { scroll-behavior: auto; }
.reduziert [data-modus=durchlauf] :is(.tuete, .gutschein, .bohne) { animation: none; }
.reduziert .karte, .reduziert .kachel, .reduziert .tuete, .reduziert .slide::scroll-marker, .reduziert .zeiger { transition: none; }
@media (prefers-reduced-motion: reduce) { .band { scroll-behavior: auto; } }
/* ===================== Varianten (Regel 13: ?v=produktreihe | buehne | peek | variable-breiten | fenster) ===================== */
.welt { display: contents; }
.welt[hidden] { display: none; }
.pultspalte { display: grid; gap: 8px; }
.pultspalte .modi { align-items: center; }
.pultspalte .modi > span { min-width: 64px; margin-right: 4px; }
html:not([data-v='produktreihe']) .modi[data-produkt-modi] { display: none; }
.modi[data-marker-modi] { display: none; }
html[data-v='buehne'] .modi[data-marker-modi] { display: flex; }

/* ---------- Variante „Vollbild-Bühne“: Rezept architektur-ruhiges-raster ---------- */
html[data-v='buehne'] { --papier: #ffffff; --tinte: #231f20; --leise: rgba(35, 31, 32, .68); --linie: rgba(35, 31, 32, .14); --gruen: #0022ff;
  font-family: 'Instrument Sans', system-ui, sans-serif; font-variation-settings: normal; }
.welt-b { --b-kurve: cubic-bezier(0.1, 0.6, 0.4, 1); --b-auf: cubic-bezier(0.1, 0.9, 0.3, 1); --b-meta: #76767a; --b-fokus: #0022ff; }
.b-kopf { display: flex; justify-content: space-between; align-items: baseline; gap: 16px 32px; padding: 20px var(--rand); }
.b-marke { font-size: 17px; font-weight: 500; letter-spacing: -.02em; }
.b-marke span { color: var(--b-meta); }
.b-kopf nav { display: flex; gap: 28px; font-size: 15px; }
.b-kopf nav a { color: var(--b-meta); transition: color 125ms var(--b-kurve); }
.b-kopf nav a:hover, .b-kopf nav a[aria-current] { color: var(--tinte); }
.b-main { display: grid; align-content: start; gap: clamp(14px, 2.2svh, 26px); padding-bottom: clamp(14px, 2.4svh, 28px); }
.b-titel { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 6fr) auto; align-items: end; gap: 16px clamp(20px, 3vw, 48px); padding: clamp(10px, 3svh, 36px) var(--rand) 0; }
.b-titel h2 { font-size: clamp(1.953rem, 1.2rem + 2.4vw, 3.052rem); font-weight: 450; letter-spacing: -.02em; line-height: 1.05; }
.b-titel h2 i { font-family: 'Instrument Serif', Georgia, serif; font-weight: 400; letter-spacing: -.01em; }
.b-meta { font-size: .8rem; color: var(--b-meta); margin-bottom: 10px; font-variant-numeric: tabular-nums; }
.b-daten { display: grid; grid-template-columns: repeat(3, auto); justify-content: start; gap: 4px clamp(18px, 2.4vw, 40px); font-size: .8rem; }
.b-daten dt { color: var(--b-meta); }
.b-daten dd { margin: 0; font-size: 1.0625rem; letter-spacing: -.012em; }
.b-platz { anchor-name: --b-knoepfe; display: flex; gap: 6px; width: 94px; height: 44px; }
.b-knopf, .b-band::scroll-button(*) { width: 44px; height: 44px; border-radius: 0; border: 1px solid rgba(35, 31, 32, .22); background: #fff; color: var(--tinte); cursor: pointer; font: 400 20px/1 'Instrument Sans', sans-serif;
  transition: background-color 250ms var(--b-kurve), color 250ms var(--b-kurve), border-color 250ms var(--b-kurve), opacity 250ms var(--b-kurve), scale 150ms var(--b-kurve); }
.b-knopf { display: none; place-items: center; }
.b-knopf svg { width: 18px; height: 18px; }
.b-knopf:hover:not(:disabled), .b-band::scroll-button(*):hover:not(:disabled), .b-knopf.druck, .b-band.druck-rechts::scroll-button(right), .b-band.druck-links::scroll-button(left) { background: var(--tinte); color: #fff; border-color: var(--tinte); }
.b-knopf.druck, .b-band.druck-rechts::scroll-button(right), .b-band.druck-links::scroll-button(left) { scale: .92; }
.b-knopf:disabled, .b-band::scroll-button(*):disabled { opacity: .28; cursor: default; }
.b-knopf:focus-visible, .b-band::scroll-button(*):focus-visible, .b-tabs button:focus-visible, .b-band:focus-visible { outline: 2px solid var(--b-fokus); outline-offset: 2px; }
/* Die Bühne: ein Bild = volle Breite, echter Scroller */
.b-band { list-style: none; display: grid; grid-auto-flow: column; grid-auto-columns: 100%; overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none; scroll-snap-type: x mandatory; scroll-behavior: smooth; outline: none; }
.b-band::-webkit-scrollbar { display: none; }
.b-slide { scroll-snap-align: center; scroll-snap-stop: always; container-type: scroll-state; }
.b-slide figure { margin: 0; }
.b-bild { position: relative; height: min(60svh, 52vw); overflow: hidden; background: #e1e3e3; }
.b-bild > :is(svg, img) { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; scale: 1.06; transition: scale 1000ms var(--b-auf); }
@container scroll-state(snapped: x) { .b-bild > :is(svg, img) { scale: 1; } }
[data-marker='eigene'] .b-slide.ist-aktiv .b-bild > :is(svg, img) { scale: 1; }
.b-slide figcaption { display: flex; justify-content: space-between; gap: 16px; padding: 10px var(--rand) 0; font-size: .8rem; color: var(--b-meta); }
.b-slide figcaption b { color: var(--tinte); font-weight: 450; }
/* Marker als Reiter mit Nummer und Titel; Modus tabs, wo der Browser ihn kennt (Chrome 154) */
.b-band { scroll-marker-group: after; }
@supports (scroll-marker-group: after tabs) { .b-band { scroll-marker-group: after tabs; } }
[data-marker='eigene'] .b-band { scroll-marker-group: none; }
.b-band::scroll-marker-group { margin-top: 8px; display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: clamp(8px, 1.2vw, 16px); padding: 0 var(--rand); }
.b-slide::scroll-marker { content: attr(data-nr) "  " attr(data-titel); display: block; padding-top: 11px; border-top: 1px solid rgba(35, 31, 32, .18); font: 500 10.5px/1.3 'Geist Mono', ui-monospace, monospace; letter-spacing: .12em; text-transform: uppercase; color: var(--b-meta); white-space: nowrap; overflow: hidden; cursor: pointer; height: 34px; box-sizing: border-box; text-decoration: none;
  transition: color 250ms var(--b-kurve), border-color 250ms var(--b-kurve); }
.b-slide::scroll-marker:hover { color: var(--tinte); }
.b-slide::scroll-marker:target-current { color: var(--tinte); border-top-color: var(--tinte); box-shadow: inset 0 1px 0 var(--tinte); }
.b-slide::scroll-marker:focus-visible { outline: 2px solid var(--b-fokus); outline-offset: 3px; }
.b-band::scroll-button(left) { content: '\2190' / 'Vorheriges Bild'; position: fixed; position-anchor: --b-knoepfe; top: anchor(top); left: anchor(left); }
.b-band::scroll-button(right) { content: '\2192' / 'Nächstes Bild'; position: fixed; position-anchor: --b-knoepfe; top: anchor(top); right: anchor(right); }
[data-marker='eigene'] .b-band::scroll-button(*) { display: none; }
/* Rückfall: eigene Reiter (role=tab) und Knöpfe */
.b-tabs { display: none; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: clamp(8px, 1.2vw, 16px); padding: 0 var(--rand); }
.b-tabs button { text-align: left; padding: 11px 0 0; border: 0; border-top: 1px solid rgba(35, 31, 32, .18); background: none; font: 500 10.5px/1.3 'Geist Mono', ui-monospace, monospace; letter-spacing: .12em; text-transform: uppercase; color: var(--b-meta); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer; transition: color 250ms var(--b-kurve), border-color 250ms var(--b-kurve); }
.b-tabs button[aria-selected='true'] { color: var(--tinte); border-top-color: var(--tinte); box-shadow: inset 0 1px 0 var(--tinte); }
[data-marker='eigene'] .b-tabs { display: grid; }
[data-marker='eigene'] .b-knopf { display: grid; }
@supports not selector(::scroll-marker) { .b-tabs { display: grid; } .b-knopf { display: grid; } }

/* ---------- Variante „Kartenreihe mit Peek“: Rezept boutique-hotel-gastgeber ---------- */
html[data-v='peek'] { --papier: #f3eee7; --tinte: #3e3a37; --leise: rgba(62, 58, 55, .78); --linie: #d5d1c8; --gruen: #6e5a47;
  font-family: 'Archivo', system-ui, sans-serif; font-variation-settings: normal; }
.welt-p { --p-ui: cubic-bezier(0.4, 0, 0.2, 1); --p-bild: cubic-bezier(0.37, 0, 0.63, 1); --p-meta: #666863; --p-holz: #6e5a47; --p-creme: #fef2e3;
  --p-breite: clamp(240px, 26vw, 360px); --p-luecke: clamp(14px, 1.6vw, 24px); }
.p-kopf { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 16px; padding: 18px var(--rand); border-bottom: 1px solid var(--linie); }
.p-kopf nav { display: flex; gap: 24px; }
.p-kopf nav a, .p-label, .p-karte .p-art, .p-buchen, .p-preis a { font: 500 .75rem/1.2 'Archivo', sans-serif; font-stretch: 125%; letter-spacing: .06em; text-transform: uppercase; }
.p-marke { justify-self: center; font: 400 26px/1 'Newsreader', Georgia, serif; letter-spacing: -.015em; text-align: center; }
.p-marke i { font-style: italic; }
.p-buchen { justify-self: end; display: inline-flex; align-items: center; height: 40px; padding: 0 18px; border-radius: 2px; background: var(--p-holz); color: var(--p-creme); transition: background-color 200ms var(--p-ui); }
.p-buchen:hover { background: #5a4939; }
.p-main { display: grid; align-content: center; gap: clamp(16px, 2.6svh, 30px); padding: clamp(18px, 4svh, 48px) 0 clamp(12px, 2svh, 24px); }
.p-titelzeile { display: flex; justify-content: space-between; align-items: flex-end; gap: 18px 40px; padding-inline: var(--rand); }
.p-label { color: var(--p-meta); display: block; margin-bottom: 14px; }
.p-titelzeile h2 { font: 300 clamp(2.5rem, 1.5rem + 4vw, 5.5rem)/1.02 'Newsreader', Georgia, serif; letter-spacing: -.015em; max-width: 13ch; }
.p-titelzeile h2 em { font-style: italic; font-weight: 300; }
.p-seitlich { display: grid; justify-items: end; gap: 14px; text-align: right; }
.p-seitlich p { font-size: 1rem; line-height: 1.6; color: var(--leise); max-width: 32ch; }
.p-platz { anchor-name: --p-knoepfe; display: flex; gap: 8px; width: 96px; height: 44px; }
.p-knopf, .p-band::scroll-button(*) { width: 44px; height: 44px; border-radius: 50%; border: 1px solid rgba(62, 58, 55, .3); background: transparent; color: var(--tinte); cursor: pointer; font: 400 22px/1 'Newsreader', Georgia, serif; padding-bottom: 3px;
  transition: background-color 200ms var(--p-ui), color 200ms var(--p-ui), border-color 200ms var(--p-ui), opacity 200ms var(--p-ui), scale 150ms var(--p-ui); }
.p-knopf { display: none; place-items: center; padding: 0; }
.p-knopf svg { width: 18px; height: 18px; }
.p-knopf:hover:not(:disabled), .p-band::scroll-button(*):hover:not(:disabled), .p-knopf.druck, .p-band.druck-rechts::scroll-button(right), .p-band.druck-links::scroll-button(left) { background: var(--p-holz); border-color: var(--p-holz); color: var(--p-creme); }
.p-knopf.druck, .p-band.druck-rechts::scroll-button(right), .p-band.druck-links::scroll-button(left) { scale: .92; }
.p-knopf:disabled, .p-band::scroll-button(*):disabled { opacity: .3; cursor: default; }
.p-knopf:focus-visible, .p-band::scroll-button(*):focus-visible, .p-karte a:focus-visible, .p-band:focus-visible { outline: 2px solid var(--p-holz); outline-offset: 3px; }
/* Kartenreihe: Start-Ausrichtung, die nächste Karte schaut an der Kante herein */
.p-band { list-style: none; display: grid; grid-auto-flow: column; grid-auto-columns: var(--p-breite); gap: var(--p-luecke); overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none; scroll-snap-type: x mandatory; scroll-behavior: smooth;
  padding: 4px var(--rand) 6px; scroll-padding-inline: var(--rand); outline: none; }
.p-band::-webkit-scrollbar { display: none; }
.p-karte { scroll-snap-align: start; display: grid; gap: 14px; align-content: start; }
.p-bild { position: relative; aspect-ratio: 4 / 3.7; overflow: hidden; border-radius: 2px; background: #e8e0d4; }
.p-bild > :is(svg, img) { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; transition: scale 1100ms var(--p-bild); }
@media (hover: hover) { .p-karte:hover .p-bild > :is(svg, img) { scale: 1.04; } }
.p-art { color: var(--p-meta); display: flex; justify-content: space-between; gap: 12px; }
.p-karte h3 { font: 400 clamp(22px, 1.9vw, 28px)/1.1 'Newsreader', Georgia, serif; letter-spacing: -.01em; }
.p-karte h3 i { font-style: italic; }
.p-karte p.p-text { font-size: 15px; line-height: 1.55; color: var(--leise); }
.p-preis { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding-top: 12px; border-top: 1px solid var(--linie); font-variant-numeric: tabular-nums; }
.p-preis b { font: 400 19px/1 'Newsreader', Georgia, serif; }
.p-preis b small { font: 400 13px/1 'Archivo', sans-serif; color: var(--p-meta); }
.p-preis a { color: var(--p-holz); }
.p-preis a::after { content: ' →'; }
/* Marker: Modus links (Standard ab Chrome 154), jede Karte ein eigener Tab-Stopp */
.p-band { scroll-marker-group: after; }
@supports (scroll-marker-group: after links) { .p-band { scroll-marker-group: after links; } }
.p-band::scroll-marker-group { display: flex; gap: 6px; padding: 6px var(--rand) 0; }
.p-karte::scroll-marker { content: ''; width: 28px; height: 2px; background: rgba(62, 58, 55, .22); cursor: pointer; transition: background-color 200ms var(--p-ui), width 200ms var(--p-ui); }
.p-karte::scroll-marker:target-current { background: var(--tinte); width: 44px; }
.p-karte::scroll-marker:focus-visible { outline: 2px solid var(--p-holz); outline-offset: 4px; }
.p-band::scroll-button(left) { content: '\2190' / 'Vorheriges Angebot'; position: fixed; position-anchor: --p-knoepfe; top: anchor(top); left: anchor(left); }
.p-band::scroll-button(right) { content: '\2192' / 'Nächstes Angebot'; position: fixed; position-anchor: --p-knoepfe; top: anchor(top); right: anchor(right); }
@supports not selector(::scroll-button(right)) { .p-knopf { display: grid; } }

/* Vorschau, Telefon, Touch, reduziert für beide neuen Varianten */
.vorschau .b-kopf nav, .vorschau .b-text, .vorschau .p-seitlich p, .vorschau .p-kopf nav { display: none; }
.vorschau .b-bild { height: 52svh; }
@media (max-width: 1000px) { .b-kopf nav { gap: 20px; } .p-kopf { grid-template-columns: 1fr auto; } .p-kopf nav { display: none; } .p-marke { justify-self: start; text-align: left; } }
@media (max-width: 760px) {
  .b-kopf nav { display: none; }
  .b-titel { grid-template-columns: 1fr; }
  .b-daten { grid-template-columns: repeat(3, auto); }
  .b-bild { height: 62vw; }
  .b-slide::scroll-marker { content: attr(data-nr); text-align: center; }
  .b-tabs button { text-align: center; }
  .b-tabs .t { display: none; }
  .b-slide figcaption { flex-direction: column; gap: 2px; }
  .welt-p { --p-breite: 80vw; }
  .p-kopf { grid-template-columns: 1fr auto; }
  .p-marke { justify-self: start; text-align: left; }
  .p-titelzeile { flex-direction: column; align-items: flex-start; }
  .p-seitlich { justify-items: start; text-align: left; }
}
@media (hover: none) {
  .b-platz, .p-platz { display: none; }
  .b-band::scroll-button(*), .p-band::scroll-button(*) { display: none; }
}
.reduziert :is(.b-band, .p-band) { scroll-behavior: auto; }
.reduziert :is(.b-bild, .p-bild) > :is(svg, img) { transition: none; scale: 1; }
.reduziert .b-slide::scroll-marker, .reduziert .p-karte::scroll-marker, .reduziert .b-tabs button { transition: none; }
@media (prefers-reduced-motion: reduce) { :is(.b-band, .p-band) { scroll-behavior: auto; } }
/* ---------- Variante „Variable Breiten“: Rezept fotograf-film-bild-zuerst ---------- */
html[data-v='variable-breiten'] { --papier: #f4f4f2; --tinte: #141414; --leise: rgba(20, 20, 20, .7); --linie: rgba(20, 20, 20, .14); --gruen: #141414;
  font-family: 'Instrument Sans', system-ui, sans-serif; font-variation-settings: normal; }
.welt-vb { --vb-kurve: cubic-bezier(0.76, 0, 0.24, 1); --vb-meta: #6D6D6D; --vb-hoehe: clamp(220px, min(54svh, 62vw), 600px); --vb-luecke: clamp(10px, 1.2vw, 16px); }
.vb-kopf { display: flex; align-items: baseline; gap: 16px 32px; padding: 20px var(--rand); }
.vb-marke { font: 400 1.5rem/1 'Instrument Serif', Georgia, serif; letter-spacing: -.01em; }
.vb-kopf nav { display: flex; gap: 24px; margin-left: auto; font-size: .9375rem; }
.vb-kopf nav a { color: var(--vb-meta); transition: color 200ms var(--vb-kurve); }
.vb-kopf nav a:hover, .vb-kopf nav a[aria-current] { color: var(--tinte); }
.vb-main { display: grid; align-content: center; gap: clamp(14px, 2.6svh, 28px); padding-bottom: clamp(14px, 2.6svh, 28px); }
.vb-titel { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr) auto; align-items: end; gap: 12px clamp(20px, 3vw, 48px); padding: 0 var(--rand); }
.vb-meta, .vb-zaehler, .vb-band figcaption { font: 400 .75rem/1.4 'Fragment Mono', ui-monospace, monospace; letter-spacing: .02em; font-variant-numeric: tabular-nums; color: var(--vb-meta); }
.vb-titel h2 { font: 400 clamp(1.75rem, 1rem + 2.6vw, 2.75rem)/1.1 'Instrument Serif', Georgia, serif; letter-spacing: -.01em; margin-top: 6px; }
.vb-text { font-size: .9375rem; line-height: 1.5; color: var(--vb-meta); max-width: 44ch; }
.vb-steuer { display: flex; align-items: center; gap: 14px; }
.vb-zaehler { min-width: 7.2em; text-align: right; color: var(--tinte); }
.vb-knopf { width: 44px; height: 44px; border: 0; border-radius: 50%; background: transparent; color: var(--tinte); display: grid; place-items: center; cursor: pointer; transition: background-color 200ms var(--vb-kurve), opacity 200ms var(--vb-kurve), scale 200ms var(--vb-kurve); }
.vb-knopf svg { width: 26px; height: 18px; }
.vb-knopf:hover:not(:disabled), .vb-knopf.druck { background: rgba(20, 20, 20, .07); }
.vb-knopf.druck { scale: .9; }
.vb-knopf:disabled { opacity: .28; cursor: default; }
.vb-knopf:focus-visible, .vb-band:focus-visible { outline: 2px solid var(--tinte); outline-offset: 2px; }
/* Das Band: feste Höhe, Breite folgt dem Bild (width auto), Snap auf start */
.vb-band { position: relative; list-style: none; display: flex; gap: var(--vb-luecke); overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none; scroll-snap-type: x mandatory; scroll-behavior: auto;
  padding: 2px var(--rand) 4px; scroll-padding-inline: var(--rand); outline: none; }
.vb-band::-webkit-scrollbar { display: none; }
.vb-band > li { flex: none; scroll-snap-align: start; }
.vb-band figure { margin: 0; display: grid; gap: 8px; width: min-content; }
.vb-band img { display: block; height: var(--vb-hoehe); width: auto; background: #e6e6e6; }
.vb-band figcaption { display: flex; gap: 10px; }
.vb-band figcaption b { color: var(--tinte); font-weight: 400; }

/* ---------- Variante „Fenster“: Rezept boutique-hotel-gastgeber (Ferienhaus) ---------- */
html[data-v='fenster'] { --papier: #F3EEE7; --tinte: #3E3A37; --leise: rgba(62, 58, 55, .78); --linie: #D5D1C8; --gruen: #6E5A47;
  font-family: 'Mona Sans', system-ui, sans-serif; font-variation-settings: normal; }
.welt-fe { --fe-kurve: cubic-bezier(0.37, 0, 0.63, 1); --fe-meta: #6A6C67; --fe-holz: #6E5A47; --fe-fenster: min(30.7vw, 48svh, 520px); }
.fe-kopf { display: flex; align-items: center; gap: 16px 32px; padding: 18px var(--rand); border-bottom: 1px solid var(--linie); }
.fe-marke { display: flex; align-items: baseline; gap: 14px; white-space: nowrap; }
.fe-marke b { font: 400 1.625rem/1 'Newsreader', Georgia, serif; letter-spacing: -.015em; }
.fe-marke b i { font-style: italic; }
.fe-marke span, .fe-kopf nav a, .fe-label, .fe-anfrage, .fe-fakten { font: 500 .75rem/1.3 'Mona Sans', sans-serif; font-stretch: 125%; letter-spacing: .06em; text-transform: uppercase; }
.fe-marke span, .fe-label { color: var(--fe-meta); }
.fe-kopf nav { display: flex; gap: 24px; margin-left: auto; }
.fe-anfrage { min-height: 40px; display: inline-flex; align-items: center; padding: 0 18px; border-radius: 2px; background: var(--fe-holz); color: #FEF2E3; transition: background-color 300ms var(--fe-kurve); }
.fe-anfrage:hover { background: #59483a; }
.fe-main { display: grid; grid-template-columns: minmax(0, 1fr) var(--fe-fenster) minmax(0, 1fr); align-items: start; padding: clamp(16px, 3.4svh, 40px) var(--rand); column-gap: clamp(16px, 3vw, 48px); }
.fe-links, .fe-rechts { position: relative; align-self: stretch; min-height: 100%; }
.fe-text { display: grid; gap: 14px; align-content: start; max-width: 30ch; }
.fe-text h2 { font: 300 clamp(1.9rem, 1.2rem + 2vw, 3.2rem)/1.04 'Newsreader', Georgia, serif; letter-spacing: -.015em; }
.fe-text h2 em { font-style: italic; }
.fe-info { position: absolute; right: 0; bottom: 0; display: grid; gap: 14px; justify-items: end; text-align: right; }
.fe-info p:not(.fe-fakten) { font-size: .9375rem; line-height: 1.6; color: var(--fe-meta); max-width: 30ch; }
.fe-fakten { color: var(--fe-meta); line-height: 1.9; }
.fe-fakten b { color: var(--tinte); font-weight: 500; }
/* Große Haarlinien-Pfeile im Leerraum neben dem Fenster, auf halber Fensterhöhe */
.fe-pfeil { position: absolute; top: calc(var(--fe-fenster) * .625 - 32px); width: min(100%, 168px); height: 64px; border: 0; background: none; color: var(--tinte); cursor: pointer; display: grid; place-items: center; }
.fe-links .fe-pfeil { right: 0; }
.fe-rechts .fe-pfeil { left: 0; }
.fe-pfeil svg { width: 100%; height: 24px; overflow: visible; }
.fe-pfeil .schaft { transform-box: fill-box; transition: transform 600ms var(--fe-kurve); }
.fe-pfeil[data-schritt='-1'] .schaft { transform-origin: 100% 50%; }
.fe-pfeil[data-schritt='1'] .schaft { transform-origin: 0 50%; }
.fe-pfeil .spitze { transition: transform 600ms var(--fe-kurve); }
.fe-pfeil:hover .schaft, .fe-pfeil.druck .schaft { transform: scaleX(.82); }
.fe-pfeil[data-schritt='1']:hover .spitze, .fe-pfeil[data-schritt='1'].druck .spitze { transform: translateX(-22px); }
.fe-pfeil[data-schritt='-1']:hover .spitze, .fe-pfeil[data-schritt='-1'].druck .spitze { transform: translateX(22px); }
.fe-pfeil:focus-visible, .fe-band:focus-visible { outline: 2px solid var(--fe-holz); outline-offset: 4px; }
/* Das Fenster: ein Bild, 4:5, echter Scroller; Klone an beiden Enden für die Schleife */
.fe-mitte { display: grid; gap: 12px; min-width: 0; }
.fe-band { position: relative; list-style: none; display: grid; grid-auto-flow: column; grid-auto-columns: 100%; aspect-ratio: 4 / 5; overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none; scroll-snap-type: x mandatory; scroll-behavior: auto; background: #e6ded2; outline: none; }
.fe-band::-webkit-scrollbar { display: none; }
.fe-band > li { scroll-snap-align: start; scroll-snap-stop: always; overflow: hidden; }
.fe-band img { display: block; width: 100%; height: 100%; object-fit: cover; }
.fe-unterzeile { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.fe-name { font: italic 400 1.25rem/1.2 'Newsreader', Georgia, serif; }
.fe-hinweis { font-size: .8125rem; color: var(--fe-meta); }

/* Telefon, Touch, reduziert, Vorschau für beide */
.vorschau .vb-kopf nav, .vorschau .vb-text, .vorschau .fe-kopf nav, .vorschau .fe-info { display: none; }
@media (max-width: 1000px) { .vb-kopf nav, .fe-kopf nav { display: none; } .fe-anfrage { margin-left: auto; } .vb-titel { grid-template-columns: minmax(0, 1fr) auto; } .vb-text { grid-column: 1 / -1; grid-row: 2; } }
@media (max-width: 760px) {
  .vb-titel { grid-template-columns: minmax(0, 1fr); }
  .vb-text { grid-row: auto; }
  .vb-steuer { justify-content: space-between; }
  .vb-zaehler { text-align: left; }
  .fe-marke span { display: none; }
  .fe-main { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); grid-template-areas: 'text text' 'mitte mitte' 'l r' 'info info'; row-gap: 14px; }
  .fe-pfeil { position: static; }
  .fe-info { position: static; grid-area: info; justify-items: start; text-align: left; }
  .welt-fe { --fe-fenster: min(78vw, 56svh); }
  .fe-links, .fe-rechts { display: contents; }
  .fe-text { grid-area: text; max-width: none; }
  .fe-mitte { grid-area: mitte; width: var(--fe-fenster); justify-self: center; }
  .fe-links .fe-pfeil { grid-area: l; justify-self: start; width: 120px; }
  .fe-rechts .fe-pfeil { grid-area: r; justify-self: end; width: 120px; }
  .fe-fakten { display: none; }
}
@media (max-width: 420px) { .fe-lang { display: none; } .fe-anfrage { padding: 0 12px; } .fe-hinweis { display: none; } }
@media (max-width: 360px) { .fe-marke b { font-size: 1.3rem; } .fe-kopf { gap: 10px; } }
@media (hover: none) { .vb-knopf { display: none; } }
.reduziert .vb-knopf, .reduziert .fe-pfeil .schaft, .reduziert .fe-pfeil .spitze { transition: none; }
.sr-nur { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
/* Kontrastmodus: Knöpfe mit Rahmen */
@media (forced-colors: active) { button, [role=button], [role=tab], [role=switch] { border: 1px solid ButtonText !important; } }
</style>
</head>
<body>
<div class="welt" data-welt="produktreihe">
<header class="kopf">
  <a class="marke" href="#"><b>Speicher <i>Neun</i></b><span class="versal">Kaffeerösterei &amp; Café · Münster</span></a>
  <nav class="navi" aria-label="Hauptnavigation"><a href="#">Café</a><a href="#">Rösterei</a><a href="#" aria-current="page">Shop</a><a href="#">Röstkurse</a></nav>
  <div class="status"><span class="offen" data-offen>Heute geöffnet</span><span class="adresse">Hafenweg 9</span><a class="reservieren" href="#">Tisch reservieren</a></div>
</header>

<main class="shop" data-shop data-modus="nativ">
  <div class="titelzeile">
    <div class="titel">
      <span class="versal">Shop · Bohnen, Abo &amp; Gutscheine</span>
      <h2>Diese Woche <em>frisch</em> aus dem Röster.</h2>
    </div>
    <div class="seitlich">
      <p>Geröstet jeden Dienstag im Speicher am Hafen. Abholen im Café oder Versand, ab 25 € ohne Versandkosten.</p>
      <p class="hinweis mono">Ihr Browser kennt ::scroll-button noch nicht: hier laufen eigene Knöpfe.</p>
      <div class="steuer">
        <p class="zaehler" aria-hidden="true" data-zaehler>01 <span>/ 08</span></p>
        <div class="knopf-platz" data-knopf-platz>
          <button type="button" class="knopf" data-schritt="-1" aria-label="Vorherige Sorte" aria-controls="sorten"><svg viewBox="0 0 18 18" aria-hidden="true"><path d="M11 3.5 5.5 9l5.5 5.5" fill="none" stroke="currentColor" stroke-width="1.5"/></svg></button>
          <button type="button" class="knopf" data-schritt="1" aria-label="Nächste Sorte" aria-controls="sorten"><svg viewBox="0 0 18 18" aria-hidden="true"><path d="m7 3.5 5.5 5.5L7 14.5" fill="none" stroke="currentColor" stroke-width="1.5"/></svg></button>
        </div>
      </div>
    </div>
  </div>

  <ul class="band" id="sorten" data-band aria-label="Bohnen, Abo und Gutscheine" tabindex="0">
    <li class="slide" aria-label="Prinzipal, 1 von 8" style="--kachel:#eac16e;--tuete:#293e3a;--band:#e1a894">
      <article class="karte">
        <div class="kachel m-wellen" aria-hidden="true">
          <span class="bohne" style="--x:12%;--y:80%;--r:-28deg"></span><span class="bohne" style="--x:80%;--y:76%;--r:40deg"></span><span class="bohne" style="--x:70%;--y:86%;--r:-10deg"></span>
          <div class="tuete"><span class="ventil"></span><div class="etikett"><small>Speicher Neun</small><b>Prinzipal</b><i>Espresso</i><span class="muster"></span></div></div>
        </div>
        <div class="info"><h3>Prinzipal</h3><p class="preis">8,90 €</p><p class="herkunft">Hausmischung Espresso · Brasilien &amp; Indien</p><p class="noten versal">Haselnuss · Kakao · Rohrzucker</p></div>
      </article>
    </li>
    <li class="slide" aria-label="Aasee, 2 von 8" style="--kachel:#e1a894;--tuete:#f4efe3;--etikett:#293e3a;--etikett-text:#f4efe3;--band:#7f937a">
      <article class="karte">
        <div class="kachel m-punkte" aria-hidden="true">
          <span class="bohne" style="--x:16%;--y:84%;--r:60deg"></span><span class="bohne" style="--x:78%;--y:80%;--r:-35deg"></span>
          <div class="tuete"><span class="ventil"></span><div class="etikett"><small>Speicher Neun</small><b>Aasee</b><i>Filter</i><span class="muster"></span></div></div>
        </div>
        <div class="info"><h3>Aasee</h3><p class="preis">11,50 €</p><p class="herkunft">Filter · Äthiopien, Guji, naturbelassen</p><p class="noten versal">Blaubeere · Jasmin · Bergamotte</p></div>
      </article>
    </li>
    <li class="slide" aria-label="Kuhviertel, 3 von 8" style="--kachel:#7f937a;--tuete:#3b2a21;--band:#eac16e">
      <article class="karte">
        <div class="kachel m-streifen" aria-hidden="true">
          <span class="bohne" style="--x:10%;--y:78%;--r:15deg"></span><span class="bohne" style="--x:18%;--y:88%;--r:-50deg"></span><span class="bohne" style="--x:82%;--y:84%;--r:30deg"></span>
          <div class="tuete"><span class="ventil"></span><div class="etikett"><small>Speicher Neun</small><b>Kuhviertel</b><i>Espresso</i><span class="muster"></span></div></div>
        </div>
        <div class="info"><h3>Kuhviertel</h3><p class="preis">10,40 €</p><p class="herkunft">Espresso · Kolumbien, Huila, gewaschen</p><p class="noten versal">Rote Johannisbeere · Panela</p></div>
      </article>
    </li>
    <li class="slide" aria-label="Sendplatz, 4 von 8" style="--kachel:#d8cfb8;--tuete:#e1a894;--band:#293e3a">
      <article class="karte">
        <div class="kachel m-karo" aria-hidden="true">
          <span class="bohne" style="--x:14%;--y:82%;--r:-70deg"></span><span class="bohne" style="--x:76%;--y:86%;--r:22deg"></span>
          <div class="tuete"><span class="ventil"></span><div class="etikett"><small>Speicher Neun</small><b>Sendplatz</b><i>Entkoffeiniert</i><span class="muster"></span></div></div>
        </div>
        <div class="info"><h3>Sendplatz</h3><p class="preis">9,60 €</p><p class="herkunft">Entkoffeiniert mit Zuckerrohr · Mexiko, Chiapas</p><p class="noten versal">Karamell · Milchschokolade</p></div>
      </article>
    </li>
    <li class="slide" aria-label="Kreativkai, 5 von 8" style="--kachel:#293e3a;--tuete:#eac16e;--etikett:#293e3a;--etikett-text:#f0ece0;--band:#e1a894">
      <article class="karte">
        <div class="kachel m-wellen" aria-hidden="true">
          <span class="bohne" style="--x:12%;--y:78%;--r:35deg"></span><span class="bohne" style="--x:82%;--y:80%;--r:-20deg"></span><span class="bohne" style="--x:74%;--y:89%;--r:70deg"></span>
          <div class="tuete"><span class="ventil"></span><div class="etikett"><small>Speicher Neun</small><b>Kreativkai</b><i>Filter</i><span class="muster"></span></div></div>
        </div>
        <div class="info"><h3>Kreativkai</h3><p class="preis">13,20 €</p><p class="herkunft">Filter · Kenia, Nyeri, Stufe AA</p><p class="noten versal">Grapefruit · Hibiskus · Rohrzucker</p></div>
      </article>
    </li>
    <li class="slide" aria-label="Kiepenkerl, 6 von 8" style="--kachel:#b9c4b0;--tuete:#b8875a;--band:#7f937a">
      <article class="karte">
        <div class="kachel m-punkte" aria-hidden="true">
          <span class="bohne" style="--x:16%;--y:80%;--r:-15deg"></span><span class="bohne" style="--x:80%;--y:84%;--r:50deg"></span>
          <div class="tuete"><span class="ventil"></span><div class="etikett"><small>Speicher Neun</small><b>Kiepenkerl</b><i>Filter</i><span class="muster"></span></div></div>
        </div>
        <div class="info"><h3>Kiepenkerl</h3><p class="preis">9,20 €</p><p class="herkunft">Hausmischung Filter · Guatemala &amp; Peru</p><p class="noten versal">Mandel · Bratapfel · Vollmilch</p></div>
      </article>
    </li>
    <li class="slide" aria-label="Röstfrisch-Abo, 7 von 8" style="--kachel:#e6dcc2;--tuete:#293e3a;--band:#eac16e">
      <article class="karte">
        <div class="kachel m-streifen" aria-hidden="true">
          <span class="bohne" style="--x:9%;--y:84%;--r:20deg"></span><span class="bohne" style="--x:86%;--y:86%;--r:-40deg"></span>
          <div class="tuete hinten" style="--tuete:#e1a894;--band:#293e3a"><span class="ventil"></span><div class="etikett"><small>Speicher Neun</small><b>Aasee</b><i>Filter</i><span class="muster"></span></div></div>
          <div class="tuete vorn"><span class="ventil"></span><div class="etikett"><small>Speicher Neun</small><b>Prinzipal</b><i>Espresso</i><span class="muster"></span></div></div>
        </div>
        <div class="info"><h3>Röstfrisch-Abo</h3><p class="preis">ab 18,40 €</p><p class="herkunft">2 × 250 g alle zwei Wochen, jederzeit pausierbar</p><p class="noten versal">Sorte frei wählbar · Versand gratis</p></div>
      </article>
    </li>
    <li class="slide" aria-label="Gutschein Röstkurs, 8 von 8" style="--kachel:#353839">
      <article class="karte">
        <div class="kachel" aria-hidden="true">
          <div class="gutschein zwei"><small>Speicher Neun</small><b>Gutschein</b><span>25 €</span></div>
          <div class="gutschein"><small>Gutschein · Röstkurs</small><b>Samstags am Röster</b><span>69 €</span></div>
        </div>
        <div class="info"><h3>Gutschein Röstkurs</h3><p class="preis">69 €</p><p class="herkunft">Drei Stunden am Röster, danach 250 g eigene Röstung</p><p class="noten versal">Auch als Wertgutschein ab 15 €</p></div>
      </article>
    </li>
  </ul>
</main>
</div>

<!-- Variante „Vollbild-Bühne“: Architekturbüro, ein Bild füllt die Breite, Marker als Reiter (Modus tabs) -->
<div class="welt welt-b" data-welt="buehne" hidden>
<header class="b-kopf">
  <a class="b-marke" href="#">Lehmkuhl <span>Architekten</span></a>
  <nav aria-label="Hauptnavigation"><a href="#" aria-current="page">Projekte</a><a href="#">Büro</a><a href="#">Wettbewerbe</a><a href="#">Kontakt</a></nav>
</header>
<main class="b-main" data-b data-marker="nativ">
  <div class="b-titel">
    <div><p class="b-meta">Projekt 041 · Wohnhaus · 2025</p><h2>Haus am <i>Kalkberg</i></h2></div>
    <dl class="b-daten"><div><dt>Ort</dt><dd>Bad Segeberg</dd></div><div><dt>BGF</dt><dd>212 m²</dd></div><div><dt>Fertig</dt><dd>Mai 2025</dd></div></dl>
    <div class="b-platz" data-b-platz>
      <button type="button" class="b-knopf" data-b-schritt="-1" aria-label="Vorheriges Bild" aria-controls="bilder"><svg viewBox="0 0 18 18" aria-hidden="true"><path d="M15 9H3m5-5L3 9l5 5" fill="none" stroke="currentColor" stroke-width="1.4"/></svg></button>
      <button type="button" class="b-knopf" data-b-schritt="1" aria-label="Nächstes Bild" aria-controls="bilder"><svg viewBox="0 0 18 18" aria-hidden="true"><path d="M3 9h12m-5-5 5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.4"/></svg></button>
    </div>
  </div>
  <ul class="b-band" id="bilder" data-b-band aria-label="Bilder Haus am Kalkberg" tabindex="0">
    <li class="b-slide" id="b-1" data-nr="01" data-titel="Ansicht Süd" aria-label="Ansicht Süd, 1 von 6"><figure><div class="b-bild" data-szene="sued"></div><figcaption><b>Ansicht Süd</b><span>Kalksandstein geschlämmt, Fensterband Eiche</span></figcaption></figure></li>
    <li class="b-slide" id="b-2" data-nr="02" data-titel="Eingang" aria-label="Eingang, 2 von 6"><figure><div class="b-bild" data-szene="eingang"></div><figcaption><b>Eingang</b><span>1,20 m tiefe Laibung, Tür Eiche massiv</span></figcaption></figure></li>
    <li class="b-slide" id="b-3" data-nr="03" data-titel="Treppe" aria-label="Treppe, 3 von 6"><figure><div class="b-bild" data-szene="treppe"></div><figcaption><b>Treppe</b><span>Licht von oben über ein 6 m langes Oberlicht</span></figcaption></figure></li>
    <li class="b-slide" id="b-4" data-nr="04" data-titel="Wohnhalle" aria-label="Wohnhalle, 4 von 6"><figure><div class="b-bild" data-szene="halle"></div><figcaption><b>Wohnhalle</b><span>Fenster 6 × 3 m zum Obstgarten</span></figcaption></figure></li>
    <li class="b-slide" id="b-5" data-nr="05" data-titel="Hof" aria-label="Hof, 5 von 6"><figure><div class="b-bild" data-szene="hof"></div><figcaption><b>Hof</b><span>Ein Apfelbaum, Kies und eine Bank</span></figcaption></figure></li>
    <li class="b-slide" id="b-6" data-nr="06" data-titel="Detail Sturz" aria-label="Detail Sturz, 6 von 6"><figure><div class="b-bild" data-szene="detail"></div><figcaption><b>Detail Sturz</b><span>Eichenbalken über Kalksandstein, Fuge 8 mm</span></figcaption></figure></li>
  </ul>
  <div class="b-tabs" role="tablist" aria-label="Bilder" data-b-tabs></div>
</main>
</div>

<!-- Variante „Kartenreihe mit Peek“: Hotel, Angebote starten links, die nächste Karte schaut herein (Marker-Modus links) -->
<div class="welt welt-p" data-welt="peek" hidden>
<header class="p-kopf">
  <nav aria-label="Hauptnavigation"><a href="#">Zimmer</a><a href="#" aria-current="page">Angebote</a><a href="#">Restaurant</a></nav>
  <a class="p-marke" href="#">Haus <i>Fernsicht</i></a>
  <a class="p-buchen" href="#">Buchen</a>
</header>
<main class="p-main" data-p>
  <div class="p-titelzeile">
    <div><span class="p-label">Angebote &amp; Pakete · Winter 2026/27</span><h2>Zeit für <em>Fernsicht</em></h2></div>
    <div class="p-seitlich">
      <p>Elf Zimmer an der Steilküste, alle mit Blick aufs Wasser. Die Pakete gelten von November bis März, Anreise täglich.</p>
      <div class="p-platz" data-p-platz>
        <button type="button" class="p-knopf" data-p-schritt="-1" aria-label="Vorheriges Angebot" aria-controls="angebote"><svg viewBox="0 0 18 18" aria-hidden="true"><path d="M15 9H3m5-5L3 9l5 5" fill="none" stroke="currentColor" stroke-width="1.4"/></svg></button>
        <button type="button" class="p-knopf" data-p-schritt="1" aria-label="Nächstes Angebot" aria-controls="angebote"><svg viewBox="0 0 18 18" aria-hidden="true"><path d="M3 9h12m-5-5 5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.4"/></svg></button>
      </div>
    </div>
  </div>
  <ul class="p-band" id="angebote" data-p-band aria-label="Angebote und Pakete" tabindex="0">
    <li class="p-karte" aria-label="Winterruhe, 1 von 5"><div class="p-bild" data-szene="see"></div><p class="p-art"><span>3 Nächte</span><span>Nov bis März</span></p><h3>Winter<i>ruhe</i></h3><p class="p-text">Seezimmer, Frühstück bis Mittag, ein Abend mit vier Gängen und die Sauna für Sie allein.</p><p class="p-preis"><b>486 € <small>für zwei</small></b><a href="#">Ansehen</a></p></li>
    <li class="p-karte" aria-label="Sauna und Salz, 2 von 5"><div class="p-bild" data-szene="sauna"></div><p class="p-art"><span>2 Nächte</span><span>Freitag bis Sonntag</span></p><h3>Sauna <i>&amp;</i> Salz</h3><p class="p-text">Holzsauna in den Dünen, Salzpeeling, danach Brühe am Kamin. Bademäntel liegen im Zimmer.</p><p class="p-preis"><b>338 € <small>für zwei</small></b><a href="#">Ansehen</a></p></li>
    <li class="p-karte" aria-label="Langes Frühstück, 3 von 5"><div class="p-bild" data-szene="fruehstueck"></div><p class="p-art"><span>Wochenende</span><span>Sa und So</span></p><h3>Langes <i>Frühstück</i></h3><p class="p-text">Wir decken bis 13 Uhr. Eier von nebenan, Brot aus dem eigenen Ofen, Zeitung inklusive.</p><p class="p-preis"><b>212 € <small>für zwei</small></b><a href="#">Ansehen</a></p></li>
    <li class="p-karte" aria-label="Mit Hund, 4 von 5"><div class="p-bild" data-szene="hund"></div><p class="p-art"><span>2 Nächte</span><span>ganzjährig</span></p><h3>Mit <i>Hund</i></h3><p class="p-text">Gartenzimmer mit eigenem Ausgang, Decke und Napf, dazu eine Karte mit den Hundestränden.</p><p class="p-preis"><b>298 € <small>für zwei</small></b><a href="#">Ansehen</a></p></li>
    <li class="p-karte" aria-label="Fernsicht-Woche, 5 von 5"><div class="p-bild" data-szene="fenster"></div><p class="p-art"><span>7 Nächte</span><span>Jan und Feb</span></p><h3>Fernsicht-<i>Woche</i></h3><p class="p-text">Sieben Nächte zum Preis von fünf, Eckzimmer im Giebel und ein Tag Segeln im März als Gutschein.</p><p class="p-preis"><b>1.090 € <small>für zwei</small></b><a href="#">Ansehen</a></p></li>
  </ul>
</main>
</div>

<!-- Variante „Variable Breiten“: Fotografin, gemischte Hoch- und Querformate in fester Höhe, Pfeile blättern um die Sichtbreite -->
<div class="welt welt-vb" data-welt="variable-breiten" hidden>
<header class="vb-kopf">
  <a class="vb-marke" href="#">Mara Seelig</a>
  <nav aria-label="Hauptnavigation"><a href="#" aria-current="page">Serien</a><a href="#">Auftrag</a><a href="#">Über</a><a href="#">Kontakt</a></nav>
</header>
<main class="vb-main" data-vb>
  <div class="vb-titel">
    <div><p class="vb-meta">Serie 07 · 2024 bis 2026 · 10 Bilder</p><h2>Stille Räume</h2></div>
    <p class="vb-text">Architektur, Licht und Dinge, die bleiben. Fotografiert in Kassel, an der Ostsee und in Werkstätten in Nordhessen.</p>
    <div class="vb-steuer">
      <p class="vb-zaehler" aria-hidden="true" data-vb-zaehler>01–03 / 10</p>
      <button type="button" class="vb-knopf" data-vb-schritt="-1" aria-label="Vorherige Bilder" aria-controls="vb-bilder"><svg viewBox="0 0 26 18" aria-hidden="true"><path d="M25 9H1m7-7L1 9l7 7" fill="none" stroke="currentColor" stroke-width="1.2"/></svg></button>
      <button type="button" class="vb-knopf" data-vb-schritt="1" aria-label="Nächste Bilder" aria-controls="vb-bilder"><svg viewBox="0 0 26 18" aria-hidden="true"><path d="M1 9h24m-7-7 7 7-7 7" fill="none" stroke="currentColor" stroke-width="1.2"/></svg></button>
    </div>
  </div>
  <ul class="vb-band" id="vb-bilder" data-vb-band aria-label="Serie Stille Räume, mit Pfeiltasten blättern" tabindex="0">
    <li aria-label="Treppe, 1 von 10"><figure><img src="bilder/beton-treppe-hoch.jpg" width="800" height="1000" alt="Freitragende Betonstufen an einer hellen Sichtbetonwand im Streiflicht"><figcaption><b>01</b>Treppe, Kulturbahnhof · Kassel 2025</figcaption></figure></li>
    <li aria-label="Habichtswald, 2 von 10"><figure><img src="bilder/huegel-nebel.jpg" width="1200" height="800" alt="Gestaffelte bewaldete Hügelketten im rosafarbenen Morgennebel" loading="lazy"><figcaption><b>02</b>Habichtswald, Morgennebel · 2024</figcaption></figure></li>
    <li aria-label="Werkstatt Bode, 3 von 10"><figure><img src="bilder/keramik-stillleben-hoch.jpg" width="800" height="1000" alt="Handgedrehte Keramik auf Gipssockeln vor einer Terrakotta-Hohlkehle" loading="lazy"><figcaption><b>03</b>Werkstatt Bode, Keramik · 2026</figcaption></figure></li>
    <li aria-label="Raum und Ding, 4 von 10"><figure><img src="bilder/magazin-doppelseite.jpg" width="1200" height="800" alt="Aufgeschlagenes Magazin mit Keramik-Foto und Artikel auf hellem Tisch" loading="lazy"><figcaption><b>04</b>Magazin Raum &amp; Ding, Doppelseite · 2026</figcaption></figure></li>
    <li aria-label="Ostsee, 5 von 10"><figure><img src="bilder/split-screen-sticky-seiten-archiv-1.jpg" width="800" height="1000" alt="Tief stehende Sonne über ruhigem Meer, lange Lichtbahnen auf dem Wasser in Sepiatönen" loading="lazy"><figcaption><b>05</b>Ostsee, Abendlicht · 2024</figcaption></figure></li>
    <li aria-label="Zitronen, 6 von 10"><figure><img src="bilder/zitronen-tisch.jpg" width="1200" height="800" alt="Heller Teller mit Zitronen auf dunklem Schiefer im Gegenlicht" loading="lazy"><figcaption><b>06</b>Zitronen, Gegenlicht · 2025</figcaption></figure></li>
    <li aria-label="Leinen, 7 von 10"><figure><img src="bilder/stoff-stapel-hoch.jpg" width="800" height="1000" alt="Gestapelte, gefaltete Leinentücher in Pflaume, Altrosa, Ocker und Creme auf einem Eichentisch" loading="lazy"><figcaption><b>07</b>Leinen, Weberei Kaufungen · 2026</figcaption></figure></li>
    <li aria-label="Schule am Hang, 8 von 10"><figure><img src="bilder/dialog-modal-nativ-schule.jpg" width="1600" height="1080" alt="Heller zweigeschossiger Flachbau mit langen dunklen Fensterbändern, Bäume, blauer Himmel" loading="lazy"><figcaption><b>08</b>Schule am Hang · Kassel 2024</figcaption></figure></li>
    <li aria-label="Düne, 9 von 10"><figure><img src="bilder/split-screen-sticky-seiten-archiv-8.jpg" width="800" height="1000" alt="Sanfte Sanddüne mit Strandhafer vor blassblauem Himmel" loading="lazy"><figcaption><b>09</b>Düne, Strandhafer · 2024</figcaption></figure></li>
    <li aria-label="Flakon, 10 von 10"><figure><img src="bilder/flakon.jpg" width="1200" height="800" alt="Rechteckiger Glasflakon mit bernsteinfarbener Füllung auf Travertin-Sockeln vor Kobaltblau" loading="lazy"><figcaption><b>10</b>Flakon auf Travertin · 2026</figcaption></figure></li>
  </ul>
  <p class="sr-nur" aria-live="polite" aria-atomic="true" data-vb-status></p>
</main>
</div>

<!-- Variante „Fenster“: Ferienhaus, ein Bild im schmalen Fenster, Endlosschleife ohne Punkte, Haarlinien-Pfeile im Leerraum -->
<div class="welt welt-fe" data-welt="fenster" hidden>
<header class="fe-kopf">
  <a class="fe-marke" href="#"><b>Haus <i>Oberhagen</i></b><span>Ferienhaus am Großen Plöner See</span></a>
  <nav aria-label="Hauptnavigation"><a href="#" aria-current="page">Das Haus</a><a href="#">Lage</a><a href="#">Preise</a></nav>
  <a class="fe-anfrage" href="#"><span class="fe-lang">Zeitraum&nbsp;</span>anfragen</a>
</header>
<main class="fe-main" data-fe>
  <div class="fe-links">
    <div class="fe-text">
      <p class="fe-label">Das Haus · 8 Bilder</p>
      <h2>Ein Raum nach <em>dem anderen</em></h2>
    </div>
    <button type="button" class="fe-pfeil" data-schritt="-1" aria-label="Vorheriges Bild" aria-controls="fe-bilder"><svg viewBox="0 0 168 24" aria-hidden="true"><path class="schaft" d="M2 12H166" fill="none" stroke="currentColor" stroke-width="1" vector-effect="non-scaling-stroke"/><path class="spitze" d="M14 1 2 12l12 11" fill="none" stroke="currentColor" stroke-width="1" vector-effect="non-scaling-stroke"/></svg></button>
  </div>
  <div class="fe-mitte">
    <ul class="fe-band" id="fe-bilder" data-fe-band aria-label="Bilder des Hauses, mit Pfeiltasten blättern" tabindex="0"></ul>
    <p class="fe-unterzeile"><span class="fe-name" data-fe-name>Das Haus vom Steg aus</span><span class="fe-hinweis">Wischen oder Pfeile</span></p>
    <p class="sr-nur" aria-live="polite" aria-atomic="true" data-fe-status></p>
  </div>
  <div class="fe-rechts">
    <button type="button" class="fe-pfeil" data-schritt="1" aria-label="Nächstes Bild" aria-controls="fe-bilder"><svg viewBox="0 0 168 24" aria-hidden="true"><path class="schaft" d="M2 12H166" fill="none" stroke="currentColor" stroke-width="1" vector-effect="non-scaling-stroke"/><path class="spitze" d="M154 1l12 11-12 11" fill="none" stroke="currentColor" stroke-width="1" vector-effect="non-scaling-stroke"/></svg></button>
    <div class="fe-info">
      <p>Backstein von 1911, umgebaut 2023. Sechs Betten, Kachelofen, eigener Steg und die Sauna am Wasser.</p>
      <p class="fe-fakten"><b>6</b> Gäste · <b>140</b> m²<br>Seegrundstück mit Steg<br>ab <b>290 €</b> je Nacht</p>
    </div>
  </div>
</main>
</div>

<footer class="fuss">
  <div class="pultspalte">
  <div class="modi mono" role="group" aria-label="Variante">
    <span>Variante</span>
    <button type="button" data-v-wahl="produktreihe" aria-pressed="true">Produktreihe</button>
    <button type="button" data-v-wahl="buehne" aria-pressed="false">Vollbild-Bühne</button>
    <button type="button" data-v-wahl="peek" aria-pressed="false">Kartenreihe mit Peek</button>
    <button type="button" data-v-wahl="variable-breiten" aria-pressed="false">Variable Breiten</button>
    <button type="button" data-v-wahl="fenster" aria-pressed="false">Fenster</button>
  </div>
  <div class="modi mono" role="group" aria-label="Technik" data-produkt-modi>
    <span>Technik</span>
    <button type="button" data-modus-knopf="nativ" aria-pressed="true">Nativ</button>
    <button type="button" data-modus-knopf="durchlauf" aria-pressed="false">Durchlauf</button>
    <button type="button" data-modus-knopf="fallback" aria-pressed="false">Fallback</button>
  </div>
  <div class="modi mono" role="group" aria-label="Marker" data-marker-modi>
    <span>Marker</span>
    <button type="button" data-marker-knopf="nativ" aria-pressed="true">::scroll-marker</button>
    <button type="button" data-marker-knopf="eigene" aria-pressed="false">Fallback: eigene Reiter</button>
  </div>
  </div>
  <p class="werte mono" data-werte aria-live="polite"></p>
</footer>

<svg class="zeiger" data-zeiger viewBox="0 0 22 22" aria-hidden="true"><path d="M3 2 18.5 11l-7 1.5-3.4 6.5Z" fill="#353839" stroke="#f0ece0" stroke-width="1.4" stroke-linejoin="round"/></svg>

<script>
var STEUER = {};   // je Variante ein Steuerobjekt { start, stopp, neustart, vorfuehren, pause, weiter }
/* ---------- Grundvariante „Produktreihe“ (Rösterei Speicher Neun) ---------- */
(() => {
  const html = document.documentElement;
  const reduziert = () => html.classList.contains('reduziert');
  const shop = document.querySelector('[data-shop]');
  const band = document.querySelector('[data-band]');
  const slides = [...band.querySelectorAll('.slide')];
  const knoepfe = [...document.querySelectorAll('[data-schritt]')];
  const zaehler = document.querySelector('[data-zaehler]');
  const werte = document.querySelector('[data-werte]');
  const zeiger = document.querySelector('[data-zeiger]');
  const platz = document.querySelector('[data-knopf-platz]');
  const nativOk = CSS.supports('selector(::scroll-button(right))');

  // Öffnungszeiten: Status aus echten Zeiten, keine Deko-Uhr
  const zeiten = { 0: [10, 17], 1: [7.5, 18], 2: [7.5, 18], 3: [7.5, 18], 4: [7.5, 18], 5: [7.5, 18], 6: [9, 18] };
  const uhr = h => `${Math.floor(h)}${h % 1 ? ':' + String(Math.round(h % 1 * 60)).padStart(2, '0') : ''} Uhr`;
  (function status() {
    const el = document.querySelector('[data-offen]'), d = new Date(), h = d.getHours() + d.getMinutes() / 60, [a, z] = zeiten[d.getDay()];
    const offen = h >= a && h < z;
    el.textContent = offen ? `Geöffnet bis ${uhr(z)}` : h < a ? `Öffnet heute ${uhr(a)}` : `Öffnet morgen ${uhr(zeiten[(d.getDay() + 1) % 7][0])}`;
    el.classList.toggle('zu', !offen);
  })();

  // Werte-Anzeige je Variante
  const WERTE = {
    nativ: 'snap <b>x mandatory</b> · align <b>center</b> · stop <b>always</b> · ::scroll-button <b>≈ 85 %</b> · scroll-state(snapped) <b>400 ms cubic-bezier(0.33, 1, 0.68, 1)</b> · JS <b>0</b>',
    durchlauf: 'wie Nativ · view-timeline <b>inline</b> · Tüte <b>translateX ±34 %, rotate ±9°</b> · Marker <b>Farbfelder</b>',
    fallback: 'snap <b>x mandatory</b> · Knöpfe <b>scrollBy ±85 % der Breite</b> · IntersectionObserver <b>Mitte ±5 %</b> · <b>400 ms cubic-bezier(0.33, 1, 0.68, 1)</b>',
  };
  let modus = 'nativ';
  function setzeModus(m) {
    modus = m;
    shop.dataset.modus = m;
    document.querySelectorAll('[data-modus-knopf]').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.modusKnopf === m)));
    werte.innerHTML = WERTE[m];
    knoepfeAktualisieren();
  }
  document.querySelectorAll('[data-modus-knopf]').forEach(b => b.addEventListener('click', () => { stopp(); setzeModus(b.dataset.modusKnopf); }));

  // Fallback: eigene Knöpfe mit scrollBy, Aktivzustand über IntersectionObserver
  const verhalten = () => (reduziert() ? 'auto' : 'smooth');
  knoepfe.forEach(k => k.addEventListener('click', () => band.scrollBy({ left: +k.dataset.schritt * band.clientWidth * 0.85, behavior: verhalten() })));
  let aktiv = 0;
  const io = new IntersectionObserver(eintraege => {
    eintraege.forEach(e => { if (e.isIntersecting) aktiv = slides.indexOf(e.target); });
    slides.forEach((s, i) => s.classList.toggle('ist-aktiv', i === aktiv));
    zaehler.firstChild.textContent = String(aktiv + 1).padStart(2, '0') + ' ';
    knoepfeAktualisieren();
  }, { root: band, rootMargin: '0px -45% 0px -45%', threshold: 0 });
  slides.forEach(s => io.observe(s));
  function knoepfeAktualisieren() {
    const max = band.scrollWidth - band.clientWidth;
    knoepfe[0].disabled = band.scrollLeft < 4;
    knoepfe[1].disabled = band.scrollLeft > max - 4;
  }
  band.addEventListener('scroll', knoepfeAktualisieren, { passive: true });
  // Pfeiltasten auf dem fokussierten Band: eine Sorte weiter
  band.addEventListener('keydown', e => {
    if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return;
    e.preventDefault();
    band.scrollBy({ left: (e.key === 'ArrowRight' ? 1 : -1) * schritt(), behavior: verhalten() });
  });
  const schritt = () => slides[1].offsetLeft - slides[0].offsetLeft;

  // Vorführung: gedachter Zeiger drückt die Pfeile, Band läuft hin und zurück
  let uhrwerk = null, richtung = 1, laeuft = false;
  const warte = ms => new Promise(r => { uhrwerk = setTimeout(r, ms); });
  function zeigerAuf(seite) {
    const r = platz.getBoundingClientRect();
    if (!r.width) return false;
    const x = seite > 0 ? r.right - 24 : r.left + 24, y = r.top + 24;
    zeiger.style.transform = `translate(${x - 4}px, ${y - 2}px)`;
    return true;
  }
  async function runde() {
    while (laeuft) {
      const max = band.scrollWidth - band.clientWidth;
      if (band.scrollLeft > max - 4) richtung = -1;
      if (band.scrollLeft < 4) richtung = 1;
      const sichtbar = zeigerAuf(richtung);
      await warte(sichtbar ? 750 : 300); if (!laeuft) return;
      const seite = richtung > 0 ? 'rechts' : 'links';
      band.classList.add('druck-' + seite);
      knoepfe[richtung > 0 ? 1 : 0].classList.add('druck');
      band.scrollBy({ left: richtung * schritt(), behavior: verhalten() });
      await warte(160);
      band.classList.remove('druck-links', 'druck-rechts');
      knoepfe.forEach(k => k.classList.remove('druck'));
      await warte(1300);
    }
  }
  function stopp() { laeuft = false; clearTimeout(uhrwerk); band.classList.remove('druck-links', 'druck-rechts'); knoepfe.forEach(k => k.classList.remove('druck')); }
  function vorfuehren() {
    stopp();
    html.classList.add('vorfuehrung');
    const r = platz.getBoundingClientRect();
    zeiger.style.transition = 'none';
    zeiger.style.transform = `translate(${r.left - 60}px, ${r.bottom + 90}px)`;
    zeiger.getBoundingClientRect();
    zeiger.style.transition = '';
    laeuft = true; runde();
  }
  function pause() { stopp(); }
  function weiter() { if (html.classList.contains('vorfuehrung') && !laeuft) { laeuft = true; runde(); } }
  function neustart() {
    stopp(); html.classList.remove('vorfuehrung');
    band.scrollTo({ left: startIndex() * schritt(), behavior: 'instant' });
    richtung = 1;
  }
  ['pointerdown', 'wheel', 'touchstart', 'keydown'].forEach(t => band.addEventListener(t, () => { if (laeuft) { stopp(); html.classList.remove('vorfuehrung'); } }, { passive: true }));

  // Breite Fenster: mit der dritten Sorte beginnen, damit links und rechts Nachbarn stehen
  const startIndex = () => (innerWidth > 760 ? 2 : 0);
  setzeModus(nativOk ? 'nativ' : 'fallback');
  band.scrollTo({ left: startIndex() * schritt(), behavior: 'instant' });

  STEUER.produktreihe = {
    start() { setzeModus(modus); band.scrollTo({ left: startIndex() * schritt(), behavior: 'instant' }); },
    stopp() { stopp(); html.classList.remove('vorfuehrung'); },
    neustart, vorfuehren, pause, weiter
  };
})();

/* ---------- Bilder der neuen Varianten: eigene SVG-Szenen (keine Fotos) ---------- */
(() => {
  const fugen = (y0, y1, h, farbe) => { let d = ''; for (let y = y0; y < y1; y += h) d += `M0 ${y}H1600`; return `<path d="${d}" stroke="${farbe}" stroke-width="2"/>`; };
  const SZENEN = {
    sued: ['Südansicht: langer, niedriger Baukörper aus hellem Stein mit dunklem Fensterband über einer Wiese, rechts ein Apfelbaum', `
      <defs><linearGradient id="s1h" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#d6dde1"/><stop offset="1" stop-color="#eef0ee"/></linearGradient>
      <linearGradient id="s1g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#4a4d4b"/><stop offset=".55" stop-color="#262624"/><stop offset="1" stop-color="#3b3d3b"/></linearGradient></defs>
      <rect width="1600" height="900" fill="url(#s1h)"/><rect y="640" width="1600" height="260" fill="#a3a891"/><rect y="640" width="1600" height="8" fill="#8d927c"/>
      <path d="M250 652H1350L1460 712H330Z" fill="#000" opacity=".12"/>
      <rect x="250" y="372" width="1100" height="280" fill="#dcd8d0"/>${fugen(400, 652, 31, '#d2cdc4').replace('M0', 'M250').replace(/H1600/g, 'H1350').replace(/M0 /g, 'M250 ')}
      <rect x="250" y="360" width="1100" height="20" fill="#c4bfb6"/>
      <rect x="330" y="462" width="640" height="150" fill="url(#s1g)"/><path d="M490 462v150M650 462v150M810 462v150" stroke="#dcd8d0" stroke-width="7"/><path d="M330 462 560 612H330Z" fill="#fff" opacity=".07"/>
      <rect x="1040" y="420" width="240" height="232" fill="#c9c4bb"/><rect x="1110" y="492" width="72" height="160" fill="#a3764b"/>
      <rect x="1452" y="470" width="12" height="180" fill="#5b5145"/><circle cx="1458" cy="440" r="74" fill="#6d7a58"/><circle cx="1408" cy="476" r="52" fill="#76835f"/><circle cx="1506" cy="470" r="56" fill="#62704f"/>`],
    eingang: ['Eingang: tiefe Laibung in einer Wand aus Kalksandstein, darin eine Tür aus Eiche, schräger Schatten', `
      <rect width="1600" height="900" fill="#d6d1c8"/>${fugen(30, 900, 62, '#c9c3b8')}
      <path d="M0 0H1000L520 900H0Z" fill="#000" opacity=".07"/>
      <rect x="600" y="230" width="400" height="670" fill="#9a948a"/><path d="M600 230h400l-40 40H640v630h-40z" fill="#7d776e"/>
      <rect x="680" y="310" width="280" height="590" fill="#a87b50"/><path d="M736 310v590M792 310v590M848 310v590M904 310v590" stroke="#986d45" stroke-width="3"/>
      <rect x="900" y="590" width="10" height="90" fill="#2b2a28"/><rect x="1190" y="110" width="20" height="560" fill="#2b2a28"/><rect x="1196" y="110" width="4" height="560" fill="#f3f1ec" opacity=".35"/>`],
    treppe: ['Treppe aus Eiche entlang einer hellen Wand, Licht fällt von oben durch ein Oberlicht', `
      <defs><linearGradient id="s3l" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#fff" stop-opacity=".75"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient></defs>
      <rect width="1600" height="900" fill="#e6e2db"/><rect width="1600" height="110" fill="#d3cec6"/><rect x="620" y="0" width="360" height="26" fill="#fbfaf7"/>
      <path d="M620 26H980L1320 900H380Z" fill="url(#s3l)"/>
      <path d="M260 900V840H350V780H440V720H530V660H620V600H710V540H800V480H890V420H980V360H1070V300H1160V240H1250V900Z" fill="#b48a5e"/>
      <path d="M260 840H350M350 780H440M440 720H530M530 660H620M620 600H710M710 540H800M800 480H890M890 420H980M980 360H1070M1070 300H1160M1160 240H1250" stroke="#8d6842" stroke-width="6"/>
      <path d="M1250 240V900H1600V240Z" fill="#cfcac1"/><path d="M300 770 1290 170" stroke="#2b2a28" stroke-width="6"/>`],
    halle: ['Wohnhalle mit einem großen Fenster zum Obstgarten, davor eine niedrige Bank, der Boden spiegelt das Licht', `
      <defs><linearGradient id="s4h" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#dfe6e6"/><stop offset=".55" stop-color="#cfd9d2"/><stop offset=".56" stop-color="#8f9c78"/><stop offset="1" stop-color="#7b8866"/></linearGradient></defs>
      <rect width="1600" height="900" fill="#e8e4dd"/><rect x="190" y="110" width="1220" height="600" fill="url(#s4h)"/>
      <circle cx="420" cy="430" r="120" fill="#6a7955"/><circle cx="520" cy="470" r="90" fill="#768560"/><rect x="452" y="470" width="14" height="150" fill="#5b5145"/>
      <circle cx="1080" cy="410" r="140" fill="#62704f"/><circle cx="1210" cy="450" r="96" fill="#6f7d59"/><rect x="1120" y="470" width="16" height="160" fill="#5b5145"/>
      <rect x="190" y="110" width="1220" height="600" fill="none" stroke="#2b2a28" stroke-width="16"/><path d="M800 110v600" stroke="#2b2a28" stroke-width="10"/>
      <rect y="710" width="1600" height="190" fill="#c5b59a"/><rect x="190" y="716" width="1220" height="110" fill="#fff" opacity=".16"/>
      <rect x="480" y="640" width="640" height="34" fill="#2f2d2a"/><rect x="500" y="674" width="18" height="40" fill="#2f2d2a"/><rect x="1082" y="674" width="18" height="40" fill="#2f2d2a"/>`],
    hof: ['Innenhof mit Kies, einem Apfelbaum und einer Bank, umgeben von hellen Mauern, lange Schatten', `
      <rect width="1600" height="900" fill="#e9e6e0"/><path d="M0 0H330V900H0Z" fill="#cdc8bf"/><path d="M1270 0H1600V900H1270Z" fill="#d9d4cb"/>
      <path d="M330 520H1270V900H330Z" fill="#d6d0c4"/><path d="M330 520H1270" stroke="#c3bdb1" stroke-width="4"/>
      <path d="M330 0H1270V520H330Z" fill="#e3e6e4"/><path d="M330 300H1270V520H330Z" fill="#d8d3ca"/>
      <path d="M760 700 1270 640V760L760 760Z" fill="#000" opacity=".1"/>
      <rect x="752" y="470" width="18" height="250" fill="#5b5145"/><circle cx="760" cy="420" r="120" fill="#6d7a58"/><circle cx="690" cy="470" r="80" fill="#76835f"/><circle cx="840" cy="470" r="84" fill="#62704f"/>
      <circle cx="720" cy="430" r="9" fill="#b9472d"/><circle cx="805" cy="455" r="9" fill="#b9472d"/>
      <rect x="430" y="690" width="220" height="24" fill="#a87b50"/><rect x="440" y="714" width="12" height="36" fill="#7d5a38"/><rect x="628" y="714" width="12" height="36" fill="#7d5a38"/>`],
    detail: ['Detail: Eichenbalken als Sturz über Mauerwerk aus Kalksandstein, schmale Fugen', (() => {
      let st = '';
      for (let r = 0; r < 8; r++) for (let c = -1; c < 8; c++) {
        const y = r < 4 ? r * 130 : r * 130 + 110, x = c * 260 + (r % 2 ? 130 : 0), t = ['#d6d1c8', '#d0cbc1', '#dbd6cd', '#cdc8be'][(r * 3 + c * 5 + 8) % 4];
        st += `<rect x="${x + 4}" y="${y + 4}" width="252" height="122" fill="${t}"/>`;
      }
      return `<rect width="1600" height="900" fill="#bfb9ae"/>${st}<rect y="520" width="1600" height="110" fill="#a87b50"/><path d="M0 548C400 540 800 560 1600 546M0 584C500 596 1000 574 1600 590M0 606C300 612 900 600 1600 610" stroke="#8f6741" stroke-width="3" fill="none"/><rect y="626" width="1600" height="8" fill="#000" opacity=".12"/>`;
    })()],
    see: ['Wintermorgen am Wasser: blasse Sonne über der Ostsee, zwei Holzpfähle im flachen Wasser', `
      <defs><linearGradient id="p1" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#e3ddd3"/><stop offset=".6" stop-color="#d9d6cf"/><stop offset=".6" stop-color="#a7b0ae"/><stop offset="1" stop-color="#c4c6bf"/></linearGradient></defs>
      <rect width="400" height="460" fill="url(#p1)"/><circle cx="250" cy="200" r="34" fill="#f6f1e8"/><rect x="0" y="276" width="400" height="2" fill="#f3eee7" opacity=".7"/>
      <path d="M0 400Q200 380 400 396V460H0Z" fill="#d6c8b0"/><rect x="118" y="300" width="9" height="104" fill="#5a4939"/><rect x="150" y="318" width="8" height="84" fill="#5a4939"/>`],
    sauna: ['Saunahütte aus dunklem Holz mit rundem Fenster zur See, davor Dampf', `
      <rect width="400" height="460" fill="#5f4d3c"/>${Array.from({ length: 20 }, (_, i) => `<rect x="${i * 20}" y="0" width="2" height="460" fill="#4c3d2f"/>`).join('')}
      <circle cx="200" cy="190" r="92" fill="#e9e3d8"/><path d="M110.6 210H289.4A92 92 0 0 1 110.6 210Z" fill="#a7b0ae"/><circle cx="200" cy="190" r="92" fill="none" stroke="#3e3a37" stroke-width="10"/>
      <path d="M120 440C100 400 150 380 130 340M190 450C170 400 220 380 200 330M260 440C240 400 290 380 270 340" stroke="#f3eee7" stroke-width="6" fill="none" opacity=".45" stroke-linecap="round"/>`],
    fruehstueck: ['Frühstück von oben: Teller mit Ei und Brot, eine Tasse Kaffee auf Leinen', `
      <rect width="400" height="460" fill="#efe7da"/><path d="M0 60H400M0 64H400" stroke="#e2d6c3" stroke-width="2"/>
      <circle cx="180" cy="250" r="118" fill="#fbf8f2"/><circle cx="180" cy="250" r="96" fill="none" stroke="#ece4d6" stroke-width="2"/>
      <ellipse cx="150" cy="232" rx="44" ry="36" fill="#fff"/><circle cx="156" cy="228" r="17" fill="#eab64a"/>
      <path d="M190 270q40-30 70 4q-6 36-46 34q-30-4-24-38z" fill="#b98a55"/><path d="M200 284q24-14 46 0" stroke="#a07040" stroke-width="3" fill="none"/>
      <circle cx="330" cy="110" r="44" fill="#fbf8f2"/><circle cx="330" cy="110" r="32" fill="#4a3528"/><path d="M372 110h18" stroke="#fbf8f2" stroke-width="10" stroke-linecap="round"/>
      <rect x="40" y="380" width="120" height="8" rx="4" fill="#b7aa96"/>`],
    hund: ['Strand mit Pfotenspuren, die zum Wasser führen, ein roter Ball', `
      <rect width="400" height="460" fill="#dacbb2"/><path d="M0 0H400V120Q200 140 0 112Z" fill="#a7b0ae"/><path d="M0 112Q200 140 400 120" stroke="#efe9df" stroke-width="5" fill="none"/>
      ${[[90, 420], [130, 380], [110, 330], [160, 300], [140, 250], [190, 220], [175, 172]].map(([x, y], i) => `<g fill="#b9a787" transform="translate(${x} ${y}) rotate(${i % 2 ? 18 : -14})"><ellipse cx="0" cy="6" rx="9" ry="7"/><circle cx="-9" cy="-6" r="3.4"/><circle cx="-3" cy="-10" r="3.4"/><circle cx="4" cy="-10" r="3.4"/><circle cx="10" cy="-5" r="3.4"/></g>`).join('')}
      <circle cx="300" cy="330" r="22" fill="#b9472d"/><path d="M282 322q18 10 36 0" stroke="#f3eee7" stroke-width="3" fill="none"/>`],
    fenster: ['Blick aus einem Giebelfenster auf Steilküste und Meer', `
      <rect width="400" height="460" fill="#efe8dd"/><path d="M60 80H340V400H60Z" fill="#cfd5d2"/>
      <path d="M60 250H340V400H60Z" fill="#96a3a1"/><path d="M60 230Q140 210 190 260T340 300V400H60Z" fill="#c9b796"/><path d="M60 300Q120 290 170 330T340 360V400H60Z" fill="#8e9a74"/>
      <path d="M60 80H340V400H60ZM200 80V400M60 240H340" fill="none" stroke="#f6f1e8" stroke-width="14"/><rect x="40" y="400" width="320" height="20" fill="#d8ccba"/>`],
  };
  document.querySelectorAll('[data-szene]').forEach(el => {
    const [alt, inhalt] = SZENEN[el.dataset.szene];
    const vb = el.classList.contains('b-bild') ? '0 0 1600 900' : '0 0 400 460';
    el.innerHTML = `<svg viewBox="${vb}" preserveAspectRatio="xMidYMid slice" role="img" aria-label="${alt}">${inhalt}</svg>`;
  });
})();

/* ---------- Gemeinsam für Bühne und Kartenreihe: Knöpfe, Aktivzustand, Vorführung ---------- */
function baueBand({ id, band, platz, knoepfe, werte, nachSchritt }) {
  const html = document.documentElement;
  const reduziert = () => html.classList.contains('reduziert');
  const slides = [...band.children];
  const verhalten = () => (reduziert() ? 'auto' : 'smooth');
  const schritt = () => slides[1].offsetLeft - slides[0].offsetLeft;
  let aktiv = 0;
  knoepfe.forEach(k => k.addEventListener('click', () => band.scrollBy({ left: +k.dataset.schritt * schritt(), behavior: verhalten() })));
  function knoepfeAktualisieren() {
    const max = band.scrollWidth - band.clientWidth;
    if (knoepfe[0]) knoepfe[0].disabled = band.scrollLeft < 4;
    if (knoepfe[1]) knoepfe[1].disabled = band.scrollLeft > max - 4;
  }
  band.addEventListener('scroll', knoepfeAktualisieren, { passive: true });
  const io = new IntersectionObserver(e => {
    e.forEach(x => { if (x.isIntersecting) aktiv = slides.indexOf(x.target); });
    slides.forEach((s, i) => s.classList.toggle('ist-aktiv', i === aktiv));
    if (nachSchritt) nachSchritt(aktiv);
    knoepfeAktualisieren();
  }, { root: band, rootMargin: '0px -45% 0px -45%', threshold: 0 });
  slides.forEach(s => io.observe(s));
  band.addEventListener('keydown', e => {
    if (e.target !== band || (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight')) return;
    e.preventDefault(); band.scrollBy({ left: (e.key === 'ArrowRight' ? 1 : -1) * schritt(), behavior: verhalten() });
  });
  // Vorführung: gedachter Zeiger drückt den Pfeil, Band läuft hin und zurück
  const zeiger = document.querySelector('[data-zeiger]');
  let uhrwerk = null, richtung = 1, laeuft = false;
  const warte = ms => new Promise(r => { uhrwerk = setTimeout(r, ms); });
  function zeigerAuf(seite) {
    const r = platz.getBoundingClientRect(); if (!r.width) return false;
    zeiger.style.transform = `translate(${(seite > 0 ? r.right - 22 : r.left + 22) - 4}px, ${r.top + 22 - 2}px)`; return true;
  }
  async function runde() {
    while (laeuft) {
      const max = band.scrollWidth - band.clientWidth;
      if (band.scrollLeft > max - 4) richtung = -1;
      if (band.scrollLeft < 4) richtung = 1;
      const sichtbar = zeigerAuf(richtung);
      await warte(sichtbar ? 750 : 300); if (!laeuft) return;
      const seite = richtung > 0 ? 'rechts' : 'links';
      band.classList.add('druck-' + seite); if (knoepfe[richtung > 0 ? 1 : 0]) knoepfe[richtung > 0 ? 1 : 0].classList.add('druck');
      band.scrollBy({ left: richtung * schritt(), behavior: verhalten() });
      await warte(160);
      band.classList.remove('druck-links', 'druck-rechts'); knoepfe.forEach(k => k.classList.remove('druck'));
      await warte(1300);
    }
  }
  function stopp() { laeuft = false; clearTimeout(uhrwerk); band.classList.remove('druck-links', 'druck-rechts'); knoepfe.forEach(k => k.classList.remove('druck')); }
  ['pointerdown', 'wheel', 'touchstart', 'keydown'].forEach(t => band.addEventListener(t, () => { if (laeuft) { stopp(); html.classList.remove('vorfuehrung'); } }, { passive: true }));
  return {
    zuIndex(i) { band.scrollTo({ left: i * schritt(), behavior: verhalten() }); },
    aktiv: () => aktiv,
    start() { werte(); band.scrollTo({ left: 0, behavior: 'instant' }); knoepfeAktualisieren(); },
    stopp() { stopp(); html.classList.remove('vorfuehrung'); },
    neustart() { stopp(); html.classList.remove('vorfuehrung'); band.scrollTo({ left: 0, behavior: 'instant' }); richtung = 1; werte(); },
    vorfuehren() {
      stopp(); werte(); html.classList.add('vorfuehrung');
      const r = platz.getBoundingClientRect();
      zeiger.style.transition = 'none'; zeiger.style.transform = `translate(${r.left - 60}px, ${r.bottom + 90}px)`; zeiger.getBoundingClientRect(); zeiger.style.transition = '';
      laeuft = true; runde();
    },
    pause() { stopp(); },
    weiter() { if (html.classList.contains('vorfuehrung') && !laeuft) { laeuft = true; runde(); } }
  };
}

/* ---------- Variante „Vollbild-Bühne“: Marker als Reiter im Modus tabs, Rückfall eigene Reiter ---------- */
(() => {
  const werteEl = document.querySelector('[data-werte]');
  const main = document.querySelector('[data-b]'), band = document.querySelector('[data-b-band]');
  const slides = [...band.children], tabs = document.querySelector('[data-b-tabs]');
  const markerOk = CSS.supports('selector(::scroll-marker)'), tabsOk = CSS.supports('scroll-marker-group', 'after tabs');
  let marker = markerOk ? 'nativ' : 'eigene';
  // Eigene Reiter (Rückfall): role=tab, ein Tab-Stopp, Pfeiltasten wechseln
  tabs.innerHTML = slides.map((s, i) => `<button type="button" role="tab" id="b-tab-${i + 1}" aria-controls="${s.id}" aria-selected="${i === 0}" tabindex="${i === 0 ? 0 : -1}"><span>${s.dataset.nr}</span><span class="t">  ${s.dataset.titel}</span></button>`).join('');
  const reiter = [...tabs.children];
  function reiterSetzen(i) {
    reiter.forEach((r, j) => { r.setAttribute('aria-selected', String(i === j)); r.tabIndex = i === j ? 0 : -1; });
    if (marker === 'eigene') slides.forEach((s, j) => s.toggleAttribute('aria-hidden', i !== j));   // wie Modus tabs: inaktive Bilder nicht im Baum
  }
  reiter.forEach((r, i) => {
    r.addEventListener('click', () => B.zuIndex(i));
    r.addEventListener('keydown', e => {
      const n = { ArrowRight: 1, ArrowLeft: -1 }[e.key]; if (!n) return;
      e.preventDefault(); const j = (i + n + reiter.length) % reiter.length; reiter[j].focus(); B.zuIndex(j);
    });
  });
  function werte() {
    werteEl.innerHTML = marker === 'nativ'
      ? `Bild <b>100 % Breite</b> · snap <b>center, stop always</b> · Marker <b>scroll-marker-group: after${tabsOk ? ' tabs' : ''}</b>${tabsOk ? ' (Modus greift ab Chrome 154: ein Tab-Stopp, Pfeiltasten, inaktive Bilder nicht im Baum)' : ' (Modus tabs unbekannt: Standardverhalten)'} · Bild <b>scale 1,06 → 1 · 1000 ms cubic-bezier(0.1, 0.9, 0.3, 1)</b> per scroll-state(snapped)`
      : 'Rückfall <b>eigene Reiter role=tab</b> · <b>ein Tab-Stopp, Pfeiltasten</b> · inaktive Bilder <b>aria-hidden</b> · Aktivzustand <b>IntersectionObserver Mitte ±5 %</b> · Knöpfe <b>scrollBy ±1 Bild</b>';
  }
  const knoepfe = [...document.querySelectorAll('[data-b-schritt]')];
  knoepfe.forEach(k => { k.dataset.schritt = k.dataset.bSchritt; });
  const B = baueBand({ id: 'buehne', band, platz: document.querySelector('[data-b-platz]'), knoepfe, werte, nachSchritt: reiterSetzen });
  function setzeMarker(m) {
    marker = (m === 'nativ' && !markerOk) ? 'eigene' : m;
    main.dataset.marker = marker;
    document.querySelectorAll('[data-marker-knopf]').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.markerKnopf === marker)));
    slides.forEach(s => s.removeAttribute('aria-hidden'));
    if (marker === 'eigene') reiterSetzen(B.aktiv());
    werte();
  }
  document.querySelectorAll('[data-marker-knopf]').forEach(b => {
    if (b.dataset.markerKnopf === 'nativ' && !markerOk) { b.disabled = true; b.title = 'Dieser Browser kennt ::scroll-marker noch nicht'; }
    b.addEventListener('click', () => setzeMarker(b.dataset.markerKnopf));
  });
  STEUER.buehne = { ...B, start() { setzeMarker(marker); B.start(); } };
})();

/* ---------- Variante „Kartenreihe mit Peek“: Start-Ausrichtung, Marker im Modus links ---------- */
(() => {
  const werteEl = document.querySelector('[data-werte]');
  const band = document.querySelector('[data-p-band]');
  const linksOk = CSS.supports('scroll-marker-group', 'after links'), knopfOk = CSS.supports('selector(::scroll-button(right))');
  function werte() {
    werteEl.innerHTML = `Karte <b>clamp(240px, 26vw, 360px)</b>, Telefon <b>80vw</b> · snap <b>start</b> · scroll-padding-inline <b>= Seitenrand</b> · Marker <b>scroll-marker-group: after${linksOk ? ' links' : ''}</b>${linksOk ? ' (ab Chrome 154: jeder Marker ein Link und Tab-Stopp)' : ''} · Pfeile <b>${knopfOk ? '::scroll-button' : 'eigene Knöpfe'}</b> · Bild-Hover <b>scale 1,04 · 1100 ms cubic-bezier(0.37, 0, 0.63, 1)</b>`;
  }
  const knoepfe = [...document.querySelectorAll('[data-p-schritt]')];
  knoepfe.forEach(k => { k.dataset.schritt = k.dataset.pSchritt; });
  STEUER.peek = baueBand({ id: 'peek', band, platz: document.querySelector('[data-p-platz]'), knoepfe, werte });
})();

/* ---------- Gemeinsam für „Variable Breiten“ und „Fenster“: Gleiten mit der Kurve des Rezepts ---------- */
function bezierKurve(x1, y1, x2, y2) {
  const cx = 3 * x1, bx = 3 * (x2 - x1) - cx, ax = 1 - cx - bx, cy = 3 * y1, by = 3 * (y2 - y1) - cy, ay = 1 - cy - by;
  const X = t => ((ax * t + bx) * t + cx) * t, Y = t => ((ay * t + by) * t + cy) * t, dX = t => (3 * ax * t + 2 * bx) * t + cx;
  return x => {
    if (x <= 0) return 0; if (x >= 1) return 1;
    let t = x;
    for (let i = 0; i < 8; i++) { const e = X(t) - x; if (Math.abs(e) < 1e-5) return Y(t); const d = dX(t); if (Math.abs(d) < 1e-6) break; t -= e / d; }
    let lo = 0, hi = 1; t = x;
    for (let i = 0; i < 24; i++) { if (X(t) < x) lo = t; else hi = t; t = (lo + hi) / 2; }
    return Y(t);
  };
}
function Gleiter(band, kurve, dauer, fertig) {
  let anim = 0, laeuft = false;
  const red = () => document.documentElement.classList.contains('reduziert');
  function stopp() { if (!laeuft) return; cancelAnimationFrame(anim); laeuft = false; band.style.scrollSnapType = ''; }
  ['wheel', 'touchstart', 'pointerdown'].forEach(t => band.addEventListener(t, stopp, { passive: true }));
  return {
    laeuft: () => laeuft, stopp,
    zu(ziel, ms = dauer) {
      stopp();
      const start = band.scrollLeft, d = ziel - start;
      if (red() || Math.abs(d) < 1) { band.style.scrollSnapType = 'none'; band.scrollLeft = ziel; band.style.scrollSnapType = ''; fertig && fertig(); return; }
      band.style.scrollSnapType = 'none'; laeuft = true;   // Snap aus, solange die Kurve läuft
      const t0 = performance.now();
      const s = j => { const p = Math.min(1, (j - t0) / ms); band.scrollLeft = start + d * kurve(p); if (p < 1) anim = requestAnimationFrame(s); else { laeuft = false; band.style.scrollSnapType = ''; fertig && fertig(); } };
      anim = requestAnimationFrame(s);
    },
    springe(ziel) { stopp(); band.style.scrollSnapType = 'none'; band.scrollLeft = ziel; band.style.scrollSnapType = ''; }
  };
}
function vorfuehrSchleife(schritt) {
  let id = 0, uhr = 0;
  const warte = ms => new Promise(r => { uhr = setTimeout(r, ms); });
  return {
    start() { const mein = ++id; document.documentElement.classList.add('vorfuehrung'); (async () => { while (mein === id) await schritt(() => mein === id, warte); })(); },
    stopp() { id++; clearTimeout(uhr); }
  };
}

/* ---------- Variante „Variable Breiten“: Mara Seelig, Fotografie ---------- */
(() => {
  const html = document.documentElement;
  const band = document.querySelector('[data-vb-band]'), folien = [...band.children];
  const knoepfe = [...document.querySelectorAll('[data-vb-schritt]')], zaehler = document.querySelector('[data-vb-zaehler]'), status = document.querySelector('[data-vb-status]');
  const zeiger = document.querySelector('[data-zeiger]'), werteEl = document.querySelector('[data-werte]');
  const pad = () => parseFloat(getComputedStyle(band).scrollPaddingInlineStart) || 0;
  const max = () => band.scrollWidth - band.clientWidth;
  const pos = i => Math.max(0, Math.min(folien[i].offsetLeft - pad(), max()));
  let ruheUhr = 0;
  function sichtbar() {
    const l = band.scrollLeft + pad() - 2, r = band.scrollLeft + band.clientWidth - pad() + 2;
    const v = folien.map((f, i) => (f.offsetLeft >= l && f.offsetLeft + f.offsetWidth <= r ? i : -1)).filter(i => i >= 0);
    if (v.length) return [v[0], v[v.length - 1]];
    const n = folien.findIndex(f => f.offsetLeft + f.offsetWidth > l); return [Math.max(0, n), Math.max(0, n)];
  }
  const z2 = i => String(i + 1).padStart(2, '0');
  function anzeigen() {
    const [a, z] = sichtbar();
    zaehler.textContent = `${z2(a)}${z > a ? '–' + z2(z) : ''} / 10`;
    knoepfe[0].disabled = band.scrollLeft < 2;
    knoepfe[1].disabled = band.scrollLeft > max() - 2;
  }
  function ruhe() { anzeigen(); const [a, z] = sichtbar(); status.textContent = `Bild ${a + 1}${z > a ? ' bis ' + (z + 1) : ''} von 10`; }
  const G = Gleiter(band, bezierKurve(0.76, 0, 0.24, 1), 800, ruhe);
  band.addEventListener('scroll', () => { if (G.laeuft()) return; anzeigen(); clearTimeout(ruheUhr); ruheUhr = setTimeout(ruhe, 140); }, { passive: true });
  // Pfeile blättern um die Sichtbreite: vor = das erste angeschnittene Bild wird das erste; zurück = um eine Sichtbreite, dann auf den nächsten Bildanfang
  function seite(r) {
    const breite = band.clientWidth - 2 * pad(), x = band.scrollLeft;
    let ziel;
    if (r > 0) {
      const i = folien.findIndex(f => f.offsetLeft + f.offsetWidth > x + pad() + breite + 2);
      ziel = i < 0 ? max() : pos(i);
      if (ziel <= x + 2) ziel = pos(Math.min(folien.length - 1, i + 1));
    } else {
      const soll = x - breite, i = folien.findIndex(f => f.offsetLeft - pad() >= soll - 2);
      ziel = pos(Math.max(0, i));
      if (ziel >= x - 2) ziel = pos(Math.max(0, i - 1));
    }
    G.zu(ziel); anzeigen();
  }
  knoepfe.forEach(k => k.addEventListener('click', () => seite(+k.dataset.vbSchritt)));
  band.addEventListener('keydown', e => {
    if (e.target !== band || (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight')) return;
    e.preventDefault();
    const [a] = sichtbar(), r = e.key === 'ArrowRight' ? 1 : -1;
    G.zu(pos(Math.max(0, Math.min(folien.length - 1, a + r))));
  });
  new ResizeObserver(anzeigen).observe(band);
  let richtung = 1;
  const V = vorfuehrSchleife(async (ok, warte) => {
    if (band.scrollLeft > max() - 2) richtung = -1;
    if (band.scrollLeft < 2) richtung = 1;
    const k = knoepfe[richtung > 0 ? 1 : 0], r = k.getBoundingClientRect();
    if (r.width) zeiger.style.transform = `translate(${r.left + r.width / 2 - 4}px, ${r.top + r.height / 2 - 2}px)`;
    await warte(r.width ? 760 : 300); if (!ok()) return;
    k.classList.add('druck'); setTimeout(() => k.classList.remove('druck'), 170);
    seite(richtung); await warte(1900);
  });
  const werte = () => { werteEl.innerHTML = 'Höhe <b>clamp(220px, min(54svh, 62vw), 600px)</b>, Breite <b>auto</b> aus width/height · snap <b>start</b>, scroll-padding <b>= Seitenrand</b> · Pfeile <b>um die Sichtbreite</b> (erstes angeschnittenes Bild wird erstes) · Gleiten <b>800 ms cubic-bezier(0.76, 0, 0.24, 1)</b> · Zähler <b>sichtbarer Bereich „03–05 / 10“</b>'; };
  STEUER['variable-breiten'] = {
    start() { werte(); G.springe(0); anzeigen(); },
    stopp() { V.stopp(); html.classList.remove('vorfuehrung'); },
    neustart() { V.stopp(); html.classList.remove('vorfuehrung'); G.springe(0); richtung = 1; anzeigen(); },
    vorfuehren() { V.stopp(); werte(); const r = knoepfe[1].getBoundingClientRect(); zeiger.style.transition = 'none'; zeiger.style.transform = `translate(${r.left - 80}px, ${r.bottom + 80}px)`; zeiger.getBoundingClientRect(); zeiger.style.transition = ''; V.start(); },
    pause() { V.stopp(); },
    weiter() { if (html.classList.contains('vorfuehrung')) V.start(); }
  };
  ['pointerdown', 'wheel', 'touchstart', 'keydown'].forEach(t => band.addEventListener(t, e => { if (e.isTrusted && html.dataset.v === 'variable-breiten') { V.stopp(); html.classList.remove('vorfuehrung'); } }, { passive: true }));
})();

/* ---------- Variante „Fenster“: Haus Oberhagen, Ferienhaus ---------- */
(() => {
  const html = document.documentElement;
  const band = document.querySelector('[data-fe-band]'), knoepfe = [...document.querySelectorAll('.welt-fe [data-schritt]')];
  const nameEl = document.querySelector('[data-fe-name]'), status = document.querySelector('[data-fe-status]');
  const zeiger = document.querySelector('[data-zeiger]'), werteEl = document.querySelector('[data-werte]');
  const BILDER = [
    ['hotel-haus-am-see-hoch.webp', 900, 1200, 'Das Haus vom Steg aus', 'Backsteinhaus mit erleuchteten Fenstern am See in der Abenddämmerung, Spiegelung im Wasser'],
    ['fenster-licht-hoch.jpg', 800, 1000, 'Diele im Morgenlicht', 'Salbeigrüne Kalkputzwand mit dem Sonnenfleck eines Sprossenfensters, davor eine Eichenbank mit Vase'],
    ['atmosphaere-partikel-kamin-hoch.jpg', 800, 1000, 'Kaminzimmer am Abend', 'Niedriges Kaminfeuer in einer dunklen Putzwand, warmer Schein auf dem Boden'],
    ['hotel-zimmer.webp', 1200, 800, 'Schlafzimmer zum See', 'Helles Zimmer mit Doppelbett und gelb gepolsterter Fensterbank'],
    ['hotel-sauna-hoch.webp', 800, 1000, 'Sauna am Steg', 'Kleine Holzsauna mit zwei erleuchteten Fenstern auf dem Steg in der Dämmerung'],
    ['hotel-zimmer-fenster.webp', 1200, 800, 'Fensterbank im Giebel', 'Breite Fensterbank mit gelbem Polster und Tasse, draußen der See'],
    ['split-screen-sticky-seiten-archiv-7.jpg', 800, 1000, 'Der See im Januar', 'Heller, winterlicher See mit Eisflächen und Schilf am fernen Ufer'],
    ['split-screen-sticky-seiten-archiv-4.jpg', 800, 1000, 'Badestelle mit Pfählen', 'Eine Reihe dunkler Holzpfähle läuft schräg ins graugrüne Wasser']
  ];
  const N = BILDER.length;
  const folie = ([d, w, h, name, alt], i, klon) => `<li${klon ? ' aria-hidden="true" inert data-klon' : ` aria-label="${name}, ${i + 1} von ${N}"`}><img src="bilder/${d}" width="${w}" height="${h}" alt="${klon ? '' : alt}" loading="lazy"></li>`;
  // Schleife: Klon des letzten Bildes vorn, Klon des ersten hinten; landet das Band auf einem Klon, springt es unsichtbar zum Original
  band.innerHTML = folie(BILDER[N - 1], N - 1, true) + BILDER.map((b, i) => folie(b, i, false)).join('') + folie(BILDER[0], 0, true);
  const folien = [...band.children];
  const pos = i => folien[i].offsetLeft;
  const idx = () => Math.round(band.scrollLeft / Math.max(1, band.clientWidth));
  let aktiv = 1, ruheUhr = 0;
  const original = i => (i - 1 + N) % N;
  function zeigen(i) {
    const o = original(i);
    nameEl.textContent = BILDER[o][3];
    folien.forEach((f, j) => { if (!f.hasAttribute('data-klon')) f.inert = j !== i; });
  }
  function ruhe() {
    let i = idx();
    if (i === 0) { G.springe(pos(N)); i = N; }
    else if (i === N + 1) { G.springe(pos(1)); i = 1; }
    aktiv = i; zeigen(i);
    status.textContent = `Bild ${original(i) + 1} von ${N}: ${BILDER[original(i)][3]}`;
  }
  const G = Gleiter(band, bezierKurve(0.37, 0, 0.63, 1), 900, ruhe);
  function schritt(r) {
    if (G.laeuft()) { G.stopp(); ruhe(); }
    aktiv = Math.max(0, Math.min(N + 1, aktiv + r)); zeigen(aktiv);
    G.zu(pos(aktiv));
  }
  knoepfe.forEach(k => k.addEventListener('click', () => schritt(+k.dataset.schritt)));
  band.addEventListener('keydown', e => { if (e.target === band && (e.key === 'ArrowLeft' || e.key === 'ArrowRight')) { e.preventDefault(); schritt(e.key === 'ArrowRight' ? 1 : -1); } });
  band.addEventListener('scroll', () => { if (G.laeuft()) return; clearTimeout(ruheUhr); ruheUhr = setTimeout(ruhe, 140); }, { passive: true });
  new ResizeObserver(() => { if (!G.laeuft()) G.springe(pos(aktiv)); }).observe(band);
  const V = vorfuehrSchleife(async (ok, warte) => {
    const k = knoepfe[1], r = k.getBoundingClientRect();
    if (r.width) zeiger.style.transform = `translate(${r.left + r.width / 2 - 4}px, ${r.top + r.height / 2 - 2}px)`;
    await warte(r.width ? 760 : 300); if (!ok()) return;
    k.classList.add('druck'); setTimeout(() => k.classList.remove('druck'), 600);
    schritt(1); await warte(2000);
  });
  const werte = () => { werteEl.innerHTML = 'Fenster <b>min(30.7vw, 48svh, 520px)</b> (Kereliott: 442 px bei 1440), <b>4:5</b>, zentriert · Schleife <b>Klon vorn und hinten, unsichtbarer Sprung</b> · keine Punkte · Pfeile <b>1 px, 168 px lang</b> im Leerraum · Gleiten <b>900 ms cubic-bezier(0.37, 0, 0.63, 1)</b> (Flickity friction 0.28, selectedAttraction 0.025: 98 % nach ≈ 830 ms)'; };
  STEUER.fenster = {
    start() { werte(); aktiv = 1; G.springe(pos(1)); zeigen(1); },
    stopp() { V.stopp(); html.classList.remove('vorfuehrung'); },
    neustart() { V.stopp(); html.classList.remove('vorfuehrung'); aktiv = 1; G.springe(pos(1)); zeigen(1); },
    vorfuehren() { V.stopp(); werte(); const r = knoepfe[1].getBoundingClientRect(); zeiger.style.transition = 'none'; zeiger.style.transform = `translate(${r.left + 40}px, ${r.bottom + 90}px)`; zeiger.getBoundingClientRect(); zeiger.style.transition = ''; V.start(); },
    pause() { V.stopp(); },
    weiter() { if (html.classList.contains('vorfuehrung')) V.start(); }
  };
  ['pointerdown', 'wheel', 'touchstart', 'keydown'].forEach(t => band.addEventListener(t, e => { if (e.isTrusted && html.dataset.v === 'fenster') { V.stopp(); html.classList.remove('vorfuehrung'); } }, { passive: true }));
})();

/* ---------- Varianten-Umschalter (Regel 13): ?v=produktreihe | buehne | peek | variable-breiten | fenster ---------- */
(() => {
  const html = document.documentElement;
  const IDS = ['produktreihe', 'buehne', 'peek', 'variable-breiten', 'fenster'];
  let v = new URLSearchParams(location.search).get('v');
  if (!IDS.includes(v)) v = IDS[0];
  let vorf = false;
  const knoepfe = [...document.querySelectorAll('[data-v-wahl]')];
  function setze(neu, url) {
    const alt = v;
    v = neu; html.dataset.v = neu;
    if (STEUER[alt]) STEUER[alt].stopp();
    document.querySelectorAll('[data-welt]').forEach(w => { w.hidden = w.dataset.welt !== neu; });
    knoepfe.forEach(b => b.setAttribute('aria-pressed', String(b.dataset.vWahl === neu)));
    if (url) {
      const q = new URLSearchParams(location.search); q.set('v', neu);
      history.replaceState(null, '', location.pathname + '?' + q.toString().replace(/=(?=&|$)/g, '') + location.hash);
    }
    STEUER[neu].start();
  }
  knoepfe.forEach(b => b.addEventListener('click', () => { setze(b.dataset.vWahl, true); if (vorf) STEUER[v].vorfuehren(); }));
  setze(v, false);

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