Zum Inhalt
← Navigation

Sound-Umschalter und Einstiegsgate in der Navigation

Reel einer Hamburger Filmproduktion mit Einstiegsgate „Mit Ton / Ohne Ton“, Equalizer-Schalter in der Ecken-Navigation und selbst erzeugtem Hafenklang per Web Audio.

Immersive Seiten bieten beim Einstieg die Wahl „Mit Ton eintreten“ oder „Ohne Ton eintreten“. Danach sitzt ein kleiner Sound-Schalter dauerhaft in einer Ecke oder in der Navigation, oft als animierte Equalizer-Balken oder Wellenlinie, die bei stumm zur flachen Linie wird. Dazu kommen oft Play/Pause- oder Stumm-Knöpfe für Hintergrundvideo oder Reel.

Kein Ton startet ungefragt, trotzdem wird Klang Teil des Markenerlebnisses. Die animierte Ikone zeigt, dass die Seite lebt.

Wann einsetzen

WebGL-Erlebnisse, Kampagnen, Musik, Film und Studios mit Sounddesign.

Wann nicht

Informationsseiten ohne Audio-Konzept.

Varianten

  • Audio-Gate vor dem Eintritt (Unseen Studio)
  • Schwebende Play- und Stumm-Steuerung (Lusion)
  • Off/On-Schalter als Typo-Sequenz (Immersive Garden, nicht bestätigt)

Umsetzung

Ton erst nach einer Nutzergeste starten: AudioContext im Klick-Handler anlegen oder mit resume() fortsetzen. Den Ton über einen Gain-Knoten in 500 bis 1000 ms ein- und ausblenden. Schalter als <button aria-pressed>. Equalizer: 3 bis 5 Balken mit CSS-Keyframes auf scaleY und versetztem animation-delay; bei stumm Animation anhalten und auf scaleY(0.2) setzen. Zustand in localStorage merken (in try/catch). Beim Tab-Wechsel (visibilitychange) den Ton pausieren.

Robust umsetzen

Telefon. Funktioniert gleich. iOS verlangt eine Nutzergeste, der Stummschalter des Geräts wird respektiert.

Ohne Bewegung. Die Balken stehen still.

Barrierefrei. aria-pressed und ein eindeutiges Label („Ton“). Ton nie automatisch starten. WCAG 1.4.2 (Audio-Steuerung) beachten.

Code

<!DOCTYPE html>
<!--
  Sound-Umschalter und Einstiegsgate in der Navigation
  Fundus · Navigation · https://cecodesign.de/lab/fundus/navigation/sound-umschalter/
  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>Sound-Umschalter und Einstiegsgate in der Navigation · 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: 'Instrument Serif'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/instrument-serif@latest/latin-400-normal.woff2') format('woff2'); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: '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; }

:root {
  /* Nebelhorn Film, Hamburg-Altona: Film-Variante des Rezepts, neutrale dunkle Fläche */
  --grund: #0e0e0e; --text: #f2f2f2; --leise: rgba(242, 242, 242, .62); --linie: rgba(242, 242, 242, .18); --platz: #1a1a1a;
  /* Eine Kurve für alles: ease-in-out (gemessen typisch für Equalizer-Loops) */
  --kurve: cubic-bezier(0.42, 0, 0.58, 1);
  --blende: 800ms;   /* Bildwechsel und Gate */
  --ui: 200ms;
}
* { box-sizing: border-box; margin: 0; }
html { background: var(--grund); color: var(--text); font-family: 'Instrument Sans', system-ui, sans-serif; -webkit-font-smoothing: antialiased; }
body { height: 100svh; overflow: hidden; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }
.mono { font: 400 12px/1.35 'Fragment Mono', ui-monospace, monospace; letter-spacing: .02em; font-variant-numeric: tabular-nums; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* ---------- Bühne: das Bild bekommt die ganze Fläche ---------- */
.buehne { position: fixed; inset: 0; background: var(--platz); }
.buehne canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

/* ---------- Ecken-Navigation ---------- */
.ecke { position: fixed; z-index: 5; }
.oben { top: clamp(16px, 2.6vw, 32px); }
.unten { bottom: clamp(16px, 2.6vw, 32px); }
.links { left: clamp(16px, 2.6vw, 36px); }
.rechts { right: clamp(16px, 2.6vw, 36px); }
.marke { font: 400 clamp(1.25rem, 1.1rem + .5vw, 1.5rem)/1 'Instrument Serif', serif; letter-spacing: -.005em; display: inline-flex; align-items: baseline; gap: .4em; }
.marke small { font: 400 11px/1 'Fragment Mono', monospace; color: var(--leise); letter-spacing: .02em; }
.nav { display: flex; align-items: center; gap: clamp(14px, 2vw, 28px); font-size: 14px; }
.nav > a { color: var(--leise); transition: color var(--ui) var(--kurve); }
.nav > a:hover, .nav > a:focus-visible { color: var(--text); }
:focus-visible { outline: 1px solid var(--text); outline-offset: 4px; }

/* ---------- Der Sound-Schalter: Equalizer-Balken ---------- */
.ton { display: inline-flex; align-items: center; gap: 10px; height: 32px; padding: 0 2px; color: var(--text); }
.balken { display: inline-flex; align-items: center; gap: 2.5px; height: 14px; }
.balken i { display: block; width: 2px; height: 14px; border-radius: 1px; background: currentColor; transform-origin: 50% 50%; transform: scaleY(.2); }
/* Klingt: jeder Balken in eigener Dauer (600 bis 1000 ms), versetzt um 120 ms */
.klingt .balken i { animation: balken var(--d) var(--kurve) infinite alternate; animation-delay: var(--v); }
.balken i:nth-child(1) { --d: 800ms; --v: 0ms; }
.balken i:nth-child(2) { --d: 640ms; --v: 120ms; }
.balken i:nth-child(3) { --d: 960ms; --v: 240ms; }
.balken i:nth-child(4) { --d: 720ms; --v: 360ms; }
@keyframes balken { from { transform: scaleY(.2); } to { transform: scaleY(1); } }
.ton__text { font: 400 12px/1 'Fragment Mono', monospace; letter-spacing: .02em; min-width: 7ch; text-align: left; white-space: nowrap; }
.ton__text .aus { color: var(--leise); }

/* Variante Off / On: Schalter als Typo-Sequenz */
.onoff { display: none; align-items: center; gap: 8px; font: 400 12px/1 'Fragment Mono', monospace; }
.onoff span { color: var(--leise); }
.onoff b { font-weight: 400; color: var(--leise); transition: color var(--ui) var(--kurve); position: relative; }
.onoff b::after { content: ""; position: absolute; left: 0; right: 0; bottom: -5px; height: 1px; background: currentColor; scale: 0 1; transition: scale var(--ui) var(--kurve); }
.onoff[aria-pressed="false"] b.b-aus, .onoff[aria-pressed="true"] b.b-an { color: var(--text); }
.onoff[aria-pressed="false"] b.b-aus::after, .onoff[aria-pressed="true"] b.b-an::after { scale: 1 1; }

/* ---------- Bildunterschrift und Zähler ---------- */
.unterschrift { display: grid; gap: 8px; max-width: min(560px, 70vw); transition: opacity 400ms var(--kurve); }
.unterschrift.wechselt { opacity: 0; }
.unterschrift h2 { font: 400 clamp(1.5rem, 3vw, 2.75rem)/1.1 'Instrument Serif', serif; letter-spacing: -.01em; }
.unterschrift h2 em { font-style: italic; }
.unterschrift p { color: var(--leise); text-wrap: balance; }
.zaehler { display: grid; justify-items: end; gap: 8px; text-align: right; }
.zaehler b { font-weight: 400; color: var(--text); }
.zaehler span { color: var(--leise); }
.pfeile { display: flex; gap: 6px; }
.pfeile button { width: 32px; height: 32px; border-radius: 50%; border: 1px solid var(--linie); display: grid; place-items: center; transition: border-color var(--ui) var(--kurve), background-color var(--ui) var(--kurve); }
.pfeile button:hover { border-color: var(--text); }
.pfeile svg { width: 12px; height: 12px; }
.pfeile [data-spiel][aria-pressed='true'] { background: var(--text); color: var(--grund); border-color: var(--text); }
.v-schwebe .pfeile [data-spiel], .reduziert .pfeile [data-spiel] { display: none; }

/* ---------- Schwebende Steuerung (Variante) ---------- */
.schwebe { position: fixed; z-index: 6; left: 50%; bottom: clamp(16px, 2.6vw, 32px); translate: -50% 0; display: none; align-items: center; gap: 4px; padding: 4px; border-radius: 999px; background: rgba(14, 14, 14, .55); -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px); border: 1px solid var(--linie); }
.schwebe button { height: 36px; min-width: 36px; padding: 0 14px; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; gap: 9px; font: 400 12px/1 'Fragment Mono', monospace; transition: background-color var(--ui) var(--kurve); }
.schwebe button:hover { background: rgba(242, 242, 242, .1); }
.schwebe .ton { height: 36px; padding: 0 14px; }
.schwebe svg { width: 12px; height: 12px; }
.schwebe .weiterlesen { color: var(--leise); }
.v-schwebe .schwebe { display: inline-flex; }
.v-schwebe .nav .ton { display: none; }
.v-onoff .nav .ton { display: none; }
.v-onoff .onoff { display: inline-flex; }

