Zum Inhalt
← Seitenübergänge

Seitenwechsel in wachsender Markenform

Die neue Seite wächst in einer Markenform: als Laib von unten, als Kreis ab dem Klick oder als Terrassen; beim Zurück schrumpft sie wieder.

Bäckerei, Konditorei, Hofkäserei: die neue Seite geht wie ein Laib auf, zurück sinkt sie ab.

Die neue Seite erscheint in einer Form, die zur Marke gehört und sichtbar wächst: ein Laib, der sich von unten wölbt, ein Kreis, der am geklickten Link aufgeht, oder Stufen, die wie Terrassen nacheinander steigen. Technisch ist das ein echter Seitenaufruf mit Cross-Document View Transition. Der Kopf der Seite bleibt stehen, darunter schneidet clip-path mit path() die neue Seite frei. Beim Zurück läuft es umgekehrt: Die Form schrumpft, und der Kreis kehrt zu dem Link zurück, von dem man kam.

Ein Seitenwechsel ist der Moment, in dem eine Marke sonst nichts sagt. Eine wiedererkennbare Form macht ihn zur Signatur, ohne den Inhalt aufzuhalten. Die Richtung erklärt die Struktur: Hinein wächst, zurück schrumpft. Weil die Form erst langsam und dann zügig wächst, nimmt das Auge sie als Form wahr und nicht als schnellen Wischer.

Wann einsetzen

Websites mit wenigen, klaren Ebenen (Übersicht und Detail) und einer Marke, die eine einfache Grundform hat: Bäckerei (Laib), Fotografie und Optik (Kreis, Blende), Weingut, Treppenbau oder Architektur (Stufe). Gut als zweite Signatur neben einem ruhigen Hero.

Wann nicht

Nicht bei Formularstrecken, Shops mit vielen schnellen Wechseln (Filter, Paginierung) oder auf Seiten, die man oft hin und her wechselt; dort kurze Überblendung. Nicht, wenn die Form nichts mit der Marke zu tun hat. Nicht mit Federkurven: Eine Fläche, die überschwingt, wirkt billig. Nicht zusätzlich zu einem Preloader.

Varianten

  • laib: weiche Wölbung von unten, die Form steigt und flacht oben ab; zurück sinkt sie wieder ab
  • klickpunkt: Kreis ab dem geklickten Link, Position per pageswap und pagereveal übergeben; zurück schrumpft der Kreis in den Link der Übersicht
  • stufe: fünf Stufen steigen mit Versatz nacheinander wie Terrassen; zurück fallen sie von oben

Umsetzung

  1. Im Kopf jeder Seite als erste Zeile <style>@view-transition{navigation:auto}</style>, damit der Übergang auch vor dem Laden der Stylesheets angemeldet ist.
  2. Der Kopf der Seite bekommt view-transition-name: kopf und steht während des Wechsels still; ::view-transition-old(root) und ::view-transition-new(root) bekommen animation: none, die Bewegung kommt aus JavaScript.
  3. Beim Klick auf einen Link die Position merken; im pageswap-Ereignis der alten Seite schreibt das Skript sie als Anteil der Fensterbreite und -höhe in sessionStorage.
  4. Im pagereveal-Ereignis der neuen Seite (Skript ohne defer im Kopf oder am Ende von body) die Richtung bestimmen: aus navigation.activation.from und entry (Ebene der Seite, bei traverse der Index). Tiefer heißt vor, höher oder Browser-Zurück heißt zurück.
  5. Nach e.viewTransition.ready mit document.documentElement.animate() die Form animieren: vor auf '::view-transition-new(root)' von der Startform zur deckenden Form, zurück auf '::view-transition-old(root)' (mit z-index darüber) von deckend zur Startform. Die alte bzw. darunterliegende Seite dunkelt dabei über eine Grundfarbe von ::view-transition ab.
  6. Die Formen als path() in Pixeln aus innerWidth und innerHeight mit gleicher Befehlsfolge in allen Schlüsselbildern, damit sie interpolieren; der Kreis als circle() bis zur fernsten Ecke. Die Endform deckt immer ganz (Kreis bis Math.hypot, Pfad über die Ränder hinaus), nie auf none animieren.
  7. Bei reduzierter Bewegung kurze Überblendung (150 ms, linear). Ohne Unterstützung navigiert der Link ganz normal.

Robust umsetzen

Telefon. Gleiche Formen, aus der Fenstergröße berechnet. Die Wölbung ist am Telefon flacher (16 % der Höhe), die Stufen bleiben fünf. Der Kreis startet am angetippten Link. Die feste untere Leiste kann wie der Kopf einen eigenen view-transition-name bekommen und stehen bleiben.

Ohne Bewegung. Kurze Überblendung von 150 ms ohne Form und ohne Abdunkeln. Die Richtung spielt keine Rolle.

Barrierefrei. Ein echter Seitenaufruf: Fokus, Titel und Verlauf verhalten sich wie gewohnt, Bildschirmleser lesen die neue Seite. Nichts blinkt, die Form läuft unter 1,2 s. Bei reduzierter Bewegung nur Überblendung. Links bleiben normale <a href>, ohne JavaScript und ohne Unterstützung navigieren sie einfach.

Code

<!DOCTYPE html>
<!--
  Seitenwechsel in wachsender Markenform
  Fundus · Seitenübergänge · https://cecodesign.de/lab/fundus/seitenuebergaenge/seitenwechsel-wachsende-form/
  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">
