Zum Inhalt
← Navigation

Scrollspy-Kapitelnavigation (Sticky-Kapitelindex)

Ein Kapitelindex klebt neben dem Text, markiert das Kapitel in der Bildmitte und füllt je Kapitel eine Haarlinie, scrollgekoppelt und ohne JavaScript.

  1. Eine Kapitelnavigation listet die Abschnitte einer langen Seite: als Seitenspalte, als Inhaltsverzeichnis oder als Dock unten.
  2. Der Eintrag des Kapitels, das gerade in der Bildmitte steht, wird hervorgehoben (Farbe, Indikator), oft mit einem Fortschrittsbalken je Kapitel.
  3. Ein Klick springt weich zum Kapitel.
  4. Seit 2026 geht das nativ: scroll-target-group: auto am Nav-Container und :target-current für den aktiven Link, ohne IntersectionObserver. Den Fortschritt je Kapitel liefern benannte view-timelines, ebenfalls ohne JavaScript.

Lange Seiten werden überschaubar. Man sieht jederzeit, wo man steht, wie viel noch kommt und wohin man springen kann. Die Gliederung wird selbst zum Gestaltungselement.

Wann einsetzen

Case Studies, Reports, Werkberichte, Dokumentation, lange Landingpages und Storytelling-Seiten mit klaren Kapiteln.

Wann nicht

Kurze Seiten mit zwei oder drei Abschnitten. Seiten mit waagerechtem Scroll-Hijacking ohne klare Abschnitte.

Varianten

  • Sticky-Seitenspalte mit nummerierten Kapiteln (01 Aufgabe, 02 Konzept …) und Indikatorlinie je Eintrag
  • Section Anchor Dock unten (Osmo)
  • One-page Progress Navigation mit Balken (Osmo)
  • Sidebar mit Scroll-Fortschrittsanzeige (Raw Materials laut Navbar Gallery)
  • Waagerechte Kapitelleiste oben, auf dem Telefon mit aufklappbarer Liste
  • Fortschrittsbalken je Kapitel statt einer Gesamtleiste
  • Festgeklebte Kapitelüberschriften, die per scroll-state(stuck) ihre Darstellung wechseln

Umsetzung

Kapitelliste als <nav aria-label="Kapitel"> mit Ankerlinks. Aktiver Link nativ: nav { scroll-target-group: auto } und nav a:is(:target-current, .aktiv) { … }. Selektoren in :is() kombinieren, nicht per Komma, sonst kippt die ganze Regel in Browsern ohne :target-current. aria-current per JavaScript mitpflegen (beim Scrollen und bei scrollend). Fallback: IntersectionObserver mit rootMargin '-50% 0px -50% 0px' setzt die Klasse .aktiv. Fortschritt ohne JavaScript: timeline-scope: --k1, --k2 … auf einem gemeinsamen Elternteil von Navigation und Kapiteln, jedes Kapitel bekommt view-timeline: --kN block, die Spur des Eintrags animation: fuellen linear both; animation-timeline: --kN; animation-range: cover 50vh cover calc(100% - 50vh), animiert scale von 0 auf 1. Die Spalte klebt mit position: sticky; top: 0; height: 100svh. Am saubersten als Rasterzelle über alle Zeilen, dann endet sie mit dem letzten Kapitel. Der Trick mit margin-bottom: -100svh lässt sie dagegen über den Fuß hinauslaufen. scroll-margin-top auf den Kapiteln, wenn eine Leiste oben klebt. Stil-Wechsel festgeklebter Überschriften: container-type: scroll-state und @container scroll-state(stuck: top) hinter @supports. Abschnitte so hoch anlegen, dass höchstens zwei gleichzeitig sichtbar sind, und nach dem letzten Kapitel mindestens 50vh Platz lassen.

Robust umsetzen

Telefon. Seitenspalte ausblenden. Stattdessen eine schmale Leiste oben mit dem aktuellen Kapitelnamen und einem Fortschritt je Kapitel; Tippen klappt die Liste auf. Alternativ ein Dock unten mit Kapitelname und Ziffern. Trefferflächen mindestens 44 px.

Ohne Bewegung. scroll-behavior: smooth nur bei prefers-reduced-motion: no-preference, sonst springt der Link direkt. Die scrollgekoppelte Füllung ist unkritisch, weil sie nur dem eigenen Scrollen folgt. Wer sie abschaltet, zeigt den Zustand statt der Bewegung: erledigte Kapitel voll, kommende leer.

Barrierefrei. :target-current wird Screenreadern nicht gemeldet, deshalb aria-current="true" am aktiven Link mitpflegen. Den Zustand nicht nur über Farbe zeigen (Gewicht, Linie, Ziffer). Nach dem Sprung den Fokus auf die Überschrift setzen (tabindex="-1", focus mit preventScroll). Die aufklappbare Telefon-Liste braucht aria-expanded und aria-controls und schließt mit Escape.

Code

<!DOCTYPE html>
<!--
  Scrollspy-Kapitelnavigation (Sticky-Kapitelindex)
  Fundus · Navigation · https://cecodesign.de/lab/fundus/navigation/scrollspy-kapitelnavigation/
  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>Scrollspy-Kapitelnavigation · 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: 'Bodoni Moda'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/bodoni-moda:vf@latest/latin-opsz-normal.woff2') format('woff2-variations'); font-weight: 400 900; font-style: normal; font-display: swap; }
@font-face { font-family: 'Bodoni Moda'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/bodoni-moda:vf@latest/latin-opsz-italic.woff2') format('woff2-variations'); font-weight: 400 900; 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: '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; }