/* ---------- Gate vor dem Eintritt ---------- */
.gate { position: fixed; inset: 0; z-index: 10; display: grid; place-items: center; padding: 24px; background: rgba(14, 14, 14, .74); transition: opacity var(--blende) var(--kurve), visibility 0s linear var(--blende); }
.gate.weg { opacity: 0; visibility: hidden; }
.gate__innen { display: grid; justify-items: center; gap: 28px; text-align: center; }
.gate__titel { font: 400 clamp(2.25rem, 1.5rem + 3vw, 4rem)/1.02 'Instrument Serif', serif; letter-spacing: -.01em; max-width: 12em; }
.gate__titel em { font-style: italic; }
.gate__wahl { display: flex; align-items: center; gap: 22px; flex-wrap: wrap; justify-content: center; }
.eintritt { height: 48px; padding: 0 22px; border-radius: 999px; background: var(--text); color: var(--grund); display: inline-flex; align-items: center; gap: 12px; font-size: 15px; font-weight: 500; transition: background-color var(--ui) var(--kurve); }
.eintritt:hover { background: #ffffff; }
.eintritt .balken i { background: var(--grund); }
.eintritt .balken i { animation: balken var(--d) var(--kurve) infinite alternate; animation-delay: var(--v); }
.still { font-size: 15px; color: var(--leise); border-bottom: 1px solid var(--linie); padding-bottom: 3px; transition: color var(--ui) var(--kurve), border-color var(--ui) var(--kurve); }
.still:hover { color: var(--text); border-color: var(--text); }
.gate .mono { color: var(--leise); text-wrap: balance; }
.v-schwebe .gate, .v-onoff .gate { display: none; }

/* ---------- Werte-Pult ---------- */
.pult { position: fixed; z-index: 12; top: clamp(12px, 2vw, 26px); left: 50%; translate: -50% 0; display: flex; align-items: center; gap: 12px; padding: 5px 5px 5px 14px; border-radius: 999px; background: rgba(14, 14, 14, .6); -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); border: 1px solid var(--linie); font: 400 11px/1 'Fragment Mono', monospace; color: var(--leise); white-space: nowrap; }
.pult .modi { display: flex; gap: 3px; }
.pult .modi button { height: 26px; padding: 0 10px; border-radius: 999px; color: var(--leise); transition: background-color var(--ui) var(--kurve), color var(--ui) var(--kurve); }
.pult .modi button[aria-pressed="true"] { background: var(--text); color: var(--grund); }
.vorschau .pult { display: none; }

/* Vorführung: gezeichneter Zeiger */
.zeiger { position: fixed; left: 0; top: 0; z-index: 30; width: 18px; height: 18px; pointer-events: none; display: none; transition: scale var(--ui) var(--kurve); }
.vorfuehrung .zeiger { display: block; }
.zeiger.drueckt { scale: .8; }