<style>@view-transition{navigation:auto}</style>
<title>Seitenwechsel in wachsender Markenform · Fundus-Demo</title>
<script>(() => {
  const h = document.documentElement, q = new URLSearchParams(location.search);
  h.classList.add('js');
  if (matchMedia('(prefers-reduced-motion: reduce)').matches) h.classList.add('reduziert');
  const v = q.get('v');
  h.dataset.v = ['laib', 'klickpunkt', 'stufe'].includes(v) ? v : 'laib';
  h.dataset.seite = q.get('seite') === 'b' ? 'b' : 'a';
  // pagereveal muss vor dem ersten Bild registriert sein: hier auffangen, unten verarbeiten
  addEventListener('pagereveal', e => { if (window.wfReveal) window.wfReveal(e); else window.wfFrueh = e; });
})();</script>
<style>
@font-face { font-family: 'Shrikhand'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/shrikhand@latest/latin-400-normal.woff2') format('woff2'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'Onest'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/onest:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-display: swap; }
@font-face { font-family: 'Instrument Serif'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/instrument-serif@latest/latin-400-normal.woff2') format('woff2'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'Instrument Serif'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/instrument-serif@latest/latin-400-italic.woff2') format('woff2'); font-weight: 400; font-style: italic; font-display: swap; }
@font-face { font-family: 'Instrument Sans'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/instrument-sans:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 400 700; font-display: swap; }
@font-face { font-family: '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: 'EB Garamond'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/eb-garamond:vf@latest/latin-wght-normal.woff') format('woff'); font-weight: 400 800; font-style: normal; font-display: swap; }
@font-face { font-family: 'EB Garamond'; font-style: italic; src: url('https://cdn.jsdelivr.net/fontsource/fonts/eb-garamond:vf@latest/latin-wght-italic.woff2') format('woff2-variations'); font-weight: 400 800; font-display: swap; }
@font-face { font-family: 'DM Mono'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/dm-mono@latest/latin-500-normal.woff2') format('woff2'); font-weight: 500; 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; }

/* ============ Der Seitenwechsel ============
   Die Form kommt aus JavaScript (Web Animations API auf ::view-transition-new/old(root)),
   die Standard-Überblendung ist deshalb aus. Der Kopf steht still. */
::view-transition { background: var(--vt-dunkel); }
::view-transition-old(root), ::view-transition-new(root) { animation: none; mix-blend-mode: normal; }
html:active-view-transition-type(zurueck)::view-transition-old(root) { z-index: 2; }
::view-transition-group(kopf), ::view-transition-group(pult), ::view-transition-group(zeiger) { animation: none; z-index: 3; }
::view-transition-old(kopf), ::view-transition-old(pult), ::view-transition-old(zeiger) { display: none; }
::view-transition-new(kopf), ::view-transition-new(pult), ::view-transition-new(zeiger) { animation: none; }
/* Reduzierte Bewegung: kurze Überblendung, keine Form */
html:active-view-transition-type(ruhig)::view-transition-old(root) { animation: wf-aus 150ms linear both; }
html:active-view-transition-type(ruhig)::view-transition-new(root) { animation: wf-ein 150ms linear both; }
html:active-view-transition-type(ruhig)::view-transition { background: none; }
html:active-view-transition-type(ruhig)::view-transition-group(root) { animation-duration: 150ms; }
@keyframes wf-aus { to { opacity: 0; } }
@keyframes wf-ein { from { opacity: 0; } }

/* ============ Grundlage ============ */
* { box-sizing: border-box; margin: 0; }
html { background: var(--grund); color: var(--text); }
body { min-height: 100svh; background: var(--grund); color: var(--text); font: 400 1.0625rem/1.6 var(--schrift-text); -webkit-font-smoothing: antialiased; }
a { color: inherit; }
:focus-visible { outline: 2px solid var(--fokus, currentColor); outline-offset: 3px; }
.rahmen { width: min(100% - 2 * var(--rand, 24px), 1320px); margin-inline: auto; }
.seite { display: none; min-height: 100svh; background: var(--grund); color: var(--text); }
html:not([data-v]) .seite[data-s=laib-a],
html[data-v=laib][data-seite=a] .seite[data-s=laib-a], html[data-v=laib][data-seite=b] .seite[data-s=laib-b],
html[data-v=klickpunkt][data-seite=a] .seite[data-s=klick-a], html[data-v=klickpunkt][data-seite=b] .seite[data-s=klick-b],
html[data-v=stufe][data-seite=a] .seite[data-s=stufe-a], html[data-v=stufe][data-seite=b] .seite[data-s=stufe-b] { display: block; }
.kopf { view-transition-name: kopf; position: relative; z-index: 2; }
/* Kopf bleibt nur stehen, wenn beide Seiten dieselbe Grundfarbe haben; sonst wächst er mit der Seite */
html[data-v=klickpunkt] .kopf, html[data-v=stufe] .kopf { view-transition-name: none; }
.ziffern { font-variant-numeric: tabular-nums; }

/* ---------- laib: Brotladen Siebold, Nienburg (lebensmittelhandwerk-theke) ---------- */
html[data-v=laib] { --grund: #F7F1E6; --text: #241B14; --vt-dunkel: #2B211B; --rand: clamp(16px, 4vw, 56px); --fokus: #8A3C12; --schrift-text: 'Onest', system-ui, sans-serif; }
[data-s^=laib] { --kruste: #EBD8BB; --ofen: #2B211B; --signal: #8A3C12; --leise: #665746; --linie: #DDCDB4; font-family: 'Onest', system-ui, sans-serif; }
[data-s=laib-b] { --grund: #EBD8BB; --linie: #CDB48F; --leise: #5E4E3D; }
.lb-kopf { display: flex; align-items: center; justify-content: space-between; gap: 12px 24px; flex-wrap: wrap; padding-block: 22px; }
.lb-marke { display: flex; align-items: baseline; gap: 12px; text-decoration: none; }
.lb-marke b { font: 400 1.75rem/1 'Shrikhand', Georgia, serif; }
.lb-marke span { font-size: .875rem; font-weight: 500; color: var(--leise); }
.lb-nav { display: flex; align-items: center; gap: 6px 22px; flex-wrap: wrap; font-weight: 500; font-size: .9375rem; }
.lb-nav a:not(.pille) { text-decoration: none; }
.pille { display: inline-flex; align-items: center; height: 44px; padding: 0 20px; border-radius: 999px; background: var(--signal); color: #fff; text-decoration: none; font-weight: 600; }
.lb-auftakt { padding-block: clamp(20px, 5vw, 72px) clamp(20px, 3vw, 40px); }
.lb-tag { font-weight: 600; font-size: .9375rem; color: var(--leise); }
.lb-auftakt h1 { font: 400 clamp(2.75rem, 1.2rem + 5vw, 5.75rem)/.98 'Shrikhand', Georgia, serif; max-width: 13ch; margin-top: 12px; text-wrap: balance; }
.brote { list-style: none; padding: 0; border-top: 2px solid var(--text); }
.brote li { border-bottom: 1px solid var(--linie); }
.zeile { display: grid; grid-template-columns: 3.5rem minmax(0, 1fr) auto auto; align-items: center; gap: 8px 24px; padding: 18px 12px; text-decoration: none; border-radius: 16px; transition: background-color 150ms linear; }
.zeile:hover { background: var(--kruste); }
.zeile .nr { font-weight: 600; color: var(--leise); font-variant-numeric: tabular-nums; }
.zeile b { font: 400 clamp(1.625rem, 1.2rem + 1.6vw, 2.5rem)/1.05 'Shrikhand', Georgia, serif; }
.zeile .meta { font-weight: 500; font-variant-numeric: tabular-nums; color: var(--leise); }
.zeile .pfeil { font-weight: 600; display: inline-flex; gap: 8px; align-items: center; }
.lb-detail { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 24px clamp(12px, 2vw, 28px); padding-block: clamp(16px, 4vw, 48px) clamp(48px, 8vw, 120px); position: relative; }
.zurueck { grid-column: 1 / -1; display: inline-flex; gap: 8px; align-items: center; font-weight: 600; width: fit-content; }
.lb-detail h1 { grid-column: 1 / -1; font: 400 clamp(2.25rem, .6rem + 8vw, 8.5rem)/.92 'Shrikhand', Georgia, serif; letter-spacing: -.01em; hyphens: auto; overflow-wrap: break-word; }
.lb-detail .lead { grid-column: 1 / span 6; font-size: 1.1875rem; max-width: 42ch; }
.lb-detail dl { grid-column: 8 / -1; display: grid; grid-template-columns: auto minmax(0, 1fr); border-top: 2px solid var(--text); }
.lb-detail dt, .lb-detail dd { padding: 10px 0; border-bottom: 1px solid var(--linie); }
.lb-detail dt { color: var(--leise); font-weight: 500; padding-right: 24px; }
.lb-detail dd { font-weight: 600; font-variant-numeric: tabular-nums; }
.lb-detail .pille { grid-column: 1 / span 6; width: fit-content; }
.lb-laib { position: absolute; right: 0; bottom: 0; width: min(46vw, 560px); aspect-ratio: 2 / 1; background: var(--ofen); border-radius: 50% 50% 0 0 / 100% 100% 0 0; opacity: .08; pointer-events: none; }

/* ---------- klickpunkt: Mareike Hellwarth Fotografie, Jever (fotograf-film-bild-zuerst) ---------- */
html[data-v=klickpunkt] { --grund: #f4f4f2; --text: #141414; --vt-dunkel: #0e0e0e; --rand: clamp(16px, 3vw, 40px); --fokus: #141414; --schrift-text: 'Instrument Sans', system-ui, sans-serif; }
[data-s^=klick] { --leise: #6D6D6D; --linie: #D0D0CE; font-family: 'Instrument Sans', system-ui, sans-serif; }
[data-s=klick-b] { --grund: #0e0e0e; --text: #f2f2f2; --leise: #A8A8A8; --linie: #2a2a2a; --fokus: #f2f2f2; }
.mono { font: 400 .75rem/1.3 'Fragment Mono', ui-monospace, monospace; letter-spacing: .08em; text-transform: uppercase; }
.kp-kopf { display: flex; align-items: baseline; justify-content: space-between; gap: 10px 24px; flex-wrap: wrap; padding-block: 20px; }
.kp-marke { text-decoration: none; display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; }
.kp-marke b { font: 400 1.5rem/1 'Instrument Serif', Georgia, serif; }
.kp-marke span { color: var(--leise); }
.kp-nav { display: flex; gap: 6px 22px; flex-wrap: wrap; }
.kp-nav a { text-decoration: none; }
.kp-titel { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding-block: clamp(16px, 4vw, 48px) clamp(14px, 2vw, 24px); border-bottom: 1px solid var(--linie); margin-bottom: clamp(14px, 2vw, 24px); }
.kp-titel h1 { font: 400 clamp(2.5rem, 1.5rem + 4vw, 5rem)/1 'Instrument Serif', Georgia, serif; }
.serien { list-style: none; padding: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(14px, 2vw, 28px) clamp(10px, 1.6vw, 20px); padding-bottom: clamp(40px, 6vw, 80px); }
.serie a { display: block; text-decoration: none; }
.serie img { display: block; width: 100%; aspect-ratio: 4 / 5; object-fit: cover; background: #e6e6e6; }
.serie p { display: flex; justify-content: space-between; gap: 12px; margin-top: 10px; }
.serie em { font: italic 400 1.25rem/1.2 'Instrument Serif', Georgia, serif; }
.serie .mono { color: var(--leise); padding-top: 4px; }
.kp-detail { padding-block: clamp(12px, 3vw, 32px) clamp(40px, 6vw, 80px); display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 20px clamp(10px, 1.6vw, 20px); }
.kp-detail .zurueck { grid-column: 1 / -1; text-decoration: none; }
.kp-detail h1 { hyphens: auto; overflow-wrap: break-word; grid-column: 1 / span 7; font: 400 clamp(2.75rem, 1rem + 6vw, 6.5rem)/.95 'Instrument Serif', Georgia, serif; align-self: end; }
.kp-detail .meta { grid-column: 9 / -1; align-self: end; color: var(--leise); display: grid; gap: 6px; }
.kp-detail img { grid-column: 1 / -1; width: 100%; aspect-ratio: 16 / 9; object-fit: cover; background: #1a1a1a; }
.kp-detail .text { grid-column: 7 / span 5; max-width: 46ch; color: #d8d8d8; }

/* ---------- stufe: Weingut Auerbeck, Bürgstadt (weingut-hofladen-herkunft) ---------- */
html[data-v=stufe] { --grund: #F1ECE2; --text: #22211F; --vt-dunkel: #1b1c1f; --rand: clamp(16px, 4vw, 56px); --fokus: #8A3A2B; --schrift-text: 'EB Garamond', Georgia, serif; }
[data-s^=stufe] { --loess: #E6DCCB; --signal: #8A3A2B; --leise: #5A564F; --linie: #D8CFC0; font-family: 'EB Garamond', Georgia, serif; font-size: 1.25rem; }
[data-s=stufe-b] { --grund: #2F3236; --text: #F1ECE2; --leise: #C9C1B2; --linie: rgba(241, 236, 226, .22); --fokus: #F1ECE2; }
.dm { font: 500 .8125rem/1.35 'DM Mono', ui-monospace, monospace; letter-spacing: .04em; font-variant-numeric: tabular-nums; }
.st-kopf { display: flex; align-items: baseline; justify-content: space-between; gap: 10px 24px; flex-wrap: wrap; padding-block: 22px; border-bottom: 1px solid var(--linie); }
.st-marke { text-decoration: none; display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; }
.st-marke b { font-weight: 500; font-size: 1.5rem; letter-spacing: .01em; }
.st-marke span { color: var(--leise); }
.st-nav { display: flex; gap: 6px 24px; flex-wrap: wrap; font-size: 1.125rem; }
.st-nav a { text-decoration: none; }
.st-auftakt { padding-block: clamp(24px, 5vw, 72px) clamp(16px, 3vw, 40px); display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 16px clamp(12px, 2vw, 28px); align-items: end; }
.st-auftakt h1 { grid-column: 1 / span 8; font-weight: 400; font-size: clamp(2.75rem, 1.6rem + 5vw, 6rem); line-height: 1; letter-spacing: -.015em; }
.st-auftakt h1 em { font-style: italic; }
.st-auftakt p { grid-column: 9 / -1; color: var(--leise); }
.lagen { list-style: none; padding: 0; border-top: 1px solid var(--text); margin-bottom: clamp(40px, 6vw, 80px); }
.lagen li { border-bottom: 1px solid var(--linie); }
.lage { display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(0, 2fr) auto; gap: 8px 28px; align-items: baseline; padding: 22px 0; text-decoration: none; }
.lage em { font-size: clamp(1.875rem, 1.3rem + 2vw, 3rem); line-height: 1.05; }
.lage .dm { color: var(--leise); display: flex; gap: 6px 22px; flex-wrap: wrap; }
.lage .weiter { font-style: italic; text-decoration: underline; text-underline-offset: .2em; text-decoration-thickness: 1px; }
.lage:hover .weiter { color: var(--signal); }
.st-detail { padding-block: clamp(16px, 4vw, 48px) clamp(48px, 7vw, 96px); display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 24px clamp(12px, 2vw, 28px); }
.st-detail .zurueck { grid-column: 1 / -1; font-style: italic; }
.st-detail h1 { hyphens: auto; overflow-wrap: break-word; grid-column: 1 / -1; font-style: italic; font-weight: 400; font-size: clamp(3.25rem, 1.4rem + 7vw, 8rem); line-height: .95; letter-spacing: -.01em; }
.st-daten { grid-column: 1 / span 5; display: grid; grid-template-columns: auto minmax(0, 1fr); border-top: 1px solid var(--linie); }
.st-daten dt, .st-daten dd { padding: 9px 0; border-bottom: 1px solid var(--linie); }
.st-daten dt { color: var(--leise); padding-right: 20px; }
.weine { grid-column: 7 / -1; list-style: none; padding: 0; border-top: 1px solid var(--linie); }
.weine li { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2px 16px; padding: 12px 0; border-bottom: 1px solid var(--linie); }
.weine .dm { color: var(--leise); grid-column: 1 / -1; }
.weine b { font-weight: 500; }

/* ---------- Zeiger der Vorführung, Pult ---------- */
.zeiger { position: fixed; left: 0; top: 0; z-index: 60; width: 28px; height: 28px; margin: -14px 0 0 -14px; border-radius: 50%; border: 1.5px solid #fff; background: rgba(255, 255, 255, .25); mix-blend-mode: difference; pointer-events: none; opacity: 0; view-transition-name: zeiger; }
.zeiger.an { opacity: 1; }
.zeiger.drueckt { transform: scale(.7); }
.pult { position: fixed; left: 16px; bottom: 16px; z-index: 50; display: flex; flex-direction: column; gap: 10px; width: min(380px, calc(100vw - 32px)); padding: 12px 14px; border-radius: 10px; background: rgba(18, 18, 20, .92); color: #f3f2ee; font: 500 11px/1.35 'Geist Mono', ui-monospace, monospace; letter-spacing: .1em; text-transform: uppercase; view-transition-name: pult; }
.pult .gruppe { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.pult .gruppe > span { color: rgba(243, 242, 238, .72); margin-right: 4px; }
.pult button { background: transparent; color: inherit; border: 1px solid rgba(243, 242, 238, .3); border-radius: 5px; height: 28px; padding: 0 9px; font: inherit; letter-spacing: inherit; text-transform: inherit; cursor: pointer; }
.pult button[aria-pressed=true] { background: #f3f2ee; color: #111417; border-color: #f3f2ee; }
.pult button:focus-visible { outline: 2px solid #f3f2ee; outline-offset: 2px; }
.pult p { text-transform: none; letter-spacing: .02em; color: rgba(243, 242, 238, .8); }

/* ---------- Telefon ---------- */
@media (max-width: 760px) {
  .zeile { grid-template-columns: 2.25rem minmax(0, 1fr); padding: 14px 8px; }
  .zeile .meta { grid-column: 2; } .zeile .pfeil { display: none; }
  .lb-detail .lead, .lb-detail dl, .lb-detail .pille { grid-column: 1 / -1; }
  .lb-nav a:not(.pille), .kp-nav, .st-nav a:not(:last-child) { display: none; }
  .serien { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .kp-detail h1, .kp-detail .meta, .kp-detail .text { grid-column: 1 / -1; }
  .st-auftakt h1, .st-auftakt p, .st-daten, .weine { grid-column: 1 / -1; }
  .lage { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 360px) { .serien { grid-template-columns: minmax(0, 1fr); } .zeile b { font-size: 1.375rem; overflow-wrap: anywhere; } .zeile { grid-template-columns: 1.75rem minmax(0, 1fr); gap: 4px 12px; } }

.vorschau body { overflow: hidden; }
:root.reduziert * { transition-duration: .01ms !important; animation-duration: .01ms !important; }
@media (forced-colors: active) {
  button, [role=button], [role=tab], [role=switch], input, select, textarea { border: 1px solid ButtonText !important; }
}
</style>
</head>
<body>

<!-- ================= laib · Seite A ================= -->
<div class="seite" data-s="laib-a">
  <header class="rahmen lb-kopf kopf"><a class="lb-marke" href="?seite=a" data-wf="a"><b>Siebold</b><span>Brotladen in Nienburg</span></a><nav class="lb-nav" aria-label="Brotladen"><a href="?seite=a" data-wf="a" aria-current="page">Sortiment</a><a href="#laib-laden">Laden</a><a class="pille" href="#laib-laden">Vorbestellen</a></nav></header>
  <main class="rahmen">
    <section class="lb-auftakt"><p class="lb-tag">Mittwoch · ab 7 Uhr in der Theke</p><h1>Was heute aus dem Ofen kommt.</h1></section>
    <ul class="brote" aria-label="Brote heute">
      <li><a class="zeile" href="?seite=b&amp;brot=roggen" data-wf="b" data-ziel="roggen"><span class="nr">01</span><b>Roggenmischbrot</b><span class="meta">1.000 g · 5,20 €</span><span class="pfeil">Ansehen <span aria-hidden="true">→</span></span></a></li>
      <li><a class="zeile" href="?seite=b&amp;brot=dinkel" data-wf="b" data-ziel="dinkel"><span class="nr">02</span><b>Dinkelvollkorn</b><span class="meta">750 g · 5,60 €</span><span class="pfeil">Ansehen <span aria-hidden="true">→</span></span></a></li>
      <li><a class="zeile" href="?seite=b&amp;brot=weizen" data-wf="b" data-ziel="weizen"><span class="nr">03</span><b>Weizenkruste</b><span class="meta">500 g · 3,90 €</span><span class="pfeil">Ansehen <span aria-hidden="true">→</span></span></a></li>
      <li><a class="zeile" href="?seite=b&amp;brot=saaten" data-wf="b" data-ziel="saaten"><span class="nr">04</span><b>Saatenlaib</b><span class="meta">750 g · 5,90 €</span><span class="pfeil">Ansehen <span aria-hidden="true">→</span></span></a></li>
    </ul>
    <p id="laib-laden" class="lb-tag" style="padding-block: 28px 40px">Lange Straße 21, 31582 Nienburg · Beispielangaben</p>
  </main>
</div>
<!-- ================= laib · Seite B ================= -->
<div class="seite" data-s="laib-b">
  <header class="rahmen lb-kopf kopf"><a class="lb-marke" href="?seite=a" data-wf="a"><b>Siebold</b><span>Brotladen in Nienburg</span></a><nav class="lb-nav" aria-label="Brotladen"><a href="?seite=a" data-wf="a">Sortiment</a><a href="#laib-laden">Laden</a><a class="pille" href="#laib-laden">Vorbestellen</a></nav></header>
  <main class="rahmen lb-detail">
    <span class="lb-laib" aria-hidden="true"></span>
    <a class="zurueck" href="?seite=a" data-wf="a"><span aria-hidden="true">←</span> Zurück zum Sortiment</a>
    <h1 data-feld="name">Roggenmischbrot</h1>
    <p class="lead" data-feld="text">Sechzig Teile Roggen, vierzig Teile Weizen, Sauerteig vom Vortag. Die Kruste wird dunkel, die Krume bleibt saftig bis Samstag.</p>
    <dl><dt>Gewicht</dt><dd data-feld="gewicht">1.000 g</dd><dt>Preis</dt><dd data-feld="preis">5,20 €</dd><dt>Grundpreis</dt><dd data-feld="grund">5,20 € je kg</dd><dt>Enthält</dt><dd data-feld="allergene">Roggen, Weizen</dd><dt>Gibt es</dt><dd data-feld="tage">Montag bis Samstag</dd></dl>
    <a class="pille" href="#laib-laden">Für morgen vorbestellen</a>
  </main>
</div>

<!-- ================= klickpunkt · Seite A ================= -->
<div class="seite" data-s="klick-a">
  <header class="rahmen kp-kopf kopf"><a class="kp-marke" href="?seite=a" data-wf="a"><b>Mareike Hellwarth</b><span class="mono">Fotografie · Jever</span></a><nav class="kp-nav mono" aria-label="Fotografie"><a href="?seite=a" data-wf="a" aria-current="page">Serien</a><a href="#kp-kontakt">Aufträge</a><a href="#kp-kontakt">Kontakt</a></nav></header>
  <main class="rahmen">
    <div class="kp-titel"><h1>Serien</h1><span class="mono">06 Serien · 2021 bis 2026</span></div>
    <ul class="serien">
      <li class="serie"><a href="?seite=b&amp;serie=geest" data-wf="b" data-ziel="geest"><img src="bilder/huegel-nebel-hoch.jpg" alt="Hügel im Nebel, Morgenlicht" width="800" height="1000"><p><em>Nebel über der Geest</em><span class="mono">2025</span></p></a></li>
      <li class="serie"><a href="?seite=b&amp;serie=fenster" data-wf="b" data-ziel="fenster"><img src="bilder/fenster-licht-hoch.jpg" alt="Licht fällt durch ein Fenster auf eine Wand" width="800" height="1000"><p><em>Nordlicht im Atelier</em><span class="mono">2024</span></p></a></li>
      <li class="serie"><a href="?seite=b&amp;serie=treppe" data-wf="b" data-ziel="treppe"><img src="bilder/beton-treppe-hoch.jpg" alt="Betontreppe mit harten Schatten" width="800" height="1000"><p><em>Treppenhäuser</em><span class="mono">2023</span></p></a></li>
      <li class="serie"><a href="?seite=b&amp;serie=keramik" data-wf="b" data-ziel="keramik"><img src="bilder/keramik-stillleben-hoch.jpg" alt="Keramikgefäße als Stillleben" width="800" height="1000"><p><em>Ton und Glasur</em><span class="mono">2026</span></p></a></li>
      <li class="serie"><a href="?seite=b&amp;serie=stoff" data-wf="b" data-ziel="stoff"><img src="bilder/stoff-stapel-hoch.jpg" alt="Gefaltete Stoffe im Stapel" width="800" height="1000"><p><em>Weberei Rasch</em><span class="mono">2022</span></p></a></li>
      <li class="serie"><a href="?seite=b&amp;serie=zitronen" data-wf="b" data-ziel="zitronen"><img src="bilder/zitronen-tisch-hoch.jpg" alt="Zitronen auf einem Tisch im Sonnenlicht" width="800" height="1000"><p><em>Sommertisch</em><span class="mono">2021</span></p></a></li>
    </ul>
    <p id="kp-kontakt" class="mono" style="padding-bottom: 40px; color: var(--leise)">Anfragen für Aufträge per Telefon oder E-Mail · Beispielangaben</p>
  </main>
</div>
<!-- ================= klickpunkt · Seite B ================= -->
<div class="seite" data-s="klick-b">
  <header class="rahmen kp-kopf kopf"><a class="kp-marke" href="?seite=a" data-wf="a"><b>Mareike Hellwarth</b><span class="mono">Fotografie · Jever</span></a><nav class="kp-nav mono" aria-label="Fotografie"><a href="?seite=a" data-wf="a">Serien</a><a href="#kp-kontakt">Aufträge</a><a href="#kp-kontakt">Kontakt</a></nav></header>
  <main class="rahmen kp-detail">
    <a class="zurueck mono" href="?seite=a" data-wf="a"><span aria-hidden="true">←</span> Alle Serien</a>
    <h1 data-feld="name">Nebel über der Geest</h1>
    <p class="meta mono"><span data-feld="jahr">2025 · 14 Bilder</span><span data-feld="ort">Friesland, Oktober</span></p>
    <img data-feld="bild" src="bilder/huegel-nebel.jpg" alt="Hügel im Nebel, Morgenlicht" width="1600" height="900">
    <p class="text" data-feld="text">Vierzehn Morgen zwischen Sande und Schortens. Immer dieselbe Stunde, immer ein anderer Nebel.</p>
  </main>
</div>

<!-- ================= stufe · Seite A ================= -->
<div class="seite" data-s="stufe-a">
  <header class="rahmen st-kopf kopf"><a class="st-marke" href="?seite=a" data-wf="a"><b>Weingut Auerbeck</b><span class="dm">Bürgstadt am Main</span></a><nav class="st-nav" aria-label="Weingut"><a href="?seite=a" data-wf="a" aria-current="page">Lagen</a><a href="#st-hof">Hofladen</a><a href="#st-hof">Weine</a></nav></header>
  <main class="rahmen">
    <section class="st-auftakt"><h1>Drei Lagen, <em>ein</em> Hang.</h1><p>Buntsandstein von unten bis oben, aber jede Terrasse schmeckt anders. Hier stehen die Lagen mit ihren Zahlen.</p></section>
    <ul class="lagen">
      <li><a class="lage" href="?seite=b&amp;lage=staffel" data-wf="b" data-ziel="staffel"><em>Hohe Staffel</em><span class="dm"><span>Neigung 48 %</span><span>210–260 m</span><span>Südsüdwest</span></span><span class="weiter">Zur Lage</span></a></li>
      <li><a class="lage" href="?seite=b&amp;lage=buckel" data-wf="b" data-ziel="buckel"><em>Kalkbuckel</em><span class="dm"><span>Neigung 31 %</span><span>180–215 m</span><span>Süd</span></span><span class="weiter">Zur Lage</span></a></li>
      <li><a class="lage" href="?seite=b&amp;lage=bruch" data-wf="b" data-ziel="bruch"><em>Am Steinbruch</em><span class="dm"><span>Neigung 22 %</span><span>150–190 m</span><span>Südost</span></span><span class="weiter">Zur Lage</span></a></li>
    </ul>
    <p id="st-hof" class="dm" style="padding-bottom: 40px; color: var(--leise)">Hofladen Freitag und Samstag · Beispielangaben</p>
  </main>
</div>
<!-- ================= stufe · Seite B ================= -->
<div class="seite" data-s="stufe-b">
  <header class="rahmen st-kopf kopf"><a class="st-marke" href="?seite=a" data-wf="a"><b>Weingut Auerbeck</b><span class="dm">Bürgstadt am Main</span></a><nav class="st-nav" aria-label="Weingut"><a href="?seite=a" data-wf="a">Lagen</a><a href="#st-hof">Hofladen</a><a href="#st-hof">Weine</a></nav></header>
  <main class="rahmen st-detail">
    <a class="zurueck" href="?seite=a" data-wf="a"><span aria-hidden="true">←</span> Alle Lagen</a>
    <h1 data-feld="name">Hohe Staffel</h1>
    <dl class="st-daten dm"><dt>Neigung</dt><dd data-feld="neigung">48 %</dd><dt>Höhe</dt><dd data-feld="hoehe">210–260 m</dd><dt>Boden</dt><dd data-feld="boden">Buntsandstein, steinig</dd><dt>Ausrichtung</dt><dd data-feld="richtung">Südsüdwest</dd><dt>Terrassen</dt><dd data-feld="terrassen">11, von Hand</dd></dl>
    <ul class="weine" data-feld="weine">
      <li><b>2024 Spätburgunder trocken</b><span class="ziffern">22,00 €</span><span class="dm">0,75 l · 29,33 € je Liter · enthält Sulfite</span></li>
      <li><b>2024 Silvaner trocken</b><span class="ziffern">14,50 €</span><span class="dm">0,75 l · 19,33 € je Liter · enthält Sulfite</span></li>
    </ul>
  </main>
</div>

<span class="zeiger" aria-hidden="true"></span>
<div class="pult">
  <div class="gruppe" role="group" aria-label="Variante"><span>Variante</span>
    <button type="button" data-v="laib" aria-pressed="true">Laib</button>
    <button type="button" data-v="klickpunkt" aria-pressed="false">Klickpunkt</button>
    <button type="button" data-v="stufe" aria-pressed="false">Stufe</button>
  </div>
  <div class="gruppe" role="group" aria-label="Wechsel"><span>Wechsel</span>
    <button type="button" id="knopf-hin">Hinein</button>
    <button type="button" id="knopf-zurueck">Zurück</button>
  </div>
  <p id="werte"></p>
  <p>Echter Seitenaufruf: einen Link anklicken, dann Browser-Zurück.</p>
</div>

<script>
(() => {
  const h = document.documentElement;
  const reduziert = () => h.classList.contains('reduziert') || matchMedia('(prefers-reduced-motion: reduce)').matches;
  const q = new URLSearchParams(location.search);

  /* ---------- Werte aus den Bewegungs-Partituren ---------- */
  const FORM = {
    laib: { vor: 800, zurueck: 540, kurve: [0.37, 0, 0.63, 1], text: 'Laib · 800 ms hinein, 540 ms zurück · cubic-bezier(0.37, 0, 0.63, 1) · Brotladen Siebold, Nienburg' },
    klickpunkt: { vor: 800, zurueck: 540, kurve: [0.76, 0, 0.24, 1], text: 'Kreis ab Klick · 800 ms hinein, 540 ms zurück · cubic-bezier(0.76, 0, 0.24, 1) · Mareike Hellwarth, Jever' },
    stufe: { vor: 1100, zurueck: 740, kurve: [0.37, 0, 0.63, 1], versatz: 90, text: '5 Stufen · 1100 ms hinein, 740 ms zurück, 90 ms Versatz · cubic-bezier(0.37, 0, 0.63, 1) · Weingut Auerbeck, Bürgstadt' }
  };

  /* ---------- Inhalte der Detailseite aus der Adresse ---------- */
  const INHALT = {
    brot: {
      roggen: { name: 'Roggenmischbrot', gewicht: '1.000 g', preis: '5,20 €', grund: '5,20 € je kg', allergene: 'Roggen, Weizen', tage: 'Montag bis Samstag', text: 'Sechzig Teile Roggen, vierzig Teile Weizen, Sauerteig vom Vortag. Die Kruste wird dunkel, die Krume bleibt saftig bis Samstag.' },
      dinkel: { name: 'Dinkelvollkorn', gewicht: '750 g', preis: '5,60 €', grund: '7,47 € je kg', allergene: 'Dinkel, Sonnenblumenkerne', tage: 'Dienstag und Freitag', text: 'Ganzes Korn, frisch gemahlen, mit Brühstück. Schmeckt nussig und hält vier Tage.' },
      weizen: { name: 'Weizenkruste', gewicht: '500 g', preis: '3,90 €', grund: '7,80 € je kg', allergene: 'Weizen', tage: 'täglich', text: 'Lange Teigführung, kräftig ausgebacken. Die Kruste knackt noch am Nachmittag.' },
      saaten: { name: 'Saatenlaib', gewicht: '750 g', preis: '5,90 €', grund: '7,87 € je kg', allergene: 'Weizen, Roggen, Sesam', tage: 'Mittwoch und Samstag', text: 'Leinsaat, Sesam und Kürbiskerne, über Nacht eingeweicht. Saftig und herzhaft.' }
    },
    serie: {
      geest: { name: 'Nebel über der Geest', jahr: '2025 · 14 Bilder', ort: 'Friesland, Oktober', bild: 'huegel-nebel.jpg', alt: 'Hügel im Nebel, Morgenlicht', text: 'Vierzehn Morgen zwischen Sande und Schortens. Immer dieselbe Stunde, immer ein anderer Nebel.' },
      fenster: { name: 'Nordlicht im Atelier', jahr: '2024 · 9 Bilder', ort: 'Jever, Winter', bild: 'fenster-licht.jpg', alt: 'Licht fällt durch ein Fenster auf eine Wand', text: 'Ein Fenster nach Norden, ein Winter lang. Was das Licht mit einer weißen Wand macht.' },
      treppe: { name: 'Treppenhäuser', jahr: '2023 · 21 Bilder', ort: 'Wilhelmshaven und Oldenburg', bild: 'beton-treppe.jpg', alt: 'Betontreppe mit harten Schatten', text: 'Sichtbeton der Sechziger, fotografiert, bevor saniert wird.' },
      keramik: { name: 'Ton und Glasur', jahr: '2026 · 12 Bilder', ort: 'Werkstatt in Varel', bild: 'keramik-stillleben.jpg', alt: 'Keramikgefäße als Stillleben', text: 'Auftrag für eine Töpferei: Gefäße ohne Requisiten, nur Licht und Fläche.' },
      stoff: { name: 'Weberei Rasch', jahr: '2022 · 16 Bilder', ort: 'Ostfriesland', bild: 'stoff-stapel.jpg', alt: 'Gefaltete Stoffe im Stapel', text: 'Eine Handweberei kurz vor dem Umzug. Stoffe, Spulen, Hände mit Einwilligung.' },
      zitronen: { name: 'Sommertisch', jahr: '2021 · 8 Bilder', ort: 'Jever, Juli', bild: 'zitronen-tisch.jpg', alt: 'Zitronen auf einem Tisch im Sonnenlicht', text: 'Die erste freie Serie. Ein Tisch, ein Sommer, sehr viel Gelb.' }
    },
    lage: {
      staffel: { name: 'Hohe Staffel', neigung: '48 %', hoehe: '210–260 m', boden: 'Buntsandstein, steinig', richtung: 'Südsüdwest', terrassen: '11, von Hand' },
      buckel: { name: 'Kalkbuckel', neigung: '31 %', hoehe: '180–215 m', boden: 'Sandstein mit Kalkeinschlüssen', richtung: 'Süd', terrassen: '6' },
      bruch: { name: 'Am Steinbruch', neigung: '22 %', hoehe: '150–190 m', boden: 'verwitterter Buntsandstein, Lehm', richtung: 'Südost', terrassen: '3' }
    }
  };
  function fuelleDetail() {
    const v = h.dataset.v, art = { laib: 'brot', klickpunkt: 'serie', stufe: 'lage' }[v];
    const daten = INHALT[art][q.get(art)];
    if (!daten) return;
    const seite = document.querySelector(`.seite[data-s="${{ laib: 'laib', klickpunkt: 'klick', stufe: 'stufe' }[v]}-b"]`);
    seite.querySelectorAll('[data-feld]').forEach(el => {
      const k = el.dataset.feld;
      if (k === 'bild') { el.src = 'bilder/' + daten.bild; el.alt = daten.alt; }
      else if (daten[k]) el.textContent = daten[k];
    });
    document.title = daten.name + ' · Seitenwechsel in wachsender Markenform';
  }

  /* ---------- Links behalten v, reduziert, vorschau ---------- */
  function setzeLinks() {
    document.querySelectorAll('a[data-wf]').forEach(a => {
      const ziel = new URL(a.getAttribute('href'), location.href), u = new URL(location.href);
      u.searchParams.delete('brot'); u.searchParams.delete('serie'); u.searchParams.delete('lage');
      u.searchParams.set('v', h.dataset.v); u.searchParams.set('seite', a.dataset.wf);
      ziel.searchParams.forEach((wert, k) => { if (k !== 'seite') u.searchParams.set(k, wert); });
      u.hash = '';
      a.href = u.pathname + u.search;
    });
  }

  /* ---------- Kurve und Formen ---------- */
  function bezier(x1, y1, x2, y2) {
    const k = (a, b, t) => ((1 - 3 * b + 3 * a) * t + (3 * b - 6 * a)) * t * t + 3 * a * t;
    return x => { if (x <= 0) return 0; if (x >= 1) return 1; let lo = 0, hi = 1, t = x; for (let i = 0; i < 24; i++) { t = (lo + hi) / 2; if (k(x1, x2, t) < x) lo = t; else hi = t; } return k(y1, y2, t); };
  }
  const r = n => Math.round(n * 10) / 10;
  // Laib: ein halbrunder Laib wächst unten aus der Mitte, wird breiter und höher, bis er die Seite deckt
  function laibPfad(p, W, H) {
    const B = H + 4, cx = W / 2, hw = W * (0.16 + 0.76 * p), top = B - p * (H * (W < 640 ? 1.5 : 1.62) + 4), sx = B - .56 * (B - top);
    return `path('M${r(cx - hw)} ${r(B)} C${r(cx - hw)} ${r(sx)} ${r(cx - hw * .56)} ${r(top)} ${r(cx)} ${r(top)} C${r(cx + hw * .56)} ${r(top)} ${r(cx + hw)} ${r(sx)} ${r(cx + hw)} ${r(B)} Z')`;
  }
  // Stufe: fünf Terrassen, jede mit eigenem Fortschritt
  function stufenPfad(ps, W, H) {
    const B = H + 4, n = ps.length; let d = `M0 ${r(B)}`;
    ps.forEach((p, i) => { const y = B - p * (B + 4); d += ` L${r(W * i / n)} ${r(y)} L${r(W * (i + 1) / n)} ${r(y)}`; });
    return `path('${d} L${r(W)} ${r(B)} Z')`;
  }
  function stufenWerte(t, dauer, versatz, kurve, umgekehrt) {
    const n = 5, lauf = dauer - versatz * (n - 1);
    return Array.from({ length: n }, (_, i) => { const j = umgekehrt ? n - 1 - i : i; return kurve(Math.min(1, Math.max(0, (t * dauer - j * versatz) / lauf))); });
  }
  function animiere(richtung, punkt) {
    const v = h.dataset.v, f = FORM[v], W = innerWidth, H = innerHeight;
    const vor = richtung !== 'zurueck', dauer = vor ? f.vor : f.zurueck, kurve = bezier(...f.kurve), css = `cubic-bezier(${f.kurve.join(', ')})`;
    const oben = vor ? '::view-transition-new(root)' : '::view-transition-old(root)', unten = vor ? '::view-transition-old(root)' : '::view-transition-new(root)';
    let clip, opt = { duration: dauer, easing: 'linear', fill: 'both', pseudoElement: oben };
    if (v === 'klickpunkt') {
      const x = punkt ? punkt.x * W : W / 2, y = punkt ? punkt.y * H : H / 2, R = Math.hypot(Math.max(x, W - x), Math.max(y, H - y)) + 2;
      const zu = `circle(0px at ${r(x)}px ${r(y)}px)`, auf = `circle(${r(R)}px at ${r(x)}px ${r(y)}px)`;
      clip = vor ? [zu, auf] : [auf, zu]; opt.easing = css;
    } else {
      clip = [];
      for (let i = 0; i <= 32; i++) {
        const t = i / 32, tt = vor ? t : 1 - t;
        clip.push(v === 'laib' ? laibPfad(kurve(tt), W, H) : stufenPfad(stufenWerte(tt, dauer, vor ? f.versatz : f.versatz * .67, kurve, !vor), W, H));
      }
    }
    h.animate({ clipPath: clip }, opt);
    // die Seite darunter tritt zurück: dunkler, beim Laib auch etwas nach oben
    const weg = v === 'laib' ? ['translateY(0)', 'translateY(-6vh)'] : ['none', 'none'];
    const unter = { opacity: [1, .5], transform: weg };
    if (!vor) { unter.opacity.reverse(); unter.transform.reverse(); }
    h.animate(unter, { duration: dauer, easing: css, fill: 'both', pseudoElement: unten });
  }
  const fang = vt => ['ready', 'finished', 'updateCallbackDone'].forEach(k => vt[k] && vt[k].catch(() => {}));

  /* ---------- Echter Seitenwechsel: pageswap (alte Seite) und pagereveal (neue Seite) ---------- */
  // Herkunft je Ziel-URL (Jury Optik 2): Beim Zurück, auch per Browser-Zurück und nach Minuten, schließt die Blende an dem Link,
  // von dem man kam, nicht in der Bildschirmmitte. sessionStorage['wf-herkunft:' + Ziel] = { quelle, x, y }.
  const adresse = u => { try { const x = new URL(u, location.href); const p = [...x.searchParams].sort((a, b) => (a[0] + a[1]).localeCompare(b[0] + b[1])); return x.pathname + '?' + p.map(q => q.join('=')).join('&'); } catch (f) { return ''; } };
  let letzterKlick = null;
  document.addEventListener('click', e => {
    const a = e.target.closest('a[data-wf]'); if (!a) return;
    const b = a.getBoundingClientRect();
    letzterKlick = (e.clientX || e.clientY) ? { x: e.clientX, y: e.clientY } : { x: b.left + b.width / 2, y: b.top + b.height / 2 };
    letzterKlick.ziel = adresse(a.href);
  });
  addEventListener('pageswap', e => {
    if (!e.viewTransition) return;
    fang(e.viewTransition);
    if (!letzterKlick) return;
    const p = { x: letzterKlick.x / innerWidth, y: letzterKlick.y / innerHeight, t: Date.now() };
    sessionStorage.setItem('wf-punkt', JSON.stringify(p));
    let ziel = letzterKlick.ziel;
    try { if (e.activation && e.activation.entry && e.activation.entry.url) ziel = adresse(e.activation.entry.url); } catch (f) {}
    if (ziel) try { sessionStorage.setItem('wf-herkunft:' + ziel, JSON.stringify({ quelle: adresse(location.href), x: p.x, y: p.y })); } catch (f) {}
  });
  // Auf der Seite, zu der man zurückkehrt: den Link messen, der zur verlassenen Seite führt
  function punktDerHerkunft(von) {
    let h = null;
    try { h = JSON.parse(sessionStorage.getItem('wf-herkunft:' + adresse(von))); } catch (f) {}
    if (!h || h.quelle !== adresse(location.href)) return null;
    const a = [...document.querySelectorAll('a[href]')].find(x => x.offsetParent && adresse(x.href) === adresse(von));
    if (a) { const b = (a.querySelector('img') || a).getBoundingClientRect(); if (b.bottom > 0 && b.top < innerHeight) return { x: (b.left + b.width / 2) / innerWidth, y: (b.top + b.height / 2) / innerHeight }; }
    return { x: h.x, y: h.y };
  }
  window.wfReveal = e => {
    if (!e.viewTransition) return;
    fang(e.viewTransition);
    let von = null, zurueck = false;
    try {
      const akt = navigation.activation;
      if (akt && akt.from) {
        von = new URL(akt.from.url).searchParams.get('seite') === 'b' ? 'b' : 'a';
        if (akt.navigationType === 'traverse' && akt.from.index > akt.entry.index) zurueck = true;
      }
    } catch (x) {}
    const nach = h.dataset.seite;
    if (von === nach && !zurueck) { e.viewTransition.skipTransition(); return; }
    const richtung = zurueck || (von === 'b' && nach === 'a') ? 'zurueck' : 'vor';
    if (reduziert()) { e.viewTransition.types.add('ruhig'); return; }
    e.viewTransition.types.add(richtung);
    let punkt = null;
    try { const p = JSON.parse(sessionStorage.getItem('wf-punkt')); if (p && Date.now() - p.t < 5000) punkt = p; } catch (x) {}
    if (richtung === 'zurueck') {
      const von = navigation.activation.from.url;
      punkt = punktDerHerkunft(von) || punktVomLink(new URL(von).searchParams) || punkt;
    }
    e.viewTransition.ready.then(() => animiere(richtung, punkt)).catch(() => {});
  };
  // Rückfall ohne gespeicherte Herkunft: Link aus den Parametern der verlassenen Seite (nur diese Demo)
  function punktVomLink(par) {
    const ziel = par.get('brot') || par.get('serie') || par.get('lage');
    const a = ziel && document.querySelector(`.seite:not([hidden]) a[data-ziel="${ziel}"]`);
    if (!a || !a.offsetParent) return null;
    const b = (a.querySelector('img') || a).getBoundingClientRect();
    return { x: (b.left + b.width / 2) / innerWidth, y: (b.top + b.height / 2) / innerHeight };
  }

  /* ---------- Gleiche Bewegung ohne Seitenaufruf (Vorführung, Pult) ---------- */
  const zeiger = document.querySelector('.zeiger');
  function wechsel(nach, richtung, punkt) {
    const setze = () => { h.dataset.seite = nach; scrollTo(0, 0); };
    if (!document.startViewTransition || h.dataset.seite === nach) { setze(); return; }
    const ruhig = reduziert();
    let vt;
    try { vt = document.startViewTransition({ update: setze, types: [ruhig ? 'ruhig' : richtung] }); }
    catch (x) { vt = document.startViewTransition(setze); }
    fang(vt);
    if (!ruhig) vt.ready.then(() => animiere(richtung, punkt)).catch(() => {});
  }
  function erster() { return document.querySelector(`.seite[data-s$="-a"]:is([data-s^="${{ laib: 'laib', klickpunkt: 'klick', stufe: 'stufe' }[h.dataset.v]}"]) a[data-ziel]`); }
  function mitte(el) { const b = (el.querySelector('img') || el).getBoundingClientRect(); return { x: (b.left + b.width * .55) / innerWidth, y: (b.top + b.height * .5) / innerHeight }; }
  function hinein() { const a = erster(); wechsel('b', 'vor', a ? mitte(a) : null); }
  document.getElementById('knopf-hin').addEventListener('click', () => { pause(); if (h.dataset.seite === 'a') hinein(); });
  document.getElementById('knopf-zurueck').addEventListener('click', () => { pause(); if (h.dataset.seite === 'b') zurueckMitPunkt(); });
  function zurueckMitPunkt() {
    // Punkt erst nach dem Tausch bekannt: Link auf der Übersicht messen, dann Kreis dorthin schrumpfen
    const vorher = h.dataset.seite; h.dataset.seite = 'a';
    const a = erster(), p = a ? mitte(a) : null; h.dataset.seite = vorher;
    wechsel('a', 'zurueck', p);
  }

  /* ---------- Variante ---------- */
  const knoepfe = [...document.querySelectorAll('.pult [data-v]')];
  function setzeVariante(v, url) {
    h.dataset.v = v;
    knoepfe.forEach(k => k.setAttribute('aria-pressed', String(k.dataset.v === v)));
    document.getElementById('werte').textContent = FORM[v].text;
    if (url) { const u = new URL(location.href); u.searchParams.set('v', v); history.replaceState(history.state, '', u); }
    setzeLinks();
  }
  knoepfe.forEach(k => k.addEventListener('click', () => { pause(); h.dataset.seite = 'a'; const u = new URL(location.href); u.searchParams.delete('seite'); history.replaceState(history.state, '', u); setzeVariante(k.dataset.v, true); }));

  /* ---------- API ---------- */
  let schleife = [];
  const nach = (ms, fn) => schleife.push(setTimeout(fn, ms));
  function zeigerZu(p, dann) {
    if (!p) { dann(); return; }
    zeiger.style.left = p.x * innerWidth + 'px'; zeiger.style.top = p.y * innerHeight + 'px';
    zeiger.classList.add('an');
    nach(350, () => { zeiger.classList.add('drueckt'); nach(140, () => { zeiger.classList.remove('drueckt'); dann(); nach(260, () => zeiger.classList.remove('an')); }); });
  }
  function neustart() { pause(); if (document.startViewTransition && h.dataset.seite === 'b') h.dataset.seite = 'a'; hinein(); }
  function vorfuehren() {
    pause();
    const lauf = () => {
      if (h.dataset.seite === 'b') { zeigerZu(null, zurueckMitPunkt); nach(1600, lauf); return; }
      const a = erster(); zeigerZu(a ? mitte(a) : null, hinein);
      nach(2600, () => { const z = [...document.querySelectorAll('.zurueck')].find(el => el.offsetParent); zeigerZu(z ? mitte(z) : null, zurueckMitPunkt); });
      nach(4700, lauf);
    };
    lauf();
  }
  function pause() { schleife.forEach(clearTimeout); schleife = []; zeiger.classList.remove('an', 'drueckt'); }
  function weiter() { vorfuehren(); }

  fuelleDetail();
  setzeVariante(h.dataset.v, false);
  if (window.wfFrueh) { window.wfReveal(window.wfFrueh); window.wfFrueh = null; }

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