Zum Inhalt
← Glas & Material

Geriffeltes Glas (Fluted Glass)

Hero einer Erfurter Kanzlei: Besprechungsraum, Domblick und Archiv liegen hinter einer Riffelglasscheibe, das Bild driftet langsam und folgt dem Zeiger, die Riffel brechen es in Streifen.

Hero mit ruhiger Bewegung für Kanzlei, Architektur, Interior oder Hotel: das Bild driftet, folgt dem Zeiger und wechselt alle 10 s; Riffelung vertikal, diagonal oder als Fenster.

Ein Bild liegt hinter einer virtuellen Riffelglasscheibe. Der Shader teilt die Fläche quer zur Riffelrichtung in Streifen und berechnet die Lage innerhalb jedes Streifens: local = fract(uv.x · n). Jeder Streifen wirkt wie eine Zylinderlinse und verschiebt die Leseposition abhängig von local, zur Mitte hin gestaucht oder gespiegelt. An den Riffelkanten kommen eine Fase, Licht und Schatten dazu. Bewegt sich das Bild hinter dem Glas (Drift, Zeiger, Scroll), flimmern die Streifen wie bei Duschglas.

Riffelglas kennt man aus Architektur und Interior. Es wirkt taktil und handwerklich und bringt ein echtes Material auf den Bildschirm. Das Bild bleibt als Stimmung lesbar, Details verschwinden. So entsteht Diskretion, ohne etwas zu verstecken.

Wann einsetzen

Heros für Architektur, Interior, Beauty, Hotellerie und Portfolios. Überall, wo Vertraulichkeit Teil der Marke ist, etwa bei Kanzleien oder Praxen. Auch als Übergang zwischen zwei Bildern.

Wann nicht

Über Text, Formularen oder Bildinhalten, die man erkennen muss (Produktfotos, Personen, Karten). Nicht für Marken, die laut und schnell auftreten.

Varianten

  • Vertikale Riffel (klassische Scheibe)
  • Diagonale Riffel, z. B. 0.36 rad wie bei Function Health
  • Horizontale Riffel
  • Riffel nur in einem Feld (Fenster), daneben klares Glas
  • Stehend als SVG-Filter (feDisplacementMap mit Streifen-Karte) ohne WebGL: das Glas wird erst auf Knopfdruck klar (Demo-Variante still)

Umsetzung

Bevorzugt als WebGL-Shader auf einem Vollbild-Quad. Pro Pixel: Lage im Riffel f = fract(s) − 0.5, Linsenversatz quer zum Riffel, an der Fase zusätzlich gebrochen, Rot und Blau leicht getrennt gelesen. Hinter dem Glas eine leicht weichgezeichnete Fassung des Bildes verwenden, die Riffelkanten bleiben trotzdem scharf. Drift und Zoom zeitbasiert und linear. Ohne WebGL: das Bild mit backdrop-filter: blur() und einem repeating-linear-gradient als Streifen belegen. SVG-Variante: feImage mit waagerechtem Rot-Verlauf als wiederholte Streifen, feDisplacementMap nur auf x. Die Award-Belege sind nicht verifiziert; das Muster ist aufgenommen, weil es in Shader-Toolkits und als Materialtrend verbreitet ist.

Robust umsetzen

Telefon. Unproblematisch. Ein einzelner Shader ist günstig. Auf dem Telefon die Frequenz senken (Function Health: 64.8 statt 82.8) und den Winkel anpassen. Statt Zeiger koppelt man die Verschiebung an Drift und Scroll.

Ohne Bewegung. Drift, Zoom und die Kopplung an Zeiger und Scroll abschalten. Das Riffelglas bleibt als stehendes Bild sichtbar, Bildwechsel ohne Überblendung.

Barrierefrei. Das Canvas ist dekorativ (aria-hidden). Die Fläche bekommt role=img mit einer Beschreibung dessen, was hinter dem Glas zu sehen ist, oder das Originalbild bleibt mit alt-Text im DOM. Automatische Bildwechsel per aria-live ankündigen, Text nie hinter das Glas legen.

Code

<!DOCTYPE html>
<!--
  Geriffeltes Glas (Fluted Glass)
  Fundus · Glas & Material · https://cecodesign.de/lab/fundus/glas-material/geriffeltes-glas/
  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>Geriffeltes Glas (Fluted Glass) · 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: 'Newsreader'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/newsreader:vf@latest/latin-opsz-normal.woff2') format('woff2-variations'); font-weight: 200 800; font-style: normal; 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: 'Schibsted Grotesk'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/schibsted-grotesk:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 400 900; font-style: normal; font-display: swap; }