/* ---------- Schmal und Touch ---------- */
@media (max-width: 1180px) { .pult .werte { display: none; } }
@media (max-width: 720px) {
  .nav > a { display: none; }
  .marke small { display: none; }
  .unterschrift { max-width: calc(100vw - 40px); }
  .zaehler { bottom: auto; top: auto; }
  .unten.rechts { bottom: auto; top: 64px; }
  .unten.rechts .pfeile button:not([data-spiel]) { display: none; }
  .gate__wahl { flex-direction: column; gap: 18px; }
  .v-schwebe .unterschrift { margin-bottom: 64px; }
}
@media (hover: none) {
  .ton { height: 44px; padding: 0 6px; }           /* größere Trefferfläche */
  .schwebe button, .schwebe .ton { height: 44px; }
  .eintritt { height: 52px; }
}

/* ---------- Reduzierte Bewegung: Balken stehen, Bilder wechseln ohne Blende ---------- */
.reduziert .klingt .balken i, .reduziert .eintritt .balken i { animation: none; }
.reduziert .klingt .balken i:nth-child(1), .reduziert .eintritt .balken i:nth-child(1) { transform: scaleY(.55); }
.reduziert .klingt .balken i:nth-child(2), .reduziert .eintritt .balken i:nth-child(2) { transform: scaleY(.9); }
.reduziert .klingt .balken i:nth-child(3), .reduziert .eintritt .balken i:nth-child(3) { transform: scaleY(.4); }
.reduziert .klingt .balken i:nth-child(4), .reduziert .eintritt .balken i:nth-child(4) { transform: scaleY(.75); }
.reduziert .gate { transition: opacity 150ms linear, visibility 0s linear 150ms; }
.reduziert .unterschrift { transition: none; }
/* Kontrastmodus: Knöpfe mit Rahmen */
@media (forced-colors: active) { button, [role=button], [role=tab], [role=switch] { border: 1px solid ButtonText !important; } }
</style>
</head>
<body>
<div class="buehne" aria-hidden="true"><canvas data-leinwand></canvas></div>

<header>
  <a class="ecke oben links marke" href="#start">Nebelhorn <small>Film · Hamburg-Altona</small></a>
  <nav class="ecke oben rechts nav" aria-label="Hauptnavigation">
    <a href="#regie">Regie</a>
    <a href="#arbeiten">Arbeiten</a>
    <a href="#kontakt">Kontakt</a>
    <button class="ton" type="button" aria-pressed="false" data-ton aria-label="Ton">
      <span class="balken" aria-hidden="true"><i></i><i></i><i></i><i></i></span>
      <span class="ton__text" aria-hidden="true" data-ton-text><span class="aus">Ton aus</span></span>
    </button>
    <button class="onoff" type="button" aria-pressed="false" data-ton aria-label="Ton">
      <span aria-hidden="true">Ton</span><b class="b-aus" aria-hidden="true">Aus</b><b class="b-an" aria-hidden="true">An</b>
    </button>
  </nav>
</header>

<main>
  <h1 class="sr">Nebelhorn Film, Produktionsfirma in Hamburg-Altona: Reel</h1>
  <div class="ecke unten links unterschrift" data-unterschrift aria-live="polite">
    <h2 data-titel>Kai 7</h2>
    <p class="mono" data-info>Kurzfilm · Regie Ilka Feddersen · 2026 · 4:12 min</p>
  </div>
  <div class="ecke unten rechts zaehler">
    <div class="pfeile">
      <button type="button" data-spiel aria-pressed="false" aria-label="Reel anhalten"><svg viewBox="0 0 12 12" aria-hidden="true" data-spiel-icon><path d="M3 2h2v8H3zm4 0h2v8H7z" fill="currentColor"/></svg></button>
      <button type="button" data-zurueck aria-label="Vorheriges Bild"><svg viewBox="0 0 12 12" aria-hidden="true"><path d="M7.5 2 3.5 6l4 4" fill="none" stroke="currentColor" stroke-width="1.2"/></svg></button>
      <button type="button" data-vor aria-label="Nächstes Bild"><svg viewBox="0 0 12 12" aria-hidden="true"><path d="M4.5 2l4 4-4 4" fill="none" stroke="currentColor" stroke-width="1.2"/></svg></button>
    </div>
    <p class="mono"><b data-nr>01</b> <span>/ 03</span></p>
    <p class="mono"><span data-tc>TC 00:00:00:00</span></p>
  </div>
</main>

<!-- Variante: schwebende Steuerung -->
<div class="schwebe" data-schwebe>
  <button type="button" data-spiel aria-pressed="false" aria-label="Reel anhalten"><svg viewBox="0 0 12 12" aria-hidden="true" data-spiel-icon><path d="M3 2h2v8H3zm4 0h2v8H7z" fill="currentColor"/></svg></button>
  <button class="ton" type="button" aria-pressed="false" data-ton aria-label="Ton">
    <span class="balken" aria-hidden="true"><i></i><i></i><i></i><i></i></span>
    <span class="ton__text" aria-hidden="true" data-ton-text><span class="aus">Ton aus</span></span>
  </button>
  <button class="weiterlesen" type="button" data-vor>Weiter</button>
</div>

<!-- Gate vor dem Eintritt -->
<div class="gate" data-gate role="dialog" aria-modal="true" aria-labelledby="gate-titel">
  <div class="gate__innen">
    <p class="mono">Nebelhorn Film · Reel 2026</p>
    <p class="gate__titel" id="gate-titel">Unsere Filme klingen nach <em>Hafen.</em></p>
    <div class="gate__wahl">
      <button class="eintritt" type="button" data-eintritt="an"><span class="balken" aria-hidden="true"><i></i><i></i><i></i><i></i></span>Mit Ton eintreten</button>
      <button class="still" type="button" data-eintritt="aus">Ohne Ton eintreten</button>
    </div>
    <p class="mono">Kopfhörer empfohlen · Ton jederzeit oben rechts</p>
  </div>
</div>

<div class="pult">
  <span class="werte">ease-in-out · 640–960 ms · +120 ms</span>
  <div class="modi" role="group" aria-label="Variante">
    <button type="button" data-variante="gate" aria-pressed="true">Gate</button>
    <button type="button" data-variante="schwebe" aria-pressed="false">Schwebend</button>
    <button type="button" data-variante="onoff" aria-pressed="false">Off / On</button>
  </div>
