Zum Inhalt
← Sektionen & Funktionen

Bild mit Markierungspunkten

Raumbild mit vier Punkten: Jeder Punkt ist ein Popover-Invoker, die Karte mit Name, Preis und „Zum Produkt“ hängt per Anchor Positioning am Punkt und weicht am Rand aus.

Onlineshops und Manufakturen mit Raumbildern: Möbel, Wohnen, Mode, Kosmetik.

Auf einem großen Raum-, Produkt- oder Maschinenbild sitzen wenige kleine Punkte. Ein Klick oder Tippen öffnet neben dem Punkt eine Karte mit Name, kurzer Angabe und Weg weiter, etwa Preis und „Zum Produkt“ oder Bauteil und Datenblatt. Die Karte weicht am Bildrand selbst aus. Auf dem Telefon tragen die Punkte Nummern und die Inhalte stehen zusätzlich als Liste unter dem Bild.

Das Bild bleibt ein Bild und wird trotzdem lesbar: Man entdeckt im Zusammenhang, statt Bild und Liste getrennt abzugleichen. Für Shops verkauft das Raumbild die einzelnen Stücke, für Industrie und Hotel erklärt es Ausstattung, ohne Pfeile ins Foto zu malen.

Wann einsetzen

Raum- und Lookbilder im Shop, Zimmerausstattung im Hotel, Bauteile einer Maschine oder eines Fahrzeugs, Ausstattung eines Objekts im Exposé. Bilder ab etwa 2000 px Breite mit klar erkennbaren Gegenständen.

Wann nicht

Wenn die Information wichtig ist und nur im Punkt stünde. Bei kleinen Bildern oder mehr als etwa sechs Punkten. Bei Porträts. Generik-Gefahr: pulsierende Plus-Punkte auf jedem Bild der Seite. Höchstens ein Hotspot-Bild pro Ansicht, Punkte ohne Dauerpuls.

Varianten

  • shop: Karte mit Name, Preis inkl. MwSt. und Link zur Produktseite
  • bauteil: nummerierte Punkte, Karte mit Kurzangabe und Link auf technische Daten
  • zimmer: Ausstattung ohne Preis, Liste unter dem Bild als Hauptweg

Umsetzung

  1. figure mit img (feste aspect-ratio) und einer ol der Punkte; jeder Punkt ist ein button popovertarget mit style="--x: 42%; --y: 63%" und position: absolute.
  2. Karte als div popover; der Invoker stellt den Anker-Bezug her, Platzierung mit position-area und position-try-fallbacks (flip-block, flip-inline).
  3. Ein- und Ausblenden per @starting-style und allow-discrete wie einblenden-starting-style; Schließen schneller als Öffnen.
  4. Liste der Inhalte als echte ol unter dem Bild (figcaption oder nachfolgend), auf dem Telefon sichtbar, am Desktop optional visuell versteckt.
  5. Punkte nur bei @media (hover: hover) zusätzlich mit Hover-Hervorhebung; kein Dauerpuls.
  6. Fallback ohne Anchor Positioning: Popover erscheint mittig über dem Bild (Standard), Inhalt bleibt nutzbar.

Robust umsetzen

Telefon. Nummerierte Punkte, Karte als Bottom-Sheet oder unter dem Bild, Liste als Hauptweg.

Ohne Bewegung. Karte erscheint ohne Weg, nur Deckkraft unter 200 ms.

Barrierefrei. Jeder Punkt hat einen sprechenden Namen (aria-label „Stehleuchte Lio, 249 €“). DOM-Reihenfolge gleich Lesereihenfolge. Esc und Klick außerhalb schließen, Fokus kehrt zum Punkt zurück. Trefferfläche 44 px. Kontrast der Punkte 3:1 gegen jeden Bildbereich (Ring in zwei Tönen). 320 px und 200 % Zoom: Liste statt Punkte. Keine Bewegung über 5 s.

Code

<!DOCTYPE html>
<!--
  Bild mit Markierungspunkten
  Fundus · Sektionen & Funktionen · https://cecodesign.de/lab/fundus/sektionen/bild-hotspots/
  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 mit Markierungspunkten · 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: 'Young Serif'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/young-serif@latest/latin-400-normal.woff2') format('woff2'); font-weight: 400; 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: 'Strichpunkt Sans'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/strichpunkt-sans:vf@latest/latin-full-normal.woff2') format('woff'); font-weight: 400 900; font-stretch: 100% 200%; font-display: swap; }