/* Welt: Uhrmacherei Kaulberg, Bamberg (Rezept „Manufaktur-Stille“) */
:root {
  --anthrazit: #23282f; --weiss: #fff; --flaeche: #f4f4f2; --meta: #676c73; --linie: #d9dadb; --messing: #a88a5a;
  --kurve: cubic-bezier(0.1, 0.6, 0.4, 1);  /* UI, Farbwechsel 250 ms */
  --auf: cubic-bezier(0.1, 0.9, 0.3, 1);    /* Menü öffnet 300 ms */
  --zu: cubic-bezier(0.4, 0, 0.2, 1);       /* Menü schließt 200 ms */
  --spalte: clamp(200px, 18vw, 260px);
  --rand: clamp(20px, 4vw, 56px);
  --leiste: 52px;
  /* Fortschritt je Kapitel: von „Kapitelanfang in Bildmitte“ bis „Kapitelende in Bildmitte“ */
  --bereich: cover 50vh cover calc(100% - 50vh);
}
[data-ton='hell']   { --grund: #fff; --text: #23282f; --leise: #676c73; --messing-text: #8a6d40; --gedaempft: #6f747b; --strich: #d9dadb; --fuellung: rgba(35, 40, 47, .5); }
[data-ton='dunkel'] { --messing-text: var(--messing); --grund: #23282f; --text: #fff; --leise: rgba(255, 255, 255, .56); --gedaempft: rgba(255, 255, 255, .64); --strich: rgba(255, 255, 255, .16); --fuellung: rgba(255, 255, 255, .55); }

* { box-sizing: border-box; margin: 0; padding: 0; }
html { background: #fff; color: var(--anthrazit); font: 400 0.9375rem/1.47 'Instrument Sans', system-ui, sans-serif; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }
@media (prefers-reduced-motion: no-preference) { html:not(.reduziert) { scroll-behavior: smooth; } }
body { overflow-x: clip; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
ol { list-style: none; }
.flaeche { background: var(--grund); color: var(--text); }
.label { font-size: 0.75rem; font-weight: 500; letter-spacing: .08em; text-transform: uppercase; color: var(--leise); }
.serif { font-family: 'Bodoni Moda', 'Didot', serif; font-optical-sizing: auto; font-weight: 400; }
.ziffern { font-variant-numeric: tabular-nums lining-nums; }
.mono { font: 500 10.5px/1.5 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; }
.unsichtbar { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* ---------- Kopfzeile der Marke ---------- */
.marke { display: flex; align-items: center; justify-content: space-between; gap: 16px; height: 56px; padding: 0 var(--rand); border-bottom: 1px solid var(--strich); position: relative; z-index: 6; }
.wortmarke { font-size: 1.0625rem; letter-spacing: .32em; text-transform: uppercase; }
.marke__mitte { position: absolute; left: 50%; translate: -50% 0; white-space: nowrap; }
.marke__termin { font-size: 0.75rem; font-weight: 500; letter-spacing: .08em; text-transform: uppercase; border-bottom: 1px solid var(--messing); padding-bottom: 2px; }

/* ---------- Bericht: gemeinsamer Elternteil für die benannten Zeitachsen ---------- */
.bericht { position: relative; timeline-scope: --k1, --k2, --k3, --k4, --k5, --k6; }
#werkstatt   { view-timeline: --k1 block; }
#kaliber     { view-timeline: --k2 block; }
#zifferblatt { view-timeline: --k3 block; }
#gehaeuse    { view-timeline: --k4 block; }
#regulierung { view-timeline: --k5 block; }
#uebergabe   { view-timeline: --k6 block; }

/* ---------- Kapitelnavigation (eine nav, drei Darstellungen) ---------- */
/* Farben wechseln ohne Übergang: Ton und Zustand folgen dem Scroll Bild für Bild, ein Nachziehen würde hinterherhinken */
.kapitel { scroll-target-group: auto; z-index: 10; color: var(--text); }
.kapitel__titel { line-height: 1.5; }
.kapitel__titel span { display: block; font-family: 'Bodoni Moda', serif; font-size: 1.0625rem; letter-spacing: 0; text-transform: none; color: var(--text); margin-top: 4px; }
.kapitel__liste a { position: relative; display: grid; grid-template-columns: 30px 1fr; align-items: baseline; color: var(--gedaempft); }
.kapitel__liste .nr { font-size: 0.6875rem; font-weight: 500; letter-spacing: .08em; font-variant-numeric: tabular-nums; }
.kapitel__liste .name { font-family: 'Bodoni Moda', serif; font-optical-sizing: auto; font-size: 1.0625rem; line-height: 1.2; }
.kapitel__liste a:is(:target-current, .aktiv) { color: var(--text); }
.kapitel__liste a:is(:target-current, .aktiv) .nr { color: var(--messing-text, var(--messing)); }
.kapitel__liste a.gedrueckt .name { text-decoration: underline 1px var(--messing); text-underline-offset: 5px; }
.kapitel__liste a:focus-visible { outline: 1px solid var(--messing); outline-offset: 4px; }
@media (hover: hover) { .kapitel__liste a:hover { color: var(--text); } }

/* Spur: Schiene + Füllung. Die Füllung hängt an der benannten view-timeline ihres Kapitels. */
.spur { position: absolute; background: var(--strich); }
.spur i { position: absolute; inset: 0; background: var(--fuellung); transform-origin: top; animation: spur-y linear both; animation-range: var(--bereich); }
:is(.kapitel__liste li, .kapitel__balken .spur):nth-child(1) i { animation-timeline: --k1; }
:is(.kapitel__liste li, .kapitel__balken .spur):nth-child(2) i { animation-timeline: --k2; }
:is(.kapitel__liste li, .kapitel__balken .spur):nth-child(3) i { animation-timeline: --k3; }
:is(.kapitel__liste li, .kapitel__balken .spur):nth-child(4) i { animation-timeline: --k4; }
:is(.kapitel__liste li, .kapitel__balken .spur):nth-child(5) i { animation-timeline: --k5; }
:is(.kapitel__liste li, .kapitel__balken .spur):nth-child(6) i { animation-timeline: --k6; }
.kapitel__liste a:is(:target-current, .aktiv) .spur i, .kapitel__balken .spur.aktiv i { background: var(--messing); }
@keyframes spur-y { from { scale: 1 0; } to { scale: 1 1; } }
@keyframes spur-x { from { scale: 0 1; } to { scale: 1 1; } }
/* Ohne scroll-gekoppelte CSS-Animationen setzt JS --p (0…1) */
@supports not (animation-timeline: view()) {
  .spur i { animation: none; scale: 1 var(--p, 0); }
  .kapitel__balken .spur i, .bericht[data-variante='dock'] .kapitel__liste .spur i { scale: var(--p, 0) 1; }
}

.kapitel__schalter, .kapitel__balken, .kapitel__stand { display: none; }
.kapitel__stand b { font-weight: 500; color: var(--text); }

/* A) Seitenspalte (Desktop): Raster mit zwei Spalten. Die nav liegt in Spalte 1 über allen Zeilen und klebt,
      die Kapitel laufen über beide Spalten, damit ihre Flächen bis an den Rand reichen. */
.bericht[data-variante='seite'] { display: grid; grid-template-columns: var(--spalte) minmax(0, 1fr); }
.bericht[data-variante='seite'] > section { grid-column: 1 / -1; }
.bericht[data-variante='seite'] > section:nth-of-type(1) { grid-row: 1; } .bericht[data-variante='seite'] > section:nth-of-type(2) { grid-row: 2; }
.bericht[data-variante='seite'] > section:nth-of-type(3) { grid-row: 3; } .bericht[data-variante='seite'] > section:nth-of-type(4) { grid-row: 4; }
.bericht[data-variante='seite'] > section:nth-of-type(5) { grid-row: 5; } .bericht[data-variante='seite'] > section:nth-of-type(6) { grid-row: 6; }
.bericht[data-variante='seite'] > section:nth-of-type(7) { grid-row: 7; }
.bericht[data-variante='seite'] .kapitel { grid-column: 1; grid-row: 1 / span 7; align-self: start; position: sticky; top: 0; height: 100svh; pointer-events: none; }
.bericht[data-variante='seite'] .kapitel__innen { pointer-events: auto; height: 100%; display: grid; grid-template-rows: 1fr auto 1fr; padding: 32px 18px 32px var(--rand); }
.bericht[data-variante='seite'] .kapitel__titel { align-self: start; }
.bericht[data-variante='seite'] .kapitel__liste { display: grid; gap: 4px; }
.bericht[data-variante='seite'] .kapitel__liste a { padding: 9px 0 9px 18px; min-height: 40px; }
.bericht[data-variante='seite'] .kapitel__liste .spur { left: 0; top: 0; bottom: 0; width: 1px; }
.bericht[data-variante='seite'] .kapitel__stand { display: block; align-self: end; }
.stand__name { display: block; font-family: 'Bodoni Moda', serif; font-size: 1.0625rem; letter-spacing: 0; text-transform: none; color: var(--text); margin-top: 4px; }
.bericht[data-variante='seite'] .flaeche-bericht { padding-left: var(--spalte); }

/* B) Dock unten: fixierte Leiste, Füllung waagerecht */
.bericht[data-variante='dock'] .kapitel { position: fixed; left: 50%; bottom: 20px; translate: -50% 0; width: min(980px, calc(100% - 40px)); background: var(--grund); border: 1px solid var(--strich); box-shadow: 0 18px 40px -24px rgba(20, 24, 30, .45); }
.bericht[data-variante='dock'] .kapitel__titel { display: none; }
.bericht[data-variante='dock'] .kapitel__liste { display: grid; grid-template-columns: repeat(6, 1fr); }
.bericht[data-variante='dock'] .kapitel__liste a { grid-template-columns: auto 1fr; column-gap: 8px; padding: 20px 14px 15px; }
.bericht[data-variante='dock'] .kapitel__liste .name { font-family: 'Instrument Sans', sans-serif; font-size: 0.75rem; font-weight: 500; letter-spacing: .08em; text-transform: uppercase; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.bericht[data-variante='dock'] .kapitel__liste .spur { left: 14px; right: 14px; top: 10px; height: 1px; }
.bericht[data-variante='dock'] .kapitel__liste .spur i { animation-name: spur-x; transform-origin: left; }
.bericht[data-variante='dock'] li + li a::before { content: ''; position: absolute; left: 0; top: 14px; bottom: 14px; width: 1px; background: var(--strich); }
.bericht[data-variante='dock'] .flaeche-bericht { padding-inline: var(--rand); }

/* ---------- Deckblatt ---------- */
.deck { min-height: calc(100svh - 56px); display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, .9fr); grid-template-rows: 1fr auto; column-gap: clamp(24px, 5vw, 88px); align-items: center; padding-top: clamp(32px, 6vh, 72px); padding-bottom: 28px; padding-right: var(--rand); }
.deck__text { display: grid; gap: 22px; max-width: 34rem; }
h1, h2 { font-family: 'Bodoni Moda', 'Didot', serif; font-optical-sizing: auto; font-weight: 400; font-size: clamp(2.5rem, 5.5vw, 5.5rem); letter-spacing: -.01em; line-height: 1.05; text-wrap: balance; }
h2 { font-size: clamp(2.25rem, 4.2vw, 4.25rem); line-height: 1.06; }
h1 em, h2 em { font-style: italic; }
h2:focus { outline: none; }
h2:focus-visible { outline: 1px solid var(--messing); outline-offset: 10px; }
.lead { font-size: 1.125rem; line-height: 1.5; max-width: 30em; color: var(--text); text-wrap: pretty; }
.deck__kennung { display: flex; flex-wrap: wrap; gap: 8px 22px; padding-top: 18px; border-top: 1px solid var(--strich); }
.deck__kennung b { font-weight: 500; color: var(--text); }
.uhr { background: var(--flaeche); aspect-ratio: 4 / 5; max-height: calc(100svh - 190px); width: 100%; justify-self: end; display: grid; place-items: center; position: relative; overflow: hidden; }
.uhr svg { width: 82%; height: 82%; }
.uhr figcaption { position: absolute; left: 18px; bottom: 14px; right: 18px; display: flex; justify-content: space-between; }
.sekunde { transform-origin: 200px 300px; transform-box: view-box; animation: sekunde 60s steps(360) infinite; }
@keyframes sekunde { to { rotate: 360deg; } }

/* Demo-Bedienung: dezent, Geist Mono */
.steuer { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 14px 28px; margin-top: 28px; padding-top: 14px; border-top: 1px dashed var(--strich); color: var(--leise); }
.modi { display: flex; align-items: center; gap: 4px; }
.modi span { margin-right: 8px; }
.modi button { font: inherit; letter-spacing: inherit; text-transform: inherit; color: var(--leise); border: 1px solid var(--strich); border-radius: 999px; height: 28px; padding: 0 12px; transition: background-color .15s var(--kurve), color .15s var(--kurve); }
.modi button[aria-pressed='true'] { background: var(--anthrazit); color: #fff; border-color: var(--anthrazit); }
.werte { display: flex; flex-wrap: wrap; gap: 4px 22px; }
.werte div { display: flex; gap: 8px; }
.werte dd { color: var(--text); text-transform: none; letter-spacing: .04em; }

/* ---------- Kapitel ---------- */
.kap { min-height: 118svh; display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: clamp(16px, 2.2vw, 36px); align-content: center; align-items: start; padding-top: clamp(96px, 17vh, 176px); padding-bottom: clamp(96px, 17vh, 176px); padding-right: var(--rand); scroll-margin-top: 0; }
.kap__text { grid-column: 1 / span 5; grid-row: 1; display: grid; gap: 24px; }
.kap__bild { grid-column: 7 / -1; grid-row: 1; margin-top: clamp(40px, 16vh, 180px); }
.kap--spiegel .kap__text { grid-column: 8 / -1; }
.kap--spiegel .kap__bild { grid-column: 1 / span 6; margin-top: 0; }
.kopfzeile { display: flex; align-items: center; gap: 14px; }
.kopfzeile::before { content: ''; width: 36px; height: 1px; background: var(--messing); order: 1; }
.kopfzeile span:last-child { order: 2; color: var(--text); }
.kap p { max-width: 31em; text-wrap: pretty; }
.kap p + p { margin-top: -8px; }
.fakten { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px; margin-top: 10px; padding-top: 22px; border-top: 1px solid var(--strich); }
.fakten dd { font-family: 'Bodoni Moda', serif; font-optical-sizing: auto; font-size: clamp(2rem, 3.4vw, 3rem); line-height: 1; font-variant-numeric: tabular-nums lining-nums; order: -1; }
.fakten div { display: flex; flex-direction: column; gap: 10px; }
.daten { margin-top: 6px; }
.daten div { display: flex; justify-content: space-between; gap: 16px; padding: 11px 0; border-bottom: 1px solid var(--strich); }
.daten div:first-child { border-top: 1px solid var(--strich); }
.daten dt { color: var(--gedaempft); }
.daten dd { font-variant-numeric: tabular-nums lining-nums; font-weight: 500; }
figure.bild { display: grid; gap: 14px; }
.bild__flaeche { background: var(--flaeche); display: grid; place-items: center; }
[data-ton='dunkel'] .bild__flaeche { background: transparent; }
.bild svg { width: 100%; height: auto; display: block; }
.bild figcaption { display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.legende { display: grid; gap: 6px; margin-top: 4px; }
.legende li { display: flex; gap: 12px; color: var(--gedaempft); }
.legende b { font-weight: 500; color: var(--messing-text, var(--messing)); font-variant-numeric: tabular-nums; }
.knopf { display: inline-flex; align-items: center; justify-content: center; gap: 12px; height: 52px; padding: 0 28px; background: var(--anthrazit); color: #fff; font-size: 0.75rem; font-weight: 500; letter-spacing: .08em; text-transform: uppercase; justify-self: start; transition: background-color .15s var(--kurve); }
.knopf:hover { background: #343a43; }
.knopf:focus-visible { outline: 1px solid var(--messing); outline-offset: 4px; }
.adresse { display: grid; gap: 4px; padding-top: 20px; border-top: 1px solid var(--strich); }
.adresse b { font-weight: 500; }

/* SVG-Zeichnungen */
.z-strich { stroke: currentColor; fill: none; vector-effect: non-scaling-stroke; }
svg text { font-family: 'Instrument Sans', sans-serif; }

/* ---------- Fuß ---------- */
.fuss { min-height: 62svh; display: grid; align-content: space-between; gap: 48px; padding: clamp(64px, 12vh, 120px) var(--rand) 40px; }
.fuss__marke { font-size: clamp(2.5rem, 9vw, 8rem); letter-spacing: .2em; text-transform: uppercase; line-height: 1; }
.fuss__zeile { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 12px 32px; }
.bericht[data-variante='dock'] ~ .fuss { padding-bottom: 120px; }

/* ---------- Telefon und schmale Fenster ---------- */
@media (max-width: 879px) {
  .marke__mitte { display: none; }
  /* Seitenspalte wird zur Leiste oben mit aufklappbarer Liste */
  .bericht[data-variante='seite'] { display: block; }
  .bericht[data-variante='seite'] .kapitel { position: sticky; top: 0; width: auto; height: var(--leiste); pointer-events: auto; background: var(--grund); border-bottom: 1px solid var(--strich); }
  .bericht[data-variante='seite'] .kapitel__innen { display: block; height: 100%; padding: 0; }
  .bericht[data-variante='seite'] .kapitel__titel, .bericht[data-variante='seite'] .kapitel__stand { display: none; }
  .bericht[data-variante='seite'] .kapitel__schalter { display: flex; align-items: center; gap: 12px; width: 100%; height: 100%; padding: 0 var(--rand); text-align: left; }
  .kapitel__schalter .nr { font-size: 0.6875rem; font-weight: 500; letter-spacing: .08em; color: var(--messing-text, var(--messing)); font-variant-numeric: tabular-nums; }
  .kapitel__schalter .name { font-family: 'Bodoni Moda', serif; font-size: 1.125rem; flex: 1; }
  .kapitel__schalter .von { font-size: 0.6875rem; letter-spacing: .08em; color: var(--leise); font-variant-numeric: tabular-nums; }
  .kapitel__schalter svg { width: 12px; height: 12px; transition: rotate .2s var(--zu); }
  .kapitel.offen .kapitel__schalter svg { rotate: 180deg; transition: rotate .3s var(--auf); }
  .bericht[data-variante='seite'] .kapitel__balken { display: grid; grid-template-columns: repeat(6, 1fr); gap: 3px; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; }
  .kapitel__balken .spur { position: relative; height: 2px; }
  .kapitel__balken .spur i { animation-name: spur-x; transform-origin: left; }
  .bericht[data-variante='seite'] .kapitel__liste { position: absolute; top: 100%; left: 0; right: 0; background: var(--grund); border-bottom: 1px solid var(--strich); padding: 12px var(--rand) 22px; display: grid; gap: 2px;
    opacity: 0; translate: 0 -8px; visibility: hidden; transition: opacity .2s var(--zu), translate .2s var(--zu), visibility 0s linear .2s; }
  .bericht[data-variante='seite'] .kapitel.offen .kapitel__liste { opacity: 1; translate: 0 0; visibility: visible; transition: opacity .3s var(--auf), translate .3s var(--auf), visibility 0s; }
  .bericht[data-variante='seite'] .kapitel__liste a { padding: 12px 0 12px 18px; min-height: 48px; }
  .bericht[data-variante='seite'] .kapitel__liste .spur { left: 0; top: 0; bottom: 0; width: 1px; }
  .bericht[data-variante='seite'] .flaeche-bericht { padding-left: var(--rand); }
  .bericht[data-variante='seite'] .kap { scroll-margin-top: var(--leiste); }

  /* Dock: zwei Zeilen, oben das aktuelle Kapitel, unten sechs Ziffern mit Füllung */
  .bericht[data-variante='dock'] .kapitel { left: 12px; right: 12px; bottom: 12px; width: auto; translate: none; }
  .bericht[data-variante='dock'] .kapitel__innen { display: flex; flex-direction: column; }
  .bericht[data-variante='dock'] .kapitel__stand { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 12px 16px 0; order: -1; }
  .bericht[data-variante='dock'] .stand__name { margin: 0; order: -1; font-size: 1.125rem; }
  .bericht[data-variante='dock'] .kapitel__liste a { grid-template-columns: 1fr; justify-items: center; padding: 18px 0 12px; min-height: 48px; }
  .bericht[data-variante='dock'] .kapitel__liste .name { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .bericht[data-variante='dock'] .kapitel__liste .spur { left: 10px; right: 10px; top: 10px; }
  .bericht[data-variante='dock'] li + li a::before { display: none; }

  .deck { grid-template-columns: 1fr; grid-template-rows: auto; row-gap: 40px; min-height: 0; padding-top: 40px; }
  .uhr { justify-self: stretch; max-height: none; aspect-ratio: 1 / 1; }
  .steuer { margin-top: 0; }
  .kap { display: block; min-height: 0; padding-top: 88px; padding-bottom: 104px; }
  .kap__bild, .kap--spiegel .kap__bild { margin-top: 48px; }
  .kap--spiegel .kap__bild { margin-top: 48px; }
  .fakten dd { font-size: 2.25rem; }
}
@media (hover: none) {
  .kapitel__liste a, .kapitel__schalter { -webkit-tap-highlight-color: transparent; }
  .modi button { height: 36px; padding: 0 14px; }
}

/* ---------- Reduzierte Bewegung: keine scroll-gekoppelte Füllung, Zustand per Klasse ---------- */
:root.reduziert * { transition-duration: .01ms !important; animation-duration: .01ms !important; }
.reduziert .spur i { animation: none !important; scale: 1 0; }
.reduziert .kapitel__balken .spur i, .reduziert .bericht[data-variante='dock'] .kapitel__liste .spur i { scale: 0 1; }
.reduziert .kapitel__liste a:is(.erledigt, [aria-current]) .spur i, .reduziert .kapitel__balken .spur:is(.erledigt, .aktiv) i { scale: 1 1; }
.reduziert .sekunde { animation: none !important; }

/* Vorschau in der Galerie: nur die Bühne */
.vorschau body { overflow: hidden; }
.vorschau .steuer { display: 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>
<header class="marke flaeche" data-ton="hell">
  <a class="wortmarke serif" href="#kopf">Kaulberg</a>
  <p class="marke__mitte label">Werkbericht Nr. 07 · Herbst 2026</p>
  <a class="marke__termin" href="#uebergabe">Termin vereinbaren</a>
</header>

<div class="bericht" data-variante="seite">
  <nav class="kapitel" aria-label="Kapitel" data-ton="hell">
    <div class="kapitel__innen">
      <p class="kapitel__titel label" data-teil>Werkbericht<span>Regnitz Handaufzug</span></p>
      <button class="kapitel__schalter" type="button" aria-expanded="false" aria-controls="kapitel-liste">
        <span class="nr" data-stand-nr>01</span><span class="name" data-stand-name>Werkstatt</span><span class="von">/ 06</span>
        <svg viewBox="0 0 12 12" aria-hidden="true"><path d="M2 4.5 6 8.5 10 4.5" fill="none" stroke="currentColor" stroke-width="1.2"/></svg>
        <span class="unsichtbar">Kapitelliste öffnen</span>
      </button>
      <div class="kapitel__balken" aria-hidden="true">
        <span class="spur"><i></i></span><span class="spur"><i></i></span><span class="spur"><i></i></span><span class="spur"><i></i></span><span class="spur"><i></i></span><span class="spur"><i></i></span>
      </div>
      <ol class="kapitel__liste" id="kapitel-liste">
        <li><a href="#werkstatt"><span class="spur" aria-hidden="true"><i></i></span><span class="nr">01</span><span class="name">Werkstatt</span></a></li>
        <li><a href="#kaliber"><span class="spur" aria-hidden="true"><i></i></span><span class="nr">02</span><span class="name">Kaliber</span></a></li>
        <li><a href="#zifferblatt"><span class="spur" aria-hidden="true"><i></i></span><span class="nr">03</span><span class="name">Zifferblatt</span></a></li>
        <li><a href="#gehaeuse"><span class="spur" aria-hidden="true"><i></i></span><span class="nr">04</span><span class="name">Gehäuse</span></a></li>
        <li><a href="#regulierung"><span class="spur" aria-hidden="true"><i></i></span><span class="nr">05</span><span class="name">Regulierung</span></a></li>
        <li><a href="#uebergabe"><span class="spur" aria-hidden="true"><i></i></span><span class="nr">06</span><span class="name">Übergabe</span></a></li>
      </ol>
      <p class="kapitel__stand label" aria-hidden="true" data-teil><span>Kapitel <b class="ziffern" data-stand-nr>01</b> von 06</span><span class="stand__name" data-stand-name>Werkstatt</span></p>
    </div>
  </nav>

  <section class="deck flaeche flaeche-bericht" data-ton="hell" id="kopf" aria-labelledby="titel">
    <div class="deck__text">
      <p class="label">Uhrmacher in Bamberg seit 1924</p>
      <h1 id="titel">Eine Uhr in <em>sechs</em> Kapiteln</h1>
      <p class="lead">Vom Rohwerk bis zur Übergabe: So entsteht eine Regnitz in unserer Werkstatt am Kaulberg. 312 Stück im Jahr, jede einzelne drei Wochen lang reguliert.</p>
      <p class="deck__kennung label"><span>Kaliber <b class="ziffern">K 24</b></span><span>Gehäuse <b class="ziffern">38 mm</b></span><span>Gangreserve <b class="ziffern">72 h</b></span></p>
    </div>
    <figure class="uhr" aria-label="Zeichnung der Regnitz Handaufzug, Zifferblatt weiß, Lederband anthrazit">
      <svg viewBox="0 0 400 500" role="img" aria-hidden="true">
        <rect x="152" y="-20" width="96" height="130" fill="#23282f"/><rect x="152" y="390" width="96" height="130" fill="#23282f"/>
        <path d="M160 -20V104M240 -20V104M160 396V520M240 396V520" stroke="rgba(255,255,255,.32)" stroke-width=".8" stroke-dasharray="4 4"/>
        <g fill="#fff" stroke="#23282f" stroke-width=".8">
          <rect x="142" y="96" width="20" height="48" rx="6"/><rect x="238" y="96" width="20" height="48" rx="6"/>
          <rect x="142" y="356" width="20" height="48" rx="6"/><rect x="238" y="356" width="20" height="48" rx="6"/>
          <rect x="326" y="239" width="13" height="22" rx="2.5"/>
          <circle cx="200" cy="250" r="128"/>
        </g>
        <path d="M330 242v16M333 242v16M336 242v16" stroke="#23282f" stroke-width=".5"/>
        <circle cx="200" cy="250" r="121" fill="none" stroke="#d9dadb" stroke-width=".8"/>
        <circle cx="200" cy="250" r="116" fill="#fbfbf9"/>
        <g data-ziffernblatt fill="none" stroke="#23282f"></g>
        <text x="200" y="199" text-anchor="middle" font-family="Bodoni Moda, serif" font-size="12" letter-spacing="3.4" fill="#23282f">KAULBERG</text>
        <text x="200" y="210" text-anchor="middle" font-size="5.4" letter-spacing="1.8" fill="#8b9097">BAMBERG · HANDAUFZUG</text>
        <g transform="rotate(304.5 200 250)"><path d="M198 263h4l-.5-73L200 184l-1.5 6z" fill="#23282f"/></g>
        <g transform="rotate(54 200 250)"><path d="M198.7 265h2.6l-.4-117-.9-6-.9 6z" fill="#23282f"/></g>
        <circle cx="200" cy="250" r="4.5" fill="#23282f"/><circle cx="200" cy="250" r="1.4" fill="#a88a5a"/>
        <g class="sekunde"><path d="M200 306V279" stroke="#a88a5a" stroke-width="1"/><circle cx="200" cy="300" r="1.8" fill="#a88a5a"/></g>
      </svg>
      <figcaption class="label"><span>Regnitz · Edelstahl · Emaille</span><span class="ziffern">Ref. 2410</span></figcaption>
    </figure>
    <div class="steuer mono">
      <div class="modi" role="group" aria-label="Variante">
        <span>Variante</span>
        <button type="button" data-variante="seite" aria-pressed="true">Seitenspalte</button>
        <button type="button" data-variante="dock" aria-pressed="false">Dock unten</button>
      </div>
      <dl class="werte">
        <div><dt>Aktiv</dt><dd data-wert-aktiv>…</dd></div>
        <div><dt>Füllung</dt><dd data-wert-fuellung>…</dd></div>
        <div><dt>Bereich</dt><dd>cover 50vh → cover 100% − 50vh</dd></div>
      </dl>
    </div>
  </section>

  <section class="kap flaeche flaeche-bericht" data-ton="dunkel" id="werkstatt" aria-labelledby="h-werkstatt">
    <div class="kap__text">
      <p class="kopfzeile label"><span class="ziffern">01</span><span>Die Werkstatt</span></p>
      <h2 id="h-werkstatt" tabindex="-1">Sieben Bänke unter dem <em>Kaulberg</em></h2>
      <p>Seit 1924 arbeiten wir im selben Haus, zwei Gassen oberhalb der Regnitz. Die Werkbänke stehen im ersten Stock am Nordfenster, weil das Licht dort den ganzen Tag gleich bleibt.</p>
      <p>Jede Regnitz geht durch drei Hände: Montage, Finissage, Regulierung. Wer eine Uhr beginnt, bringt sie auch zu Ende.</p>
      <dl class="fakten">
        <div><dt class="label">Gegründet</dt><dd>1924</dd></div>
        <div><dt class="label">Uhrmacher</dt><dd>7</dd></div>
        <div><dt class="label">Uhren im Jahr</dt><dd>312</dd></div>
      </dl>
    </div>
    <figure class="kap__bild bild">
      <div class="bild__flaeche">
        <svg viewBox="0 0 500 500" aria-hidden="true">
          <defs><clipPath id="linse"><circle cx="250" cy="250" r="188"/></clipPath></defs>
          <g clip-path="url(#linse)" fill="none" stroke="#fff">
            <circle cx="340" cy="380" r="236" stroke-width="26" stroke-dasharray="9 9" opacity=".22"/>
            <circle cx="340" cy="380" r="222" stroke-width=".8" opacity=".5"/>
            <circle cx="340" cy="380" r="160" stroke-width=".6" opacity=".35"/>
            <g data-speichen opacity=".35" stroke-width=".6"></g>
            <circle cx="132" cy="118" r="54" stroke-width="12" stroke-dasharray="5 5" opacity=".24"/>
            <circle cx="132" cy="118" r="46" stroke-width=".7" opacity=".5"/>
            <circle cx="132" cy="118" r="14" stroke="#a88a5a" stroke-width="1"/>
            <circle cx="132" cy="118" r="6" fill="#fff" stroke="none" opacity=".7"/>
            <circle cx="340" cy="380" r="30" stroke="#a88a5a" stroke-width="1.2"/>
            <circle cx="340" cy="380" r="12" fill="#fff" stroke="none" opacity=".55"/>
          </g>
          <circle cx="250" cy="250" r="200" fill="none" stroke="#a88a5a" stroke-width="1.2"/>
          <circle cx="250" cy="250" r="191" fill="none" stroke="rgba(255,255,255,.18)" stroke-width=".8"/>
          <text x="250" y="478" text-anchor="middle" font-size="11" letter-spacing="2" fill="rgba(255,255,255,.56)">LUPE 10 ×  ·  RADSATZ UND LAGERSTEIN</text>
        </svg>
      </div>
      <figcaption class="label"><span>Abb. 1 · Blick durch die Lupe</span><span class="ziffern">Bank 3</span></figcaption>
    </figure>
  </section>

  <section class="kap kap--spiegel flaeche flaeche-bericht" data-ton="hell" id="kaliber" aria-labelledby="h-kaliber">
    <div class="kap__text">
      <p class="kopfzeile label"><span class="ziffern">02</span><span>Das Kaliber</span></p>
      <h2 id="h-kaliber" tabindex="-1">Kaliber <em>K 24</em>, von Hand aufgezogen</h2>
      <p>Ein Werk ohne Rotor, damit der Blick durch den Glasboden frei bleibt. Die Brücken sind aus Neusilber, von Hand angliert und mit Genfer Streifen verziert.</p>
      <dl class="daten">
        <div><dt>Gangreserve</dt><dd>72 h</dd></div>
        <div><dt>Frequenz</dt><dd>21.600 A/h · 3 Hz</dd></div>
        <div><dt>Lagersteine</dt><dd>19</dd></div>
        <div><dt>Durchmesser</dt><dd>30,4 mm</dd></div>
        <div><dt>Bauhöhe</dt><dd>4,2 mm</dd></div>
        <div><dt>Einzelteile</dt><dd>164</dd></div>
      </dl>
    </div>
    <figure class="kap__bild bild">
      <div class="bild__flaeche">
        <svg viewBox="0 0 400 400" aria-hidden="true" data-werk>
          <defs>
            <pattern id="streifen" width="12" height="12" patternUnits="userSpaceOnUse" patternTransform="rotate(-28)"><rect width="12" height="12" fill="#fcfcfb"/><rect width="6" height="12" fill="#eeeeeb"/></pattern>
            <mask id="bruecke"><circle cx="200" cy="200" r="156" fill="#fff"/><circle cx="134" cy="266" r="70" fill="#000"/></mask>
            <path id="bogen" d="M64 200a136 136 0 0 1 272 0"/>
          </defs>
          <circle cx="200" cy="200" r="176" fill="#fff" stroke="#23282f" stroke-width=".8"/>
          <circle cx="200" cy="200" r="168" fill="none" stroke="#d9dadb" stroke-width=".8"/>
          <circle cx="200" cy="200" r="160" fill="#f7f7f5" stroke="#23282f" stroke-width=".5"/>
          <circle cx="200" cy="200" r="156" fill="url(#streifen)" mask="url(#bruecke)"/>
          <circle cx="200" cy="200" r="156" fill="none" stroke="#23282f" stroke-width=".6" mask="url(#bruecke)"/>
          <circle cx="134" cy="266" r="70" fill="none" stroke="#23282f" stroke-width=".6"/>
          <path d="M150 128 266 92M262 300 320 214" stroke="#23282f" stroke-width=".5" fill="none" opacity=".55"/>
          <text font-size="7.2" letter-spacing="2.6" fill="#8b9097"><textPath href="#bogen" startOffset="50%" text-anchor="middle">KAULBERG · BAMBERG · KALIBER K 24 · NEUNZEHN STEINE</textPath></text>
          <!-- Sperrrad -->
          <circle cx="242" cy="150" r="56" fill="none" stroke="#23282f" stroke-width="5" stroke-dasharray="1.7 1.7"/>
          <circle cx="242" cy="150" r="52" fill="#fff" stroke="#23282f" stroke-width=".6"/>
          <g data-sonne stroke="#d9dadb" stroke-width=".5"></g>
          <circle cx="242" cy="150" r="9" fill="#23282f"/><path d="M235 146 249 154" stroke="#fff" stroke-width="1.4"/>
          <!-- Kronrad -->
          <circle cx="318" cy="216" r="19" fill="none" stroke="#23282f" stroke-width="4" stroke-dasharray="1.6 1.6"/>
          <circle cx="318" cy="216" r="15.5" fill="#fff" stroke="#23282f" stroke-width=".5"/>
          <circle cx="318" cy="216" r="4" fill="#23282f"/>
          <!-- Unruhkloben und Unruh -->
          <path d="M58 340 112 286a12 12 0 0 1 17 17l-54 54z" fill="url(#streifen)" stroke="#23282f" stroke-width=".6"/>
          <circle cx="134" cy="266" r="54" fill="none" stroke="#23282f" stroke-width="2.4"/>
          <circle cx="134" cy="266" r="48" fill="none" stroke="#23282f" stroke-width=".5"/>
          <path d="M134 266V218M134 266l41.6 24M134 266l-41.6 24" stroke="#23282f" stroke-width="1.4"/>
          <path data-spirale fill="none" stroke="#8b9097" stroke-width=".5"/>
          <!-- Lagersteine -->
          <g fill="#fff" stroke="#a88a5a" stroke-width="1.1">
            <circle cx="134" cy="266" r="5"/><circle cx="200" cy="208" r="5"/><circle cx="262" cy="246" r="5"/><circle cx="292" cy="290" r="5"/><circle cx="196" cy="310" r="5"/><circle cx="214" cy="74" r="5"/>
          </g>
          <g fill="#23282f"><circle cx="134" cy="266" r="1.8"/><circle cx="200" cy="208" r="1.8"/><circle cx="262" cy="246" r="1.8"/><circle cx="292" cy="290" r="1.8"/><circle cx="196" cy="310" r="1.8"/><circle cx="214" cy="74" r="1.8"/></g>
          <!-- Schrauben -->
          <g fill="#23282f"><circle cx="108" cy="130" r="6"/><circle cx="312" cy="120" r="6"/><circle cx="330" cy="290" r="6"/><circle cx="246" cy="338" r="6"/><circle cx="80" cy="214" r="6"/></g>
          <g stroke="#fff" stroke-width="1.2"><path d="M104 127l8 6M308 124l8-8M327 286l6 8M242 341l8-6M76 212l8 4"/></g>
          <!-- Marken der Legende -->
          <g font-size="10" font-weight="600" fill="#a88a5a" text-anchor="middle">
            <circle cx="70" cy="290" r="9" fill="#fff" stroke="#a88a5a" stroke-width=".8"/><text x="70" y="293.5">1</text>
            <circle cx="298" cy="96" r="9" fill="#fff" stroke="#a88a5a" stroke-width=".8"/><text x="298" y="99.5">2</text>
            <circle cx="350" cy="244" r="9" fill="#fff" stroke="#a88a5a" stroke-width=".8"/><text x="350" y="247.5">3</text>
          </g>
        </svg>
      </div>
      <figcaption class="label"><span>Abb. 2 · K 24, Werkseite</span><span class="ziffern">Maßstab 4 : 1</span></figcaption>
      <ol class="legende">
        <li><b>1</b>Unruh mit Breguet-Spirale, 21.600 A/h</li>
        <li><b>2</b>Sperrrad mit Sonnenschliff über dem Federhaus, 72 h</li>
        <li><b>3</b>Kronrad, Aufzug über die Krone</li>
      </ol>
    </figure>
  </section>

  <section class="kap flaeche flaeche-bericht" data-ton="dunkel" id="zifferblatt" aria-labelledby="h-zifferblatt">
    <div class="kap__text">
      <p class="kopfzeile label"><span class="ziffern">03</span><span>Das Zifferblatt</span></p>
      <h2 id="h-zifferblatt" tabindex="-1">Weiß, das <em>dreimal</em> gebrannt wird</h2>
      <p>Das Zifferblatt ist Emaille auf Kupfer. Es geht dreimal bei rund 800 °C in den Ofen, bis die Oberfläche glatt und tief wirkt.</p>
      <p>Danach wird die Minuterie gedruckt. Die zwölf Indizes setzen wir einzeln, mit der Pinzette und unter der Lupe.</p>
      <dl class="fakten">
        <div><dt class="label">Brände</dt><dd>3</dd></div>
        <div><dt class="label">Minutenstriche</dt><dd>60</dd></div>
        <div><dt class="label">Indizes</dt><dd>12</dd></div>
      </dl>
    </div>
    <figure class="kap__bild bild">
      <div class="bild__flaeche">
        <svg viewBox="0 0 500 500" aria-hidden="true">
          <defs>
            <radialGradient id="emaille" cx="470" cy="470" r="430" gradientUnits="userSpaceOnUse"><stop offset=".55" stop-color="#fbfbf9"/><stop offset="1" stop-color="#ecebe6"/></radialGradient>
          </defs>
          <circle cx="470" cy="470" r="452" fill="none" stroke="#a88a5a" stroke-width="1"/>
          <circle cx="470" cy="470" r="436" fill="url(#emaille)"/>
          <g data-makro fill="none" stroke="#23282f"></g>
          <text x="470" y="236" text-anchor="middle" font-family="Bodoni Moda, serif" font-size="34" letter-spacing="9" fill="#23282f">KAULBERG</text>
          <g transform="rotate(304.5 470 470)"><path d="M461 520h18l-2.4-296L470 196l-6.6 28z" fill="#23282f"/><path d="M470 214v220" stroke="rgba(255,255,255,.35)" stroke-width="1"/></g>
        </svg>
      </div>
      <figcaption class="label"><span>Abb. 3 · Emaille, Ausschnitt 9 bis 12 Uhr</span><span class="ziffern">Maßstab 6 : 1</span></figcaption>
    </figure>
  </section>

  <section class="kap kap--spiegel flaeche flaeche-bericht" data-ton="hell" id="gehaeuse" aria-labelledby="h-gehaeuse">
    <div class="kap__text">
      <p class="kopfzeile label"><span class="ziffern">04</span><span>Das Gehäuse</span></p>
      <h2 id="h-gehaeuse" tabindex="-1">38 Millimeter, <em>kein</em> Millimeter mehr</h2>
      <p>Das Gehäuse aus Edelstahl ist dreiteilig und nur 9,8 mm hoch. Die Bandanstöße fallen nach innen ab, damit die Uhr auch an einem schmalen Handgelenk flach aufliegt.</p>
      <dl class="daten">
        <div><dt>Durchmesser</dt><dd>38,0 mm</dd></div>
        <div><dt>Höhe</dt><dd>9,8 mm</dd></div>
        <div><dt>Bandanstoß</dt><dd>19 mm</dd></div>
        <div><dt>Glas</dt><dd>Saphir, beidseitig</dd></div>
        <div><dt>Wasserdicht</dt><dd>5 bar</dd></div>
      </dl>
    </div>
    <figure class="kap__bild bild">
      <div class="bild__flaeche">
        <svg viewBox="0 0 680 330" aria-hidden="true">
          <g fill="#fff" stroke="#23282f" stroke-width=".9">
            <path d="M118 166c-26 6-50 22-64 46l10 7c14-18 34-31 58-36z"/>
            <path d="M522 166c26 6 50 22 64 46l-10 7c-14-18-34-31-58-36z"/>
            <rect x="110" y="146" width="420" height="52" rx="8"/>
            <path d="M126 146c0-8 6-14 14-14h360c8 0 14 6 14 14z"/>
            <path d="M144 198h352l-22 16H166z"/>
            <path d="M232 214h176c-12 8-30 10-88 10s-76-2-88-10z"/>
            <path d="M144 132c40-16 312-16 352 0" fill="none"/>
            <rect x="530" y="160" width="10" height="10"/>
            <rect x="540" y="152" width="24" height="26" rx="4"/>
          </g>
          <path d="M546 153v24M551 153v24M556 153v24" stroke="#23282f" stroke-width=".5"/>
          <rect x="168" y="156" width="304" height="34" fill="none" stroke="#8b9097" stroke-width=".8" stroke-dasharray="5 4"/>
          <text x="320" y="177" text-anchor="middle" font-size="10.5" letter-spacing="1.4" fill="#8b9097">KALIBER K 24 · 4,2 MM</text>
          <g stroke="#a88a5a" stroke-width=".8" fill="none">
            <path d="M110 204v76M530 204v76M116 268h408"/>
            <path d="M116 268l7-3.5v7zM524 268l-7-3.5v7z" fill="#a88a5a"/>
            <path d="M500 125h108M500 224h108M600 131v87"/>
            <path d="M600 131l-3.5 7h7zM600 218l-3.5-7h7z" fill="#a88a5a"/>
          </g>
          <text x="320" y="298" text-anchor="middle" font-size="14" fill="#23282f" style="font-variant-numeric: tabular-nums">38,0 mm</text>
          <text x="612" y="179" text-anchor="start" font-size="14" fill="#23282f" style="font-variant-numeric: tabular-nums">9,8 mm</text>
        </svg>
      </div>
      <figcaption class="label"><span>Abb. 4 · Seitenansicht mit Kaliber</span><span class="ziffern">Maßstab 10 : 1</span></figcaption>
    </figure>
  </section>

  <section class="kap flaeche flaeche-bericht" data-ton="dunkel" id="regulierung" aria-labelledby="h-regulierung">
    <div class="kap__text">
      <p class="kopfzeile label"><span class="ziffern">05</span><span>Die Regulierung</span></p>
      <h2 id="h-regulierung" tabindex="-1">Einundzwanzig Tage <em>Geduld</em></h2>
      <p>Jede Uhr wird drei Wochen lang in fünf Lagen und bei drei Temperaturen geprüft. Erst wenn die tägliche Abweichung zwischen −2 und +4 Sekunden bleibt, unterschreibt die Uhrmacherin das Prüfblatt.</p>
      <p>Das Blatt liegt jeder Regnitz bei, mit Namen und Datum.</p>
      <dl class="fakten">
        <div><dt class="label">Tage</dt><dd>21</dd></div>
        <div><dt class="label">Lagen</dt><dd>5</dd></div>
        <div><dt class="label">Temperaturen</dt><dd>3</dd></div>
      </dl>
    </div>
    <figure class="kap__bild bild">
      <div class="bild__flaeche">
        <svg viewBox="0 0 600 340" aria-hidden="true" data-kurve>
          <rect x="56" y="82" width="524" height="156" fill="rgba(255,255,255,.045)"/>
          <g data-gitter></g>
          <g data-linien fill="none"></g>
        </svg>
      </div>
      <figcaption class="label"><span>Abb. 5 · Gangabweichung je Tag, Prüfblatt 2410-118</span><span class="ziffern">s / Tag</span></figcaption>
    </figure>
  </section>

  <section class="kap kap--spiegel flaeche flaeche-bericht" data-ton="hell" id="uebergabe" aria-labelledby="h-uebergabe">
    <div class="kap__text">
      <p class="kopfzeile label"><span class="ziffern">06</span><span>Die Übergabe</span></p>
      <h2 id="h-uebergabe" tabindex="-1">Abgeholt in der <em>Sandstraße</em></h2>
      <p>Wir verschicken keine Regnitz. Sie holen Ihre Uhr in der Boutique ab. Wir passen das Band an und zeigen Ihnen, wie man sie richtig aufzieht. Dafür nehmen wir uns eine Stunde Zeit.</p>
      <a class="knopf" href="#kopf">Termin vereinbaren</a>
      <div class="adresse">
        <b>Boutique Kaulberg</b>
        <span>Obere Sandstraße 14 · 96049 Bamberg</span>
        <span class="ziffern" style="color: var(--gedaempft)">Dienstag bis Samstag, 10 bis 18 Uhr</span>
      </div>
    </div>
    <figure class="kap__bild bild">
      <div class="bild__flaeche" style="aspect-ratio: 4 / 3">
        <svg viewBox="0 0 480 360" aria-hidden="true">
          <rect x="70" y="150" width="340" height="150" fill="#fff" stroke="#23282f" stroke-width=".9"/>
          <path d="M70 150l40-40h260l40 40" fill="#fbfbf9" stroke="#23282f" stroke-width=".9"/>
          <path d="M110 110v-6h260v6" fill="none" stroke="#23282f" stroke-width=".6"/>
          <rect x="190" y="178" width="100" height="96" fill="#f4f4f2" stroke="#d9dadb" stroke-width=".8"/>
          <circle cx="240" cy="226" r="30" fill="#fff" stroke="#23282f" stroke-width=".8"/>
          <path d="M240 226l-12-9M240 226l6-18" stroke="#23282f" stroke-width="1.4"/>
          <path d="M70 300h340" stroke="#a88a5a" stroke-width="1"/>
          <text x="240" y="138" text-anchor="middle" font-family="Bodoni Moda, serif" font-size="11" letter-spacing="3" fill="#23282f">KAULBERG</text>
          <text x="240" y="328" text-anchor="middle" font-size="10" letter-spacing="2" fill="#8b9097">ETUI AUS NUSSBAUM · PRÜFBLATT · ZWEITES BAND</text>
        </svg>
      </div>
      <figcaption class="label"><span>Abb. 6 · Was Sie mitnehmen</span><span class="ziffern">Ref. 2410</span></figcaption>
    </figure>
  </section>
</div>

<footer class="fuss flaeche" data-ton="dunkel">
  <p class="fuss__marke serif">Kaulberg</p>
  <div class="fuss__zeile label">
    <span>Uhrmacher in Bamberg seit 1924</span>
    <span>Obere Sandstraße 14 · 96049 Bamberg</span>
    <span>Werkbericht Nr. 07</span>
  </div>
</footer>

<script>
(() => {
  const html = document.documentElement;
  const reduziert = () => html.classList.contains('reduziert');
  const SVG = 'http://www.w3.org/2000/svg';
  const $ = (s, el = document) => el.querySelector(s);
  const $$ = (s, el = document) => [...el.querySelectorAll(s)];
  const el = (name, attr, eltern) => { const e = document.createElementNS(SVG, name); for (const k in attr) e.setAttribute(k, attr[k]); eltern && eltern.appendChild(e); return e; };
  const rad = g => (g - 90) * Math.PI / 180;
  const strich = (g, cx, cy, r1, r2, w, eltern, extra = {}) => el('line', { x1: cx + r1 * Math.cos(rad(g)), y1: cy + r1 * Math.sin(rad(g)), x2: cx + r2 * Math.cos(rad(g)), y2: cy + r2 * Math.sin(rad(g)), 'stroke-width': w, ...extra }, eltern);

  /* ---------- Zeichnungen erzeugen ---------- */
  const zb = $('[data-ziffernblatt]');
  el('circle', { cx: 200, cy: 250, r: 112, 'stroke-width': .4 }, zb); el('circle', { cx: 200, cy: 250, r: 106, 'stroke-width': .4 }, zb);
  for (let i = 0; i < 60; i++) strich(i * 6, 200, 250, 112, 106, .6, zb);
  for (let i = 0; i < 12; i++) {
    if (i === 0) { strich(-1.6, 200, 250, 100, 80, 3, zb); strich(1.6, 200, 250, 100, 80, 3, zb); }
    else if (i !== 6) strich(i * 30, 200, 250, 100, 82, 3.2, zb);
  }
  el('circle', { cx: 200, cy: 300, r: 22, 'stroke-width': .5 }, zb);
  for (let i = 0; i < 60; i++) strich(i * 6, 200, 300, 22, i % 5 ? 19.5 : 16.5, i % 5 ? .35 : .6, zb);

  const sp = $('[data-speichen]');
  for (let i = 0; i < 5; i++) strich(i * 72 + 20, 340, 380, 36, 156, 6, sp);

  const sonne = $('[data-sonne]');
  for (let i = 0; i < 72; i++) strich(i * 5, 242, 150, 12, 50, .5, sonne);
  let d = ''; for (let t = 0; t <= 7 * 2 * Math.PI; t += .12) { const r = 7 + t * 0.85; d += (t ? 'L' : 'M') + (134 + r * Math.cos(t)).toFixed(1) + ' ' + (266 + r * Math.sin(t)).toFixed(1); }
  $('[data-spirale]').setAttribute('d', d);

  const mk = $('[data-makro]');
  el('circle', { cx: 470, cy: 470, r: 418, 'stroke-width': .8 }, mk); el('circle', { cx: 470, cy: 470, r: 398, 'stroke-width': .8 }, mk);
  for (let i = 0; i < 60; i++) strich(i * 6, 470, 470, 418, 398, 1.2, mk);
  for (let i = 0; i < 12; i++) {
    if (i === 0) { strich(-1.3, 470, 470, 380, 300, 9, mk); strich(1.3, 470, 470, 380, 300, 9, mk); }
    else strich(i * 30, 470, 470, 380, 306, 10, mk);
  }

  // Prüfkurve: fünf Lagen über 21 Tage (feste Werte, kein Zufall)
  const kv = $('[data-kurve]'), gitter = $('[data-gitter]', kv), linien = $('[data-linien]', kv);
  const X = t => 56 + (t - 1) / 20 * 524, Y = v => 30 + (6 - v) / 10 * 260;
  [-4, -2, 0, 2, 4, 6].forEach(v => {
    const band = v === -2 || v === 4;
    el('line', { x1: 56, x2: 580, y1: Y(v), y2: Y(v), stroke: band ? '#a88a5a' : 'rgba(255,255,255,' + (v === 0 ? .3 : .09) + ')', 'stroke-width': band ? .9 : .8, 'stroke-dasharray': band ? '4 4' : 'none' }, gitter);
    const tx = el('text', { x: 46, y: Y(v) + 3.5, 'text-anchor': 'end', 'font-size': 12.5, fill: band ? '#a88a5a' : 'rgba(255,255,255,.5)' }, gitter);
    tx.textContent = (v > 0 ? '+' : v < 0 ? '−' : '') + Math.abs(v) + (band ? ' s' : '');
  });
  [1, 7, 14, 21].forEach(t => { const tx = el('text', { x: X(t), y: 318, 'text-anchor': 'middle', 'font-size': 12.5, fill: 'rgba(255,255,255,.5)' }, gitter); tx.textContent = 'Tag ' + t; });
  [[8, '8 °C'], [12, '38 °C']].forEach(([t, s]) => { el('rect', { x: X(t), y: 30, width: X(t + 2) - X(t), height: 260, fill: 'rgba(255,255,255,.035)' }, gitter); const tx = el('text', { x: X(t + 1), y: 21, 'text-anchor': 'middle', 'font-size': 12, fill: 'rgba(255,255,255,.5)' }, gitter); tx.textContent = s; });
  const lagen = [[1.2, .9, 0], [2.1, .7, 1.3], [.4, 1.1, 2.1], [1.6, .6, 3.4], [.9, .8, 4.6]];
  const mittel = Array(21).fill(0);
  lagen.forEach(([basis, amp, ph]) => {
    let p = '';
    for (let t = 1; t <= 21; t++) {
      const temp = (t >= 8 && t < 10) ? -.9 : (t >= 12 && t < 14) ? .8 : 0;
      const v = Math.max(-1.7, Math.min(3.7, basis + amp * Math.sin(t * .62 + ph) + .45 * Math.sin(t * 1.7 + ph * 2) + temp));
      mittel[t - 1] += v / lagen.length;
      p += (t > 1 ? 'L' : 'M') + X(t).toFixed(1) + ' ' + Y(v).toFixed(1);
    }
    el('path', { d: p, stroke: 'rgba(255,255,255,.28)', 'stroke-width': .8 }, linien);
  });
  el('path', { d: mittel.map((v, i) => (i ? 'L' : 'M') + X(i + 1).toFixed(1) + ' ' + Y(v).toFixed(1)).join(''), stroke: '#fff', 'stroke-width': 1.6 }, linien);

  /* ---------- Scrollspy ---------- */
  const bericht = $('.bericht'), nav = $('.kapitel');
  const links = $$('.kapitel__liste a');
  const balken = $$('.kapitel__balken .spur');
  const kapitel = links.map(a => document.getElementById(a.hash.slice(1)));
  const flaechen = $$('.flaeche');
  const schalter = $('.kapitel__schalter');
  const teile = $$('[data-teil], .kapitel__liste li', nav);
  const schmal = matchMedia('(max-width: 879px)');
  const nativ = CSS.supports('scroll-target-group: auto') && (() => { try { document.querySelector(':target-current'); return true; } catch (e) { return false; } })();
  const zeitachse = CSS.supports('animation-timeline: view()') && CSS.supports('timeline-scope: --a');
  let aktiv = -1, ioAktiv = 0;

  // Fallback ohne :target-current: IntersectionObserver auf die Bildmitte
  if (!nativ) {
    const io = new IntersectionObserver(es => {
      es.forEach(e => { if (e.isIntersecting) ioAktiv = kapitel.indexOf(e.target); });
      planen();
    }, { rootMargin: '-50% 0px -50% 0px' });
    kapitel.forEach(k => io.observe(k));
  }

  const tonBei = y => { for (const f of flaechen) { const r = f.getBoundingClientRect(); if (r.top <= y && r.bottom > y) return f.dataset.ton; } return 'hell'; };

  function aktualisieren() {
    let i = nativ ? links.indexOf(nav.querySelector('.kapitel__liste a:target-current')) : ioAktiv;
    if (i < 0) i = 0;
    if (i !== aktiv) {
      aktiv = i;
      links.forEach((a, n) => {
        if (n === i) a.setAttribute('aria-current', 'true'); else a.removeAttribute('aria-current');
        a.classList.toggle('erledigt', n < i);
        if (!nativ) a.classList.toggle('aktiv', n === i);
      });
      balken.forEach((b, n) => { b.classList.toggle('erledigt', n < i); b.classList.toggle('aktiv', n === i); });
      const nr = String(i + 1).padStart(2, '0'), name = links[i].querySelector('.name').textContent;
      $$('[data-stand-nr]').forEach(e => e.textContent = nr);
      $$('[data-stand-name]').forEach(e => e.textContent = name);
    }
    // Farbe der Navigation folgt der Fläche darunter (Dock: Gegenton)
    // Seitenspalte: Titel, Liste und Stand liegen über verschiedenen Flächen, jedes Teil prüft seine eigene Mitte
    const dock = bericht.dataset.variante === 'dock';
    if (!dock && !schmal.matches) {
      // Mitte des sichtbaren Teils (ein halb unter den Rand ragender Stand richtet sich nach der Fläche, auf der er sichtbar ist)
      teile.forEach(t => { const r = t.getBoundingClientRect(); t.dataset.ton = tonBei((Math.max(r.top, 0) + Math.min(r.bottom, innerHeight)) / 2); });
      nav.dataset.ton = tonBei(innerHeight / 2);
    } else {
      const ton = tonBei(dock ? innerHeight - 40 : nav.getBoundingClientRect().bottom + 1);
      nav.dataset.ton = dock ? (ton === 'hell' ? 'dunkel' : 'hell') : ton;
      teile.forEach(t => t.dataset.ton = nav.dataset.ton);
    }
    // Fallback ohne scroll-gekoppelte CSS-Animationen: Fortschritt selbst rechnen
    if (!zeitachse) kapitel.forEach((k, n) => {
      const r = k.getBoundingClientRect();
      const p = Math.min(1, Math.max(0, (innerHeight / 2 - r.top) / r.height)).toFixed(3);
      links[n].querySelector('.spur i').style.setProperty('--p', p);
      balken[n].querySelector('i').style.setProperty('--p', p);
    });
  }
  // :target-current wird erst nach dem Scroll-Frame neu bestimmt: sofort lesen und kurz danach noch einmal
  let geplant = false, nachzug = null;
  function planen() {
    clearTimeout(nachzug); nachzug = setTimeout(aktualisieren, 120);
    if (geplant) return; geplant = true; requestAnimationFrame(() => { geplant = false; aktualisieren(); });
  }
  addEventListener('scroll', planen, { passive: true });
  addEventListener('scrollend', aktualisieren);   // aria-current sicher nachziehen
  document.fonts.ready.then(planen); addEventListener('load', planen);   // Lage nach dem Laden der Schriften neu prüfen
  addEventListener('resize', () => { if (!schmal.matches) liste(false); planen(); });

  // Klick: weicher Sprung (außer bei reduzierter Bewegung), Fokus auf die Überschrift
  function springen(i, still) {
    const ziel = kapitel[i];
    ziel.scrollIntoView({ behavior: reduziert() ? 'instant' : 'smooth', block: 'start' });
    ziel.querySelector('h2').focus({ preventScroll: true, focusVisible: !still });
    liste(false);
  }
  links.forEach((a, i) => a.addEventListener('click', e => { e.preventDefault(); springen(i); }));

  // Telefon: Liste aufklappen (öffnet 300 ms, schließt 200 ms)
  function liste(auf) { nav.classList.toggle('offen', auf); schalter.setAttribute('aria-expanded', String(auf)); }
  schalter.addEventListener('click', () => liste(!nav.classList.contains('offen')));
  addEventListener('keydown', e => { if (e.key === 'Escape' && nav.classList.contains('offen')) { liste(false); schalter.focus(); } });
  document.addEventListener('click', e => { if (!nav.contains(e.target)) liste(false); });

  // Variante wechseln, ohne die Leseposition zu verlieren
  const knoepfe = $$('.modi [data-variante]');
  function setzeVariante(v) {
    const k = kapitel[Math.max(0, aktiv)], r = k.getBoundingClientRect();
    const anteil = (innerHeight / 2 - r.top) / r.height, oben = scrollY < 10;
    bericht.dataset.variante = v;
    liste(false);
    knoepfe.forEach(b => b.setAttribute('aria-pressed', String(b.dataset.variante === v)));
    $('[data-wert-aktiv]').textContent = nativ ? 'scroll-target-group + :target-current' : 'IntersectionObserver −50 %';
    $('[data-wert-fuellung]').textContent = (zeitachse ? 'view-timeline --k1…--k6 · linear · ' : 'JS-Fallback · ') + (v === 'seite' && !schmal.matches ? 'scaleY' : 'scaleX') + ' 0 → 1';
    if (!oben) { const r2 = k.getBoundingClientRect(); scrollTo({ top: scrollY + r2.top + anteil * r2.height - innerHeight / 2, behavior: 'instant' }); }
    aktiv = -1; aktualisieren();
  }
  knoepfe.forEach(b => b.addEventListener('click', () => { stopp(); setzeVariante(b.dataset.variante); }));
  setzeVariante('seite');

  /* ---------- Vorführung: liest sich selbst durch, springt per Klick zurück, wechselt die Variante ---------- */
  let uhr = null, warte = null, runde = 0;
  const FAHRT = 15000;
  function pause() { cancelAnimationFrame(uhr); clearTimeout(warte); uhr = null; warte = null; }
  function stopp() { pause(); html.classList.remove('vorfuehrung'); }
  function klickAuf(i) {
    const a = links[i]; a.classList.add('gedrueckt');
    warte = setTimeout(() => { a.classList.remove('gedrueckt'); springen(i, true); warte = setTimeout(naechsteRunde, 2600); }, 600);
  }
  function naechsteRunde() { runde++; starteRunde(); }
  function starteRunde() {
    pause();
    setzeVariante(runde % 2 ? 'dock' : 'seite');
    scrollTo({ top: 0, behavior: 'instant' });
    const max = () => document.documentElement.scrollHeight - innerHeight;
    if (reduziert()) {           // ohne Fahrt: Kapitel für Kapitel springen
      let n = 0;
      const schritt = () => { if (n < kapitel.length) { kapitel[n++].scrollIntoView({ behavior: 'instant' }); warte = setTimeout(schritt, 1600); } else klickAuf(1); };
      warte = setTimeout(schritt, 1200); return;
    }
    let t0 = null;
    const fahrt = t => {
      if (t0 === null) t0 = t;
      const q = Math.min(1, Math.max(0, (t - t0 - 900) / FAHRT));
      scrollTo({ top: max() * (0.5 - 0.5 * Math.cos(q * Math.PI)), behavior: 'instant' });
      if (q < 1) uhr = requestAnimationFrame(fahrt);
      else { uhr = null; warte = setTimeout(() => klickAuf(1), 900); }
    };
    uhr = requestAnimationFrame(fahrt);
  }
  function vorfuehren() { pause(); html.classList.add('vorfuehrung'); runde = 0; starteRunde(); }
  function neustart() { stopp(); scrollTo({ top: 0, behavior: 'instant' }); setzeVariante(bericht.dataset.variante); }
  ['wheel', 'touchstart', 'keydown'].forEach(t => addEventListener(t, () => { if (!html.classList.contains('vorschau') && html.classList.contains('vorfuehrung')) stopp(); }, { passive: true }));

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