</div>
<svg class="zeiger" data-zeiger viewBox="0 0 18 18" aria-hidden="true"><path d="M2 1.5 15.5 9.2l-6 1.3-2.9 5.6Z" fill="#f2f2f2" stroke="#0e0e0e" stroke-width="1.2" stroke-linejoin="round"/></svg>

<script>
(() => {
  const html = document.documentElement;
  const reduziert = () => html.classList.contains('reduziert');
  const $ = (s, w = document) => w.querySelector(s);
  const $$ = (s, w = document) => [...w.querySelectorAll(s)];

  const BLENDE_MS = 800, BILD_MS = 6500, TON_EIN_MS = 800, TON_AUS_MS = 500, UNTERSCHRIFT_MS = 400;
  // ease-in-out als Funktion (cubic-bezier 0.42, 0, 0.58, 1)
  function bezier(x1, y1, x2, y2) {
    const A = (a, b) => 1 - 3 * b + 3 * a, B = (a, b) => 3 * b - 6 * a, C = a => 3 * a;
    const f = (t, a, b) => ((A(a, b) * t + B(a, b)) * t + C(a)) * t;
    const d = (t, a, b) => 3 * A(a, b) * t * t + 2 * B(a, b) * t + C(a);
    return x => { let t = x; for (let i = 0; i < 6; i++) { const s = d(t, x1, x2); if (!s) break; t -= (f(t, x1, x2) - x) / s; } return f(Math.min(1, Math.max(0, t)), y1, y2); };
  }
  const inOut = bezier(0.42, 0, 0.58, 1);

  /* ---------- Bilder: drei eigene Szenen auf Canvas (Stellvertreter für Video) ---------- */
  const SZENEN = [
    { titel: 'Kai 7', info: 'Kurzfilm · Regie Ilka Feddersen · 2026 · 4:12 min', male: kai },
    { titel: 'Die <em>letzte</em> Fähre', info: 'Kampagne Elbhaus Kaffee · Regie Jonte Rasmussen · 2025 · 0:45 min', male: faehre },
    { titel: 'Salz', info: 'Musikvideo Möwenmotor · Regie Merle Ottens · 2026 · 3:38 min', male: strand },
  ];
  const lw = $('[data-leinwand]'), cx = lw.getContext('2d');
  let W = 0, H = 0, DPR = 1;
  function groesse() {
    DPR = Math.min(1.5, devicePixelRatio || 1);
    W = innerWidth; H = innerHeight;
    lw.width = Math.round(W * DPR); lw.height = Math.round(H * DPR);
    cx.setTransform(DPR, 0, 0, DPR, 0, 0);
  }
  const verlauf = (y0, y1, stopps) => { const g = cx.createLinearGradient(0, y0, 0, y1); stopps.forEach(([o, c]) => g.addColorStop(o, c)); return g; };
  const glow = (x, y, r, farbe) => { const g = cx.createRadialGradient(x, y, 0, x, y, r); g.addColorStop(0, farbe); g.addColorStop(1, 'rgba(0,0,0,0)'); return g; };
  function vignette(st = .55) {
    const g = cx.createRadialGradient(W / 2, H * .55, Math.min(W, H) * .25, W / 2, H * .55, Math.max(W, H) * .8);
    g.addColorStop(0, 'rgba(0,0,0,0)'); g.addColorStop(1, `rgba(0,0,0,${st})`);
    cx.fillStyle = g; cx.fillRect(0, 0, W, H);
  }
  // Pseudo-Zufall, damit jede Szene in jedem Frame gleich aussieht
  const zufall = s => () => (s = (s * 16807) % 2147483647) / 2147483647;

  // 1) Kai 7: Containerbrücken im Morgennebel
  function kai(t) {
    const hz = H * .6;
    cx.fillStyle = verlauf(0, hz, [[0, '#7f9497'], [.6, '#b9c4c0'], [1, '#d9ddd5']]); cx.fillRect(0, 0, W, hz);
    cx.fillStyle = verlauf(hz, H, [[0, '#9aa8a5'], [.35, '#6c7c7c'], [1, '#2f3a3c']]); cx.fillRect(0, hz, W, H - hz);
    // Gegenufer
    cx.fillStyle = 'rgba(88,104,106,.55)'; cx.fillRect(0, hz - H * .018, W, H * .018);
    const s = Math.min(W, H) / 720;
    const kran = (x, sk, a, ausleger) => {
      cx.save(); cx.translate(x, hz); cx.scale(sk * s, sk * s); cx.fillStyle = `rgba(44,54,57,${a})`; cx.strokeStyle = `rgba(44,54,57,${a})`;
      cx.lineWidth = 5;
      cx.beginPath(); cx.moveTo(-40, 0); cx.lineTo(-34, -210); cx.moveTo(40, 0); cx.lineTo(34, -210); cx.stroke();      // Beine
      cx.lineWidth = 3; cx.beginPath(); cx.moveTo(-37, -100); cx.lineTo(37, -100); cx.moveTo(-36, -100); cx.lineTo(36, -205); cx.stroke();
      cx.fillRect(-60, -222, 120 + ausleger, 12);                                                                   // Ausleger
      cx.fillRect(-14, -300, 9, 82); cx.fillRect(10, -300, 9, 82);                                                    // A-Rahmen
      cx.lineWidth = 2; cx.beginPath(); cx.moveTo(-10, -300); cx.lineTo(60 + ausleger, -222); cx.moveTo(14, -300); cx.lineTo(-60, -222); cx.stroke();
      cx.fillRect(16, -214, 22, 16);                                                                                 // Katze
      cx.restore();
    };
    kran(W * .12, .55, .32, 120); kran(W * .3, .62, .38, 140); kran(W * .58, .8, .55, 170); kran(W * .86, 1.05, .78, 190);
    // Container am Kai
    const r = zufall(7); const farben = ['#5d4a44', '#4b5a5c', '#6a5a43', '#3f4c55'];
    for (let x = W * .44; x < W; x += 34 * s) { const n = 1 + Math.floor(r() * 3); for (let k = 0; k < n; k++) { cx.fillStyle = farben[Math.floor(r() * 4)]; cx.globalAlpha = .75; cx.fillRect(x, hz - (k + 1) * 13 * s, 32 * s, 12 * s); } }
    cx.globalAlpha = 1;
    // Wasser: ruhige Lichtlinien
    const r2 = zufall(11);
    for (let i = 0; i < 70; i++) {
      const y = hz + 6 + Math.pow(r2(), 1.6) * (H - hz), b = 20 + r2() * 140, x = ((r2() * W + t * (8 + r2() * 10)) % (W + b)) - b;
      cx.fillStyle = `rgba(220,228,224,${.05 + r2() * .12})`; cx.fillRect(x, y, b, 1.2);
    }
    // Nebelbänke ziehen langsam
    for (let i = 0; i < 4; i++) {
      const x = ((t * (6 + i * 3) + i * W * .37) % (W * 1.6)) - W * .3, y = hz - H * (.04 + i * .05);
      cx.fillStyle = glow(x, y, W * .45, 'rgba(226,230,224,.42)'); cx.fillRect(0, 0, W, H);
    }
    vignette(.5);
  }

  // 2) Die letzte Fähre: Nacht, Stadtlichter, Fähre zieht vorbei
  function faehre(t) {
    const hz = H * .58;
    cx.fillStyle = verlauf(0, hz, [[0, '#060b14'], [.7, '#12203a'], [1, '#2a3550']]); cx.fillRect(0, 0, W, hz);
    cx.fillStyle = glow(W * .72, hz, W * .5, 'rgba(232,150,80,.18)'); cx.fillRect(0, 0, W, hz);
    cx.fillStyle = verlauf(hz, H, [[0, '#0f1a2c'], [1, '#04070d']]); cx.fillRect(0, hz, W, H - hz);
    // Ferne Ebene: Dächer, Kräne, ein Turm, im Dunst
    const r = zufall(23), s = Math.min(W, H) / 720, lichter = [];
    cx.fillStyle = '#121c2e';
    for (let x = 0; x < W;) { const b = (10 + r() * 34) * s, h = (14 + r() * 46) * s; cx.fillRect(x, hz - h, b, h); x += b; }
    cx.strokeStyle = '#121c2e'; cx.lineWidth = 3 * s;
    [[.08, 1], [.16, .8], [.9, 1.1]].forEach(([fx0, k]) => { const x = W * fx0, h = 120 * s * k; cx.beginPath(); cx.moveTo(x - 14 * s * k, hz); cx.lineTo(x - 10 * s * k, hz - h); cx.moveTo(x + 14 * s * k, hz); cx.lineTo(x + 10 * s * k, hz - h); cx.moveTo(x - 30 * s * k, hz - h); cx.lineTo(x + 80 * s * k, hz - h); cx.stroke(); });
    cx.fillStyle = '#121c2e'; cx.fillRect(W * .47, hz - 190 * s, 7 * s, 190 * s); cx.fillRect(W * .47 - 6 * s, hz - 150 * s, 19 * s, 12 * s);
    cx.fillStyle = 'rgba(255,80,60,.8)'; cx.fillRect(W * .47 + 2 * s, hz - 194 * s, 3 * s, 3 * s);
    // Nahe Ebene: Speicher mit Giebeln, Fensterraster, ein Kirchturm
    let x = -10 * s;
    while (x < W) {
      const b = (44 + r() * 80) * s, h = (34 + r() * 70) * s, dach = r();
      cx.fillStyle = '#070b14';
      cx.fillRect(x, hz - h, b, h);
      if (dach < .45) { cx.beginPath(); cx.moveTo(x, hz - h); cx.lineTo(x + b / 2, hz - h - (16 + r() * 18) * s); cx.lineTo(x + b, hz - h); cx.fill(); }
      else if (dach < .7) { cx.fillRect(x + b * .2, hz - h - 10 * s, b * .6, 10 * s); }
      const sp = 9 * s, zl = 7 * s;
      for (let yy = hz - h + 8 * s; yy < hz - 6 * s; yy += sp) for (let xx = x + 5 * s; xx < x + b - 6 * s; xx += zl) if (r() < .22) lichter.push([xx, yy, r()]);
      x += b + (r() < .3 ? 6 * s : 0);
    }
    const kx = W * .3;
    cx.fillStyle = '#070b14'; cx.fillRect(kx, hz - 150 * s, 22 * s, 150 * s);
    cx.beginPath(); cx.moveTo(kx - 2 * s, hz - 150 * s); cx.lineTo(kx + 11 * s, hz - 235 * s); cx.lineTo(kx + 24 * s, hz - 150 * s); cx.fill();
    lichter.forEach(([lx, ly, z]) => { const fl = .7 + .3 * Math.sin(t * .4 + z * 40); cx.fillStyle = `rgba(255,${185 + z * 45},${105 + z * 60},${(.4 + .5 * z) * fl})`; cx.fillRect(lx, ly, 3 * s, 3.4 * s); });
    // Lichtspiegelungen
    lichter.slice(0, 160).forEach(([lx, , z], i) => {
      const y = hz + 4 + (i % 9) * 7 * s + Math.sin(t * 1.4 + i) * 2;
      cx.fillStyle = `rgba(255,190,120,${.06 + z * .12})`; cx.fillRect(lx - 3 + Math.sin(t * 2 + i) * 3, y, (4 + z * 10) * s, 1.4);
    });
    // Fähre
    const fx = ((W * .3 + t * 18 * s) % (W + 420 * s)) - 280 * s, fy = hz + 26 * s;
    cx.fillStyle = '#05080e';
    cx.beginPath(); cx.moveTo(fx, fy); cx.lineTo(fx + 260 * s, fy); cx.lineTo(fx + 245 * s, fy + 18 * s); cx.lineTo(fx + 14 * s, fy + 18 * s); cx.closePath(); cx.fill();
    cx.fillRect(fx + 40 * s, fy - 26 * s, 170 * s, 26 * s); cx.fillRect(fx + 90 * s, fy - 44 * s, 70 * s, 18 * s);
    for (let k = 0; k < 13; k++) { cx.fillStyle = `rgba(255,214,150,${.75 + .2 * Math.sin(k * 3.1)})`; cx.fillRect(fx + 48 * s + k * 12.5 * s, fy - 18 * s, 7 * s, 6 * s); }
    for (let k = 0; k < 5; k++) { cx.fillStyle = 'rgba(255,224,170,.85)'; cx.fillRect(fx + 98 * s + k * 12 * s, fy - 38 * s, 6 * s, 5 * s); }
    cx.fillStyle = glow(fx + 130 * s, fy - 10 * s, 160 * s, 'rgba(255,190,110,.16)'); cx.fillRect(0, 0, W, H);
    for (let k = 0; k < 18; k++) { cx.fillStyle = `rgba(255,200,130,${.22 - k * .011})`; cx.fillRect(fx + 50 * s + Math.sin(t * 2.2 + k) * 6, fy + 22 * s + k * 7 * s, 150 * s, 1.5); }
    // Positionslichter
    cx.fillStyle = '#ff4a3a'; cx.fillRect(fx + 4 * s, fy - 4 * s, 3 * s, 3 * s);
    cx.fillStyle = '#4dff8a'; cx.fillRect(fx + 252 * s, fy - 4 * s, 3 * s, 3 * s);
    vignette(.6);
  }

  // 3) Salz: Sonnenaufgang am Elbstrand, ein Frachter am Horizont
  function strand(t) {
    const hz = H * .62, s = Math.min(W, H) / 720;
    cx.fillStyle = verlauf(0, hz, [[0, '#38455f'], [.45, '#a7838a'], [.8, '#e8a77d'], [1, '#f6d6a4']]); cx.fillRect(0, 0, W, hz);
    const sx = W * .64, sy = hz - 6 * s + Math.sin(t * .05) * 2;
    cx.fillStyle = glow(sx, sy, W * .45, 'rgba(255,214,160,.55)'); cx.fillRect(0, 0, W, H);
    cx.fillStyle = '#fff1d6'; cx.beginPath(); cx.arc(sx, sy, 26 * s, Math.PI, 0); cx.fill();
    cx.fillStyle = verlauf(hz, H, [[0, '#d99b78'], [.4, '#7b6470'], [1, '#2a2632']]); cx.fillRect(0, hz, W, H - hz);
    // Frachter am Horizont
    const fx = W * .12 + ((t * 6 * s) % (W * .9));
    cx.fillStyle = 'rgba(52,40,48,.85)';
    cx.beginPath(); cx.moveTo(fx - 8 * s, hz - 10 * s); cx.lineTo(fx + 232 * s, hz - 10 * s); cx.lineTo(fx + 226 * s, hz); cx.lineTo(fx, hz); cx.closePath(); cx.fill();
    cx.fillRect(fx + 196 * s, hz - 30 * s, 24 * s, 20 * s); cx.fillRect(fx + 192 * s, hz - 33 * s, 32 * s, 4 * s); cx.fillRect(fx + 210 * s, hz - 42 * s, 5 * s, 9 * s);
    const r = zufall(5); for (let k = 0; k < 14; k++) { const lagen = 2 + Math.floor(r() * 3); cx.fillRect(fx + 8 * s + k * 13 * s, hz - 10 * s - lagen * 5 * s, 12 * s, lagen * 5 * s); }
    // Glitzern der Sonne
    const r2 = zufall(17);
    for (let i = 0; i < 120; i++) {
      const y = hz + 3 + Math.pow(r2(), 1.3) * (H - hz) * .7, spread = 10 + (y - hz) * .45, x = sx + (r2() - .5) * spread * 2;
      const a = Math.max(0, Math.sin(t * (1.5 + r2() * 2) + i)) * (1 - (y - hz) / (H - hz));
      cx.fillStyle = `rgba(255,236,200,${a * .8})`; cx.fillRect(x, y, (6 + r2() * 22) * s, 1.5);
    }
    // Strand und Halme im Vordergrund
    cx.fillStyle = '#1d1a22';
    cx.beginPath(); cx.moveTo(0, H * .86); cx.bezierCurveTo(W * .3, H * .8, W * .55, H * .9, W, H * .84); cx.lineTo(W, H); cx.lineTo(0, H); cx.closePath(); cx.fill();
    cx.strokeStyle = '#1d1a22'; cx.lineWidth = 1.4 * s;
    const r3 = zufall(41);
    for (let i = 0; i < 70; i++) {
      const bx = r3() * W * .4, by = H * .84 + r3() * 20 * s, h = (30 + r3() * 70) * s, w = Math.sin(t * .8 + i) * 4 * s + (r3() - .5) * 20 * s;
      cx.beginPath(); cx.moveTo(bx, by); cx.quadraticCurveTo(bx + w * .3, by - h * .6, bx + w, by - h); cx.stroke();
    }
    vignette(.45);
  }

  /* ---------- Reel: Crossfade nur über die Deckkraft ---------- */
  let aktuell = 0, naechste = -1, blendeStart = 0, bildStart = performance.now(), spielt = true, rahmenZeit = 0, letzte = performance.now(), anim = 0;
  const unterschrift = $('[data-unterschrift]'), titel = $('[data-titel]'), info = $('[data-info]'), nr = $('[data-nr]'), tc = $('[data-tc]');
  function zeigeUnterschrift(i) {
    if (reduziert()) { setze(); return; }
    unterschrift.classList.add('wechselt');
    setTimeout(() => { setze(); unterschrift.classList.remove('wechselt'); }, UNTERSCHRIFT_MS);
    function setze() { titel.innerHTML = SZENEN[i].titel; info.textContent = SZENEN[i].info; nr.textContent = String(i + 1).padStart(2, '0'); }
  }
  function geheZu(i) {
    i = (i + SZENEN.length) % SZENEN.length;
    if (i === aktuell || naechste >= 0) return;
    naechste = i; blendeStart = performance.now(); bildStart = blendeStart;
    zeigeUnterschrift(i);
    if (reduziert()) { aktuell = i; naechste = -1; malen(); }
  }
  function malen(jetzt = performance.now()) {
    const t = reduziert() ? 4 : rahmenZeit / 1000;
    cx.globalAlpha = 1; SZENEN[aktuell].male(t);
    if (naechste >= 0) {
      const p = Math.min(1, (jetzt - blendeStart) / BLENDE_MS);
      cx.save(); cx.globalAlpha = inOut(p); SZENEN[naechste].male(t); cx.restore();
      if (p >= 1) { aktuell = naechste; naechste = -1; }
    }
  }
  function bild(jetzt) {
    const dt = Math.min(100, jetzt - letzte); letzte = jetzt;
    if (spielt && !reduziert()) {
      rahmenZeit += dt;
      if (jetzt - bildStart > BILD_MS && naechste < 0) geheZu(aktuell + 1);
      const f = Math.floor(rahmenZeit / 40), ss = Math.floor(f / 25);
      tc.textContent = `TC 00:${String(Math.floor(ss / 60) % 60).padStart(2, '0')}:${String(ss % 60).padStart(2, '0')}:${String(f % 25).padStart(2, '0')}`;
    }
    malen(jetzt);
    anim = requestAnimationFrame(bild);
  }
  $$('[data-vor]').forEach(b => b.addEventListener('click', () => geheZu(aktuell + 1)));
  $('[data-zurueck]').addEventListener('click', () => geheZu(aktuell - 1));
  addEventListener('keydown', e => { if (e.key === 'ArrowRight') geheZu(aktuell + 1); if (e.key === 'ArrowLeft') geheZu(aktuell - 1); });
  // Pause-Knöpfe (Ecke und schwebende Leiste): Name bleibt „Reel anhalten“, aria-pressed zeigt den Zustand (WCAG 2.2.2)
  const spielKnoepfe = $$('[data-spiel]');
  function zeigeSpiel() {
    spielKnoepfe.forEach(k => {
      k.setAttribute('aria-pressed', String(!spielt));
      k.querySelector('[data-spiel-icon]').innerHTML = spielt ? '<path d="M3 2h2v8H3zm4 0h2v8H7z" fill="currentColor"/>' : '<path d="M3 1.8v8.4L10 6z" fill="currentColor"/>';
    });
  }
  spielKnoepfe.forEach(k => k.addEventListener('click', () => { spielt = !spielt; bildStart = performance.now(); zeigeSpiel(); }));

  /* ---------- Ton: Web Audio, selbst erzeugt, erst nach Klick ---------- */
  let ac = null, master = null, tonAn = false, hornUhr = 0;
  function klangBauen() {
    ac = new (window.AudioContext || window.webkitAudioContext)();
    master = ac.createGain(); master.gain.value = 0; master.connect(ac.destination);
    // Wasser und Wind: braunes Rauschen, Tiefpass mit langsamer Bewegung
    const len = ac.sampleRate * 4, buf = ac.createBuffer(1, len, ac.sampleRate), d = buf.getChannelData(0);
    let last = 0; for (let i = 0; i < len; i++) { const w = Math.random() * 2 - 1; last = (last + .02 * w) / 1.02; d[i] = last * 3.2; }
    const rausch = ac.createBufferSource(); rausch.buffer = buf; rausch.loop = true;
    const tp = ac.createBiquadFilter(); tp.type = 'lowpass'; tp.frequency.value = 420;
    const lfo = ac.createOscillator(), lfoG = ac.createGain(); lfo.frequency.value = .07; lfoG.gain.value = 160; lfo.connect(lfoG).connect(tp.frequency);
    const rG = ac.createGain(); rG.gain.value = .55;
    rausch.connect(tp).connect(rG).connect(master); rausch.start(); lfo.start();
    // Tiefer Grundton
    const dG = ac.createGain(); dG.gain.value = .05;
    const dTp = ac.createBiquadFilter(); dTp.type = 'lowpass'; dTp.frequency.value = 260;
    [55, 82.4, 110.3].forEach((f, i) => { const o = ac.createOscillator(); o.type = i === 2 ? 'triangle' : 'sine'; o.frequency.value = f; o.connect(dTp); o.start(); });
    dTp.connect(dG).connect(master);
    hornPlanen(4000);
  }
  // Fernes Nebelhorn alle 13 bis 19 Sekunden
  function horn() {
    if (!ac || !tonAn) return;
    const t0 = ac.currentTime, g = ac.createGain(), tp = ac.createBiquadFilter();
    tp.type = 'lowpass'; tp.frequency.value = 480; g.gain.setValueAtTime(0, t0);
    g.gain.linearRampToValueAtTime(.07, t0 + .45); g.gain.setValueAtTime(.07, t0 + 2.1); g.gain.linearRampToValueAtTime(0, t0 + 3.4);
    [98, 98.7, 147.2].forEach(f => { const o = ac.createOscillator(); o.type = 'sawtooth'; o.frequency.value = f; o.connect(tp); o.start(t0); o.stop(t0 + 3.5); });
    tp.connect(g).connect(master);
  }
  function hornPlanen(ms) { clearTimeout(hornUhr); hornUhr = setTimeout(() => { horn(); hornPlanen(13000 + Math.random() * 6000); }, ms); }

  const tonKnoepfe = $$('[data-ton]');
  function anzeige(an) {
    tonKnoepfe.forEach(k => k.setAttribute('aria-pressed', String(an)));
    $$('[data-ton-text]').forEach(t => { t.innerHTML = an ? 'Ton an' : '<span class="aus">Ton aus</span>'; });
    $$('.ton').forEach(k => {
      const balken = $$('.balken i', k);
      if (an) { k.classList.add('klingt'); return; }
      // Stumm: jeder Balken sinkt aus seiner aktuellen Höhe auf 0.2
      const hoehen = balken.map(b => { const m = getComputedStyle(b).transform.match(/matrix\(([^)]+)\)/); return m ? +m[1].split(',')[3] : .2; });
      k.classList.remove('klingt');
      if (!reduziert()) balken.forEach((b, i) => b.animate([{ transform: `scaleY(${hoehen[i]})` }, { transform: 'scaleY(.2)' }], { duration: 200, easing: 'cubic-bezier(0.42, 0, 0.58, 1)' }));
    });
  }
  // still = nur Anzeige (Vorführung): ohne echte Nutzergeste wird nie Ton gestartet
  function setzeTon(an, { still = false } = {}) {
    tonAn = an; anzeige(an);
    if (still) { if (ac && master) master.gain.setTargetAtTime(0, ac.currentTime, .05); return; }
    try { localStorage.setItem('nebelhorn-ton', an ? 'an' : 'aus'); } catch (e) {}
    if (an) {
      if (!ac) klangBauen();
      ac.resume();
      const t0 = ac.currentTime; master.gain.cancelScheduledValues(t0); master.gain.setValueAtTime(master.gain.value, t0);
      master.gain.linearRampToValueAtTime(.9, t0 + TON_EIN_MS / 1000);
      hornPlanen(3000);
    } else if (ac) {
      const t0 = ac.currentTime; master.gain.cancelScheduledValues(t0); master.gain.setValueAtTime(master.gain.value, t0);
      master.gain.linearRampToValueAtTime(0, t0 + TON_AUS_MS / 1000);
      clearTimeout(hornUhr);
    }
  }
  tonKnoepfe.forEach(k => k.addEventListener('click', () => { if (html.classList.contains('vorfuehrung')) neustart(); setzeTon(!tonAn); }));
  // Tab im Hintergrund: Ton anhalten, beim Zurückkehren weiter
  document.addEventListener('visibilitychange', () => { if (!ac) return; if (document.hidden) ac.suspend(); else if (tonAn) ac.resume(); });

  /* ---------- Gate ---------- */
  const gate = $('[data-gate]');
  // Solange das Gate offen ist (Dialog), ist die Seite dahinter nicht fokussierbar
  function gateSperre() { const offen = variante === 'gate' && !gate.classList.contains('weg'); $$('body > header, body > main').forEach(el => { el.inert = offen; }); }
  function eintreten(an, opt) { gate.classList.add('weg'); gateSperre(); setzeTon(an, opt); }
  $$('[data-eintritt]').forEach(b => b.addEventListener('click', () => {
    if (html.classList.contains('vorfuehrung')) { stoppe(); laeuft = false; html.classList.remove('vorfuehrung'); }
    eintreten(b.dataset.eintritt === 'an');
  }));

  /* ---------- Varianten ---------- */
  let variante = 'gate';
  function setzeVariante(v) {
    variante = v;
    $$('[data-variante]').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.variante === v)));
    html.classList.toggle('v-schwebe', v === 'schwebe'); html.classList.toggle('v-onoff', v === 'onoff');
    if (v === 'gate') gate.classList.remove('weg'); else gate.classList.add('weg');
    gateSperre();
  }
  $$('[data-variante]').forEach(b => b.addEventListener('click', () => { if (tonAn) setzeTon(false); setzeVariante(b.dataset.variante); }));
  gateSperre();

  /* ---------- Vorführung ---------- */
  const zeiger = $('[data-zeiger]');
  let uhren = [], zpos = { x: 0, y: 0 }, zAnim = 0, laeuft = false, runde = 0;
  function fahre(el, ms = 700) {
    if (!el) return;
    const r = el.getBoundingClientRect(), von = { ...zpos }, ziel = { x: r.left + r.width * .5, y: r.top + r.height * .6 }, t0 = performance.now();
    cancelAnimationFrame(zAnim);
    const s = t => { const p = Math.min(1, (t - t0) / ms), k = inOut(p); zpos = { x: von.x + (ziel.x - von.x) * k, y: von.y + (ziel.y - von.y) * k }; zeiger.style.transform = `translate(${zpos.x - 2}px, ${zpos.y - 1}px)`; if (p < 1) zAnim = requestAnimationFrame(s); };
    zAnim = requestAnimationFrame(s);
  }
  const druecke = fn => { zeiger.classList.add('drueckt'); setTimeout(() => { zeiger.classList.remove('drueckt'); fn(); }, 150); };
  const nach = (ms, fn) => uhren.push(setTimeout(fn, ms));
  const sichtbarerTon = () => $$('[data-ton]').find(k => k.offsetParent !== null);
  function stoppe() { uhren.forEach(clearTimeout); uhren = []; cancelAnimationFrame(zAnim); }
  function rundeSpielen() {
    if (!laeuft) return;
    const v = ['gate', 'gate', 'schwebe', 'onoff'][runde % 4]; runde++;
    setzeVariante(v); setzeTon(false, { still: true });
    let t = 0;
    if (v === 'gate') {
      nach(t += 500, () => fahre($('.eintritt')));
      nach(t += 900, () => druecke(() => eintreten(true, { still: true })));
      t += BLENDE_MS;
    } else {
      nach(t += 400, () => fahre(sichtbarerTon()));
      nach(t += 900, () => druecke(() => setzeTon(true, { still: true })));
    }
    nach(t += 1400, () => fahre(sichtbarerTon()));
    nach(t += 1300, () => druecke(() => setzeTon(false, { still: true })));
    nach(t += 1700, () => druecke(() => setzeTon(true, { still: true })));
    nach(t += 1800, () => fahre(document.querySelector('[data-unterschrift]'), 900));
    nach(t += 1400, rundeSpielen);
  }
  function neustart() {
    stoppe(); laeuft = false; html.classList.remove('vorfuehrung');
    setzeTon(false, { still: true }); setzeVariante(variante);
  }
  function vorfuehren() {
    stoppe(); laeuft = true; runde = 0;
    if (tonAn && ac) setzeTon(false);     // laufender Ton wird für die Vorführung ausgeblendet
    html.classList.add('vorfuehrung');
    zpos = { x: innerWidth * .62, y: innerHeight * .7 }; zeiger.style.transform = `translate(${zpos.x}px, ${zpos.y}px)`;
    rundeSpielen();
  }
  function pause() { stoppe(); laeuft = false; spielt = false; zeigeSpiel(); }
  function weiter() { spielt = true; zeigeSpiel(); if (html.classList.contains('vorfuehrung') && !laeuft) { laeuft = true; rundeSpielen(); } }

  groesse(); addEventListener('resize', () => { groesse(); malen(); });
  if (reduziert()) { malen(); } else anim = requestAnimationFrame(bild);
  // Wird .reduziert später gesetzt (Detailseite), läuft die Schleife trotzdem und malt ein stehendes Bild

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