Zum Inhalt
← Hero & Section-Layouts

Bild-Scheiben in der Tiefe

Kampagnen-Hero eines Ladens für Duft, Leinen und Keramik: drei Scheiben aus dem Produktbild heben sich unscharf ab, werden scharf, zoomen stärker als das Bild und verschieben sich mit Zeiger und Scroll, während das Bild dahinter zurücktritt.

Produkt- oder Kampagnenbild mit einem klaren Objekt: Streifen schneiden durch Flakon, Fassade oder Figur.

Aus einem Hero-Bild werden zwei bis vier Streifen oder Rechtecke herausgeschnitten. Sie zeigen genau den Bildteil, der dahinter liegt, heben sich aber leicht ab: Sie zoomen etwas stärker als das Bild, werfen einen Schatten und versetzen sich bei Zeigerbewegung oder beim Scrollen. So entsteht Tiefe aus einem einzigen Foto.

Das Auge liest den Versatz an den Schnittkanten als Abstand. Die Scheiben folgen dem Raster der Seite oder des Motivs (Lamellen, Geschosse, Fenster) und machen das Raster selbst zum Bild. Es braucht kein 3D-Modell und kein zweites Motiv.

Wann einsetzen

Hero mit einem starken Foto, das klare Linien hat: Architektur, Innenraum, Mode-Kampagne, Boutique-Hotel als ruhige Alternative zum Ken-Burns-Hero, Kreativstudio.

Wann nicht

Werke von Fotograf:innen (verändert die Komposition fremder Bilder). Bilder mit Gesichtern oder Schrift an den Schnittkanten. Produktfotos im Shop. Zusammen mit mehrschicht-parallax oder zoom-parallax auf demselben Bild. Wenn das Motiv keine Linien hat, an denen die Schnitte sitzen können.

Varianten

  • Senkrecht: drei bis vier Streifen wie Lamellen oder Fassadenachsen
  • Waagerecht: Bänder wie Geschosse oder Stufen
  • Rechtecke: zwei bis drei freie Ausschnitte nach dem Seitenraster (Slicey-Grundform)
  • Auslöser: Zeiger (Desktop), Scroll (Hero verlässt den Bildschirm), einmal beim Laden

Umsetzung

Bühne mit container-type: size, darin das Bild als <img> (object-fit: cover). Jede Scheibe ist ein Rahmen mit overflow: hidden an Position x, y, b, h in Prozent der Bühne; darin dasselbe Bild in voller Bühnengröße (100cqw × 100cqh), um −x und −y verschoben. Damit zeigt die Scheibe deckungsgleich den Bildteil dahinter. Tiefe je Scheibe (0 bis 1) steuert scale 1 + Tiefe × (0,1 + Scrollhub × 0,07), Versatz Zeiger × Tiefe × 30 px und einen weichen Schatten. Beim Laden heben sich die Scheiben in 1,1 s (110 ms versetzt) und werden dabei von 7 px Unschärfe scharf; das Bild dahinter wird gleichzeitig dunkler und etwas entsättigt (brightness .68, saturate .75), damit die Scheiben im Licht stehen. Zeiger: pointermove setzt --zx, --zy (−1 bis 1), weich nachgezogen (Lerp 0,08). Scroll: @property --hub per animation-timeline: view() von 0 auf 1, solange der Hero aus dem Bild scrollt; ohne Scroll-Timeline setzt ein Skript --hub.

Robust umsetzen

Telefon. Ohne Zeiger nur Scroll und Anheben beim Laden. Weniger Scheiben (die äußere entfällt unter 760 px), Hochformat-Bild.

Ohne Bewegung. Scheiben stehen still in ruhender Tiefe, Schatten bleibt; kein Zeiger- und kein Scrollversatz.

Barrierefrei. Die Scheiben sind Dekoration (aria-hidden, Hintergrundbild ohne Alt), das eigentliche <img> trägt den Alt-Text. Kein Autoplay: Bewegung nur einmal beim Laden (gut 1 s) und als Antwort auf Zeiger und Scroll, deshalb kein Pause-Knopf nötig. Text liegt nie auf den Scheiben.

Code

<!DOCTYPE html>
<!--
  Bild-Scheiben in der Tiefe
  Fundus · Hero & Section-Layouts · https://cecodesign.de/lab/fundus/hero-layouts/bild-scheiben-tiefe/
  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>Bild-Scheiben in der Tiefe · 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: 'Zalando Sans'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/zalando-sans:vf@latest/latin-wdth-normal.woff2') format('woff2-variations'); font-weight: 200 900; font-stretch: 75% 125%; 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: 'Bricolage Grotesque'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/bricolage-grotesque:vf@latest/latin-opsz-normal.woff2') format('woff2-variations'); font-weight: 200 800; font-display: swap; }