/* Variante „still“: Rezept bestattung-wuerdig (Source Serif 4 + Commissioner) */
@font-face { font-family: 'Source Serif 4'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/source-serif-4:vf@latest/latin-opsz-normal.woff2') format('woff'); font-weight: 200 900; 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: 'Geist Mono'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/geist-mono:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-display: swap; }

:root {
  /* Rezept kanzlei-beratung-souveraen: ein Markenton, warme Graus, Akzent nur für Fokus/aktive Filter */
  --grund: #f9f8f8; --flaeche: #efebeb; --linie: #d2cdc7; --marke: #1e1e50; --text: #212529; --meta: #6e5f52; --akzent: #00bee6;
  --ui: cubic-bezier(0.4, 0, 0.22, 1);   /* Rezept-Kurve, 125 / 250 ms */
  --rand: clamp(20px, 3.4vw, 56px);
}
* { box-sizing: border-box; margin: 0; }
html { background: var(--grund); color: var(--text); font-family: 'Schibsted Grotesk', system-ui, sans-serif; font-size: 100%; -webkit-font-smoothing: antialiased; }
body { overflow-x: hidden; }
a { color: inherit; }
button { font: inherit; color: inherit; }
.mono { font: 500 11px/1.4 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; color: var(--meta); }
.nur-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
:focus-visible { outline: 2px solid var(--akzent); outline-offset: 3px; }

/* ---------- Kopf ---------- */
.buehne { min-height: 100svh; display: grid; grid-template-columns: minmax(0, 1fr); grid-template-rows: auto 1fr auto; }
.kopf { display: flex; align-items: center; gap: 40px; padding: clamp(16px, 2vw, 26px) var(--rand); border-bottom: 1px solid var(--linie); }
.marke { display: flex; align-items: baseline; gap: 12px; text-decoration: none; color: var(--marke); }
.marke b { font: 400 24px/1 'Newsreader', serif; letter-spacing: -.01em; }
.marke span { font-size: 13px; color: var(--meta); }
.kopf nav { display: flex; gap: 28px; margin-left: auto; font-size: 15px; font-weight: 500; }
.kopf nav a { text-decoration: none; color: var(--text); background: linear-gradient(var(--marke), var(--marke)) 0 100% / 0 1px no-repeat; padding-bottom: 3px; transition: background-size .125s var(--ui); }
.kopf nav a:hover, .kopf nav a[aria-current] { background-size: 100% 1px; }
.portal { font-size: 14px; font-weight: 500; text-decoration: none; border: 1px solid var(--linie); border-radius: 2px; padding: 9px 14px; color: var(--marke); transition: border-color .125s var(--ui); }
.portal:hover { border-color: var(--marke); }
.menue { display: none; margin-left: auto; background: none; border: 1px solid var(--linie); border-radius: 2px; height: 38px; padding: 0 14px; font-size: 14px; font-weight: 500; color: var(--marke); }

/* ---------- Hero ---------- */
.held { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: clamp(28px, 4.4vw, 80px); padding: clamp(28px, 3.4vw, 52px) var(--rand) clamp(20px, 2.4vw, 32px); }
.text { display: flex; flex-direction: column; min-width: 0; }
.fakten { margin-top: auto; }
.mitte { margin-bottom: 32px; }
.ort { font-size: 13px; color: var(--meta); display: flex; gap: 10px; align-items: center; font-variant-numeric: tabular-nums; }
.ort i { width: 28px; height: 1px; background: var(--linie); }
h1 { font-family: 'Newsreader', serif; font-weight: 400; font-size: clamp(2.25rem, 1.3rem + 2.6vw, 3.75rem); line-height: 1.06; letter-spacing: -.015em; color: var(--marke); text-wrap: balance; margin-top: 18px; }
h1 em { font-style: italic; }
.lead { font-size: 1.0625rem; line-height: 1.6; max-width: 40ch; margin-top: 24px; text-wrap: pretty; }
.aktionen { display: flex; flex-wrap: wrap; align-items: center; gap: 14px 22px; margin-top: 30px; }
.knopf { display: inline-flex; align-items: center; gap: 10px; height: 48px; padding: 0 22px; background: var(--marke); color: var(--grund); text-decoration: none; font-size: 15px; font-weight: 500; border-radius: 2px; transition: background-color .125s var(--ui); }
.knopf:hover { background: #2c2c6c; }
.knopf svg { transition: transform .25s var(--ui); }
.knopf:hover svg { transform: translateX(3px); }
.tel { font-size: 15px; font-weight: 500; color: var(--marke); text-decoration: none; }
.tel span { display: block; font-size: 13px; font-weight: 400; color: var(--meta); }
.fakten { display: grid; grid-template-columns: repeat(3, auto); justify-content: start; gap: 0 clamp(20px, 2.6vw, 40px); border-top: 1px solid var(--linie); padding-top: 16px; }
.fakten dt { font-size: 13px; color: var(--meta); }
.fakten dd { font: 400 26px/1.2 'Newsreader', serif; color: var(--marke); font-variant-numeric: tabular-nums lining-nums; margin-top: 4px; }

/* ---------- Die Scheibe ---------- */
.scheibe { position: relative; min-width: 0; display: flex; flex-direction: column; gap: 12px; }
.glas { position: relative; overflow: hidden; background: #15153a; border-radius: 2px; flex: 1 1 auto; min-height: 420px; max-height: 860px; isolation: isolate; }
.glas canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.glas .ersatz { object-fit: cover; display: none; }
.kein-gl .glas .ersatz { display: block; }
.kein-gl .glas .gl { display: none; }
/* ohne WebGL: CSS-Näherung aus Streifen und Unschärfe über dem gemalten Raum */
.glas .riffel-css { position: absolute; inset: 0; display: none; backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px);
  background: repeating-linear-gradient(90deg, rgba(255,255,255,.09) 0 2px, rgba(255,255,255,0) 4px 12px, rgba(0,0,0,.16) 15px 17px); }
.kein-gl .glas .riffel-css { display: block; }
.schild { position: absolute; z-index: 2; top: 20px; left: 20px; background: rgba(249, 248, 248, .94); color: var(--marke); padding: 11px 14px 12px; border-radius: 2px; min-width: 168px; transition: left .25s var(--ui); }
.schild b { display: block; font-size: 15px; font-weight: 500; }
.schild span { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--meta); margin-top: 3px; }
.schild span::before { content: ''; width: 7px; height: 7px; border-radius: 50%; background: #c2583d; }
.fenster-an .schild { left: calc(40% + 20px); }
.glas .rahmen { position: absolute; z-index: 1; top: 0; bottom: 0; left: 40%; width: 6px; margin-left: -3px; background: #2a2a2e; box-shadow: inset 1px 0 0 rgba(255,255,255,.18); opacity: 0; transition: opacity .25s var(--ui); pointer-events: none; }
.fenster-an .glas .rahmen { opacity: 1; }

.unterzeile { display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto; align-items: center; gap: 16px; font-size: 13px; }
/* Zugang (WCAG 2.2.2): die Raumfolge läuft von selbst, also gibt es einen Knopf zum Anhalten */
.anhalten { width: 28px; height: 28px; display: grid; place-items: center; padding: 0; border: 1px solid var(--linie); border-radius: 999px; background: none; color: var(--marke); cursor: pointer; transition: border-color .125s var(--ui); }
.anhalten:hover { border-color: var(--marke); }
.anhalten svg { width: 10px; height: 10px; fill: currentColor; }
.anhalten .abspielen, .anhalten[aria-pressed='true'] .pause { display: none; }
.anhalten[aria-pressed='true'] .abspielen { display: block; }
.vorschau .anhalten, .reduziert .anhalten { display: none; }
.unterzeile .nr { font-variant-numeric: tabular-nums; color: var(--meta); }
.unterzeile .raum { color: var(--text); font-weight: 500; }
.unterzeile .raum span { color: var(--meta); font-weight: 400; }
.szenen { display: flex; gap: 6px; }
.szenen button { position: relative; width: 44px; height: 24px; background: none; border: 0; padding: 0; cursor: pointer; }
.szenen button::before { content: ''; position: absolute; left: 0; right: 0; top: 11px; height: 2px; background: var(--linie); }
.szenen button i { position: absolute; left: 0; top: 11px; height: 2px; width: 0; background: var(--marke); }
.szenen button[aria-current='true'] i { width: var(--fortschritt, 0%); }

/* ---------- Fuß der Bühne: Varianten und Werte ---------- */
.fuss { display: flex; justify-content: space-between; align-items: center; gap: 24px; padding: 14px var(--rand) clamp(18px, 2vw, 26px); }
.modi { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.modi > span { margin-right: 8px; }
.modi button { font: inherit; letter-spacing: inherit; text-transform: inherit; color: var(--meta); background: none; border: 1px solid var(--linie); border-radius: 999px; height: 30px; padding: 0 12px; cursor: pointer; transition: color .125s var(--ui), border-color .125s var(--ui), background-color .125s var(--ui); }
.modi button:hover { color: var(--marke); border-color: var(--meta); }
.modi button[aria-pressed='true'] { background: var(--marke); border-color: var(--marke); color: var(--grund); }
.modi button.drueckt { background: #3a3a78; color: var(--grund); }
.werte { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 4px 16px; max-width: 900px; text-align: right; }
.werte b { color: var(--text); font-weight: 500; font-variant-numeric: tabular-nums; }

/* ---------- Unter der Bühne ---------- */
.gebiete { background: var(--flaeche); padding: clamp(56px, 7vw, 104px) var(--rand); display: grid; grid-template-columns: minmax(0, 4fr) minmax(0, 8fr); gap: clamp(28px, 4vw, 72px); }
h2 { font: 400 clamp(1.625rem, 1.2rem + 1.6vw, 2.5rem)/1.12 'Newsreader', serif; letter-spacing: -.01em; color: var(--marke); text-wrap: balance; }
.gebiete .einl p { margin-top: 16px; line-height: 1.6; max-width: 34ch; }
.gebiete ol { list-style: none; padding: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(20px, 2.6vw, 40px); counter-reset: g; }
.gebiete li { border-top: 1px solid var(--linie); padding-top: 18px; counter-increment: g; }
.gebiete li::before { content: '0' counter(g); font-size: 13px; color: var(--meta); font-variant-numeric: tabular-nums; }
.gebiete h3 { font: 400 1.5rem/1.2 'Newsreader', serif; color: var(--marke); margin: 10px 0 10px; }
.gebiete li p { font-size: 15px; line-height: 1.6; }
.gebiete li a { display: inline-block; margin-top: 14px; font-size: 14px; font-weight: 500; color: var(--marke); text-underline-offset: 4px; text-decoration-color: var(--linie); transition: text-decoration-color .125s var(--ui); }
.gebiete li a:hover { text-decoration-color: var(--marke); }
.insights { padding: clamp(56px, 7vw, 104px) var(--rand); display: grid; grid-template-columns: minmax(0, 4fr) minmax(0, 8fr); gap: clamp(28px, 4vw, 72px); }
.insights ul { list-style: none; padding: 0; }
.insights li a { display: grid; grid-template-columns: 7.5rem 9rem 1fr; gap: 16px; align-items: baseline; padding: 18px 0; border-top: 1px solid var(--linie); text-decoration: none; }
.insights li:last-child a { border-bottom: 1px solid var(--linie); }
.insights time, .insights .rubrik { font-size: 13px; color: var(--meta); font-variant-numeric: tabular-nums; }
.insights strong { font: 400 1.25rem/1.3 'Newsreader', serif; color: var(--marke); }
.insights li a:hover strong { text-decoration: underline; text-underline-offset: 4px; text-decoration-thickness: 1px; }
.fussleiste { border-top: 1px solid var(--linie); padding: 28px var(--rand) 36px; display: flex; flex-wrap: wrap; justify-content: space-between; gap: 12px 32px; font-size: 13px; color: var(--meta); }

/* Gedachter Zeiger der Vorführung */
.zeiger { position: fixed; z-index: 5; left: 0; top: 0; width: 26px; height: 26px; margin: -13px 0 0 -13px; border: 1.5px solid #fff; border-radius: 50%; mix-blend-mode: difference; pointer-events: none; display: none; transition: scale .125s var(--ui), background-color .125s var(--ui); }
.zeiger.drueckt { scale: .7; background: rgba(255, 255, 255, .5); }
.vorfuehrung .zeiger { display: block; }

/* ---------- Vorschau 960 × 720: nur die Bühne ---------- */
.vorschau body { overflow: hidden; }
.vorschau .gebiete, .vorschau .insights, .vorschau .fussleiste, .vorschau .kopf nav, .vorschau .portal, .vorschau .lead, .vorschau .werte, .vorschau .fakten, .vorschau .tel { display: none; }
.vorschau .buehne { height: 100vh; min-height: 0; }
.vorschau .held { padding-top: 26px; padding-bottom: 14px; grid-template-columns: minmax(0, 4.4fr) minmax(0, 7.6fr); }
.vorschau h1 { font-size: 46px; }
.vorschau .glas { min-height: 0; }
.vorschau .text { justify-content: center; }

:root.reduziert * { transition-duration: .01ms !important; animation-duration: .01ms !important; }

@media (max-width: 1100px) { .werte span:nth-child(n+6) { display: none; } .kopf nav { gap: 20px; } }
@media (max-width: 960px) { .kopf nav, .portal { display: none; } .menue { display: block; } }
@media (max-width: 760px) {
  .held { display: flex; flex-direction: column; gap: 0; padding-top: 22px; }
  .text { display: contents; }
  .oben { order: 1; } .scheibe { order: 2; margin-top: 24px; } .mitte { order: 3; margin: 4px 0 28px; } .fakten { order: 4; }
  .glas { flex: none; min-height: 0; aspect-ratio: 4 / 5; }
  .fakten dd { font-size: 22px; }
  .marke span { display: none; }
  .fuss { flex-direction: column; align-items: stretch; gap: 12px; padding-top: 20px; }
  .werte { justify-content: flex-start; text-align: left; }
  .gebiete, .insights { grid-template-columns: 1fr; }
  .gebiete ol { grid-template-columns: 1fr; }
  .insights li a { grid-template-columns: auto 1fr; gap: 6px 14px; }
  .insights strong { grid-column: 1 / -1; }
  .unterzeile .raum span { display: none; }
  .schild { top: 14px; left: 14px; min-width: 0; }
  .fenster-an .schild { left: calc(40% + 12px); }
}
@media (max-width: 480px) { .unterzeile { gap: 12px; } .szenen button { width: 32px; } }
@media (max-width: 360px) { .unterzeile { grid-template-columns: auto minmax(0, 1fr) auto; gap: 8px 12px; } .unterzeile .raum { grid-column: 2 / -1; } .szenen { grid-column: 1 / 3; grid-row: 2; } .anhalten { grid-column: 3; grid-row: 2; } }
@media (max-height: 820px) and (min-width: 761px) { .lead { display: none; } }
@media (hover: none) {
  .modi button { height: 36px; padding: 0 14px; }
  .szenen button { height: 36px; }
  .szenen button::before, .szenen button i { top: 17px; }
}
@media (forced-colors: active) { button, [role=button], [role=tab], [role=switch] { border: 1px solid ButtonText !important; } }
/* ===================== Varianten-Umschalter ===================== */
[hidden] { display: none !important; }
.pult-gruppen { display: flex; flex-wrap: wrap; gap: 8px 22px; align-items: center; }
.fassung button { border-style: dashed; }
.fassung button[aria-pressed='true'] { border-style: solid; }
.vorschau .fassung { display: none; }

/* ===================== Variante „still“: stehendes Riffelglas als SVG-Filter (bestattung-wuerdig) ===================== */
.sw {
  --s-kalk: #F4F1EB; --s-text: #24221F; --s-sand: #E8E1D5; --s-schiefer: #2B2D2C; --s-moos: #3F5B48; --s-moos-hell: #8FB59A;
  --s-leise: #5A554D; --s-linie: #D3CCC0;
  --s-weich: cubic-bezier(0.37, 0, 0.63, 1);     /* token-luxus-weich: nur Raumbild und Riffelglas, 1400 ms */
  --s-auf: cubic-bezier(0.16, 1, 0.3, 1);        /* UI öffnen 300 ms (token-toggle-asymmetrisch) */
  --s-rand: clamp(20px, 4vw, 64px);
  background: var(--s-kalk); color: var(--s-text); min-height: 100vh; min-height: 100svh;
  font-family: 'Commissioner', system-ui, sans-serif; font-variation-settings: 'FLAR' 0, 'VOLM' 0; font-size: 1.1875rem; line-height: 1.65;
  display: grid; grid-template-columns: minmax(0, 1fr); grid-template-rows: auto auto 1fr auto;
}
.sw :focus-visible { outline: 3px solid var(--s-moos); outline-offset: 3px; }
.sw a { color: inherit; }
.s-serif { font-family: 'Source Serif 4', Georgia, serif; font-weight: 400; font-optical-sizing: auto; }
.s-sofort { background: var(--s-schiefer); color: var(--s-kalk); padding: 10px var(--s-rand); display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px 24px; font-size: 1rem; }
.s-sofort :focus-visible { outline-color: var(--s-moos-hell); }
.s-sofort .s-tel { font-size: 1.5rem; font-weight: 600; font-variant-numeric: tabular-nums; text-decoration: none; }
.s-sofort .s-tel:hover { text-decoration: underline; text-underline-offset: 5px; text-decoration-thickness: 1px; }
.s-kopf { display: flex; align-items: center; gap: 16px 36px; padding: 18px var(--s-rand); border-bottom: 1px solid var(--s-linie); flex-wrap: wrap; }
.s-marke { text-decoration: none; display: grid; }
.s-marke b { font: 400 1.75rem/1.1 'Source Serif 4', Georgia, serif; letter-spacing: -.01em; }
.s-marke small { font-size: 1rem; color: var(--s-leise); line-height: 1.3; }
.s-nav { display: flex; gap: 26px; margin-left: auto; font-size: 1rem; font-weight: 500; }
.s-nav a { text-decoration: none; padding-block: 6px; border-bottom: 1px solid transparent; transition: border-color 150ms linear; }
.s-nav a:hover, .s-nav a[aria-current] { border-color: currentColor; }
.s-knopf { display: inline-flex; align-items: center; gap: 10px; min-height: 48px; padding: 0 22px; border-radius: 3px; background: var(--s-moos); color: #fff !important; font-weight: 500; font-size: 1rem; text-decoration: none; transition: background-color 150ms linear; }
.s-knopf:hover { background: #33493a; }

.s-main { padding: clamp(32px, 6vh, 72px) var(--s-rand) clamp(36px, 6vh, 72px); }
.s-raeume { display: grid; grid-template-columns: minmax(0, 8fr) minmax(0, 4fr); gap: 24px clamp(28px, 4vw, 64px); }
.s-einl { grid-column: 1 / -1; display: grid; grid-template-columns: minmax(0, 8fr) minmax(0, 4fr); gap: 8px clamp(28px, 4vw, 64px); align-items: end; }
.s-rubrik { font-size: 1rem; font-weight: 500; letter-spacing: .06em; text-transform: uppercase; color: var(--s-leise); grid-column: 1 / -1; }
.sw h1, .sw h2 { color: var(--s-text); margin: 0; }
.s-einl h1 { font-size: clamp(2.25rem, 1.6rem + 2.6vw, 3.5rem); line-height: 1.12; letter-spacing: -.01em; text-wrap: balance; }
.s-einl .s-lead { max-width: 46ch; color: var(--s-text); text-wrap: pretty; }
.s-reiter { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 0 28px; border-bottom: 1px solid var(--s-linie); }
.s-reiter button { font: inherit; font-size: 1.0625rem; font-weight: 500; color: var(--s-leise); background: none; border: 0; border-bottom: 2px solid transparent; margin-bottom: -1px; min-height: 48px; padding: 0 2px; cursor: pointer; transition: color 150ms linear, border-color 150ms linear; }
.s-reiter button:hover { color: var(--s-text); }
.s-reiter button[aria-selected='true'] { color: var(--s-text); border-bottom-color: var(--s-text); }
.s-buehne { grid-column: 1 / -1; display: grid; grid-template-columns: minmax(0, 8fr) minmax(0, 4fr); gap: 24px clamp(28px, 4vw, 64px); }
.s-glas { position: relative; margin: 0; min-width: 0; }
.s-bild { position: relative; aspect-ratio: 4 / 3; overflow: hidden; border-radius: 3px; background: var(--s-sand); }   /* Klick aufs Bild schaltet mit, der Knopf ist der eigentliche Weg */
.s-bild svg { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.s-raum { opacity: 0; transition: opacity 1400ms var(--s-weich); }
.s-raum.an { opacity: 1; }
.s-ansehen { position: absolute; left: 16px; top: 16px; display: inline-flex; align-items: center; gap: 10px; min-height: 48px; padding: 0 20px 0 16px; border-radius: 3px; border: 1px solid var(--s-schiefer); background: rgba(244, 241, 235, .94); color: var(--s-text); font: 500 1rem/1 'Commissioner', sans-serif; font-variation-settings: 'FLAR' 0, 'VOLM' 0; cursor: pointer; transition: background-color 150ms linear, color 150ms linear; }
.s-ansehen:hover { background: #fff; }
.s-ansehen[aria-pressed='true'] { background: var(--s-schiefer); color: var(--s-kalk); }
.s-ansehen svg { width: 22px; height: 14px; flex: none; }
.s-ansehen .offen, .s-ansehen[aria-pressed='true'] .zu { display: none; }
.s-ansehen[aria-pressed='true'] .offen { display: block; }
.s-ansehen.drueckt { background: var(--s-sand); }
.s-fakten { display: grid; gap: 14px; align-content: start; }
.s-fakten h2 { font-size: clamp(1.75rem, 1.4rem + 1.4vw, 2.5rem); line-height: 1.15; }
.s-fakten > p { max-width: 62ch; text-wrap: pretty; }
.s-fakten dl { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 0; margin-top: 4px; border-top: 1px solid var(--s-linie); }
.s-fakten dt, .s-fakten dd { padding: 10px 0; border-bottom: 1px solid var(--s-linie); }
.s-fakten dt { color: var(--s-leise); font-size: 1rem; font-weight: 500; padding-right: 18px; }
.s-fakten dd { font-variant-numeric: tabular-nums; }
.s-hinweis { font-size: 1rem; color: var(--s-leise); }
.s-leiste { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 10px 24px; padding: 14px var(--s-rand) 22px; border-top: 1px solid var(--s-linie); }
.sw .mono { color: var(--s-leise); }
.sw .modi button { color: var(--s-leise); border-color: #BDB5A8; }
.sw .modi button:hover { color: var(--s-text); border-color: var(--s-text); }
.sw .modi button[aria-pressed='true'] { background: var(--s-schiefer); border-color: var(--s-schiefer); color: var(--s-kalk); }
.s-werte { display: flex; flex-wrap: wrap; gap: 4px 16px; }
.s-werte b { color: var(--s-text); font-weight: 500; }
/* reduzierte Bewegung: Glas sofort klar (Skript), Raumbilder 500 ms nur Deckkraft */
:root.reduziert .sw .s-raum { transition-duration: .5s !important; transition-timing-function: linear !important; }

@media (max-width: 1100px) { .s-nav { display: none; } .s-kopf .s-knopf { margin-left: auto; } }
@media (max-width: 860px) {
  .s-raeume, .s-einl, .s-buehne { grid-template-columns: minmax(0, 1fr); }
  .s-bild { aspect-ratio: 4 / 5; }
}
@media (max-width: 480px) {
  .sw { font-size: 1.1875rem; }
  .s-sofort { font-size: .9375rem; }
  .s-sofort .s-tel { font-size: 1.375rem; }
  .s-marke small { display: none; }
  .s-reiter { gap: 0 18px; }
  .s-ansehen { left: 10px; top: 10px; }
  .s-werte .lang { display: none; }
}
@media (max-width: 360px) { .s-kopf .s-knopf { padding: 0 14px; } .s-reiter button { font-size: 1rem; } .s-fakten dl { grid-template-columns: minmax(0, 1fr); } .s-fakten dt { border-bottom: 0; padding-bottom: 0; } }
@media (hover: none) { .sw .modi button { height: 36px; padding: 0 14px; } }
.vorschau .sw .s-nav, .vorschau .sw .s-leiste, .vorschau .sw .s-lead, .vorschau .sw .s-fakten dl, .vorschau .sw .s-hinweis, .vorschau .sw .s-kopf { display: none; }
.vorschau .sw .s-main { padding-top: 22px; }
.vorschau .sw .s-bild { aspect-ratio: auto; height: calc(100vh - 290px); }
.vorschau .sw .s-einl h1 { font-size: 2.5rem; }
</style>
</head>
<body>
<section class="buehne" aria-labelledby="titel" data-welt="basis">
  <header class="kopf">
    <a class="marke" href="#titel"><b>Vellmar Rothe</b><span>Rechtsanwälte · Steuerberater</span></a>
    <nav aria-label="Hauptmenü"><a href="#gebiete">Leistungen</a><a href="#gebiete">Köpfe</a><a href="#insights">Insights</a><a href="#kontakt">Kontakt</a></nav>
    <a class="portal" href="#kontakt">Mandantenportal</a>
    <button class="menue" type="button" aria-expanded="false">Menü</button>
  </header>

  <div class="held">
    <div class="text">
      <div class="oben">
        <p class="ort"><span>Erfurt</span><i></i><span>Futterstraße 9</span></p>
        <h1 id="titel">Was hinter dieser Scheibe gesagt wird, <em>bleibt hier.</em></h1>
      </div>
      <div class="mitte">
        <p class="lead">Wir beraten Familienunternehmen in Thüringen bei Nachfolge, Gesellschaftsrecht und Steuern. Ein festes Team pro Mandat, Termine auch am Abend.</p>
        <div class="aktionen">
          <a class="knopf" href="#kontakt">Erstgespräch vereinbaren <svg width="16" height="10" viewBox="0 0 16 10" aria-hidden="true"><path d="M0 5h14M10 1l4 4-4 4" fill="none" stroke="currentColor" stroke-width="1.4"/></svg></a>
          <a class="tel" href="tel:+4936155402">0361 55 40 20<span>Montag bis Freitag, 8 bis 19 Uhr</span></a>
        </div>
      </div>
      <dl class="fakten">
        <div><dt>Gegründet</dt><dd>1994</dd></div>
        <div><dt>Berufsträger</dt><dd>14</dd></div>
        <div><dt>Standorte</dt><dd>Erfurt, Weimar</dd></div>
      </dl>
    </div>

    <figure class="scheibe">
      <div class="glas" data-glas role="img" aria-label="Besprechungsraum hinter Riffelglas: zwei Personen am Tisch unter einer Pendelleuchte, nur als Umrisse erkennbar.">
        <canvas class="gl" data-gl aria-hidden="true"></canvas>
        <div class="riffel-css" aria-hidden="true"></div>
        <div class="rahmen" aria-hidden="true"></div>
        <p class="schild" aria-hidden="true"><b data-schild>2.07 · Besprechung</b><span data-belegt>Belegt bis 15:30</span></p>
      </div>
      <figcaption class="unterzeile">
        <span class="nr" data-nr>01 / 03</span>
        <span class="raum" data-raum>Besprechungsraum <span>· 2. OG</span></span>
        <span class="szenen" role="group" aria-label="Raum wählen">
          <button type="button" data-szene="0" aria-current="true" aria-label="Besprechungsraum"><i></i></button>
          <button type="button" data-szene="1" aria-current="false" aria-label="Blick auf den Domplatz"><i></i></button>
          <button type="button" data-szene="2" aria-current="false" aria-label="Mandantenarchiv"><i></i></button>
        </span>
        <button class="anhalten" type="button" data-anhalten aria-pressed="false" aria-label="Bewegung anhalten" title="Bewegung anhalten"><svg class="pause" viewBox="0 0 10 10" aria-hidden="true"><rect x="1.5" y="1" width="2.4" height="8" rx=".6"/><rect x="6.1" y="1" width="2.4" height="8" rx=".6"/></svg><svg class="abspielen" viewBox="0 0 10 10" aria-hidden="true"><path d="M2.5 1.2v7.6L9 5z"/></svg></button>
      </figcaption>
    </figure>
  </div>

  <div class="fuss">
    <div class="pult-gruppen">
    <div class="modi fassung mono" role="group" aria-label="Variante der Demo">
      <span>Variante</span>
      <button type="button" data-variante="basis" aria-pressed="true">WebGL</button>
      <button type="button" data-variante="still" aria-pressed="false">Still</button>
    </div>
    <div class="modi mono" role="group" aria-label="Riffelung wählen">
      <span>Riffelung</span>
      <button type="button" data-modus="vertikal" aria-pressed="true">Vertikal</button>
      <button type="button" data-modus="diagonal" aria-pressed="false">Diagonal</button>
      <button type="button" data-modus="fenster" aria-pressed="false">Fenster</button>
    </div>
    </div>
    <p class="werte mono" data-werte></p>
  </div>
</section>

<section class="gebiete" id="gebiete" aria-labelledby="gebiete-titel" data-welt="basis">
  <div class="einl">
    <h2 id="gebiete-titel">Drei Schwerpunkte, ein Team.</h2>
    <p>Recht und Steuern greifen bei Familienunternehmen ineinander. Deshalb sitzen Anwältinnen und Steuerberater bei uns am selben Tisch.</p>
  </div>
  <ol>
    <li><h3>Unternehmensnachfolge</h3><p>Testament, Gesellschaftsvertrag und Schenkungsteuer in einem Plan. Wir begleiten die Übergabe über mehrere Jahre.</p><a href="#gebiete">Zur Nachfolge</a></li>
    <li><h3>Gesellschaftsrecht</h3><p>Gründung, Umwandlung, Gesellschafterstreit. Wir verhandeln lieber am Tisch als vor Gericht, können aber beides.</p><a href="#gebiete">Zum Gesellschaftsrecht</a></li>
    <li><h3>Steuerberatung</h3><p>Jahresabschluss, Lohn und Betriebsprüfung für Unternehmen bis 250 Beschäftigte. Belege laden Sie im Portal hoch.</p><a href="#gebiete">Zur Steuerberatung</a></li>
  </ol>
</section>

<section class="insights" id="insights" aria-labelledby="insights-titel" data-welt="basis">
  <h2 id="insights-titel">Insights</h2>
  <ul>
    <li><a href="#insights"><time datetime="2026-10-22">22.10.2026</time><span class="rubrik">Nachfolge</span><strong>Familienpool statt Einzelschenkung: was die neue Rechtsprechung ändert</strong></a></li>
    <li><a href="#insights"><time datetime="2026-10-08">08.10.2026</time><span class="rubrik">Steuern</span><strong>Fristen zum Jahresende: acht Termine für Thüringer Unternehmen</strong></a></li>
    <li><a href="#insights"><time datetime="2026-09-17">17.09.2026</time><span class="rubrik">Gesellschaftsrecht</span><strong>Die Familien-GmbH & Co. KG im Generationswechsel</strong></a></li>
  </ul>
</section>

<footer class="fussleiste" id="kontakt" data-welt="basis">
  <span>Vellmar Rothe Partnerschaft mbB · Futterstraße 9, 99084 Erfurt · Marktstraße 4, 99423 Weimar</span>
  <span>Rechtsanwaltskammer Thüringen · Steuerberaterkammer Thüringen</span>
</footer>

<!-- ===================== Variante „still“: Bestattungshaus, Riffelglas vor den Abschiedsräumen ===================== -->
<div class="sw" data-welt="still" hidden>
  <div class="s-sofort"><span>Im Trauerfall: Tag und Nacht erreichbar</span><a class="s-tel" href="tel:+495641774800">05641 77 48 0</a></div>
  <header class="s-kopf">
    <a class="s-marke" href="#s-titel"><b>Wehrend</b><small>Bestattungshaus in Warburg</small></a>
    <nav class="s-nav" aria-label="Hauptmenü"><a href="#s-titel">Im Trauerfall</a><a href="#s-titel">Bestattungsarten</a><a href="#s-titel">Kosten</a><a href="#s-titel">Vorsorge</a><a href="#s-titel" aria-current="page">Räume</a></nav>
    <a class="s-knopf" href="tel:+495641774800">Anrufen</a>
  </header>

  <main class="s-main">
    <section class="s-raeume" aria-labelledby="s-titel">
      <div class="s-einl">
        <p class="s-rubrik">Abschied nehmen</p>
        <h1 class="s-serif" id="s-titel">Unsere Räume</h1>
        <p class="s-lead">Sie können sich bei uns in Ruhe verabschieden, so lange Sie möchten. Vor jedem Foto liegt Glas. Sehen Sie hinein, wenn Sie bereit sind.</p>
      </div>
      <div class="s-reiter" role="tablist" aria-label="Raum wählen" data-s-reiter>
        <button type="button" role="tab" id="s-tab-0" aria-controls="s-panel" aria-selected="true" tabindex="0">Abschiedsraum</button>
        <button type="button" role="tab" id="s-tab-1" aria-controls="s-panel" aria-selected="false" tabindex="-1">Feierhalle</button>
        <button type="button" role="tab" id="s-tab-2" aria-controls="s-panel" aria-selected="false" tabindex="-1">Garten</button>
        <button type="button" role="tab" id="s-tab-3" aria-controls="s-panel" aria-selected="false" tabindex="-1">Beratungszimmer</button>
      </div>

      <div class="s-buehne" role="tabpanel" id="s-panel" aria-labelledby="s-tab-0" data-s-panel>
        <figure class="s-glas">
          <div class="s-bild" role="img" data-s-bild aria-label="Abschiedsraum hinter Riffelglas">
            <svg viewBox="0 0 1200 900" preserveAspectRatio="xMidYMid slice" aria-hidden="true" focusable="false" data-s-svg>
              <defs>
                <filter id="s-riffel" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse" x="0" y="0" width="1200" height="900" color-interpolation-filters="sRGB">
                  <feGaussianBlur in="SourceGraphic" stdDeviation="3.2" result="weich" data-s-unschaerfe/>
                  <feImage x="0" y="0" width="1200" height="900" preserveAspectRatio="none" result="karte" data-s-karte/>
                  <feDisplacementMap in="weich" in2="karte" scale="46" xChannelSelector="R" yChannelSelector="G" data-s-versatz/>
                </filter>
                <linearGradient id="s-rippe" x1="0" y1="0" x2="1" y2="0">
                  <stop offset="0" stop-color="#fff" stop-opacity=".34"/><stop offset=".12" stop-color="#fff" stop-opacity=".08"/>
                  <stop offset=".55" stop-color="#fff" stop-opacity="0"/><stop offset=".88" stop-color="#000" stop-opacity=".06"/><stop offset="1" stop-color="#000" stop-opacity=".2"/>
                </linearGradient>
                <pattern id="s-riffelmuster" patternUnits="userSpaceOnUse" x="0" y="0" width="25" height="900" data-s-muster>
                  <rect width="25" height="900" fill="url(#s-rippe)" data-s-musterflaeche/>
                </pattern>
                <linearGradient id="s-licht" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#fff6e2" stop-opacity=".6"/><stop offset="1" stop-color="#fff6e2" stop-opacity="0"/></linearGradient>
                <linearGradient id="s-himmel" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#d9e2df"/><stop offset="1" stop-color="#eef0e9"/></linearGradient>
              </defs>
              <g transform="translate(600 450) scale(1.04) translate(-600 -450)"><!-- 4 % größer: die Ränder des Versatzes liegen außerhalb des Bildes -->
              <g data-s-raeume filter="url(#s-riffel)">
                <!-- 0 Abschiedsraum: Tageslicht, Bank, Beistelltisch mit Gräsern -->
                <g class="s-raum an" data-s-raum="0">
                  <rect width="1200" height="900" fill="#e6ded1"/>
                  <rect width="1200" height="64" fill="#efe9de"/>
                  <rect x="680" y="120" width="420" height="500" fill="url(#s-himmel)"/>
                  <g fill="#a7b89b"><circle cx="740" cy="430" r="96"/><circle cx="880" cy="390" r="126"/><circle cx="1040" cy="440" r="104"/></g>
                  <rect x="680" y="480" width="420" height="140" fill="#8fa584"/>
                  <g fill="none" stroke="#f6f2ea" stroke-width="16"><rect x="680" y="120" width="420" height="500"/><path d="M890 120 V620 M680 340 H1100"/></g>
                  <path d="M600 100 C590 300 612 500 596 640 L660 640 C650 480 668 300 652 100 Z" fill="#efe7da"/>
                  <path d="M612 120 C606 300 622 480 612 630 M632 110 C630 320 640 500 634 636" fill="none" stroke="#dcd1bf" stroke-width="3"/>
                  <rect y="628" width="1200" height="14" fill="#d6cbb9"/>
                  <rect y="642" width="1200" height="258" fill="#bf996b"/>
                  <g stroke="#ad875b" stroke-width="2"><path d="M0 700 H1200 M0 770 H1200 M0 850 H1200"/></g>
                  <path d="M690 642 L1100 642 L860 900 L280 900 Z" fill="url(#s-licht)" opacity=".7"/>
                  <rect x="150" y="220" width="250" height="180" fill="#d5cab7"/><rect x="168" y="238" width="214" height="144" fill="#c8bba4"/>
                  <path d="M168 340 C230 300 290 330 382 296 V382 H168 Z" fill="#b5a68c"/>
                  <rect x="100" y="560" width="400" height="44" rx="6" fill="#8a6a4c"/><rect x="100" y="540" width="400" height="26" rx="8" fill="#d9cebd"/>
                  <rect x="118" y="604" width="12" height="70" fill="#6e5238"/><rect x="470" y="604" width="12" height="70" fill="#6e5238"/>
                  <rect x="520" y="590" width="92" height="12" fill="#7d5d41"/><rect x="560" y="602" width="12" height="80" fill="#6e5238"/>
                  <path d="M548 590 C544 556 552 534 566 528 C580 534 588 556 584 590 Z" fill="#cfc3ae"/>
                  <g fill="none" stroke="#a69669" stroke-width="3" stroke-linecap="round"><path d="M566 530 C556 470 534 430 514 412 M566 530 C566 460 572 420 584 392 M566 530 C580 480 604 446 626 430 M566 530 C548 490 548 452 556 420"/></g>
                </g>
                <!-- 1 Feierhalle: hohe Fenster, Stuhlreihen, Rednerpult aus Holz -->
                <g class="s-raum" data-s-raum="1">
                  <rect width="1200" height="900" fill="#e3dacb"/>
                  <g fill="url(#s-himmel)"><rect x="180" y="90" width="170" height="350"/><rect x="515" y="90" width="170" height="350"/><rect x="850" y="90" width="170" height="350"/></g>
                  <g fill="none" stroke="#f3eee4" stroke-width="12"><rect x="180" y="90" width="170" height="350"/><rect x="515" y="90" width="170" height="350"/><rect x="850" y="90" width="170" height="350"/><path d="M265 90 V440 M600 90 V440 M935 90 V440"/></g>
                  <rect y="470" width="1200" height="200" fill="#b28c62"/>
                  <g stroke="#a07b52" stroke-width="3"><path d="M100 470 V670 M260 470 V670 M420 470 V670 M580 470 V670 M740 470 V670 M900 470 V670 M1060 470 V670"/></g>
                  <rect y="670" width="1200" height="230" fill="#a17c53"/>
                  <g fill="#fff6e2" opacity=".2"><path d="M180 440 L350 440 L300 900 L60 900 Z"/><path d="M515 440 L685 440 L680 900 L440 900 Z"/><path d="M850 440 L1020 440 L1080 900 L820 900 Z"/></g>
                  <path d="M560 520 L640 520 L630 680 L570 680 Z" fill="#7a573a"/><rect x="548" y="508" width="104" height="16" fill="#6a4a30"/>
                  <g fill="#f3efe6"><circle cx="700" cy="640" r="14"/><circle cx="722" cy="630" r="12"/><circle cx="716" cy="652" r="13"/><circle cx="740" cy="646" r="11"/></g>
                  <g fill="#6f8a5a"><ellipse cx="720" cy="668" rx="36" ry="10"/></g>
                  <rect x="700" y="668" width="40" height="22" fill="#cfc3ae"/>
                  <g data-s-stuehle fill="#6e5038"></g>
                </g>
                <!-- 2 Garten: Mauer, Ahorn, Kiesweg, Bank -->
                <g class="s-raum" data-s-raum="2">
                  <rect width="1200" height="900" fill="url(#s-himmel)"/>
                  <g fill="#b9c4ae"><circle cx="980" cy="330" r="120"/><circle cx="1120" cy="350" r="110"/><circle cx="860" cy="360" r="90"/></g>
                  <rect y="380" width="1200" height="180" fill="#cbbfa9"/>
                  <g stroke="#b6a98f" stroke-width="3"><path d="M0 425 H1200 M0 470 H1200 M0 515 H1200"/><path d="M80 380 V425 M240 380 V425 M400 380 V425 M560 380 V425 M720 380 V425 M880 380 V425 M1040 380 V425 M160 425 V470 M320 425 V470 M480 425 V470 M640 425 V470 M800 425 V470 M960 425 V470 M1120 425 V470 M80 470 V515 M240 470 V515 M400 470 V515 M560 470 V515 M720 470 V515 M880 470 V515 M1040 470 V515"/></g>
                  <rect y="372" width="1200" height="12" fill="#dcd2bf"/>
                  <rect y="560" width="1200" height="340" fill="#9db27b"/>
                  <path d="M520 560 C560 640 420 720 300 900 L700 900 C720 760 640 660 600 560 Z" fill="#ddd0b6"/>
                  <ellipse cx="300" cy="640" rx="260" ry="40" fill="#6c8653" opacity=".4"/>
                  <path d="M286 640 C282 520 270 420 250 330 L300 330 C308 430 318 540 322 640 Z" fill="#6b5543"/>
                  <g fill="#87a067"><circle cx="270" cy="220" r="140"/><circle cx="150" cy="300" r="100"/><circle cx="400" cy="290" r="110"/></g>
                  <g fill="#9bb47a"><circle cx="230" cy="170" r="90"/><circle cx="350" cy="200" r="86"/></g>
                  <g fill="#6f8a5a"><rect x="760" y="520" width="440" height="70" rx="30"/></g>
                  <rect x="820" y="640" width="260" height="24" rx="4" fill="#8a6a4c"/><rect x="820" y="610" width="260" height="18" rx="4" fill="#8a6a4c"/>
                  <rect x="836" y="664" width="12" height="44" fill="#6e5238"/><rect x="1052" y="664" width="12" height="44" fill="#6e5238"/>
                  <ellipse cx="700" cy="660" rx="54" ry="16" fill="#b9ad98"/><ellipse cx="700" cy="654" rx="44" ry="10" fill="#a9bcc0"/>
                </g>
                <!-- 3 Beratungszimmer: runder Tisch, Regal, Leuchte -->
                <g class="s-raum" data-s-raum="3">
                  <rect width="1200" height="900" fill="#ebe5da"/>
                  <rect x="80" y="140" width="320" height="480" fill="#d9cfbf"/>
                  <g data-s-regal></g>
                  <rect x="820" y="160" width="280" height="360" fill="url(#s-himmel)"/>
                  <g fill="#a7b89b"><circle cx="900" cy="420" r="70"/><circle cx="1020" cy="400" r="90"/></g>
                  <g fill="none" stroke="#f6f2ea" stroke-width="14"><rect x="820" y="160" width="280" height="360"/><path d="M960 160 V520"/></g>
                  <rect y="640" width="1200" height="260" fill="#b9946a"/>
                  <path d="M620 0 V250" stroke="#4a443c" stroke-width="3"/>
                  <path d="M560 300 C566 262 590 250 620 250 C650 250 674 262 680 300 Z" fill="#3f5b48"/>
                  <ellipse cx="620" cy="302" rx="62" ry="8" fill="#fff3d6"/>
                  <path d="M520 302 L720 302 L820 600 L420 600 Z" fill="#fff3d6" opacity=".18"/>
                  <ellipse cx="620" cy="600" rx="220" ry="44" fill="#9b7553"/><rect x="606" y="600" width="28" height="140" fill="#7d5d41"/>
                  <ellipse cx="620" cy="742" rx="90" ry="14" fill="#7d5d41"/>
                  <rect x="566" y="560" width="22" height="34" rx="4" fill="#f2efe8" opacity=".9"/><rect x="600" y="566" width="16" height="28" rx="3" fill="#f2efe8" opacity=".8"/><rect x="660" y="572" width="70" height="10" fill="#efe6d4"/>
                  <g fill="#5b4632"><path d="M360 560 h90 v40 h-16 v160 h-12 v-160 h-34 v160 h-12 v-160 h-16 Z"/><path d="M790 560 h90 v40 h-16 v160 h-12 v-160 h-34 v160 h-12 v-160 h-16 Z"/></g>
                  <g fill="#6b5640"><rect x="352" y="470" width="100" height="94" rx="10"/><rect x="782" y="470" width="100" height="94" rx="10"/></g>
                  <rect x="1080" y="560" width="70" height="90" fill="#cfc3ae"/><g fill="#6c8653"><ellipse cx="1100" cy="520" rx="30" ry="56"/><ellipse cx="1130" cy="500" rx="26" ry="62"/><ellipse cx="1112" cy="470" rx="20" ry="50"/></g>
                </g>
              </g>
              </g>
              <g data-s-glasschicht>
                <rect width="1200" height="900" fill="url(#s-riffelmuster)"/>
                <rect width="1200" height="900" fill="#f4f1eb" opacity=".12" data-s-milch/>
              </g>
            </svg>
          </div>
          <button class="s-ansehen" type="button" aria-pressed="false" data-s-ansehen>
            <svg class="offen" viewBox="0 0 22 14" aria-hidden="true"><path d="M1 7c3-4 6.5-6 10-6s7 2 10 6c-3 4-6.5 6-10 6S4 11 1 7Z" fill="none" stroke="currentColor" stroke-width="1.5"/><circle cx="11" cy="7" r="3" fill="currentColor"/></svg>
            <svg class="zu" viewBox="0 0 22 14" aria-hidden="true"><path d="M1 4c3 4 6.5 6 10 6s7-2 10-6" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M4 7.5 2.5 10M8 9.4 7.4 12.3M14 9.4l.6 2.9M18 7.5l1.5 2.5" stroke="currentColor" stroke-width="1.5"/></svg>
            Raum ansehen
          </button>
        </figure>
        <div class="s-fakten">
          <h2 class="s-serif" data-s-name>Abschiedsraum</h2>
          <p data-s-text></p>
          <dl data-s-dl></dl>
          <p class="s-hinweis">Das Glas wird klar, wenn Sie „Raum ansehen“ drücken. Ein zweiter Druck macht es wieder trüb.</p>
        </div>
      </div>
    </section>
  </main>

  <div class="s-leiste">
    <div class="modi fassung mono" role="group" aria-label="Variante der Demo"><span>Variante</span>
      <button type="button" data-variante="basis" aria-pressed="false">WebGL</button>
      <button type="button" data-variante="still" aria-pressed="true">Still</button>
    </div>
    <p class="s-werte mono" data-s-werte></p>
  </div>
</div>

<div class="zeiger" data-zeiger aria-hidden="true"></div>
<p class="nur-sr" aria-live="polite" data-ansage></p>

<script type="importmap">{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.186.0/build/three.module.js"}}</script>
<script type="module">
/* Zwei Varianten: „basis“ (three.js, Drift) lädt three erst bei Bedarf; „still“ (SVG-Filter) kommt ganz ohne three.js aus */
const DEMOS = {};
let basisLaedt = null;
const ladeBasis = () => basisLaedt ??= import('three').then(THREE => (DEMOS.basis = basis(THREE)));

function basis(THREE) {

const html = document.documentElement;
const reduziert = () => html.classList.contains('reduziert');
const beruehrung = matchMedia('(hover: none)').matches;
const $ = s => document.querySelector(s);
const glas = $('[data-glas]'), canvas = $('[data-gl]'), zeiger = $('[data-zeiger]'), ansage = $('[data-ansage]'), werteEl = $('[data-werte]');
const knoepfe = [...document.querySelectorAll('[data-modus]')];
const szenenKnoepfe = [...document.querySelectorAll('[data-szene]')];

/* ---------- Werte (meta.json → messwerte, Function Health „Flutie“) ---------- */
const W = {
  frequenzDesktop: 82.8, frequenzMobil: 64.8,   // Riffel pro Viewport-Breite
  amplitude: 0.0255,                            // Linsenversatz in uv
  aberration: 0.04,                             // Kanaltrennung, Anteil am Versatz
  kanteBreite: 0.195, kanteStaerke: 0.0095,     // Fase am Riffelrand
  licht: 0.02, schatten: 0.02,                  // Kantenlicht / -schatten
  winkelDesktop: 0.36, winkelMobil: -0.11,      // Diagonale in rad
  drift: 0.543,                                 // × 0.01 uv/s, linear
  zoom: 1.08,                                   // Ken Burns 1.08 → 1.00 je Raum
  raum: 10000,                                  // ms je Raum (Beleg: max. 30 s)
  wechsel: 1200,                                // ms Überblendung, linear
  zeiger: 0.05, lerp: 0.06,                     // Zeiger-Versatz uv, Nachlauf pro Frame
  scroll: 0.04,                                 // Scroll-Versatz uv über die Bühnenhöhe
  milch: 0.06,                                  // leichte Trübung des Glases
  fenster: 0.4,                                 // Fenster-Variante: Glas ab 40 % der Breite
};
const RAEUME = [
  { name: 'Besprechungsraum', zusatz: '· 2. OG', schild: '2.07 · Besprechung', belegt: 'Belegt bis 15:30', alt: 'Besprechungsraum hinter Riffelglas: zwei Personen am Tisch unter einer Pendelleuchte, nur als Umrisse erkennbar.' },
  { name: 'Blick auf den Domplatz', zusatz: '· Büro 3.02', schild: '3.02 · Dr. Vellmar', belegt: 'Frei ab 17:00', alt: 'Hinter Riffelglas der angestrahlte Erfurter Dom und St. Severi am Abend, in senkrechte Streifen gebrochen.' },
  { name: 'Mandantenarchiv', zusatz: '· UG, verschlossen', schild: 'U.04 · Archiv', belegt: 'Nur mit Schlüssel', alt: 'Regal mit farbigen Aktenordnern hinter Riffelglas, die Beschriftungen sind nicht lesbar.' },
];
const MODI = {
  vertikal: { winkel: 0, split: 0 },
  diagonal: { winkel: null, split: 0 },
  fenster: { winkel: 0, split: W.fenster },
};
let modus = 'vertikal';
const mobil = () => innerWidth <= 760;
const frequenz = () => mobil() ? W.frequenzMobil : W.frequenzDesktop;
const winkel = () => MODI[modus].winkel ?? (mobil() ? W.winkelMobil : W.winkelDesktop);

/* ---------- Räume: eigene Bildwelt, auf Canvas gemalt (1200 × 1200) ---------- */
const G = 1200;
function leinwand() { const c = document.createElement('canvas'); c.width = c.height = G; return [c, c.getContext('2d')]; }
function zufall(s) { return () => (s = (s * 16807) % 2147483647) / 2147483647; }
function verlauf(x, x0, y0, x1, y1, stopps) { const g = x.createLinearGradient(x0, y0, x1, y1); stopps.forEach(([o, c]) => g.addColorStop(o, c)); return g; }
function glut(x, cx, cy, r, farbe, a) { const g = x.createRadialGradient(cx, cy, 0, cx, cy, r); g.addColorStop(0, `rgba(${farbe},${a})`); g.addColorStop(1, `rgba(${farbe},0)`); x.fillStyle = g; x.fillRect(cx - r, cy - r, r * 2, r * 2); }

function malBesprechung() {
  const [c, x] = leinwand();
  x.fillStyle = verlauf(x, 0, 0, 0, G, [[0, '#262660'], [.7, '#16163c'], [1, '#0c0c22']]); x.fillRect(0, 0, G, G);
  /* Fenster hinten rechts: Abendhimmel, Dächer */
  x.fillStyle = verlauf(x, 0, 170, 0, 760, [[0, '#2d3376'], [.55, '#8a76a4'], [1, '#e8a985']]); x.fillRect(690, 170, 400, 590);
  const r = zufall(7);
  x.fillStyle = '#1b1838';
  for (let i = 0; i < 9; i++) { const w = 40 + r() * 50, h = 60 + r() * 140; x.fillRect(690 + i * 46, 760 - h, w, h); }
  x.fillStyle = '#ffcf87';
  for (let i = 0; i < 26; i++) x.fillRect(700 + r() * 380, 640 + r() * 110, 6, 8);
  x.fillStyle = '#0e0e26'; x.fillRect(682, 162, 416, 16); x.fillRect(682, 752, 416, 16); x.fillRect(682, 162, 16, 606); x.fillRect(1082, 162, 16, 606); x.fillRect(882, 162, 12, 606);
  /* Licht der Pendelleuchte */
  glut(x, 420, 400, 620, '255,186,104', .5);
  glut(x, 420, 380, 220, '255,214,150', .55);
  x.strokeStyle = '#0a0a1e'; x.lineWidth = 4; x.beginPath(); x.moveTo(420, 0); x.lineTo(420, 330); x.stroke();
  x.fillStyle = verlauf(x, 300, 0, 540, 0, [[0, '#c98a45'], [.5, '#f7d59c'], [1, '#c4803e']]);
  x.beginPath(); x.moveTo(320, 410); x.quadraticCurveTo(330, 330, 420, 326); x.quadraticCurveTo(510, 330, 520, 410); x.closePath(); x.fill();
  x.fillStyle = '#fff3d6'; x.beginPath(); x.ellipse(420, 410, 100, 12, 0, 0, Math.PI * 2); x.fill();
  /* Person rechts vor dem Fenster (Gegenlicht) */
  x.fillStyle = '#0a0a1d';
  x.beginPath(); x.roundRect(760, 600, 250, 300, 60); x.fill();
  x.beginPath(); x.arc(885, 548, 64, 0, Math.PI * 2); x.fill();
  /* Person links, Streiflicht der Lampe */
  x.fillStyle = '#121230';
  x.beginPath(); x.roundRect(110, 640, 270, 290, 70); x.fill();
  x.beginPath(); x.arc(240, 580, 66, 0, Math.PI * 2); x.fill();
  x.strokeStyle = 'rgba(255,190,120,.75)'; x.lineWidth = 7;
  x.beginPath(); x.arc(240, 580, 63, -1.2, .5); x.stroke();
  x.beginPath(); x.moveTo(372, 700); x.quadraticCurveTo(380, 660, 340, 646); x.stroke();
  /* Tisch: Nussbaum mit Lichtreflex, Papiere, Tasse */
  x.fillStyle = verlauf(x, 0, 800, 0, 920, [[0, '#7a4e2c'], [1, '#3b2414']]);
  x.beginPath(); x.moveTo(40, 800); x.lineTo(1160, 800); x.lineTo(1200, 910); x.lineTo(0, 910); x.closePath(); x.fill();
  glut(x, 430, 840, 300, '255,214,160', .45);
  x.fillStyle = '#24150b'; x.fillRect(0, 910, G, 34);
  x.fillStyle = '#efe6d6';
  x.save(); x.translate(560, 840); x.rotate(-.08); x.fillRect(0, 0, 150, 40); x.restore();
  x.save(); x.translate(250, 850); x.rotate(.06); x.fillRect(0, 0, 130, 36); x.restore();
  x.fillStyle = '#f4f1ea'; x.fillRect(930, 812, 40, 38);
  /* Pflanze links */
  x.fillStyle = '#1f3d31';
  for (let i = 0; i < 9; i++) { x.save(); x.translate(40, 790); x.rotate(-1.9 + i * .26); x.beginPath(); x.ellipse(0, -150, 34, 150, 0, 0, Math.PI * 2); x.fill(); x.restore(); }
  x.fillStyle = '#08081a'; x.fillRect(0, 944, G, 256);
  /* Stuhllehnen im Vordergrund */
  [[150, 330], [700, 360]].forEach(([sx, sw]) => {
    x.fillStyle = '#05050f'; x.beginPath(); x.roundRect(sx, 960, sw, 300, 46); x.fill();
    x.strokeStyle = 'rgba(255,196,130,.32)'; x.lineWidth = 5; x.beginPath(); x.moveTo(sx + 40, 963); x.lineTo(sx + sw - 40, 963); x.stroke();
  });
  return c;
}

function malDom() {
  const [c, x] = leinwand();
  x.fillStyle = verlauf(x, 0, 0, 0, 820, [[0, '#101744'], [.6, '#26357a'], [1, '#4b5694']]); x.fillRect(0, 0, G, G);
  const r = zufall(3);
  x.fillStyle = 'rgba(255,255,255,.6)';
  for (let i = 0; i < 16; i++) x.fillRect(r() * G, r() * 300, 2, 2);
  /* Domberg */
  x.fillStyle = '#2a211e'; x.beginPath(); x.moveTo(0, 850); x.lineTo(160, 768); x.lineTo(1080, 768); x.lineTo(1200, 830); x.lineTo(1200, 1200); x.lineTo(0, 1200); x.fill();
  glut(x, 620, 700, 660, '255,168,84', .3);
  /* Flutlicht von unten: Stein unten hell, nach oben dunkler; Lichtseite links, Schattenseite rechts */
  const hell = (y0, y1) => verlauf(x, 0, y1, 0, y0, [[0, '#f6d096'], [.55, '#d9a161'], [1, '#8a5a32']]);
  const dunkel = (y0, y1) => verlauf(x, 0, y1, 0, y0, [[0, '#c48a50'], [.55, '#9d6a3a'], [1, '#5e3c22']]);
  const fenster = (fx, fy, fw, fh) => { x.fillStyle = '#3b2616'; x.beginPath(); x.moveTo(fx, fy + fh); x.lineTo(fx, fy + fw * .6); x.quadraticCurveTo(fx + fw / 2, fy - fw * .3, fx + fw, fy + fw * .6); x.lineTo(fx + fw, fy + fh); x.fill();
    x.fillStyle = 'rgba(255,210,140,.22)'; x.fillRect(fx + fw / 2 - 1.5, fy + fw * .4, 3, fh - fw * .4); };
  function turm(tx, tw, yHelm, ySpitze, yFuss) {
    x.fillStyle = hell(yHelm, yFuss); x.fillRect(tx, yHelm, tw / 2, yFuss - yHelm);
    x.fillStyle = dunkel(yHelm, yFuss); x.fillRect(tx + tw / 2, yHelm, tw / 2, yFuss - yHelm);
    x.fillStyle = hell(ySpitze, yHelm + 10); x.beginPath(); x.moveTo(tx - 4, yHelm + 4); x.lineTo(tx + tw / 2, ySpitze); x.lineTo(tx + tw / 2, yHelm + 4); x.fill();
    x.fillStyle = dunkel(ySpitze, yHelm + 10); x.beginPath(); x.moveTo(tx + tw / 2, ySpitze); x.lineTo(tx + tw + 4, yHelm + 4); x.lineTo(tx + tw / 2, yHelm + 4); x.fill();
    x.fillStyle = '#f9dcaa'; x.fillRect(tx - 6, yHelm, tw + 12, 5);
    /* Ecktürmchen am Helmfuß */
    [tx - 2, tx + tw - 6].forEach(ex => { x.fillStyle = '#e2b073'; x.beginPath(); x.moveTo(ex, yHelm); x.lineTo(ex + 4, yHelm - 34); x.lineTo(ex + 8, yHelm); x.fill(); });
  }
  /* Dom: Hochchor mit Strebepfeilern und hohen Maßwerkfenstern */
  const cy0 = 520, cy1 = 768;
  x.fillStyle = hell(cy0, cy1); x.fillRect(230, cy0, 420, cy1 - cy0);
  for (let i = 0; i < 6; i++) fenster(252 + i * 66, cy0 + 40, 38, 190);
  for (let i = 0; i <= 6; i++) { const sx = 238 + i * 66 - 10; x.fillStyle = dunkel(cy0 - 30, cy1); x.fillRect(sx, cy0 - 20, 12, cy1 - cy0 + 20); x.fillStyle = '#efc386'; x.beginPath(); x.moveTo(sx - 2, cy0 - 20); x.lineTo(sx + 6, cy0 - 62); x.lineTo(sx + 14, cy0 - 20); x.fill(); }
  x.fillStyle = verlauf(x, 0, 430, 0, 524, [[0, '#3a2b2c'], [1, '#5a4236']]); x.beginPath(); x.moveTo(226, 522); x.lineTo(440, 436); x.lineTo(654, 522); x.fill();
  x.fillStyle = 'rgba(255,214,150,.35)'; x.fillRect(226, 518, 428, 4);
  /* Drei Türme: Mittelturm, zwei Flankentürme */
  turm(352, 58, 380, 250, 524);
  turm(530, 58, 380, 250, 524);
  turm(426, 92, 316, 120, 524);
  fenster(456, 340, 30, 90); fenster(366, 400, 26, 70); fenster(544, 400, 26, 70);
  /* St. Severi: Halle mit drei spitzen Helmen */
  const sy0 = 576;
  x.fillStyle = hell(sy0, cy1); x.fillRect(740, sy0, 300, cy1 - sy0);
  for (let i = 0; i < 4; i++) fenster(764 + i * 70, sy0 + 40, 34, 120);
  x.fillStyle = verlauf(x, 0, 500, 0, 580, [[0, '#3a2b2c'], [1, '#5a4236']]); x.beginPath(); x.moveTo(734, 578); x.lineTo(890, 506); x.lineTo(1046, 578); x.fill();
  turm(776, 46, 410, 300, 578);
  turm(948, 46, 410, 300, 578);
  turm(862, 56, 360, 196, 578);
  /* Domstufen als Fächer, Platz mit Laternen */
  for (let i = 0; i < 10; i++) { x.fillStyle = i % 2 ? '#7d5634' : '#b0814f'; x.fillRect(190 - i * 12, 774 + i * 13, 840 + i * 24, 6); }
  x.fillStyle = '#16121b'; x.fillRect(0, 906, G, 300);
  for (let i = 0; i < 7; i++) { const lx = 80 + i * 170; glut(x, lx, 896, 80, '255,200,120', .5); x.fillStyle = '#ffe1a8'; x.fillRect(lx - 4, 888, 8, 10); x.fillStyle = '#0c0a10'; x.fillRect(lx - 2, 898, 4, 70); x.fillStyle = 'rgba(255,200,120,.16)'; x.fillRect(lx - 3, 980, 6, 110); }
  /* Fensterkreuz des Büros im Vordergrund */
  x.fillStyle = '#0d0d20'; x.fillRect(0, 0, 34, G); x.fillRect(1166, 0, 34, G); x.fillRect(0, 0, G, 30); x.fillRect(588, 0, 24, G);
  return c;
}
function malArchiv() {
  const [c, x] = leinwand();
  x.fillStyle = '#231a14'; x.fillRect(0, 0, G, G);
  const farben = ['#1e1e50', '#7a2e2e', '#c08a3e', '#9a948c', '#3f5a4a', '#2f3f6e', '#d9cfbf', '#5b3d5e'];
  const r = zufall(11);
  for (let reihe = 0; reihe < 4; reihe++) {
    const boden = 290 + reihe * 300;
    let px = 30;
    while (px < G - 30) {
      const w = 54 + Math.floor(r() * 22), h = 214 + Math.floor(r() * 26), f = farben[Math.floor(r() * farben.length)];
      const vor = r() < .06 ? 10 : 0;
      x.fillStyle = f; x.fillRect(px, boden - h, w - 4, h);
      x.fillStyle = 'rgba(0,0,0,.22)'; x.fillRect(px + w - 12, boden - h, 8, h);
      x.fillStyle = '#f4efe4'; x.fillRect(px + 9, boden - h + 22 - vor, w - 22, 62);
      x.fillStyle = 'rgba(30,30,80,.55)'; x.fillRect(px + 14, boden - h + 34, w - 32, 4); x.fillRect(px + 14, boden - h + 46, w - 40, 4);
      x.fillStyle = 'rgba(0,0,0,.5)'; x.beginPath(); x.arc(px + (w - 4) / 2, boden - 52, 11, 0, Math.PI * 2); x.fill();
      px += w;
    }
    x.fillStyle = '#6b4f37'; x.fillRect(0, boden, G, 20);
    x.fillStyle = '#a7835c'; x.fillRect(0, boden, G, 4);
    x.fillStyle = 'rgba(0,0,0,.45)'; x.fillRect(0, boden + 20, G, 16);
  }
  glut(x, 600, 120, 900, '255,206,140', .32);
  x.fillStyle = verlauf(x, 0, 0, G, 0, [[0, 'rgba(10,8,6,.55)'], [.25, 'rgba(10,8,6,0)'], [.75, 'rgba(10,8,6,0)'], [1, 'rgba(10,8,6,.55)']]); x.fillRect(0, 0, G, G);
  return c;
}
const bilder = [malBesprechung(), malDom(), malArchiv()];
/* Diffusion hinter dem Glas: verkleinert und wieder vergrößert (≈ 4 px Unschärfe, ohne ctx.filter) */
function weich(b) {
  const k = document.createElement('canvas'); k.width = k.height = G / 4;
  const kx = k.getContext('2d'); kx.imageSmoothingQuality = 'high'; kx.drawImage(b, 0, 0, G / 4, G / 4);
  const [c, x] = leinwand(); x.imageSmoothingQuality = 'high'; x.drawImage(k, 0, 0, G, G);
  return c;
}
const weichBilder = bilder.map(weich);

/* ---------- Shader: Riffel als Zylinderlinsen ---------- */
const vertex = /* glsl */`
varying vec2 vUv;
void main() { vUv = uv; gl_Position = vec4(position.xy, 0.0, 1.0); }`;

const fragment = /* glsl */`
precision highp float;
varying vec2 vUv;
uniform sampler2D uA, uB, uAw, uBw;
uniform float uMix, uZoomA, uZoomB, uPeriod, uAngle, uAmp, uCA, uBevelW, uBevelI, uLight, uShadow, uSplit, uMilk;
uniform vec2 uPlane, uShiftA, uShiftB;

vec2 cover(vec2 uv) {
  float rp = uPlane.x / uPlane.y;
  vec2 s = rp < 1.0 ? vec2(rp, 1.0) : vec2(1.0, 1.0 / rp);
  return (uv - 0.5) * s + 0.5;
}
vec2 lage(vec2 uv, float zoom, vec2 shift) { return clamp(cover((uv - 0.5) / zoom + 0.5) + shift, 0.001, 0.999); }
/* g = 1: hinter dem Glas (weiche Fassung), g = 0: klares Glas der Fenster-Variante */
vec3 szene(vec2 uv, float g) {
  vec2 pa = lage(uv, uZoomA, uShiftA);
  vec3 a = g > 0.5 ? texture2D(uAw, pa).rgb : texture2D(uA, pa).rgb;
  if (uMix <= 0.001) return a;
  vec2 pb = lage(uv, uZoomB, uShiftB);
  vec3 b = g > 0.5 ? texture2D(uBw, pb).rgb : texture2D(uB, pb).rgb;
  return mix(a, b, uMix);
}

void main() {
  vec2 uv = vUv;
  vec2 px = uv * uPlane;
  vec2 c = px - 0.5 * uPlane;
  vec2 n = vec2(cos(uAngle), sin(uAngle));          /* Normale quer zu den Riffeln */
  float s = dot(c, n) / uPeriod;
  float f = fract(s) - 0.5;                          /* -0.5 … 0.5 innerhalb eines Riffels */
  float kante = smoothstep(0.5 - uBevelW, 0.5, abs(f));
  float glas = uSplit > 0.0 ? step(uSplit, uv.x) : 1.0;

  /* Zylinderlinse: Versatz quer zum Riffel, an der Fase zusätzlich gebrochen */
  float lens = (-2.0 * f * uAmp + sign(f) * kante * uBevelI) * glas;
  vec2 d = n * lens * vec2(1.0, uPlane.x / uPlane.y);

  vec3 col;
  col.r = szene(uv + d * (1.0 + uCA), glas).r;
  col.g = szene(uv + d, glas).g;
  col.b = szene(uv + d * (1.0 - uCA), glas).b;

  /* Rundung: Licht an der einen, Schatten an der anderen Fase, sanfte Wölbung dazwischen */
  float licht = kante * (f < 0.0 ? uLight : -uShadow) * 6.0;
  float woelbung = (0.5 - abs(f)) * 0.08 - 0.02;
  col += (licht + woelbung) * glas;
  col = mix(col, vec3(0.93, 0.93, 0.96), uMilk * glas);

  gl_FragColor = vec4(col, 1.0);
}`;

/* ---------- Renderer, robust ohne WebGL ---------- */
let renderer = null, szene3, kamera, material;
const probe = document.createElement('canvas');
if (probe.getContext('webgl2') || probe.getContext('webgl')) {
  try { renderer = new THREE.WebGLRenderer({ canvas, antialias: false, alpha: false, powerPreference: 'low-power' }); } catch (e) { renderer = null; }
}
function ohneGL() {
  html.classList.add('kein-gl');
  const ersatz = bilder[0]; ersatz.className = 'ersatz'; ersatz.setAttribute('aria-hidden', 'true');
  glas.prepend(ersatz);
}
const textur = b => { const t = new THREE.CanvasTexture(b); t.minFilter = THREE.LinearFilter; t.magFilter = THREE.LinearFilter; t.generateMipmaps = false; return t; };
const texturen = bilder.map(textur), weichTex = weichBilder.map(textur);
if (renderer) {
  szene3 = new THREE.Scene();
  kamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
  material = new THREE.ShaderMaterial({
    vertexShader: vertex, fragmentShader: fragment, depthTest: false, depthWrite: false,
    uniforms: {
      uA: { value: texturen[0] }, uB: { value: texturen[1] }, uAw: { value: weichTex[0] }, uBw: { value: weichTex[1] }, uMix: { value: 0 }, uZoomA: { value: 1 }, uZoomB: { value: 1 },
      uShiftA: { value: new THREE.Vector2() }, uShiftB: { value: new THREE.Vector2() }, uPlane: { value: new THREE.Vector2(1, 1) },
      uPeriod: { value: 17 }, uAngle: { value: 0 }, uAmp: { value: W.amplitude }, uCA: { value: W.aberration },
      uBevelW: { value: W.kanteBreite }, uBevelI: { value: W.kanteStaerke }, uLight: { value: W.licht }, uShadow: { value: W.schatten },
      uSplit: { value: 0 }, uMilk: { value: W.milch },
    },
  });
  const quad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material); quad.frustumCulled = false;
  szene3.add(quad);
  canvas.addEventListener('webglcontextlost', e => { e.preventDefault(); renderer = null; ohneGL(); });
} else ohneGL();

let schmutzig = true;
function groesse() {
  if (!renderer) return;
  const r = glas.getBoundingClientRect();
  renderer.setPixelRatio(Math.min(devicePixelRatio || 1, 1.5));
  renderer.setSize(Math.max(1, Math.round(r.width)), Math.max(1, Math.round(r.height)), false);
  material.uniforms.uPlane.value.set(r.width, r.height);
  material.uniforms.uPeriod.value = innerWidth / frequenz();
  material.uniforms.uAngle.value = winkel();
  werteZeigen(); schmutzig = true;
}
new ResizeObserver(groesse).observe(glas);

/* ---------- Zustand: Raum, Überblendung, Drift ---------- */
let raum = 0, naechster = -1, raumStart = performance.now(), wechselStart = 0, pausiert = false;
const zeigerZiel = new THREE.Vector2(), zeigerIst = new THREE.Vector2();
function zeigeRaum(i, ansagen) {
  const R = RAEUME[i];
  $('[data-nr]').textContent = `0${i + 1} / 03`;
  $('[data-raum]').innerHTML = `${R.name} <span>${R.zusatz}</span>`;
  $('[data-belegt]').textContent = R.belegt; $('[data-schild]').textContent = R.schild;
  glas.setAttribute('aria-label', R.alt);
  szenenKnoepfe.forEach((b, k) => b.setAttribute('aria-current', k === i ? 'true' : 'false'));
  if (html.classList.contains('kein-gl')) { const e = glas.querySelector('.ersatz'); if (e) { const n = bilder[i]; n.className = 'ersatz'; n.setAttribute('aria-hidden', 'true'); e.replaceWith(n); } }
  if (ansagen) ansage.textContent = `Raum: ${R.name}`;
}
function wechsleZu(i, sofort) {
  if (i === raum && naechster < 0) return;
  if (sofort || reduziert() || !renderer) { raum = i; naechster = -1; raumStart = performance.now(); zeigeRaum(i); schmutzig = true; return; }
  naechster = i; wechselStart = performance.now(); zeigeRaum(i);
}
szenenKnoepfe.forEach(b => b.addEventListener('click', () => { stopVorfuehrung(); wechsleZu(+b.dataset.szene); ansage.textContent = `Raum: ${RAEUME[+b.dataset.szene].name}`; }));

/* ---------- Varianten und Werte ---------- */
function werteZeigen() {
  const per = (innerWidth / frequenz()).toFixed(1);
  const w = winkel();
  const teile = [
    ['Riffel', `${frequenz()} / Viewport · ${per} px`],
    ['Winkel', `${w} rad`],
    ['Amplitude', `${W.amplitude} uv`],
    ['Aberration', `${W.aberration}`],
    ['Fase', `${W.kanteBreite} · ${W.kanteStaerke}`],
    ['Licht/Schatten', `${W.licht}`],
  ];
  if (modus === 'fenster') teile.splice(2, 0, ['Glas ab', '40 %']);
  if (reduziert()) teile.push(['Reduziert', 'Drift, Zoom, Zeiger aus'], ['Zoom', '1.04 fest'], ['Wechsel', 'nur per Klick, sofort']);
  else teile.push(['Drift', `${W.drift} linear`], ['Zoom', `${W.zoom} → 1 / ${W.raum / 1000} s`], ['Wechsel', `${W.wechsel} ms`]);
  werteEl.innerHTML = teile.map(([a, b]) => `<span>${a} <b>${b}</b></span>`).join('');
}
function setzeModus(m) {
  modus = m;
  knoepfe.forEach(b => b.setAttribute('aria-pressed', b.dataset.modus === m ? 'true' : 'false'));
  html.classList.toggle('fenster-an', m === 'fenster');
  if (material) { material.uniforms.uAngle.value = winkel(); material.uniforms.uSplit.value = MODI[m].split; }
  werteZeigen(); schmutzig = true;
}
knoepfe.forEach(b => b.addEventListener('click', () => { stopVorfuehrung(); setzeModus(b.dataset.modus); ansage.textContent = `Riffelung: ${b.textContent}`; }));
new MutationObserver(() => { werteZeigen(); schmutzig = true; }).observe(html, { attributes: true, attributeFilter: ['class'] });

/* ---------- Zeiger und Scroll verschieben das Bild hinter den Riffeln ---------- */
const zp = { x: 0, y: 0, da: false };
addEventListener('pointermove', e => {
  if (e.pointerType === 'touch') return;
  if (vorfuehrt) stopVorfuehrung();
  zp.x = e.clientX; zp.y = e.clientY; zp.da = true;
}, { passive: true });
document.addEventListener('pointerleave', () => { if (!vorfuehrt) zp.da = false; });
glas.addEventListener('click', e => { if (e.pointerType === 'touch' || beruehrung) { stopVorfuehrung(); wechsleZu((raum + 1) % 3); } });

let sichtbar = true;
new IntersectionObserver(([e]) => { sichtbar = e.isIntersecting; }).observe(glas);

/* ---------- Schleife ---------- */
let letzte = performance.now();
const lin = t => Math.max(0, Math.min(1, t));
function bild(jetzt) {
  requestAnimationFrame(bild);
  const dt = Math.min(64, jetzt - letzte) / 16.667; letzte = jetzt;
  if (pausiert) return;
  if (vorfuehrt) vorfuehrSchritt(jetzt);
  const ruhig = reduziert();
  const t = jetzt;

  /* Raumfolge: alle 10 s, Überblendung 1200 ms linear */
  if (!ruhig && renderer && naechster < 0 && t - raumStart > W.raum) { naechster = (raum + 1) % 3; wechselStart = jetzt; zeigeRaum(naechster); }
  let mixWert = 0;
  if (naechster >= 0) {
    mixWert = lin((jetzt - wechselStart) / W.wechsel);
    if (mixWert >= 1) { raum = naechster; naechster = -1; raumStart = t - W.wechsel; mixWert = 0; }
  }
  const fort = ruhig ? 0 : lin((t - raumStart) / W.raum);
  const knopf = szenenKnoepfe[naechster >= 0 ? naechster : raum];
  knopf.style.setProperty('--fortschritt', ruhig ? '100%' : (naechster >= 0 ? 0 : fort * 100).toFixed(1) + '%');

  if (!renderer || !sichtbar) return;
  const r = glas.getBoundingClientRect();
  /* Zeigerversatz (gelerpt) und Scrollversatz */
  if (zp.da && !ruhig && zp.x >= r.left && zp.x <= r.right && zp.y >= r.top && zp.y <= r.bottom) {
    zeigerZiel.set(((zp.x - r.left) / r.width - .5) * W.zeiger, -((zp.y - r.top) / r.height - .5) * W.zeiger * .4);
  } else zeigerZiel.set(0, 0);
  const a = 1 - Math.pow(1 - W.lerp, dt);
  zeigerIst.lerp(zeigerZiel, a);
  const sc = ruhig ? 0 : Math.max(-1, Math.min(1, -r.top / Math.max(1, r.height))) * W.scroll;

  const u = material.uniforms;
  const drift = s => ruhig ? 0 : (s - 5) * W.drift * 0.01;           // Sekunden seit Raumbeginn, linear
  const sA = (t - raumStart) / 1000, sB = (jetzt - wechselStart) / 1000;
  u.uA.value = texturen[raum]; u.uAw.value = weichTex[raum];
  u.uZoomA.value = ruhig ? 1.04 : W.zoom - (W.zoom - 1) * fort;
  u.uShiftA.value.set(drift(sA) + zeigerIst.x + sc, zeigerIst.y);
  u.uMix.value = mixWert;
  if (naechster >= 0) {
    u.uB.value = texturen[naechster]; u.uBw.value = weichTex[naechster];
    u.uZoomB.value = W.zoom;
    u.uShiftB.value.set(drift(sB) + zeigerIst.x + sc, zeigerIst.y);
  }
  if (ruhig && !schmutzig) return;
  renderer.render(szene3, kamera);
  schmutzig = false;
}

/* ---------- Vorführung: gedachter Zeiger gleitet über die Scheibe, Varianten wechseln ---------- */
const FOLGE = ['diagonal', 'fenster', 'vertikal'];
const SEGMENT = 6000;
let vorfuehrt = false, vStart = 0, vSeg = -1, vDrueck = -1;
function mitte(el) { const r = el.getBoundingClientRect(); return { x: r.left + r.width / 2, y: r.top + r.height / 2, r }; }
const glatt = t => t * t * (3 - 2 * t);
function bahn(t) {
  const { x, y, r } = mitte(glas), w = t * Math.PI * 2 * .2;
  return { x: x + Math.sin(w) * r.width * .36, y: y + Math.sin(w * 2 + .6) * r.height * .22 };
}
function vorfuehrSchritt(jetzt) {
  const z = jetzt - vStart, seg = Math.floor(z / SEGMENT) % FOLGE.length, s = (z % SEGMENT) / 1000;
  if (seg !== vSeg) { vSeg = seg; setzeModus(FOLGE[seg]); }
  let p = bahn(z / 1000);
  if (s > 4.9) {                                    // zum nächsten Knopf und drücken
    const k = knoepfe[(seg + 1) % FOLGE.length], km = mitte(k), e = glatt(lin((s - 4.9) / .8));
    p = { x: p.x + (km.x - p.x) * e, y: p.y + (km.y - p.y) * e };
    const nr = Math.floor(z / SEGMENT);
    if (s > 5.75 && vDrueck !== nr) {
      vDrueck = nr; zeiger.classList.add('drueckt'); k.classList.add('drueckt');
      setTimeout(() => { zeiger.classList.remove('drueckt'); knoepfe.forEach(b => b.classList.remove('drueckt')); }, 160);
    }
  } else if (s < .5 && z > SEGMENT) {               // vom Knopf zurück auf die Scheibe
    const km = mitte(knoepfe[seg]), e = glatt(s / .5);
    p = { x: km.x + (p.x - km.x) * e, y: km.y + (p.y - km.y) * e };
  }
  zp.x = p.x; zp.y = p.y; zp.da = true;
  zeiger.style.transform = `translate(${p.x}px, ${p.y}px)`;
}
function stopVorfuehrung() {
  if (!vorfuehrt) return;
  vorfuehrt = false; html.classList.remove('vorfuehrung'); zp.da = false;
}
function neustart() {
  stopVorfuehrung(); pausiert = false; anhaltenZeigen();
  setzeModus('vertikal'); wechsleZu(0, true);
  raumStart = performance.now(); schmutzig = true;
}
function vorfuehren() {
  neustart();
  if (!html.classList.contains('vorschau')) scrollTo(0, 0);
  vorfuehrt = true; vStart = performance.now(); vSeg = -1; vDrueck = -1;
  html.classList.add('vorfuehrung');
}
function pause() { stopVorfuehrung(); if (!pausiert) pauseSeit = performance.now(); pausiert = true; anhaltenZeigen(); }
function weiter() { pausiert = false; vorfuehren(); anhaltenZeigen(); }
/* Knopf „Bewegung anhalten“: gleiche Pause wie FUNDUS_DEMO.pause(), beim Fortsetzen läuft die Raumfolge dort weiter, wo sie stand */
let pauseSeit = 0;
const anhaltenKnopf = document.querySelector('[data-anhalten]');
function anhaltenZeigen() { anhaltenKnopf.setAttribute('aria-pressed', pausiert ? 'true' : 'false'); }
anhaltenKnopf.addEventListener('click', () => {
  if (!pausiert) { pause(); return; }
  const d = performance.now() - pauseSeit; raumStart += d; wechselStart += d;
  pausiert = false; schmutzig = true; anhaltenZeigen();
});

zeigeRaum(0); setzeModus('vertikal'); groesse();
requestAnimationFrame(bild);

return { neustart, vorfuehren, pause, weiter };
}
/* ===================== Variante „still“: Riffelglas als SVG-Filter, klärt auf Knopfdruck ===================== */
(() => {
  const html = document.documentElement;
  const reduziert = () => html.classList.contains('reduziert');
  const welt = document.querySelector('[data-welt="still"]');
  const q = s => welt.querySelector(s);
  const NS = 'http://www.w3.org/2000/svg';
  const bild = q('[data-s-bild]'), svg = q('[data-s-svg]'), gruppe = q('[data-s-raeume]'), knopf = q('[data-s-ansehen]');
  const karte = q('[data-s-karte]'), versatz = q('[data-s-versatz]'), unschaerfe = q('[data-s-unschaerfe]');
  const muster = q('[data-s-muster]'), musterFlaeche = q('[data-s-musterflaeche]'), glasschicht = q('[data-s-glasschicht]'), milch = q('[data-s-milch]');
  const reiter = [...q('[data-s-reiter]').querySelectorAll('[role=tab]')], panel = q('[data-s-panel]');
  const raumGruppen = [...welt.querySelectorAll('[data-s-raum]')];

  /* Werte: Rezept bestattung-wuerdig (token-luxus-weich 1400 ms) und eigene Festlegungen für die SVG-Fassung */
  const S = { versatz: 46, unschaerfe: 3.2, milch: .12, rippePx: 13, rippenMin: 24, rippenMax: 72, dauer: 1400 };
  const RAEUME = [
    { name: 'Abschiedsraum', text: 'Ein heller Raum zum Garten. Hier können Sie Ihren Verstorbenen noch einmal sehen, allein oder mit der Familie.',
      fakten: [['Größe', '24 m²'], ['Plätze', 'bis 12 Personen'], ['Zugang', 'ebenerdig vom Hof, ohne Stufen'], ['Zeiten', 'nach Absprache, auch abends']],
      trueb: 'Abschiedsraum hinter Riffelglas: ein heller Raum mit großem Fenster zum Garten, nur als weiche Streifen in Beige und Grün zu erkennen.',
      klar: 'Abschiedsraum: heller Raum mit Holzboden und großem Sprossenfenster zum Garten, links eine gepolsterte Bank, daneben ein kleiner Tisch mit Gräsern in einer Vase.' },
    { name: 'Feierhalle', text: 'Für Trauerfeiern jeder Weltanschauung. Den Raum gestalten Sie mit uns, Symbole bringen Sie selbst mit oder lassen sie weg.',
      fakten: [['Größe', '96 m²'], ['Plätze', '70 Sitzplätze'], ['Zugang', 'ohne Stufen, Hörschleife'], ['Technik', 'Musik, Mikrofon, Bildschirm']],
      trueb: 'Feierhalle hinter Riffelglas: drei hohe, helle Fenster und dunkle Stuhlreihen, in senkrechte Streifen gebrochen.',
      klar: 'Feierhalle: drei hohe Fenster über einer Holzvertäfelung, davor ein schlichtes Rednerpult aus Holz und weiße Blumen, im Vordergrund Stuhlreihen mit Mittelgang.' },
    { name: 'Garten', text: 'Hinter dem Haus, von einer alten Mauer geschützt. Viele Familien treffen sich hier nach der Feier.',
      fakten: [['Größe', '400 m²'], ['Plätze', 'Bänke für 30 Personen'], ['Zugang', 'feste Wege, rollstuhlgeeignet'], ['Bei Regen', 'überdachter Teil am Haus']],
      trueb: 'Garten hinter Riffelglas: Grün, eine helle Mauer und ein Baum, nur als Farbflächen zu sehen.',
      klar: 'Garten: ein großer Ahorn auf dem Rasen, dahinter eine Natursteinmauer und eine Hecke, ein heller Kiesweg führt zu einer Holzbank und einer flachen Wasserschale.' },
    { name: 'Beratungszimmer', text: 'Für das erste Gespräch. Wenn Ihnen der Weg zu schwer ist, kommen wir auch zu Ihnen nach Hause.',
      fakten: [['Größe', '18 m²'], ['Plätze', '4 Personen'], ['Zugang', 'Erdgeschoss, ohne Stufen'], ['Gespräch', 'bei uns oder bei Ihnen']],
      trueb: 'Beratungszimmer hinter Riffelglas: ein runder Tisch unter einer grünen Leuchte, Regal und Fenster als weiche Streifen.',
      klar: 'Beratungszimmer: ein runder Holztisch mit zwei Stühlen und Wassergläsern unter einer grünen Pendelleuchte, links ein Regal mit Ordnern, rechts ein Fenster und eine Pflanze.' },
  ];

  /* Bildwelt ergänzen: Stuhlreihen der Feierhalle, Regal im Beratungszimmer */
  const el = (tag, attr, ziel) => { const n = document.createElementNS(NS, tag); for (const k in attr) n.setAttribute(k, attr[k]); ziel.appendChild(n); return n; };
  const stuehle = q('[data-s-stuehle]');
  for (let r = 0; r < 4; r++) {
    const y = 700 + r * 52, w = 52 + r * 12, h = 36 + r * 14;
    for (const seite of [-1, 1]) for (let c = 0; c < 4; c++) {
      const x = 600 + seite * (70 + r * 18 + c * (w + 14)) - (seite < 0 ? w : 0);
      el('rect', { x: x.toFixed(0), y, width: w, height: h, rx: 6 }, stuehle);
    }
  }
  const regal = q('[data-s-regal]'), farben = ['#8c7a62', '#5f6b5c', '#a4937a', '#6d5e4c', '#b9ad98', '#4f5a54'];
  for (let b = 0; b < 4; b++) {
    const boden = 250 + b * 120; let x = 98;
    el('rect', { x: 80, y: boden, width: 320, height: 10, fill: '#b8aa93' }, regal);
    for (let i = 0; x < 380; i++) { const w = 18 + (i * 7 + b * 5) % 16, h = 70 + (i * 13 + b * 3) % 30; el('rect', { x, y: boden - h, width: w - 3, height: h, fill: farben[(i + b) % farben.length] }, regal); x += w; }
  }

  /* Riffel-Karte: je Riffel ein Rot-Verlauf 255 → 0 (Zylinderlinse, Versatz nur waagerecht), Grün 128 = kein senkrechter Versatz */
  let rippen = 0;
  function karteBauen() {
    const r = bild.getBoundingClientRect(); if (!r.width) return;
    const massstab = Math.max(r.width / 1200, r.height / 900) * 1.04;
    const n = Math.max(S.rippenMin, Math.min(S.rippenMax, Math.round(1200 * massstab / S.rippePx)));
    if (n === rippen) return;
    rippen = n;
    const c = document.createElement('canvas'), B = 16; c.width = n * B; c.height = 2;
    const x = c.getContext('2d'), d = x.createImageData(c.width, 2);
    for (let i = 0; i < c.width; i++) {
      const f = ((i % B) + .5) / B - .5, rot = Math.round(128 - 2 * f * 127);
      for (const zeile of [0, 1]) { const o = (zeile * c.width + i) * 4; d.data[o] = rot; d.data[o + 1] = 128; d.data[o + 2] = 128; d.data[o + 3] = 255; }
    }
    x.putImageData(d, 0, 0);
    const url = c.toDataURL('image/png');
    karte.setAttribute('href', url); karte.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', url);
    const w = (1200 / n).toFixed(3);
    muster.setAttribute('width', w); musterFlaeche.setAttribute('width', w);
    werteZeigen();
  }
  new ResizeObserver(karteBauen).observe(bild);

  /* Zustand Glas: k = 0 trüb, 1 klar */
  let k = 0, klar = false, tween = null;
  const kurve = (() => { const f = (t, a, b) => 3 * a * (1 - t) ** 2 * t + 3 * b * (1 - t) * t * t + t ** 3;
    return x => { let lo = 0, hi = 1; for (let i = 0; i < 24; i++) { const m = (lo + hi) / 2; if (f(m, .37, .63) < x) lo = m; else hi = m; } return f((lo + hi) / 2, 0, 1); }; })();
  function setzeK(w) {
    k = w;
    if (w >= 1) gruppe.removeAttribute('filter'); else gruppe.setAttribute('filter', 'url(#s-riffel)');
    versatz.setAttribute('scale', (S.versatz * (1 - w)).toFixed(2));
    unschaerfe.setAttribute('stdDeviation', (S.unschaerfe * (1 - w)).toFixed(2));
    glasschicht.setAttribute('opacity', (1 - w).toFixed(3));
    milch.setAttribute('opacity', (S.milch * (1 - w)).toFixed(3));
  }
  function glas(neu, ohneUebergang) {
    klar = neu;
    knopf.setAttribute('aria-pressed', String(klar));
    beschreiben();
    cancelAnimationFrame(tween);
    const ziel = klar ? 1 : 0;
    if (ohneUebergang || reduziert()) { setzeK(ziel); return; }   // reduzierte Bewegung: sofort klar
    const start = k, t0 = performance.now(), dauer = S.dauer * Math.abs(ziel - start);
    const schritt = t => { const p = Math.min(1, (t - t0) / dauer); setzeK(start + (ziel - start) * kurve(p)); if (p < 1) tween = requestAnimationFrame(schritt); };
    tween = requestAnimationFrame(schritt);
  }

  /* Räume: Reiter mit Pfeiltasten, Bild blendet 1400 ms über (reduziert 500 ms) */
  let raum = 0;
  function beschreiben() { const R = RAEUME[raum]; bild.setAttribute('aria-label', klar ? R.klar : R.trueb); }
  function zeigeRaum(i, fokus) {
    raum = i; const R = RAEUME[i];
    reiter.forEach((t, n) => { t.setAttribute('aria-selected', String(n === i)); t.tabIndex = n === i ? 0 : -1; });
    if (fokus) reiter[i].focus();
    panel.setAttribute('aria-labelledby', reiter[i].id);
    raumGruppen.forEach((g, n) => g.classList.toggle('an', n === i));
    q('[data-s-name]').textContent = R.name; q('[data-s-text]').textContent = R.text;
    q('[data-s-dl]').innerHTML = R.fakten.map(([a, b]) => `<dt>${a}</dt><dd>${b}</dd>`).join('');
    beschreiben();
  }
  reiter.forEach((t, n) => {
    t.addEventListener('click', () => { stopp(); zeigeRaum(n); });
    t.addEventListener('keydown', e => {
      const z = { ArrowRight: n + 1, ArrowLeft: n - 1, Home: 0, End: reiter.length - 1 }[e.key];
      if (z === undefined) return;
      e.preventDefault(); stopp(); zeigeRaum((z + reiter.length) % reiter.length, true);
    });
  });
  knopf.addEventListener('click', () => { stopp(); glas(!klar); });
  bild.addEventListener('click', () => { stopp(); glas(!klar); });   // auch die Scheibe selbst reagiert (Maus, Touch)

  function werteZeigen() {
    const r = bild.getBoundingClientRect(), massstab = Math.max(r.width / 1200, r.height / 900) * 1.04, px = rippen ? (1200 / rippen * massstab).toFixed(1) : '–';
    q('[data-s-werte]').innerHTML = [
      ['Riffel', `${rippen} je Bild · ${px} px`],
      ['Versatz', `feDisplacementMap ${S.versatz} → 0`],
      ['Unschärfe', `${S.unschaerfe} → 0`],
      ['Klar', reduziert() ? 'sofort (reduziert)' : `${S.dauer} ms cubic-bezier(0.37, 0, 0.63, 1)`],
      ['Raumwechsel', reduziert() ? '500 ms Deckkraft' : `${S.dauer} ms Deckkraft`],
      ['Bewegung sonst', 'keine, ohne three.js'],
    ].map(([a, b], i) => `<span${i > 3 ? ' class="lang"' : ''}>${a} <b>${b}</b></span>`).join('');
  }
  new MutationObserver(werteZeigen).observe(html, { attributes: true, attributeFilter: ['class'] });

  /* Vorführung: trüb → Knopf drücken → klar → wieder trüb → nächster Raum */
  let uhr = null, schrittNr = 0, angehalten = false;
  const FOLGE = [
    [1700, () => { knopf.classList.add('drueckt'); setTimeout(() => knopf.classList.remove('drueckt'), 180); glas(true); }],
    [3600, () => { knopf.classList.add('drueckt'); setTimeout(() => knopf.classList.remove('drueckt'), 180); glas(false); }],
    [1800, () => zeigeRaum((raum + 1) % RAEUME.length)],
  ];
  function stopp() { clearTimeout(uhr); uhr = null; }
  function takt() {
    if (angehalten) return;
    const [warte, tu] = FOLGE[schrittNr];
    uhr = setTimeout(() => { tu(); schrittNr = (schrittNr + 1) % FOLGE.length; takt(); }, warte);
  }
  function neustart() { stopp(); angehalten = false; schrittNr = 0; zeigeRaum(0); glas(false, true); karteBauen(); werteZeigen(); }
  function vorfuehren() { neustart(); if (!html.classList.contains('vorschau')) scrollTo(0, 0); takt(); }
  function pause() { angehalten = true; stopp(); cancelAnimationFrame(tween); if (k > 0 && k < 1) setzeK(klar ? 1 : 0); }
  function weiter() { if (angehalten) { angehalten = false; takt(); } }
  zeigeRaum(0); setzeK(0); werteZeigen();
  DEMOS.still = { neustart, vorfuehren, pause, weiter, an: karteBauen };
})();

/* ===================== Variante wählen (Regel 13: ?v=, Umschalter schreibt die URL) ===================== */
const VARIANTEN = ['basis', 'still'];
let variante = (() => { const v = new URLSearchParams(location.search).get('v'); return VARIANTEN.includes(v) ? v : VARIANTEN[0]; })();
const demo = () => variante === 'basis' ? ladeBasis() : Promise.resolve(DEMOS[variante]);
function zeigeVariante(v, schreiben) {
  if (v !== variante && DEMOS[variante]) DEMOS[variante].pause();
  variante = v;
  document.querySelectorAll('[data-welt]').forEach(w => { w.hidden = w.dataset.welt !== v; });
  document.querySelectorAll('[data-variante]').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.variante === v)));
  if (v === 'still') DEMOS.still.an();
  if (schreiben) {
    const u = new URL(location.href);
    u.searchParams.set('v', v);   // andere Parameter (reduziert, vorschau) bleiben stehen
    history.replaceState(history.state, '', u);
    demo().then(d => d.neustart());
  }
}
document.querySelectorAll('[data-variante]').forEach(b => b.addEventListener('click', () => {
  if (b.dataset.variante === variante) return;
  const fokus = document.activeElement === b;
  zeigeVariante(b.dataset.variante, true);
  const ziel = document.querySelector(`[data-welt="${variante}"] [data-variante="${variante}"]`);
  if (ziel && fokus) ziel.focus({ preventScroll: true });
}));
zeigeVariante(variante, false);
if (variante === 'basis') ladeBasis();

</script>
</body>
</html>