@font-face { font-family: 'Archivo'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/archivo:vf@latest/latin-full-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-stretch: 62% 125%; font-display: swap; }
@font-face { font-family: 'Newsreader'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/newsreader:vf@latest/latin-opsz-normal.woff2') format('woff2-variations'); font-weight: 200 800; font-display: swap; }
@font-face { font-family: 'Mona Sans'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/mona-sans:vf@latest/latin-wdth-normal.woff2') format('woff2-variations'); font-weight: 200 900; font-stretch: 75% 125%; font-display: swap; }
@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; }

/* Eine Kurve für die ganze Demo: token-toggle-asymmetrisch (Öffnen Ausklang, Schließen Anlauf, 2/3 der Dauer) */
:root {
  --auf-kurve: cubic-bezier(0.16, 1, 0.3, 1);
  --zu-kurve: cubic-bezier(0.7, 0, 0.84, 0);
  --auf: 250ms; --zu: 165ms; --ui: 150ms; --staffel: 60ms;
}
* { box-sizing: border-box; margin: 0; }
html { -webkit-font-smoothing: antialiased; }
body { min-height: 100svh; overflow-x: clip; }
a, button { color: inherit; font: inherit; }
img { display: block; max-width: 100%; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.welt[hidden] { display: none; }

/* ── Welten (je Variante ein Rezept) ─────────────────────────────────── */
.welt-shop { --grund: #F5F4F1; --kachel: #ECE9E2; --text: #212121; --leise: #696968; --linie: #D3D2D0; --signal: #212121; --gruen: #10714E;
  --kopf: 'Young Serif', Georgia, serif; --fliess: 'Instrument Sans', system-ui, sans-serif; --r-k: 12px; --r-b: 999px; }
.welt-bauteil { --grund: #F3F3F0; --kachel: #E4E5E1; --text: #111417; --leise: #4E555B; --linie: rgba(17, 20, 23, .13); --signal: #0E518D; --dunkel: #16202A;
  --kopf: 'Strichpunkt Sans', system-ui, sans-serif; --fliess: 'Archivo', system-ui, sans-serif; --r-k: 0; --r-b: 0; }
.welt-zimmer { --grund: #F3EEE7; --kachel: #FEF2E3; --text: #3E3A37; --leise: #6A6C67; --linie: #D5D1C8; --signal: #6E5A47;
  --kopf: 'Newsreader', Georgia, serif; --fliess: 'Mona Sans', system-ui, sans-serif; --r-k: 6px; --r-b: 999px; }
.welt { background: var(--grund); color: var(--text); font-family: var(--fliess); min-height: 100svh; padding: 0 clamp(16px, 3.4vw, 48px) clamp(48px, 7vw, 96px); }
:focus-visible { outline: 2px solid var(--text); outline-offset: 3px; }

/* Kopfzeilen der Welten */
.leiste { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; padding: 18px 0; border-bottom: 1px solid var(--linie); }
.leiste nav ul { list-style: none; padding: 0; display: flex; flex-wrap: wrap; gap: 4px 20px; font-size: .9375rem; }
.leiste nav a { text-decoration: none; color: var(--leise); }
.leiste nav a:hover { color: var(--text); }
.marke { font-family: var(--kopf); text-decoration: none; }
.welt-shop .marke { font-size: 1.625rem; letter-spacing: -.01em; }
.welt-bauteil .marke { font-weight: 700; font-stretch: 118%; font-size: 1.125rem; letter-spacing: .02em; }
.welt-zimmer .marke { font-size: 1.5rem; font-weight: 400; font-style: normal; }
.korb { font-size: .9375rem; text-decoration: none; display: inline-flex; align-items: center; gap: 8px; min-height: 44px; }
.korb b { display: inline-grid; place-items: center; width: 22px; height: 22px; border-radius: 50%; background: var(--text); color: var(--grund); font-size: .75rem; }

.kopf { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, auto); align-items: end; gap: 12px 32px; padding: clamp(28px, 4vw, 56px) 0 clamp(20px, 2.4vw, 32px); }
.etikett { font-size: .75rem; letter-spacing: .06em; text-transform: uppercase; color: var(--leise); font-weight: 600; }
h1, h2 { font-family: var(--kopf); font-weight: 400; text-wrap: balance; }
.welt-shop h2 { font-size: clamp(2.25rem, 1.4rem + 3.4vw, 4.25rem); line-height: 1.04; letter-spacing: -.01em; margin-top: 10px; }
.welt-bauteil h2 { font-weight: 600; font-stretch: 112%; font-size: clamp(2rem, 1.3rem + 2.6vw, 3.5rem); line-height: .98; letter-spacing: -.03em; margin-top: 10px; }
.welt-zimmer h2 { font-weight: 350; font-size: clamp(2.25rem, 1.4rem + 3.2vw, 4.5rem); line-height: 1.04; letter-spacing: -.015em; margin-top: 10px; font-variation-settings: 'opsz' 72; }
.welt-zimmer h2 em { font-style: italic; }
.kopf p { max-width: 42ch; color: var(--leise); line-height: 1.5; }

/* ── Bild mit Punkten ───────────────────────────────────────────────── */
.hs { position: relative; border-radius: var(--r-k); }
.hs img { width: 100%; height: auto; aspect-ratio: 3 / 2; object-fit: cover; border-radius: var(--r-k); background: var(--kachel); }
.welt-bauteil .hs img { aspect-ratio: 1860 / 1085; }
.punkte { list-style: none; padding: 0; position: absolute; inset: 0; pointer-events: none; }
.punkt {
  position: absolute; left: var(--x); top: var(--y); width: 44px; height: 44px; margin: -22px 0 0 -22px; /* Trefferfläche 44 px */
  display: grid; place-items: center; padding: 0; border: 0; background: none; cursor: pointer; pointer-events: auto;
  opacity: 0; scale: .6;
  transition: opacity var(--auf) var(--auf-kurve), scale var(--auf) var(--auf-kurve);
  transition-delay: calc(var(--i) * var(--staffel));
}
.gezeigt .punkt { opacity: 1; scale: 1; }
html:not(.js) .punkt { opacity: 1; scale: 1; }
/* Sichtbarer Punkt 24 px: weißer Ring auf dunklem Kern, außen dunkler Saum (3:1 auf hellem und dunklem Grund) */
.punkt i { display: grid; place-items: center; width: 24px; height: 24px; border-radius: 50%; background: rgba(20, 20, 20, .72); box-shadow: 0 0 0 2px #fff, 0 0 0 3px rgba(0, 0, 0, .45), 0 2px 10px rgba(0, 0, 0, .25);
  font: 600 12px/1 var(--fliess); font-style: normal; color: #fff; font-variant-numeric: tabular-nums;
  transition: scale var(--ui) var(--auf-kurve), background-color var(--ui) var(--auf-kurve); }
.punkt i::after { content: ''; width: 8px; height: 8px; border-radius: 50%; background: #fff; }
.punkt .nr { display: none; }
.mit-nummer .punkt i::after { display: none; }
.mit-nummer .punkt .nr { display: block; }
@media (hover: hover) { .punkt:hover i, .punkt.hervor i { scale: 1.18; } }
.punkt.hervor i { scale: 1.18; }
.punkt.offen i { background: #fff; color: #111; box-shadow: 0 0 0 2px #111, 0 0 0 5px rgba(255, 255, 255, .7), 0 2px 10px rgba(0, 0, 0, .25); scale: 1.18; }
.punkt.offen i::after { background: #111; }
.punkt:focus-visible { outline: none; }
.punkt:focus-visible i { outline: 2px solid #fff; outline-offset: 4px; box-shadow: 0 0 0 2px #fff, 0 0 0 3px rgba(0,0,0,.45), 0 0 0 7px #111; }
.welt-bauteil .punkt i { border-radius: 0; background: var(--signal); }
.welt-bauteil .punkt.offen i { background: #fff; color: var(--signal); box-shadow: 0 0 0 2px var(--signal), 0 0 0 5px rgba(255,255,255,.75); }

/* ── Karte: Popover mit Anchor Positioning ──────────────────────────── */
.karte[popover] {
  width: min(290px, calc(100vw - 32px)); padding: 16px 18px 18px; border: 0; border-radius: var(--r-k);
  background: #fff; color: var(--text); font-family: var(--fliess); box-shadow: 0 1px 2px rgba(0,0,0,.08), 0 12px 32px -8px rgba(0,0,0,.28);
  opacity: 0; translate: 0 6px;
  transition: opacity var(--zu) var(--zu-kurve), translate var(--zu) var(--zu-kurve), display var(--zu) allow-discrete, overlay var(--zu) allow-discrete;
}
.karte[popover]:popover-open { opacity: 1; translate: 0 0; transition-timing-function: var(--auf-kurve); transition-duration: var(--auf); }
@starting-style { .karte[popover]:popover-open { opacity: 0; translate: 0 6px; } }
/* Rechts neben dem Punkt, mittig zum Anker; am Rand links, sonst darunter oder darüber. 8 px Abstand zum sichtbaren Punkt (Trefferfläche 44 px, Punkt 24 px: 10 px − 2 px) */
@supports (position-area: top) {
  .karte[popover] { inset: auto; margin: 0 0 0 -2px; position-area: right; align-self: anchor-center; position-try-fallbacks: flip-inline, --unten, --oben; }
}
@position-try --unten { position-area: bottom; justify-self: anchor-center; align-self: start; margin: -2px 0 0 0; }
@position-try --oben { position-area: top; justify-self: anchor-center; align-self: end; margin: 0 0 -2px 0; }
.karte::backdrop { background: transparent; }
.karte .k-zu { position: absolute; top: 4px; right: 4px; width: 44px; height: 44px; border: 0; background: none; cursor: pointer; display: grid; place-items: center; color: var(--leise); border-radius: var(--r-b); }
.karte .k-zu:hover { color: var(--text); }
.karte .k-zu svg { width: 12px; height: 12px; }
.k-titel { font-weight: 600; font-size: 1.0625rem; padding-right: 36px; line-height: 1.3; }
.k-text { color: var(--leise); font-size: .9375rem; line-height: 1.45; margin-top: 4px; }
.k-preis { margin-top: 12px; font-weight: 600; font-variant-numeric: tabular-nums; font-size: 1.0625rem; }
.k-preis small { display: block; font-weight: 400; font-size: .8125rem; color: var(--leise); margin-top: 2px; }
.k-link { display: inline-flex; align-items: center; gap: 8px; margin-top: 14px; min-height: 44px; padding: 0 18px; border-radius: var(--r-b); background: var(--signal); color: #fff; text-decoration: none; font-weight: 600; font-size: .9375rem; }
.k-link svg { width: 13px; height: 13px; transition: translate var(--ui) var(--auf-kurve); }
.k-link:hover svg { translate: 3px 0; }
.welt-bauteil .karte { border-top: 3px solid var(--signal); }
.welt-bauteil .k-titel { font-family: var(--kopf); font-stretch: 108%; }
.welt-bauteil .k-nr { font: 600 .75rem/1 var(--fliess); font-stretch: 75%; letter-spacing: .08em; color: var(--leise); text-transform: uppercase; display: block; margin-bottom: 6px; }
.welt-bauteil .k-link { background: transparent; color: var(--signal); padding: 0; min-height: 44px; }
.welt-zimmer .k-titel { font-family: var(--kopf); font-weight: 400; font-size: 1.375rem; }

/* ── Liste unter bzw. neben dem Bild ───────────────────────────────── */
.liste { list-style: none; padding: 0; counter-reset: n; }
.liste li { counter-increment: n; }
/* Shop und Bauteil: Text links, Bild rechts */
.buehne { display: grid; grid-template-columns: minmax(0, 4fr) minmax(0, 8fr); gap: 24px clamp(24px, 4vw, 64px); align-items: start; padding-top: clamp(28px, 4vw, 56px); }
.spalte { display: flex; flex-direction: column; gap: 14px; }
.einleitung { color: var(--leise); line-height: 1.5; max-width: 40ch; }
.welt-shop .unter { margin-top: clamp(12px, 3vw, 40px); }
.welt-shop .liste { margin-top: 10px; border-top: 1px solid var(--linie); }
.welt-shop .liste a { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--linie); text-decoration: none; min-height: 48px; }
.welt-shop .liste a::before { content: counter(n); display: grid; place-items: center; width: 24px; height: 24px; border-radius: 50%; border: 1px solid var(--linie); font-size: .75rem; color: var(--leise); font-variant-numeric: tabular-nums; transition: background-color var(--ui) var(--auf-kurve), color var(--ui) var(--auf-kurve); }
.welt-shop .liste b { font-weight: 600; font-size: .9375rem; }
.welt-shop .liste span { color: var(--leise); font-size: .875rem; font-variant-numeric: tabular-nums; }
.welt-shop .liste a:hover b, .welt-shop .liste a.hervor b { text-decoration: underline; text-underline-offset: 3px; }
.welt-shop .liste a.hervor::before { background: var(--text); color: var(--grund); border-color: var(--text); }
.welt-shop h2 { margin-top: 0; }
.welt-bauteil h2 { margin-top: 0; }
/* Bauteil: Datentabelle als Liste */
.daten { margin-top: 28px; }
.daten table { width: 100%; border-collapse: collapse; font-stretch: 87%; font-size: .9375rem; font-variant-numeric: tabular-nums; }
.daten caption { text-align: left; font-weight: 600; font-stretch: 75%; font-size: .75rem; letter-spacing: .08em; text-transform: uppercase; color: var(--leise); padding-bottom: 10px; }
.daten th, .daten td { text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--linie); vertical-align: top; }
.daten thead th { background: var(--kachel); font-size: .8125rem; }
.daten td.nr { width: 48px; font-weight: 600; color: var(--signal); }
.daten tbody tr { transition: background-color var(--ui) var(--auf-kurve); scroll-margin-top: 24px; }
.daten tbody tr:target, .daten tbody tr.hervor { background: rgba(14, 81, 141, .08); }
/* Zimmer: Liste als Hauptweg neben dem Bild */
.zimmer-raster { display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: clamp(24px, 4vw, 64px); align-items: start; }
.welt-zimmer .liste { border-top: 1px solid var(--text); }
.welt-zimmer .liste li { display: grid; grid-template-columns: 40px minmax(0, 1fr); gap: 4px 12px; padding: 16px 0; border-bottom: 1px solid var(--linie); transition: background-color var(--ui) var(--auf-kurve); }
.welt-zimmer .liste li::before { content: counter(n); grid-row: span 2; display: grid; place-items: center; width: 28px; height: 28px; border-radius: 50%; border: 1px solid var(--text); font-size: .8125rem; font-variant-numeric: tabular-nums; }
.welt-zimmer .liste h3 { font-family: var(--kopf); font-weight: 400; font-size: 1.375rem; line-height: 1.2; }
.welt-zimmer .liste p { color: var(--leise); font-size: .9375rem; line-height: 1.5; }
.welt-zimmer .liste li.hervor { background: var(--kachel); }
.welt-zimmer .liste li.hervor::before { background: var(--text); color: var(--grund); }
.zimmer-fuss { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 20px; margin-top: 24px; }
.knopf { display: inline-flex; align-items: center; min-height: 48px; padding: 0 24px; border-radius: var(--r-b); background: var(--signal); color: #fff; text-decoration: none; font-weight: 600; font-stretch: 112%; font-size: .875rem; letter-spacing: .04em; text-transform: uppercase; }
.zimmer-fuss small { color: var(--leise); font-size: .875rem; }
.hinweis-punkte { display: flex; align-items: center; gap: 8px; color: var(--leise); font-size: .875rem; }
.welt-bauteil .hinweis-punkte i { border-radius: 0; background: var(--signal); }
.hinweis-punkte i { width: 14px; height: 14px; border-radius: 50%; background: rgba(20,20,20,.72); box-shadow: 0 0 0 2px #fff, 0 0 0 3px rgba(0,0,0,.35); flex: none; }

/* ── Telefon: Nummern, Karte als Blatt unten ───────────────────────── */
@media (max-width: 640px) {
  .mit-nummer-mobil .punkt i::after { display: none; }
  .mit-nummer-mobil .punkt .nr { display: block; }
  .karte[popover] { position-area: none; inset: auto 0 0 0; width: 100%; max-width: none; margin: 0; border-radius: 16px 16px 0 0; padding: 20px 20px calc(20px + env(safe-area-inset-bottom)); translate: 0 24px; }
  .karte[popover]:popover-open { translate: 0 0; }
  @starting-style { .karte[popover]:popover-open { translate: 0 24px; } }
  .karte::backdrop { background: rgba(0, 0, 0, .28); }
  .leiste nav { display: none; }
  .kopf { grid-template-columns: minmax(0, 1fr); }
  .daten th, .daten td { padding: 10px 6px; }
}
@media (max-width: 900px) { .zimmer-raster, .buehne { grid-template-columns: minmax(0, 1fr); } .welt-shop .unter { order: 3; } .welt-shop .spalte { display: contents; } .welt-shop .buehne > .hs { order: 2; } .buehne { row-gap: 14px; } .welt-shop .buehne > .hs { margin-top: 10px; } }
/* 320 px und 200 % Zoom: Liste statt Punkte */
@media (max-width: 22.4rem), (max-height: 30rem) {
  .punkte { display: none; }
  .hinweis-punkte { display: none; }
}

/* Vorschau: nur die Bühne */
.vorschau body { overflow: hidden; }
.vorschau .leiste nav, .vorschau .korb, .vorschau .zimmer-fuss { display: none; }
.vorschau .daten { margin-top: 20px; }
.vorschau .kopf { padding: 20px 0 16px; }
.vorschau .buehne { padding-top: 24px; }
.vorschau .einleitung { display: none; }

/* ── Pult ────────────────────────────────────────────────────────────── */
.pult { position: fixed; right: 12px; bottom: 12px; z-index: 40; display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; padding: 6px 6px 6px 12px; border-radius: 12px; background: rgba(24, 24, 24, .92); color: rgba(255, 255, 255, .72); font: 500 10.5px/1 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; max-width: calc(100vw - 24px); }
.pult .gruppe { display: flex; flex-wrap: wrap; gap: 4px; }
.pult button { font: inherit; letter-spacing: inherit; text-transform: inherit; height: 30px; padding: 0 10px; border-radius: 999px; border: 1px solid rgba(255, 255, 255, .22); background: none; color: rgba(255, 255, 255, .82); cursor: pointer; }
.pult button[aria-pressed='true'] { background: #fff; color: #181818; border-color: #fff; }
.pult :focus-visible { outline-color: #fff; }

:root.reduziert .punkt, :root.reduziert .punkt i { transition: opacity 120ms linear !important; scale: 1 !important; }
:root.reduziert .karte[popover] { translate: 0 0 !important; transition: opacity 120ms linear, display 120ms allow-discrete, overlay 120ms allow-discrete !important; }
:root.reduziert * { animation-duration: .01ms !important; }
@media (hover: none) { .k-link:hover svg { translate: 0 0; } }
@media (forced-colors: active) {
  button, [role=button], [role=tab], [role=switch], input, select, textarea { border: 1px solid ButtonText !important; }
  .punkt i { forced-color-adjust: none; background: Canvas; color: CanvasText; box-shadow: 0 0 0 2px CanvasText; }
  .karte { border: 1px solid CanvasText !important; }
}
</style>
</head>
<body data-v="shop">
<main>
<!-- ═════ Variante shop · Rezept d2c-produkt-zuerst · Ruhwerk, Rastatt ═════ -->
<section class="welt welt-shop mit-nummer-mobil" data-welt="shop" aria-labelledby="h-shop">
  <header class="leiste">
    <a class="marke" href="#h-shop">Ruhwerk</a>
    <nav aria-label="Shop"><ul><li><a href="#h-shop">Möbel</a></li><li><a href="#h-shop">Objekte</a></li><li><a href="#h-shop">Kalkfarben</a></li><li><a href="#h-shop">Räume</a></li></ul></nav>
    <a class="korb" href="#h-shop">Warenkorb <b>0</b></a>
  </header>
  <div class="buehne">
    <div class="spalte">
      <p class="etikett">Raum 04 · Flur in Salbei</p>
      <h2 id="h-shop">Ein Flur, vier Stücke.</h2>
      <p class="einleitung">Tipp auf die Punkte: Alles im Bild gibt es einzeln. Gefertigt und gemischt in Rastatt.</p>
      <div class="unter">
        <p class="etikett">Im Bild</p>
        <ol class="liste">
          <li><a href="#h-shop" data-nr="0"><b>Kalkfarbe Salbei</b><span>59,00 € · 2,5 l</span></a></li>
          <li><a href="#h-shop" data-nr="1"><b>Vase Krug</b><span>89,00 €</span></a></li>
          <li><a href="#h-shop" data-nr="2"><b>Bank Ostra</b><span>690,00 €</span></a></li>
          <li><a href="#h-shop" data-nr="3"><b>Schale Mulde</b><span>34,00 €</span></a></li>
        </ol>
      </div>
    </div>
    <figure class="hs">
      <img src="bilder/fenster-licht.jpg" width="1200" height="800" alt="Flur mit salbeigrüner Kalkputzwand im Abendlicht, davor eine Eichenbank mit dunkler Vase, Bücherstapel und weißer Schale">
      <ul class="punkte" aria-label="Stücke im Bild">
        <li><button class="punkt" type="button" popovertarget="k-farbe" style="--x: 17%; --y: 31%; --i: 0; anchor-name: --p-farbe" aria-label="Kalkfarbe Salbei, 59 €"><i><span class="nr">1</span></i></button></li>
        <li><button class="punkt" type="button" popovertarget="k-vase" style="--x: 46.5%; --y: 58%; --i: 1; anchor-name: --p-vase" aria-label="Vase Krug, 89 €"><i><span class="nr">2</span></i></button></li>
        <li><button class="punkt" type="button" popovertarget="k-bank" style="--x: 62%; --y: 69%; --i: 2; anchor-name: --p-bank" aria-label="Bank Ostra, 690 €"><i><span class="nr">3</span></i></button></li>
        <li><button class="punkt" type="button" popovertarget="k-schale" style="--x: 84%; --y: 66%; --i: 3; anchor-name: --p-schale" aria-label="Schale Mulde, 34 €"><i><span class="nr">4</span></i></button></li>
      </ul>
    </figure>
  </div>
  <div class="karte" id="k-farbe" popover style="position-anchor: --p-farbe">
    <p class="k-titel">Kalkfarbe Salbei</p>
    <p class="k-text">Mineralisch, matt, atmet mit. 2,5 l reichen für etwa 15 m².</p>
    <p class="k-preis">59,00 € <small>inkl. MwSt., zzgl. Versand · 23,60 € / l</small></p>
    <a class="k-link" href="#h-shop">Zum Produkt <svg viewBox="0 0 14 14" aria-hidden="true"><path d="M1 7h11M8 3l4 4-4 4" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></a>
    <button class="k-zu" type="button" popovertarget="k-farbe" popovertargetaction="hide" aria-label="Karte schließen"><svg viewBox="0 0 12 12" aria-hidden="true"><path d="M1 1l10 10M11 1L1 11" stroke="currentColor" stroke-width="1.6"/></svg></button>
  </div>
  <div class="karte" id="k-vase" popover style="position-anchor: --p-vase">
    <p class="k-titel">Vase Krug</p>
    <p class="k-text">Steinzeug, Glasur Erde, 32 cm hoch. Jede ein wenig anders.</p>
    <p class="k-preis">89,00 € <small>inkl. MwSt., zzgl. Versand</small></p>
    <a class="k-link" href="#h-shop">Zum Produkt <svg viewBox="0 0 14 14" aria-hidden="true"><path d="M1 7h11M8 3l4 4-4 4" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></a>
    <button class="k-zu" type="button" popovertarget="k-vase" popovertargetaction="hide" aria-label="Karte schließen"><svg viewBox="0 0 12 12" aria-hidden="true"><path d="M1 1l10 10M11 1L1 11" stroke="currentColor" stroke-width="1.6"/></svg></button>
  </div>
  <div class="karte" id="k-bank" popover style="position-anchor: --p-bank">
    <p class="k-titel">Bank Ostra</p>
    <p class="k-text">Eiche massiv, geölt, 140 cm. Trägt zwei Personen oder einen Stapel Bücher.</p>
    <p class="k-preis">690,00 € <small>inkl. MwSt., zzgl. Versand · Lieferung in 3 Wochen</small></p>
    <a class="k-link" href="#h-shop">Zum Produkt <svg viewBox="0 0 14 14" aria-hidden="true"><path d="M1 7h11M8 3l4 4-4 4" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></a>
    <button class="k-zu" type="button" popovertarget="k-bank" popovertargetaction="hide" aria-label="Karte schließen"><svg viewBox="0 0 12 12" aria-hidden="true"><path d="M1 1l10 10M11 1L1 11" stroke="currentColor" stroke-width="1.6"/></svg></button>
  </div>
  <div class="karte" id="k-schale" popover style="position-anchor: --p-schale">
    <p class="k-titel">Schale Mulde</p>
    <p class="k-text">Steinzeug weiß, Ø 22 cm. Für Schlüssel, Obst oder nichts.</p>
    <p class="k-preis">34,00 € <small>inkl. MwSt., zzgl. Versand</small></p>
    <a class="k-link" href="#h-shop">Zum Produkt <svg viewBox="0 0 14 14" aria-hidden="true"><path d="M1 7h11M8 3l4 4-4 4" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></a>
    <button class="k-zu" type="button" popovertarget="k-schale" popovertargetaction="hide" aria-label="Karte schließen"><svg viewBox="0 0 12 12" aria-hidden="true"><path d="M1 1l10 10M11 1L1 11" stroke="currentColor" stroke-width="1.6"/></svg></button>
  </div>
</section>

<!-- ═════ Variante bauteil · Rezept industrie-b2b-mittelstand · Fallstrom Pumpenbau, Remscheid ═════ -->
<section class="welt welt-bauteil mit-nummer" data-welt="bauteil" aria-labelledby="h-bauteil" hidden>
  <header class="leiste">
    <a class="marke" href="#h-bauteil">FALLSTROM</a>
    <nav aria-label="Produkte"><ul><li><a href="#h-bauteil">Normpumpen</a></li><li><a href="#h-bauteil">Ersatzteile</a></li><li><a href="#h-bauteil">Service</a></li><li><a href="#h-bauteil">Kontakt</a></li></ul></nav>
  </header>
  <div class="buehne">
  <div class="spalte">
    <p class="etikett" style="font-stretch: 75%; letter-spacing: .08em">Normpumpe · Baureihe FN</p><h2 id="h-bauteil">FN 80-250. Aufbau im Überblick.</h2>
    <p class="einleitung">Sechs Baugruppen, jede mit Werkstoff und Anschlussmaß. Gebaut in Remscheid, Ersatzteile 15 Jahre lieferbar.</p>
    <p class="hinweis-punkte"><i></i>Nummern im Bild öffnen die Baugruppe.</p>
  </div>
  <figure class="hs">
    <img src="bilder/bild-hotspots-pumpe.webp" width="1860" height="1085" alt="Normpumpe auf Grundrahmen: links Saugstutzen und Spiralgehäuse mit Druckstutzen nach oben, in der Mitte die Laterne mit Kupplung, rechts der Elektromotor mit Klemmenkasten">
    <ul class="punkte" aria-label="Baugruppen im Bild">
      <li><button class="punkt" type="button" popovertarget="k-saug" style="--x: 21.5%; --y: 52.5%; --i: 0; anchor-name: --p-saug" aria-label="1, Saugstutzen DN 100"><i><span class="nr">1</span></i></button></li>
      <li><button class="punkt" type="button" popovertarget="k-spirale" style="--x: 39%; --y: 53%; --i: 1; anchor-name: --p-spirale" aria-label="2, Spiralgehäuse"><i><span class="nr">2</span></i></button></li>
      <li><button class="punkt" type="button" popovertarget="k-druck" style="--x: 35.3%; --y: 12.4%; --i: 2; anchor-name: --p-druck" aria-label="3, Druckstutzen DN 80"><i><span class="nr">3</span></i></button></li>
      <li><button class="punkt" type="button" popovertarget="k-laterne" style="--x: 46.5%; --y: 45.3%; --i: 3; anchor-name: --p-laterne" aria-label="4, Laterne mit Kupplung"><i><span class="nr">4</span></i></button></li>
      <li><button class="punkt" type="button" popovertarget="k-klemmen" style="--x: 62.4%; --y: 16.2%; --i: 4; anchor-name: --p-klemmen" aria-label="5, Klemmenkasten"><i><span class="nr">5</span></i></button></li>
      <li><button class="punkt" type="button" popovertarget="k-motor" style="--x: 67.3%; --y: 38.1%; --i: 5; anchor-name: --p-motor" aria-label="6, Motor 15 kW"><i><span class="nr">6</span></i></button></li>
    </ul>
  </figure>
  </div>
  <div class="karte" id="k-saug" popover style="position-anchor: --p-saug"><span class="k-nr">Baugruppe 1</span><p class="k-titel">Saugstutzen</p><p class="k-text">DN 100, PN 16, axial. Flansch nach EN 1092-2.</p><a class="k-link" href="#td-saug" data-zeile>Technische Daten <svg viewBox="0 0 14 14" aria-hidden="true"><path d="M7 1v11M3 8l4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></a><button class="k-zu" type="button" popovertarget="k-saug" popovertargetaction="hide" aria-label="Karte schließen"><svg viewBox="0 0 12 12" aria-hidden="true"><path d="M1 1l10 10M11 1L1 11" stroke="currentColor" stroke-width="1.6"/></svg></button></div>
  <div class="karte" id="k-spirale" popover style="position-anchor: --p-spirale"><span class="k-nr">Baugruppe 2</span><p class="k-titel">Spiralgehäuse</p><p class="k-text">Grauguss EN-GJL-250, Laufrad Ø 250 mm, Gehäusedeckel mit 12 Schrauben.</p><a class="k-link" href="#td-spirale" data-zeile>Technische Daten <svg viewBox="0 0 14 14" aria-hidden="true"><path d="M7 1v11M3 8l4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></a><button class="k-zu" type="button" popovertarget="k-spirale" popovertargetaction="hide" aria-label="Karte schließen"><svg viewBox="0 0 12 12" aria-hidden="true"><path d="M1 1l10 10M11 1L1 11" stroke="currentColor" stroke-width="1.6"/></svg></button></div>
  <div class="karte" id="k-druck" popover style="position-anchor: --p-druck"><span class="k-nr">Baugruppe 3</span><p class="k-titel">Druckstutzen</p><p class="k-text">DN 80, PN 16, nach oben. Förderhöhe bis 80 m.</p><a class="k-link" href="#td-druck" data-zeile>Technische Daten <svg viewBox="0 0 14 14" aria-hidden="true"><path d="M7 1v11M3 8l4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></a><button class="k-zu" type="button" popovertarget="k-druck" popovertargetaction="hide" aria-label="Karte schließen"><svg viewBox="0 0 12 12" aria-hidden="true"><path d="M1 1l10 10M11 1L1 11" stroke="currentColor" stroke-width="1.6"/></svg></button></div>
  <div class="karte" id="k-laterne" popover style="position-anchor: --p-laterne"><span class="k-nr">Baugruppe 4</span><p class="k-titel">Laterne mit Kupplung</p><p class="k-text">Elastische Kupplung, Sichtfenster zum Ausrichten. Motor tauschbar ohne die Rohrleitung zu lösen.</p><a class="k-link" href="#td-laterne" data-zeile>Technische Daten <svg viewBox="0 0 14 14" aria-hidden="true"><path d="M7 1v11M3 8l4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></a><button class="k-zu" type="button" popovertarget="k-laterne" popovertargetaction="hide" aria-label="Karte schließen"><svg viewBox="0 0 12 12" aria-hidden="true"><path d="M1 1l10 10M11 1L1 11" stroke="currentColor" stroke-width="1.6"/></svg></button></div>
  <div class="karte" id="k-klemmen" popover style="position-anchor: --p-klemmen"><span class="k-nr">Baugruppe 5</span><p class="k-titel">Klemmenkasten</p><p class="k-text">IP55, in 90°-Schritten drehbar, Kabelverschraubung M32.</p><a class="k-link" href="#td-klemmen" data-zeile>Technische Daten <svg viewBox="0 0 14 14" aria-hidden="true"><path d="M7 1v11M3 8l4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></a><button class="k-zu" type="button" popovertarget="k-klemmen" popovertargetaction="hide" aria-label="Karte schließen"><svg viewBox="0 0 12 12" aria-hidden="true"><path d="M1 1l10 10M11 1L1 11" stroke="currentColor" stroke-width="1.6"/></svg></button></div>
  <div class="karte" id="k-motor" popover style="position-anchor: --p-motor"><span class="k-nr">Baugruppe 6</span><p class="k-titel">Motor</p><p class="k-text">IE4, 15 kW, 2.900 1/min, 400 V. Typenschild seitlich.</p><a class="k-link" href="#td-motor" data-zeile>Technische Daten <svg viewBox="0 0 14 14" aria-hidden="true"><path d="M7 1v11M3 8l4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></a><button class="k-zu" type="button" popovertarget="k-motor" popovertargetaction="hide" aria-label="Karte schließen"><svg viewBox="0 0 12 12" aria-hidden="true"><path d="M1 1l10 10M11 1L1 11" stroke="currentColor" stroke-width="1.6"/></svg></button></div>
  <div class="daten">
    <table>
      <caption>Technische Daten FN 80-250 · Stand 03.11.2026</caption>
      <thead><tr><th scope="col">Nr.</th><th scope="col">Baugruppe</th><th scope="col">Ausführung</th></tr></thead>
      <tbody>
        <tr id="td-saug"><td class="nr">1</td><th scope="row">Saugstutzen</th><td>DN 100, PN 16, axial, EN 1092-2</td></tr>
        <tr id="td-spirale"><td class="nr">2</td><th scope="row">Spiralgehäuse</th><td>EN-GJL-250, Laufrad Ø 250 mm</td></tr>
        <tr id="td-druck"><td class="nr">3</td><th scope="row">Druckstutzen</th><td>DN 80, PN 16, Förderhöhe bis 80 m</td></tr>
        <tr id="td-laterne"><td class="nr">4</td><th scope="row">Laterne mit Kupplung</th><td>elastische Kupplung, Sichtfenster</td></tr>
        <tr id="td-klemmen"><td class="nr">5</td><th scope="row">Klemmenkasten</th><td>IP55, drehbar, M32</td></tr>
        <tr id="td-motor"><td class="nr">6</td><th scope="row">Motor</th><td>IE4, 15 kW, 2.900 1/min, 400 V</td></tr>
      </tbody>
    </table>
  </div>
</section>

<!-- ═════ Variante zimmer · Rezept boutique-hotel-gastgeber · Hotel Strandvogt, Fehmarn ═════ -->
<section class="welt welt-zimmer mit-nummer" data-welt="zimmer" aria-labelledby="h-zimmer" hidden>
  <header class="leiste">
    <a class="marke" href="#h-zimmer">Strandvogt</a>
    <nav aria-label="Hotel"><ul><li><a href="#h-zimmer">Zimmer</a></li><li><a href="#h-zimmer">Frühstück</a></li><li><a href="#h-zimmer">Anreise</a></li></ul></nav>
  </header>
  <div class="kopf">
    <div><p class="etikett">Zimmer 7 · Seeseite · 28 m²</p><h2 id="h-zimmer">Was im Zimmer <em>steht.</em></h2></div>
  </div>
  <div class="zimmer-raster">
    <figure class="hs">
      <img src="bilder/hotel-zimmer.webp" width="1200" height="800" alt="Helles Hotelzimmer am Abend: Sitzfenster mit Blick aufs Meer, gelber Sessel, Nachttischleuchte und breites Bett auf Eichendielen">
      <ul class="punkte" aria-label="Ausstattung im Bild">
        <li><button class="punkt" type="button" popovertarget="k-fenster" style="--x: 37%; --y: 53%; --i: 0; anchor-name: --p-fenster" aria-label="1, Sitzfenster zur Ostsee"><i><span class="nr">1</span></i></button></li>
        <li><button class="punkt" type="button" popovertarget="k-sessel" style="--x: 19%; --y: 63%; --i: 1; anchor-name: --p-sessel" aria-label="2, Lesesessel"><i><span class="nr">2</span></i></button></li>
        <li><button class="punkt" type="button" popovertarget="k-leuchte" style="--x: 51.5%; --y: 51%; --i: 2; anchor-name: --p-leuchte" aria-label="3, Nachttischleuchte"><i><span class="nr">3</span></i></button></li>
        <li><button class="punkt" type="button" popovertarget="k-bett" style="--x: 72%; --y: 61%; --i: 3; anchor-name: --p-bett" aria-label="4, Bett 1,80 mal 2,00 m"><i><span class="nr">4</span></i></button></li>
        <li><button class="punkt" type="button" popovertarget="k-dielen" style="--x: 42%; --y: 82%; --i: 4; anchor-name: --p-dielen" aria-label="5, Eichendielen"><i><span class="nr">5</span></i></button></li>
      </ul>
    </figure>
    <div>
      <ol class="liste" aria-label="Ausstattung von Zimmer 7">
        <li data-nr="0"><h3>Sitzfenster zur Ostsee</h3><p>Polster aus Wollstoff, 1,60 m breit. Morgens kommt die Sonne hier zuerst an.</p></li>
        <li data-nr="1"><h3>Lesesessel</h3><p>Schurwolle in Senf, daneben Platz für Tee und Buch.</p></li>
        <li data-nr="2"><h3>Nachttischleuchte</h3><p>Warmes Licht, dimmbar. Steckdose und USB-C darunter.</p></li>
        <li data-nr="3"><h3>Bett 1,80 × 2,00 m</h3><p>Matratze mittelfest. Decken aus Daunen oder auf Wunsch ohne Federn.</p></li>
        <li data-nr="4"><h3>Eichendielen</h3><p>Geölt, mit Fußbodenheizung. Barfuß ist erlaubt.</p></li>
      </ol>
      <div class="zimmer-fuss"><a class="knopf" href="#h-zimmer">Zimmer 7 anfragen</a><small>ab 164 € pro Nacht für zwei, mit Frühstück</small></div>
    </div>
  </div>
  <div class="karte" id="k-fenster" popover style="position-anchor: --p-fenster"><p class="k-titel">Sitzfenster zur Ostsee</p><p class="k-text">Polster aus Wollstoff, 1,60 m breit. Morgens kommt die Sonne hier zuerst an.</p><button class="k-zu" type="button" popovertarget="k-fenster" popovertargetaction="hide" aria-label="Karte schließen"><svg viewBox="0 0 12 12" aria-hidden="true"><path d="M1 1l10 10M11 1L1 11" stroke="currentColor" stroke-width="1.6"/></svg></button></div>
  <div class="karte" id="k-sessel" popover style="position-anchor: --p-sessel"><p class="k-titel">Lesesessel</p><p class="k-text">Schurwolle in Senf, daneben Platz für Tee und Buch.</p><button class="k-zu" type="button" popovertarget="k-sessel" popovertargetaction="hide" aria-label="Karte schließen"><svg viewBox="0 0 12 12" aria-hidden="true"><path d="M1 1l10 10M11 1L1 11" stroke="currentColor" stroke-width="1.6"/></svg></button></div>
  <div class="karte" id="k-leuchte" popover style="position-anchor: --p-leuchte"><p class="k-titel">Nachttischleuchte</p><p class="k-text">Warmes Licht, dimmbar. Steckdose und USB-C darunter.</p><button class="k-zu" type="button" popovertarget="k-leuchte" popovertargetaction="hide" aria-label="Karte schließen"><svg viewBox="0 0 12 12" aria-hidden="true"><path d="M1 1l10 10M11 1L1 11" stroke="currentColor" stroke-width="1.6"/></svg></button></div>
  <div class="karte" id="k-bett" popover style="position-anchor: --p-bett"><p class="k-titel">Bett 1,80 × 2,00 m</p><p class="k-text">Matratze mittelfest. Decken aus Daunen oder auf Wunsch ohne Federn.</p><button class="k-zu" type="button" popovertarget="k-bett" popovertargetaction="hide" aria-label="Karte schließen"><svg viewBox="0 0 12 12" aria-hidden="true"><path d="M1 1l10 10M11 1L1 11" stroke="currentColor" stroke-width="1.6"/></svg></button></div>
  <div class="karte" id="k-dielen" popover style="position-anchor: --p-dielen"><p class="k-titel">Eichendielen</p><p class="k-text">Geölt, mit Fußbodenheizung. Barfuß ist erlaubt.</p><button class="k-zu" type="button" popovertarget="k-dielen" popovertargetaction="hide" aria-label="Karte schließen"><svg viewBox="0 0 12 12" aria-hidden="true"><path d="M1 1l10 10M11 1L1 11" stroke="currentColor" stroke-width="1.6"/></svg></button></div>
</section>
</main>

<div class="pult">
  <span>Variante</span>
  <div class="gruppe" role="group" aria-label="Variante">
    <button type="button" data-v="shop" aria-pressed="true">Shop</button>
    <button type="button" data-v="bauteil" aria-pressed="false">Bauteil</button>
    <button type="button" data-v="zimmer" aria-pressed="false">Zimmer</button>
  </div>
</div>

<script>
(() => {
  const h = document.documentElement;
  const reduziert = () => h.classList.contains('reduziert');
  const welten = [...document.querySelectorAll('.welt')];
  const pultKnoepfe = [...document.querySelectorAll('.pult [data-v]')];
  let v = 'shop', welt = welten[0], zeit = [], laeuft = false;

  /* Punkt und Karte koppeln: offen-Klasse, Listen-Hervorhebung */
  document.querySelectorAll('.karte').forEach(k => {
    const punkt = document.querySelector('[popovertarget="' + k.id + '"].punkt');
    k.addEventListener('toggle', e => {
      const auf = e.newState === 'open';
      punkt.classList.toggle('offen', auf);
      markiere(auf ? +punkt.style.getPropertyValue('--i') : -1);
    });
  });
  function markiere(i) {
    welt.querySelectorAll('.liste [data-nr]').forEach(el => el.classList.toggle('hervor', +el.dataset.nr === i));
    welt.querySelectorAll('.daten tbody tr').forEach((tr, n) => tr.classList.toggle('hervor', n === i));
  }
  /* Liste ↔ Punkt: Zeiger oder Fokus auf einem Eintrag hebt seinen Punkt hervor */
  document.querySelectorAll('.liste [data-nr]').forEach(el => {
    const an = () => { const p = el.closest('.welt').querySelectorAll('.punkt')[+el.dataset.nr]; if (p) p.classList.add('hervor'); };
    const aus = () => el.closest('.welt').querySelectorAll('.punkt').forEach(p => p.classList.remove('hervor'));
    el.addEventListener('pointerenter', an); el.addEventListener('pointerleave', aus);
    el.addEventListener('focusin', an); el.addEventListener('focusout', aus);
  });
  /* Bauteil: Link „Technische Daten“ schließt die Karte und springt zur Zeile */
  document.querySelectorAll('[data-zeile]').forEach(a => a.addEventListener('click', () => { const k = a.closest('.karte'); if (k.matches(':popover-open')) k.hidePopover(); }));

  /* Punkte einmal gestaffelt zeigen (kein Dauerpuls) */
  function zeigePunkte() {
    welt.querySelectorAll('.hs').forEach(f => { f.classList.remove('gezeigt'); void f.offsetWidth; f.classList.add('gezeigt'); });
  }

  function setzeVariante(neu, url) {
    v = ['shop', 'bauteil', 'zimmer'].includes(neu) ? neu : 'shop';
    document.body.dataset.v = v;
    document.querySelectorAll('.karte').forEach(k => { if (k.matches(':popover-open')) k.hidePopover(); });
    welten.forEach(w => { w.hidden = w.dataset.welt !== v; });
    welt = welten.find(w => w.dataset.welt === v);
    pultKnoepfe.forEach(b => b.setAttribute('aria-pressed', b.dataset.v === v ? 'true' : 'false'));
    zeigePunkte();
    if (url) { const u = new URL(location.href); u.searchParams.set('v', v); history.replaceState(history.state, '', u); }
  }
  pultKnoepfe.forEach(b => b.addEventListener('click', () => { pause(); setzeVariante(b.dataset.v, true); }));

  /* Vorführung: Punkte der Reihe nach öffnen, wie per Klick */
  const warte = ms => new Promise(r => zeit.push(setTimeout(r, ms)));
  async function runde(lauf) {
    const punkte = [...welt.querySelectorAll('.punkt')];
    await warte(700);
    for (const p of punkte) {
      if (laeuft !== lauf) return;
      p.classList.add('hervor'); await warte(350); p.classList.remove('hervor');
      if (laeuft !== lauf) return;
      p.click(); await warte(1700);
    }
    const offen = welt.parentElement.querySelector('.karte:popover-open'); if (offen) offen.hidePopover();
    await warte(600);
    if (laeuft === lauf) runde(lauf);
  }
  function neustart() { pause(); setzeVariante(v, false); }
  function vorfuehren() { pause(); laeuft = Symbol(); runde(laeuft); }
  function pause() { laeuft = false; zeit.forEach(clearTimeout); zeit = []; welt.querySelectorAll('.punkt.hervor').forEach(p => p.classList.remove('hervor')); }
  function weiter() { vorfuehren(); }

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

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