@font-face { font-family: 'Azeret Mono'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/azeret-mono:vf@latest/latin-wght-normal.woff2') format('woff'); font-weight: 100 900; font-display: swap; }

/* Welt: Haff, Duft, Leinen und Keramik aus Ueckermünde · Rezept mode-beauty-kollektion
   Warmes Off-White, eine Grotesk (Zalando Sans, breit, nicht fett), Formel in Fragment Mono. */
@property --heben { syntax: '<number>'; inherits: false; initial-value: 0; }
@property --licht { syntax: '<number>'; inherits: false; initial-value: 0; }
@property --hub { syntax: '<number>'; inherits: true; initial-value: 0; }

:root {
  --grund: #f6f3ee; --flaeche: #ebe7e0; --linie: #cecdc6; --text: #121010; --leise: #6b645b;
  --fokus: #121010;
  --kurve: cubic-bezier(0.25, 1, 0.5, 1);      /* Kampagnenbild (Toteme-Karussell) */
  --ui: 250ms;
  --heben-dauer: 1100ms; --stagger: 110ms;
  --kopf: 68px;
  --rand: clamp(16px, 2.6vw, 40px);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { background: var(--grund); color: var(--text); font-family: 'Zalando Sans', system-ui, sans-serif; font-weight: 400; font-stretch: 100%; -webkit-font-smoothing: antialiased; }
a { color: inherit; text-underline-offset: .3em; text-decoration-thickness: 1px; }
:focus-visible { outline: 2px solid var(--fokus); outline-offset: 3px; }
.mono { font: 400 .75rem/1.4 'Fragment Mono', ui-monospace, monospace; letter-spacing: .06em; text-transform: uppercase; font-variant-numeric: tabular-nums; }

/* ---------- Kopf ---------- */
.kopf { height: var(--kopf); display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 16px; padding: 0 var(--rand); }
.marke { justify-self: center; grid-column: 2; font-stretch: 125%; font-weight: 500; font-size: 1.375rem; letter-spacing: .02em; text-decoration: none; min-height: 44px; display: inline-flex; align-items: center; }
.kopf nav { grid-column: 1; grid-row: 1; }
.kopf ul { display: flex; gap: clamp(14px, 2vw, 28px); list-style: none; }
.kopf ul a { text-decoration: none; font-size: .9375rem; padding: 12px 0; }
.kopf ul a:hover, .kopf ul a[aria-current] { text-decoration: underline; }
.kopf__rechts { grid-column: 3; justify-self: end; display: flex; gap: 18px; align-items: center; font-size: .9375rem; }
.kopf__rechts a { text-decoration: none; padding: 12px 0; }
.kopf__rechts a:hover { text-decoration: underline; }
.menue { display: none; font: inherit; background: none; border: 0; min-height: 44px; padding: 0 4px; color: inherit; cursor: pointer; }

/* ---------- Bühne: Kampagnenbild in voller Breite + Scheiben ---------- */
.buehne {
  position: relative; container-type: size; overflow: hidden; background: #1f2a6b;
  height: max(380px, calc(100svh - var(--kopf) - 30px - 132px));
  --zx: 0; --zy: 0;
  --bewegung: translate(calc(var(--zx) * -8px), calc(var(--zy) * -6px)) scale(calc(1.03 + var(--hub) * .03));
  transition: --licht var(--heben-dauer) var(--kurve);
}
.buehne.gehoben { --licht: 1; }
.buehne > img, .scheibe > span { transform: var(--bewegung); }
/* Bild dahinter tritt zurück: dunkler und leicht entsättigt, die Scheiben bleiben im vollen Licht */
.buehne > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; filter: brightness(calc(1 - var(--licht) * .32)) saturate(calc(1 - var(--licht) * .25)); }
@supports (animation-timeline: view()) {
  .buehne { animation: hub linear both; animation-timeline: view(); animation-range: exit 0% exit 100%; }
  body[data-simuliert] .buehne { animation: none; }
}
@keyframes hub { to { --hub: 1; } }

.scheibe {
  --t: calc(var(--heben) * var(--tiefe));
  position: absolute; left: calc(var(--sx) * 1%); top: calc(var(--sy) * 1%); width: calc(var(--sb) * 1%); height: calc(var(--sh) * 1%);
  overflow: hidden;
  transform: translate(calc(var(--zx) * var(--t) * 30px), calc(var(--zy) * var(--t) * 20px)) scale(calc(1 + var(--t) * (.1 + var(--hub) * .07)));
  box-shadow: 0 calc(var(--t) * 34px) calc(var(--t) * 70px) rgba(8, 10, 30, calc(var(--t) * .5)), 0 calc(var(--t) * 6px) calc(var(--t) * 14px) rgba(8, 10, 30, calc(var(--t) * .25));
  filter: blur(calc((1 - var(--heben)) * 7px));      /* Slicey-Prinzip: unscharf von, scharf bis */
  transition: --heben var(--heben-dauer) var(--kurve) calc(var(--j) * var(--stagger));
  will-change: transform;
}
.gehoben .scheibe { --heben: 1; }
.sofort, .sofort .scheibe { transition: none !important; }
/* Inneres: dasselbe Bild in Bühnengröße, um −x/−y versetzt → deckungsgleich mit dem Bild dahinter */
.scheibe > span { position: absolute; left: calc(var(--sx) * -1cqw); top: calc(var(--sy) * -1cqh); width: 100cqw; height: 100cqh; background: var(--bild) center / cover no-repeat; transform-origin: 50cqw 50cqh; }
.scheibe::after { content: ''; position: absolute; inset: 0; box-shadow: inset 0 0 0 1px rgba(255, 255, 255, calc(var(--t) * .55)); pointer-events: none; }

/* Marken über dem Bild: was die Scheibe zeigt (Duftnote, Ton, Stück) */
.achsen { position: relative; height: 30px; }
.achsen span { position: absolute; bottom: 7px; color: var(--leise); translate: -50% 0; white-space: nowrap; }
.achsen span::after { content: ''; position: absolute; left: 50%; top: calc(100% + 2px); width: 1px; height: 6px; background: currentColor; }

/* ---------- Titelzeile: Name, Formel, Preis (wie Typology) ---------- */
.titelzeile { display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: 12px 32px; align-items: end; padding: 22px var(--rand) 0; }
.titelzeile h1 { font-weight: 400; font-stretch: 125%; font-size: clamp(2rem, 1.2rem + 2.6vw, 3.75rem); line-height: 1.04; letter-spacing: -.01em; }
.titelzeile h1 i { font-style: normal; font-stretch: 100%; font-weight: 300; font-size: .62em; color: var(--leise); white-space: nowrap; }
.titelzeile .rechts { display: flex; justify-content: space-between; align-items: end; gap: 12px 20px; flex-wrap: wrap; padding-bottom: 6px; }
.titelzeile .rechts p { max-width: 36ch; color: var(--text); }
.titelzeile .rechts a { font-weight: 500; white-space: nowrap; min-height: 44px; display: inline-flex; align-items: center; gap: 10px; padding: 0 22px; border-radius: 999px; background: var(--text); color: var(--grund); text-decoration: none; transition: background-color var(--ui) var(--kurve); }
.titelzeile .rechts a:hover { background: #3a3431; }

/* ---------- Danach ---------- */
.haus { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: 32px; padding: clamp(64px, 12vh, 128px) var(--rand); }
.haus h2 { font-weight: 400; font-stretch: 125%; font-size: 1.75rem; letter-spacing: -.01em; }
.haus p { font-size: 1.0625rem; line-height: 1.5; max-width: 52ch; }
.haus p + p { margin-top: 1em; }
.sortiment { list-style: none; margin-top: 36px; border-top: 1px solid var(--linie); max-width: 64ch; }
.sortiment li { display: flex; justify-content: space-between; gap: 16px; padding: 14px 0; border-bottom: 1px solid var(--linie); flex-wrap: wrap; }
.sortiment a { text-decoration: none; } .sortiment a:hover { text-decoration: underline; }
.sortiment .mono { color: var(--leise); }
.fuss { background: var(--text); color: #f6f3ee; display: grid; grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr)); gap: 24px; padding: 48px var(--rand); font-size: .9375rem; line-height: 1.5; }
.fuss .mono { color: #c9c2b8; }

/* ---------- Pult der Demo ---------- */
.pult { position: fixed; z-index: 10; right: 12px; top: calc(var(--kopf) + 38px); display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; max-width: calc(100vw - 24px); padding: 8px 10px; background: rgba(18, 16, 16, .92); color: #f6f3ee; border-radius: 10px; font: 400 .75rem/1.3 'Fragment Mono', ui-monospace, monospace; }
.pult button { font: inherit; color: inherit; background: none; border: 1px solid rgba(246, 243, 238, .32); border-radius: 999px; min-height: 30px; padding: 0 12px; cursor: pointer; }
.pult button[aria-pressed=true] { background: #f6f3ee; color: var(--text); }
.pult__werte { color: #d3ccc2; font-variant-numeric: tabular-nums; }

/* ---------- Telefon ---------- */
@media (max-width: 760px) {
  .kopf { position: relative; grid-template-columns: auto minmax(0, 1fr) auto; }
  .marke { grid-column: 1; justify-self: start; }
  .kopf nav { grid-column: 2; }
  .kopf ul { display: none; }
  .kopf.offen ul { display: grid; gap: 0; position: absolute; z-index: 5; left: 0; right: 0; top: var(--kopf); background: var(--grund); padding: 8px var(--rand) 20px; border-bottom: 1px solid var(--linie); }
  .kopf.offen ul a { display: block; padding: 12px 0; font-size: 1.25rem; }
  .kopf__rechts .nur-breit { display: none; }
  .menue { display: inline-flex; align-items: center; }
  .buehne { height: max(360px, 62svh); }
  .scheibe[data-aussen] { display: none; }
  .titelzeile { grid-template-columns: minmax(0, 1fr); }
  .haus { grid-template-columns: minmax(0, 1fr); }
  .achsen span { font-size: .6875rem; letter-spacing: .03em; }
  .achsen span[data-aussen] { display: none; }
}
@media (max-width: 760px) and (hover: none) { .achsen { display: none; } }

/* ---------- Variante Geschosse: Speicherhof Coworking, Elmshorn · Rezept coworking-buero-raeume ----------
   Hausbild 4:3 ohne Beschnitt, drei waagerechte Scheiben auf den Geschossdecken, daneben die Geschosse als Text.
   Zeigen auf ein Geschoss (Liste oder Bild) hebt seine Scheibe, die anderen sinken. */
@property --an { syntax: '<number>'; inherits: false; initial-value: 0; }
[data-welt][hidden] { display: none !important; }
:root:has(> body[data-v=geschosse]) {
  --grund: #eeede8; --flaeche: #d7e2d9; --linie: #cccbc7; --text: #1a1a18; --leise: #595954; --fokus: #1a1a18;
  --signal: #b8361f; --dunkel: #232629;
  --kurve: cubic-bezier(0.625, 0.05, 0, 1);   /* token-studio-signatur */
  --heben-dauer: 725ms; --stagger: 40ms; --ui: 250ms;
  font-family: 'Bricolage Grotesque', system-ui, sans-serif; font-variation-settings: 'opsz' 14;
}
[data-v=geschosse] .mono { font-family: 'Azeret Mono', ui-monospace, monospace; font-size: .8125rem; font-weight: 450; letter-spacing: .06em; }
[data-v=geschosse] .marke { font-stretch: 100%; font-weight: 700; font-size: 1.25rem; letter-spacing: -.01em; font-variation-settings: 'opsz' 32; }
[data-v=geschosse] main { display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); grid-template-rows: auto auto auto 1fr; grid-template-areas: "bild titel" "bild etagen" "bild info" "bild cta" "haus haus"; column-gap: clamp(24px, 3.6vw, 56px); padding: 10px var(--rand) 0; }
[data-v=geschosse] .achsen { display: none; }
[data-v=geschosse] .buehne { grid-area: bild; height: auto; aspect-ratio: 4 / 3; background: #dfe3dd; animation-name: none; border-radius: 2px; }
[data-v=geschosse] .buehne > img { filter: brightness(calc(1 - var(--licht) * .14)) saturate(calc(1 - var(--licht) * .2)); }
[data-v=geschosse] .titelzeile, [data-v=geschosse] .titelzeile .rechts { display: contents; }
[data-v=geschosse] .titelzeile h1 { grid-area: titel; font-family: 'Bricolage Grotesque', system-ui, sans-serif; font-weight: 700; font-stretch: 90%; font-variation-settings: 'opsz' 96; font-size: clamp(2.25rem, 1rem + 3.2vw, 4rem); line-height: .95; letter-spacing: -.03em; padding-top: 4px; }
[data-v=geschosse] h1 .zeile { display: block; overflow: clip; padding-bottom: .08em; margin-bottom: -.08em; }
[data-v=geschosse] h1 .zeile > span { display: block; translate: 0 100%; transition: translate 525ms var(--kurve) calc(var(--z) * 40ms); }
body.steigt[data-v=geschosse] h1 .zeile > span { translate: 0 0; }
[data-v=geschosse] .titelzeile .rechts p { grid-area: info; max-width: 40ch; color: var(--leise); margin-top: 18px; font: 400 1rem/1.5 'Bricolage Grotesque', system-ui, sans-serif; letter-spacing: 0; text-transform: none; }
[data-v=geschosse] .titelzeile .rechts a { grid-area: cta; justify-self: start; align-self: start; margin-top: 16px; border-radius: 3px; background: var(--signal); color: #fff; font-weight: 600; font-variation-settings: 'opsz' 14; padding: 0 22px; min-height: 48px; }
[data-v=geschosse] .titelzeile .rechts a:hover { background: #9c2d19; }
.etagen { grid-area: etagen; list-style: none; margin-top: clamp(18px, 3vh, 32px); border-top: 1px solid var(--text); }
.etagen a { display: grid; grid-template-columns: 3.2em minmax(0, 1fr); gap: 2px 12px; padding: 12px 10px 12px 0; border-bottom: 1px solid var(--linie); text-decoration: none; transition: background-color 280ms var(--kurve), padding-left 280ms var(--kurve); }
.etagen a:hover, .etagen a:focus-visible, .etagen a.ist-an { background: var(--flaeche); padding-left: 10px; transition-duration: 400ms; }
.etagen .mono:first-child { grid-row: span 2; align-self: start; padding-top: 3px; color: var(--text); font-weight: 600; }
.etagen b { font-weight: 600; font-size: 1.125rem; line-height: 1.25; }
.etagen span + b + span { color: var(--leise); font-size: .75rem; }
[data-v=geschosse] .haus { grid-area: haus; padding-left: 0; padding-right: 0; }
[data-v=geschosse] .haus h2 { font-stretch: 100%; font-weight: 650; font-variation-settings: 'opsz' 48; font-size: clamp(1.75rem, 1.2rem + 2.2vw, 3rem); letter-spacing: -.02em; line-height: 1.05; }
[data-v=geschosse] .fuss { background: var(--dunkel); }
[data-v=geschosse] .fuss .mono { color: #c9ccc8; }
/* Scheiben: Tiefe der Scheibe ± Wahl (gewählt +.45, andere −.45), scale bis 1,1, Versatz höchstens 30 px */
body[data-v=geschosse] .scheibe {
  --t: calc(var(--heben) * clamp(.12, var(--tiefe) + var(--an) * .45, 1));
  transform: translate(calc(var(--zx) * var(--t) * 30px), calc(var(--zy) * var(--t) * 20px)) scale(calc(1 + var(--t) * .1));
  filter: blur(calc((1 - var(--heben)) * 7px)) brightness(calc(.86 + var(--t) * .14));   /* flach = so hell wie das Bild dahinter */
  transition: --heben var(--heben-dauer) var(--kurve) calc(120ms + var(--j) * var(--stagger)), --an 280ms var(--kurve);
}
body[data-v=geschosse] .scheibe[data-an] { z-index: 2; transition: --heben var(--heben-dauer) var(--kurve) calc(120ms + var(--j) * var(--stagger)), --an 400ms var(--kurve); }
/* Touch und reduzierte Bewegung: Bild liegt flach, die Geschosse stehen als Text daneben */
@media (hover: none) { body[data-v=geschosse] .buehne .scheibe { --heben: 0; filter: none; box-shadow: none; } body[data-v=geschosse] .buehne > img { filter: none; } body[data-v=geschosse] h1 .zeile > span { translate: 0 0; transition: none; } }
:root.reduziert body[data-v=geschosse] .buehne .scheibe { --heben: 0; filter: none; box-shadow: none; }
:root.reduziert body[data-v=geschosse] .buehne > img { filter: none; }
:root.reduziert body[data-v=geschosse] h1 .zeile > span { translate: 0 0; }
@media (max-width: 760px) {
  [data-v=geschosse] main { grid-template-columns: minmax(0, 1fr); grid-template-rows: none; grid-template-areas: "titel" "bild" "etagen" "info" "cta" "haus"; padding-top: 6px; }
  [data-v=geschosse] .buehne { height: auto; margin-top: 18px; }
  [data-v=geschosse] .etagen { margin-top: 18px; }
}
@media (forced-colors: active) { .etagen a.ist-an, .etagen a:hover { outline: 2px solid Highlight; } }

.vorschau body { overflow: hidden; }
.vorschau .pult { display: none; }

/* Reduzierte Bewegung: Scheiben stehen in ruhender Tiefe, kein Zeiger, kein Scroll */
:root.reduziert .scheibe { --heben: 1; transition: none; }
:root.reduziert .buehne { --licht: 1; --zx: 0 !important; --zy: 0 !important; animation: none; transition: none; }
:root.reduziert * { transition-duration: .01ms !important; }
@media (hover: none) { .titelzeile .rechts a { transition: none; } }
@media (forced-colors: active) {
  button, [role=button], [role=tab] { border: 1px solid ButtonText !important; }
  .titelzeile .rechts a { border: 1px solid LinkText; }
  .pult button[aria-pressed=true] { outline: 2px solid Highlight; }
}
</style>
</head>
<body data-v="senkrecht">
<header class="kopf">
  <nav aria-label="Hauptnavigation">
    <ul id="hauptmenue">
      <li><a href="#start" aria-current="page">Duft</a></li>
      <li><a href="#haus">Leinen</a></li>
      <li><a href="#haus">Keramik</a></li>
      <li><a href="#haus">Laden</a></li>
    </ul>
  </nav>
  <a class="marke" href="#start" aria-label="Haff, Startseite">HAFF</a>
  <div class="kopf__rechts">
    <a class="nur-breit" href="#haus">Suche</a>
    <a href="#haus">Korb (0)</a>
    <button class="menue" type="button" aria-expanded="false" aria-controls="hauptmenue">Menü</button>
  </div>
</header>

<main id="start">
  <div class="achsen mono" aria-hidden="true" data-achsen></div>
  <figure class="buehne" data-buehne>
    <img data-bild src="bilder/flakon.jpg" alt="" width="1200" height="800" fetchpriority="high">
  </figure>
  <ol class="etagen" data-etagen data-welt="speicher" aria-label="Das Haus, Geschoss für Geschoss" hidden>
    <li><a href="#das-haus" data-geschoss="0"><span class="mono">DG</span><b>Büros und Besprechung</b><span class="mono">Gaube, 10 Pers. · 18 €/Std.</span></a></li>
    <li><a href="#das-haus" data-geschoss="1"><span class="mono">OG</span><b>Flex Desks am Fenster</b><span class="mono">42 Plätze · Monat 229 €</span></a></li>
    <li><a href="#das-haus" data-geschoss="2"><span class="mono">EG</span><b>Café, Empfang, Tagespass</b><span class="mono">ab 8 Uhr · Tag 25 €</span></a></li>
  </ol>
  <div class="titelzeile">
    <h1 data-titel>Strandgut <i>Eau de Parfum</i></h1>
    <div class="rechts">
      <p class="mono" data-unterschrift>Labdanum 12 % · Iris 4 % · Treibholz · 50 ml · 128 €</p>
      <a href="#haus" data-cta>Zum Duft</a>
    </div>
  </div>

  <section class="haus" id="das-haus" data-welt="speicher" aria-labelledby="h-speicher" hidden>
    <h2 id="h-speicher">Heute im Speicherhof</h2>
    <div>
      <p>Komm ohne Anmeldung zwischen 8 und 20 Uhr. Am Empfang im Erdgeschoss bekommst du deine Karte, den WLAN-Zugang und einen Kaffee. Am Wochenende kommen nur Mitglieder mit fester Karte rein.</p>
      <p>Aufzug bis ins Dachgeschoss, Türen 90 cm breit, ein barrierefreies WC im Erdgeschoss. Duschen und Fahrradkeller liegen im Untergeschoss.</p>
      <ul class="sortiment" aria-label="Plätze und Preise, Endpreise inklusive Mehrwertsteuer">
        <li><a href="#start">Tagespass, Platz im Obergeschoss</a><span class="mono">8–20 Uhr · 25 €</span></li>
        <li><a href="#start">Zehnerkarte, ein Jahr gültig</a><span class="mono">10 Tage · 210 €</span></li>
        <li><a href="#start">Fester Platz mit Schrank</a><span class="mono">Monat · 289 €</span></li>
        <li><a href="#start">Besprechungsraum Gaube</a><span class="mono">bis 10 Pers. · 18 € / Std.</span></li>
      </ul>
    </div>
  </section>
  <section class="haus" id="haus" data-welt="haff" aria-labelledby="h-titel">
    <h2 id="h-titel">Der Laden am Hafen</h2>
    <div>
      <p>Wir mischen Düfte, lassen Leinen weben und drehen Keramik, alles in kleinen Auflagen. Der Laden liegt in einem alten Netzschuppen am Ueckermünder Hafen, geöffnet Mittwoch bis Samstag.</p>
      <p>Jeder Duft trägt seine Formel auf dem Etikett. Leinen und Keramik sind Einzelstücke, kleine Abweichungen gehören dazu.</p>
      <ul class="sortiment" aria-label="Aus dem Sortiment">
        <li><a href="#start">Strandgut, Eau de Parfum</a><span class="mono">50 ml · 128 €</span></li>
        <li><a href="#start">Tischtuch, gewaschenes Leinen</a><span class="mono">fünf Töne · ab 38 €</span></li>
        <li><a href="#start">Flaschenvase, Steinzeug</a><span class="mono">Einzelstück · 64 €</span></li>
        <li><a href="#start">Probenset, drei Düfte</a><span class="mono">3 × 2 ml · 18 €</span></li>
      </ul>
    </div>
  </section>
</main>
<footer class="fuss" id="kontakt" data-welt="haff">
  <p>Haff · Duft, Leinen, Keramik<br>Am Hafen 7, 17373 Ueckermünde</p>
  <p>Mi–Sa 11–18 Uhr<br>laden@haff.example</p>
  <p class="mono">Versand in drei Werktagen<br>Rücksendung 30 Tage kostenlos</p>
</footer>
<footer class="fuss" id="anfahrt" data-welt="speicher" hidden>
  <p>Speicherhof Coworking<br>Speicherstraße 9, 25335 Elmshorn</p>
  <p>Mo–Fr 8–20 Uhr, Empfang bis 18 Uhr<br>hallo@speicherhof.example</p>
  <p class="mono">Bahnhof 6 Min. zu Fuß<br>Preise Stand 11/2026</p>
</footer>

<div class="pult" role="group" aria-label="Variante der Demo">
  <span>Schnitt</span>
  <button type="button" data-variante="senkrecht" aria-pressed="true">Senkrecht</button>
  <button type="button" data-variante="waagerecht" aria-pressed="false">Waagerecht</button>
  <button type="button" data-variante="rechtecke" aria-pressed="false">Rechtecke</button>
  <button type="button" data-variante="geschosse" aria-pressed="false">Geschosse</button>
  <span class="pult__werte" data-werte></span>
</div>

<script>
(() => {
  const html = document.documentElement, body = document.body;
  const reduziert = () => html.classList.contains('reduziert');
  const buehne = document.querySelector('[data-buehne]');
  const bild = document.querySelector('[data-bild]');
  const achsen = document.querySelector('[data-achsen]');

  /* Schnitte in Prozent der Bühne: x, y, Breite, Höhe, Tiefe (0–1), aussen (entfällt unter 760 px), Marke über dem Bild */
  const VARIANTEN = {
    senkrecht: {
      bild: 'flakon', alt: 'Rechteckiger Glasflakon mit bernsteinfarbener Füllung und schwarzer Kappe auf einem runden Steinsockel vor kobaltblauem Grund, daneben eine kleine Kugel',
      titel: 'Strandgut <i>Eau de Parfum</i>', unterschrift: 'Labdanum 12 % · Iris 4 % · Treibholz · 50 ml · 128 €', cta: 'Zum Duft',
      werte: '3 Lamellen · Tiefe .7 / 1 / .55 · scale bis 1,17',
      scheiben: [[8.33, 10, 8.33, 78, .7, 1, 'Kopfnote'], [25, 4, 12.5, 90, 1, 0, 'Herznote'], [66.66, 16, 8.33, 74, .55, 0, 'Basisnote']]
    },
    waagerecht: {
      bild: 'stoff-stapel', alt: 'Stapel gefalteter Leinentücher in Pflaume, Altrosa, Ocker und Creme auf einem Eichentisch',
      titel: 'Leinen <i>gewaschen</i>', unterschrift: 'Tischwäsche aus Leinen · fünf Töne · 140 × 240 cm · ab 38 €', cta: 'Zum Leinen',
      werte: '3 Bänder · Tiefe .55 / 1 / .75 · scale bis 1,17',
      scheiben: [[16.66, 26, 66.66, 10, .55, 1], [8.33, 45, 83.33, 11, 1], [25, 62, 58.33, 9, .75]]
    },
    rechtecke: {
      bild: 'keramik-stillleben', alt: 'Helle Flaschenvase, schwarzes Kugelgefäß, ockerfarbener Becher und flache Schalen auf Gipssockeln vor terrakottafarbener Wand',
      titel: 'Keramik <i>vom Haff</i>', unterschrift: 'Steinzeug, in Ueckermünde gedreht · Einzelstücke · ab 24 €', cta: 'Zur Keramik',
      werte: '3 Rechtecke · Tiefe 1 / .6 / .8 · scale bis 1,17',
      scheiben: [[29.16, 14, 16.66, 44, 1, 0, 'Flaschenvase'], [53.5, 58, 7.5, 18, .6, 1, 'Becher'], [75, 81, 16.66, 17, .8, 0, 'Schale']]
    },
    /* Geschosse: Hausbild 4:3 ohne Beschnitt, Schnitte auf den Geschossdecken (Dach, Obergeschoss, Erdgeschoss) */
    geschosse: {
      bild: 'bild-scheiben-tiefe-speicher', hoch: false, welt: 'speicher', alt: 'Alter Backsteinspeicher mit drei Geschossen: Mansarddach mit fünf erleuchteten Gauben, darunter sechs hohe Sprossenfenster mit Arbeitsplätzen, im Erdgeschoss Rundbogenfenster eines Cafés und ein offenes Tor mit der Hausnummer 9, davor Fahrradbügel und eine Bank',
      titel: '<span class="zeile" style="--z:0"><span>Drei Geschosse,</span></span><span class="zeile" style="--z:1"><span>ein Tagespass.</span></span>',
      unterschrift: 'Speicherstraße 9, Elmshorn · Mo–Fr 8–20 Uhr · Tagespass 25 € inkl. MwSt.', cta: 'Tagespass buchen',
      werte: '3 Geschosse · Tiefe .85 / .7 / 1 · gewählt ± .45 · scale bis 1,1',
      scheiben: [[19.5, 15, 61, 18, .85], [15, 36.5, 70, 21.5, .7], [15, 61.5, 70, 22.5, 1]],
      kopf: { marke: 'Speicherhof', label: 'Speicherhof Coworking, Startseite', nav: [['Heute', '#das-haus'], ['Plätze und Preise', '#das-haus'], ['Räume', '#das-haus'], ['Das Haus', '#das-haus']], rechts: [['Anfahrt', '#anfahrt'], ['Login', '#anfahrt']] }
    }
  };
  /* Kopf der Grundwelt merken, damit der Wechsel zurück sauber ist */
  const kopfHaff = {
    marke: document.querySelector('.marke').textContent, label: document.querySelector('.marke').getAttribute('aria-label'),
    nav: [...document.querySelectorAll('#hauptmenue a')].map(a => [a.textContent, a.getAttribute('href')]),
    rechts: [...document.querySelectorAll('.kopf__rechts a')].map(a => [a.textContent, a.getAttribute('href')])
  };
  function setzeKopf(k) {
    const m = document.querySelector('.marke'); m.textContent = k.marke; m.setAttribute('aria-label', k.label);
    document.querySelectorAll('#hauptmenue a').forEach((a, i) => { a.textContent = k.nav[i][0]; a.setAttribute('href', k.nav[i][1]); });
    document.querySelectorAll('.kopf__rechts a').forEach((a, i) => { a.textContent = k.rechts[i][0]; a.setAttribute('href', k.rechts[i][1]); });
  }

  /* Geschoss wählen: gewählte Scheibe +.45 Tiefe (400 ms), die anderen −.45 (zurück 280 ms) */
  const etagenLinks = [...document.querySelectorAll('[data-geschoss]')];
  let gewaehlt = null;
  function waehle(i) {
    if (i === gewaehlt) return;
    gewaehlt = i;
    buehne.querySelectorAll('.scheibe').forEach((s, j) => {
      s.style.setProperty('--an', i === null ? '0' : (i === j ? '1' : '-1'));
      if (i === j) s.dataset.an = ''; else delete s.dataset.an;
    });
    etagenLinks.forEach((a, j) => a.classList.toggle('ist-an', i === j));
  }
  etagenLinks.forEach((a, j) => {
    a.addEventListener('pointerenter', () => { if (!uhr) waehle(j); });
    a.addEventListener('pointerleave', () => { if (!uhr) waehle(null); });
    a.addEventListener('focus', () => { pause(); waehle(j); });
    a.addEventListener('blur', () => waehle(null));
  });

  let v = 'senkrecht';
  function setzeVariante(neu) {
    if (!VARIANTEN[neu]) neu = 'senkrecht';
    v = neu; body.dataset.v = v;
    const c = VARIANTEN[v];
    const welt = c.welt || 'haff';
    document.querySelectorAll('[data-welt]').forEach(e => { e.hidden = e.dataset.welt !== welt; });
    setzeKopf(c.kopf || kopfHaff);
    gewaehlt = null; etagenLinks.forEach(a => a.classList.remove('ist-an'));
    bild.width = welt === 'speicher' ? 1440 : 1200; bild.height = welt === 'speicher' ? 1080 : 800;
    const quelle = 'bilder/' + c.bild + (c.hoch !== false && matchMedia('(max-width: 760px)').matches ? '-hoch' : '') + '.jpg';
    bild.src = quelle; bild.alt = c.alt;
    document.querySelector('[data-titel]').innerHTML = c.titel;
    document.querySelector('[data-unterschrift]').textContent = c.unterschrift;
    document.querySelector('[data-cta]').textContent = c.cta;
    document.querySelector('[data-werte]').textContent = c.werte;
    buehne.querySelectorAll('.scheibe').forEach(s => s.remove());
    achsen.textContent = '';
    c.scheiben.forEach(([x, y, b, h, tiefe, aussen, marke], j) => {
      const s = document.createElement('div');
      s.className = 'scheibe'; s.setAttribute('aria-hidden', 'true');
      if (aussen) s.dataset.aussen = '';
      s.style.cssText = `--sx:${x};--sy:${y};--sb:${b};--sh:${h};--tiefe:${tiefe};--j:${j};--bild:url('${quelle}')`;
      s.appendChild(document.createElement('span'));
      buehne.appendChild(s);
      if (marke) { const m = document.createElement('span'); m.textContent = marke; m.style.left = (x + b / 2) + '%'; if (aussen) m.dataset.aussen = ''; achsen.appendChild(m); }
    });
    document.querySelectorAll('[data-variante]').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.variante === v)));
    const q = new URLSearchParams(location.search); q.set('v', v);
    history.replaceState(null, '', location.pathname + '?' + q.toString().replace(/=(?=&|$)/g, '') + location.hash);
    heben();
  }
  document.querySelectorAll('[data-variante]').forEach(b => b.addEventListener('click', () => { pause(); setzeVariante(b.dataset.variante); }));

  /* Anheben: einmal beim Laden (1100 ms, 110 ms versetzt), dabei werden die Scheiben scharf */
  function heben() {
    buehne.classList.add('sofort'); buehne.classList.remove('gehoben'); body.classList.remove('steigt'); void buehne.offsetWidth; buehne.classList.remove('sofort');
    if (reduziert()) { buehne.classList.add('gehoben'); body.classList.add('steigt'); return; }
    const los = () => requestAnimationFrame(() => requestAnimationFrame(() => { buehne.classList.add('gehoben'); body.classList.add('steigt'); }));
    if (bild.complete && bild.naturalWidth) los(); else bild.addEventListener('load', los, { once: true });
  }

  /* Zeiger: weich nachgezogen (Lerp 0,08 je Frame), nur mit echtem Hover */
  let zx = 0, zy = 0, ziel = { x: 0, y: 0 }, laeuft = false;
  function setzeZeiger(x, y) { buehne.style.setProperty('--zx', x.toFixed(4)); buehne.style.setProperty('--zy', y.toFixed(4)); }
  function nachziehen() {
    zx += (ziel.x - zx) * .08; zy += (ziel.y - zy) * .08;
    setzeZeiger(zx, zy);
    if (Math.abs(ziel.x - zx) + Math.abs(ziel.y - zy) > .001) requestAnimationFrame(nachziehen); else laeuft = false;
  }
  function anstossen() { if (!laeuft) { laeuft = true; requestAnimationFrame(nachziehen); } }
  if (matchMedia('(hover: hover) and (pointer: fine)').matches) {
    buehne.addEventListener('pointermove', e => {
      if (reduziert() || uhr) return;
      const r = buehne.getBoundingClientRect();
      ziel.x = ((e.clientX - r.left) / r.width) * 2 - 1; ziel.y = ((e.clientY - r.top) / r.height) * 2 - 1;
      anstossen();
      /* Geschosse: das Geschoss unter dem Zeiger ist gewählt, auch in der Liste */
      if (v === 'geschosse') {
        const y = (e.clientY - r.top) / r.height * 100;
        const i = VARIANTEN.geschosse.scheiben.findIndex(([, sy, , sh]) => y >= sy && y <= sy + sh);
        waehle(i < 0 ? null : i);
      }
    });
    buehne.addEventListener('pointerleave', () => { ziel.x = 0; ziel.y = 0; anstossen(); if (v === 'geschosse' && !uhr) waehle(null); });
  }

  /* Scroll-Hub ohne Scroll-Timeline: Skript setzt --hub */
  if (!CSS.supports('animation-timeline: view()')) {
    const hub = () => { if (reduziert() || uhr) return; const r = buehne.getBoundingClientRect(); buehne.style.setProperty('--hub', Math.min(1, Math.max(0, -r.top / r.height)).toFixed(4)); };
    addEventListener('scroll', () => requestAnimationFrame(hub), { passive: true });
  }

  setzeVariante(new URLSearchParams(location.search).get('v'));

  /* Menü auf dem Telefon */
  const kopf = document.querySelector('.kopf'), menue = document.querySelector('.menue');
  menue.addEventListener('click', () => { const auf = !kopf.classList.contains('offen'); kopf.classList.toggle('offen', auf); menue.setAttribute('aria-expanded', String(auf)); menue.textContent = auf ? 'Schließen' : 'Menü'; });
  kopf.addEventListener('keydown', e => { if (e.key === 'Escape' && kopf.classList.contains('offen')) { menue.click(); menue.focus(); } });
  kopf.querySelectorAll('ul a').forEach(a => a.addEventListener('click', () => { if (kopf.classList.contains('offen')) menue.click(); }));

  /* ---------- Vorführung: Anheben, gedachter Zeiger kreist, Scroll-Hub schwillt ---------- */
  let uhr = null, t0 = 0;
  const RUNDE = 8000;
  function vorfuehren() {
    pause(); scrollTo(0, 0);
    body.dataset.simuliert = '';
    t0 = performance.now(); let runde = -1;
    const schritt = t => {
      const z = t - t0, n = Math.floor(z / RUNDE), q = (z % RUNDE) / RUNDE;
      if (n !== runde) { runde = n; waehle(null); heben(); }
      if (v === 'geschosse') {
        /* gedachter Zeiger wandert über die Geschosse: EG, OG, Dach, dann loslassen */
        const i = q < .2 || q > .86 ? null : 2 - Math.min(2, Math.floor((q - .2) / .22));
        waehle(i);
        if (!reduziert()) {
          const a = Math.max(0, Math.min(1, (q - .14) / .1)) * Math.max(0, Math.min(1, (.95 - q) / .08));
          zx = Math.sin(q * Math.PI * 3) * .6 * a; zy = (i === null ? 0 : (i - 1) * .55) * a;
          setzeZeiger(zx, zy);
        }
      } else if (!reduziert()) {
        const a = Math.max(0, Math.min(1, (q - .12) / .1)) * Math.max(0, Math.min(1, (.95 - q) / .08));
        const w = q * Math.PI * 2 * 1.5;
        zx = Math.sin(w) * .95 * a; zy = Math.cos(w * .8) * .7 * a;
        setzeZeiger(zx, zy);
        buehne.style.setProperty('--hub', (Math.max(0, Math.sin(Math.max(0, q - .55) / .4 * Math.PI)) * .9).toFixed(4));
      }
      uhr = requestAnimationFrame(schritt);
    };
    uhr = requestAnimationFrame(schritt);
  }
  function pause() {
    cancelAnimationFrame(uhr); uhr = null;
    if ('simuliert' in body.dataset) { delete body.dataset.simuliert; buehne.style.removeProperty('--hub'); }
  }
  function neustart() { pause(); waehle(null); zx = zy = 0; ziel = { x: 0, y: 0 }; setzeZeiger(0, 0); scrollTo(0, 0); heben(); }

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