Farb-/Theme-Wechsel beim Scrollen
Der ganze Raum färbt sich mit jedem Kapitel um: hell für die Fotoserie, dunkel für den Film. Kapitel ohne Trennlinien.
Portfolio mit eigener Farbe je Projekt, hell für Fotoserien, dunkel für Filme; jedes Kapitel der Seite trägt sein Farbpaar.
- Jede Section trägt ihr Farbpaar, etwa data-bg und data-fg.
- Erreicht eine Section die Mitte des Viewports, wechseln Hintergrund- und Textfarbe der ganzen Seite: weich in 400 bis 800 ms oder direkt an den Scroll gekoppelt.
- Kopf, Logo und Bedienelemente färben sich mit. Es gibt keine harte Kante zwischen den Sections, der Raum färbt sich um.
Kapitel und Stimmungswechsel entstehen ohne Trennlinien. Die Seite wirkt wie eine durchgehende Bühne, auf der das Licht wechselt.
Wann einsetzen
Portfolios mit eigener Farbe pro Projekt, Storytelling, Marken mit starker Palette. Bei Fotografie und Film: hell für Fotoserien, dunkel für Filme.
Wann nicht
Bei Inhalten, die schnell gescannt werden. Wenn Zwischenzustände unter 4,5 : 1 Kontrast fallen und lange stehen bleiben. Nicht an jedem kleinen Block, sonst wirkt es hektisch.
Varianten
- Schwelle: Wechsel per IntersectionObserver oder ScrollTrigger, zeitbasiert mit Transition
- Gekoppelt: Fläche mischt zwischen zwei Farben, während die Grenze durch den Viewport wandert
- Kopf invertiert sich per mix-blend-mode: difference statt mitzufärben
- Rein CSS: scroll(root)- oder view()-Timeline mit Farbkeyframes
- Farbe aus dem Bild: das aktuelle Projekt liefert seinen Ton aus den Daten
- Tageslauf: drei Lichtstimmungen Tag, Abend, Nacht nur in einem Erzähl-Kapitel; davor und danach steht die Seite auf dem hellen Grundton (Demo-Variante tageslauf)
Umsetzung
Farben als Custom Properties auf :root, registriert mit @property --bg { syntax: '<color>'; inherits: true; initial-value: #fff }, damit sie sich überblenden lassen. Dann reicht html { background: var(--bg); color: var(--fg); transition: --bg .6s cubic-bezier(.33,1,.68,1), --fg .6s cubic-bezier(.33,1,.68,1) }. Auslöser ohne Bibliothek: IntersectionObserver mit rootMargin '-50% 0px -50% 0px' setzt beim Eintritt die Properties der Section. Mit GSAP: ScrollTrigger.create({ trigger: s, start: 'top 50%', end: 'bottom 50%', onToggle: self => self.isActive && gsap.to(root, { '--bg': s.dataset.bg, '--fg': s.dataset.fg, duration: .6, ease: 'power2.out' }) }). Gekoppelt: Fortschritt der Grenze berechnen und die Fläche in OKLab mischen; den Text nicht mitmischen, sondern zum besseren Kontrast umschalten. Abgeleitete Töne per color-mix(in oklab, var(--fg) 64%, var(--bg)). Wechsel nur an Kapitelgrenzen, Farbpaare vorab auf Kontrast prüfen, meta theme-color mitziehen. Werkbilder nie mittönen.
Robust umsetzen
Telefon. Unverändert, Scroll ist die Geste. Der Wechsel funktioniert auch einspaltig.
Ohne Bewegung. Ein Farbwechsel ist keine Bewegung und darf bleiben. Dauer auf etwa 200 ms kürzen oder sofort wechseln.
Barrierefrei. Jedes Farbpaar erreicht mindestens 4,5 : 1. Zwischenzustände kurz halten: Text am Kipppunkt umschalten statt mitzumischen. Informationen nie nur über Farbe vermitteln, das Kapitel steht zusätzlich als Text da.
Code
<!DOCTYPE html>
<!--
Farb-/Theme-Wechsel beim Scrollen
Fundus · Scroll & Section-Animationen · https://cecodesign.de/lab/fundus/scroll/theme-farbwechsel-scroll/
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">
<meta name="theme-color" content="#f4f4f2">
<title>Farb-/Theme-Wechsel beim Scrollen · Fundus-Demo</title>
<script>document.documentElement.classList.add('js');if(matchMedia('(prefers-reduced-motion: reduce)').matches)document.documentElement.classList.add('reduziert')</script>
<style>
@font-face { font-family: 'Instrument 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: 'Instrument Serif'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/instrument-serif@latest/latin-400-normal.woff2') format('woff2'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'Instrument Serif'; font-style: italic; src: url('https://cdn.jsdelivr.net/fontsource/fonts/instrument-serif@latest/latin-400-italic.woff2') format('woff2'); font-weight: 400; font-display: swap; }
/* Variante „tageslauf“: Rezept eventlocation-hochzeit (Imbue + Archivo) */
@font-face { font-family: 'Imbue'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/imbue:vf@latest/latin-opsz-normal.woff2') format('woff'); font-weight: 100 900; 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: '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; }
@font-face { font-family: 'Fragment Mono'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/fragment-mono@latest/latin-400-normal.woff2') format('woff2'); font-weight: 400; font-display: swap; }
/* Farbthema als registrierte Custom Properties: nur so lassen sie sich weich überblenden */
@property --grund { syntax: '<color>'; inherits: true; initial-value: #f4f4f2; }
@property --text { syntax: '<color>'; inherits: true; initial-value: #141414; }
:root {
--kurve: cubic-bezier(0.33, 1, 0.68, 1); /* power2.out */
--dauer: 600ms;
--leise: color-mix(in oklab, var(--text) 64%, var(--grund));
--linie: color-mix(in oklab, var(--text) 16%, var(--grund));
--rand: clamp(16px, 3.2vw, 44px);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html {
background: var(--grund); color: var(--text);
font: 400 1rem/1.5 'Instrument Sans', system-ui, sans-serif; font-stretch: 95%;
-webkit-font-smoothing: antialiased;
transition: --grund var(--dauer) var(--kurve), --text var(--dauer) var(--kurve);
}
/* Gekoppelt: Fläche folgt dem Scroll direkt, nur der Text kippt kurz um */
html.gekoppelt { transition: --text 150ms linear; }
body { background: var(--grund); color: var(--text); overflow-x: hidden; }
a { color: inherit; text-decoration: none; }
.mono { font: 400 .75rem/1.45 'Fragment Mono', ui-monospace, monospace; letter-spacing: .02em; font-variant-numeric: tabular-nums; }
.leise { color: var(--leise); }
.nur-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.serif { font-family: 'Instrument Serif', Georgia, serif; font-weight: 400; letter-spacing: -.01em; }
/* Kopf: fix, färbt sich mit */
.kopf { position: fixed; inset: 0 0 auto; z-index: 5; background: var(--grund); display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 16px; padding: 18px var(--rand); }
.marke { font-size: 1.375rem; line-height: 1; }
.kopf__ort { justify-self: center; }
.kopf nav { justify-self: end; display: flex; align-items: center; gap: clamp(14px, 2vw, 28px); font-size: .875rem; }
.kopf nav a:not(.pille):hover { color: var(--leise); }
.pille { display: inline-flex; align-items: center; height: 32px; padding: 0 14px; border-radius: 999px; background: var(--text); color: var(--grund); }
.menue { display: none; }
/* Kapitel */
.kapitel { min-height: 100svh; padding: 104px var(--rand) 128px; display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: clamp(12px, 1.6vw, 24px); align-content: center; }
.kopfzeile { grid-column: 1 / -1; display: flex; justify-content: space-between; gap: 16px; padding-bottom: 14px; margin-bottom: clamp(24px, 4vh, 44px); border-bottom: 1px solid var(--linie); }
.info { grid-column: 1 / 4; align-self: end; display: grid; gap: 18px; padding-bottom: 6px; }
.info h2 { font-size: clamp(1.75rem, 3vw, 2.75rem); line-height: 1.05; }
.info p:not(.mono) { font-size: .9375rem; max-width: 34ch; }
.werk { grid-column: 4 / -1; display: grid; gap: 12px; }
.werk svg { display: block; width: 100%; height: auto; }
.k-fulda .werk { justify-self: end; width: 100%; max-width: calc(60svh * 1.5); }
.werk figcaption { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 2px 16px; }
.werk figcaption span:first-child { white-space: nowrap; }
.k-nacht .werk svg { aspect-ratio: 12 / 5; }
.k-wald .info { grid-column: 8 / -1; grid-row: 2; }
.k-wald .werk { grid-column: 2 / 7; grid-row: 2; width: 100%; max-width: calc(64svh * .8); }
.film { display: inline-flex; align-items: center; gap: 10px; }
.film i { width: 26px; height: 26px; border: 1px solid currentColor; border-radius: 50%; display: grid; place-items: center; font-style: normal; font-size: 8px; }
/* Einstieg */
.k-start { align-content: end; }
.bogen { grid-column: 6 / -1; display: grid; grid-template-columns: 1.5fr 2.4fr .8fr; gap: 10px 12px; margin-bottom: clamp(36px, 7vh, 80px); }
.bogen a { display: block; }
.bogen svg { display: block; width: 100%; height: auto; transition: opacity 200ms; }
.bogen a:hover svg { opacity: .82; }
.bogen figcaption { grid-column: 1 / -1; display: flex; justify-content: space-between; gap: 12px; }
.k-start .name { grid-column: 1 / -1; font-size: clamp(3.4rem, 11.5vw, 11rem); line-height: .86; letter-spacing: -.035em; margin-bottom: clamp(28px, 5vh, 56px); }
.k-start .name em { font-style: italic; }
.k-start .intro { grid-column: 1 / 5; font-size: .9375rem; max-width: 38ch; align-self: start; }
.index { grid-column: 6 / -1; list-style: none; border-top: 1px solid var(--linie); }
.index a { display: grid; grid-template-columns: 2.4rem 1fr auto 14px; align-items: baseline; gap: 14px; padding: 11px 0 10px; border-bottom: 1px solid var(--linie); }
.index b { font: 400 1.25rem/1.1 'Instrument Serif', Georgia, serif; }
.index a:hover b { font-style: italic; }
.ton { width: 14px; height: 14px; border-radius: 50%; align-self: center; background: var(--ton); box-shadow: inset 0 0 0 1px var(--linie); }
/* Anfrage + Fuß */
.k-anfrage { align-content: end; }
.k-anfrage h2 { grid-column: 1 / -1; font-size: clamp(3rem, 9vw, 8.5rem); line-height: .9; letter-spacing: -.03em; margin-bottom: clamp(28px, 5vh, 56px); }
.k-anfrage h2 em { font-style: italic; }
.k-anfrage .text { grid-column: 1 / 5; font-size: .9375rem; max-width: 36ch; display: grid; gap: 18px; align-content: start; }
.karte { grid-column: 6 / -1; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px 24px; border-top: 1px solid var(--linie); padding-top: 14px; }
.karte dt { color: var(--leise); margin-bottom: 4px; }
.frei { grid-column: 6 / -1; margin-top: 26px; display: grid; gap: 10px; }
.frei__kopf { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 6px 16px; }
.frei__kopf i { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin: 0 5px 0 10px; vertical-align: 0; }
.frei ol { list-style: none; display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 4px; }
.frei li { height: 34px; display: grid; place-items: center; border-radius: 4px; }
.m-voll { background: var(--text); color: var(--grund); }
.m-wenig { box-shadow: inset 0 0 0 1px var(--text); }
.m-frei { box-shadow: inset 0 0 0 1px var(--linie); color: var(--leise); }
i.m-frei { box-shadow: inset 0 0 0 1px var(--leise); }
.fuss { grid-column: 1 / -1; display: flex; justify-content: space-between; gap: 16px; margin-top: clamp(48px, 9vh, 96px); padding-top: 14px; border-top: 1px solid var(--linie); }
.linkpfeil { border-bottom: 1px solid currentColor; padding-bottom: 2px; justify-self: start; }
/* Ecken: aktuelles Kapitel (rechts) und Steuerung mit Werten (links) */
.ecke { position: fixed; bottom: 16px; z-index: 6; }
.stand { right: var(--rand); text-align: right; }
.stand b { font-weight: 400; }
.steuer { left: var(--rand); display: grid; gap: 6px; padding: 10px 12px; border-radius: 10px; background: color-mix(in oklab, var(--grund) 86%, transparent); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); box-shadow: inset 0 0 0 1px var(--linie); max-width: calc(100vw - 2 * var(--rand)); }
.zeile { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.zeile > span:first-child { width: 4.8rem; color: var(--leise); }
.steuer button { font: inherit; color: var(--leise); background: none; border: 1px solid var(--linie); border-radius: 999px; height: 24px; padding: 0 10px; cursor: pointer; transition: color 150ms, background-color 150ms, border-color 150ms; }
.steuer button:hover { color: var(--text); }
.steuer button[aria-pressed='true'] { background: var(--text); border-color: var(--text); color: var(--grund); }
.werte { color: var(--leise); }
.werte b { font-weight: 400; color: var(--text); }
:focus-visible { outline: 2px solid var(--text); outline-offset: 3px; }
/* Reduzierte Bewegung: Farbwechsel bleibt (er ist keine Bewegung), aber fast sofort */
.reduziert, .reduziert.gekoppelt { transition: --grund 200ms linear, --text 200ms linear; }
.reduziert * { scroll-behavior: auto !important; }
/* Vorschau: nur die Bühne */
.vorschau body { overflow: hidden; }
.vorschau .steuer { display: none; }
/* ===================== Varianten-Umschalter ===================== */
[hidden] { display: none !important; }
.zeile-variante button { border-style: dashed; }
.zeile-variante button[aria-pressed='true'] { border-style: solid; }
/* ===================== Variante „tageslauf“: Tag → Abend → Nacht (eventlocation-hochzeit) ===================== */
/* Farben setzt das Skript (Tween mit Kipppunkt); die CSS-Überblendung der Grundvariante ist hier aus */
:root.tl-an { transition: none !important; }
:root { --t-leise: #5c574e; --t-linie: #d8cfc0; --t-messing: #8c6a3c; } /* Startwerte (Kalk); das Skript setzt sie je Stimmung am <html> */
.tl {
--t-kurve: cubic-bezier(0.625, 0.05, 0, 1); /* token-studio-signatur */
--t-mohn: #C43B2B; --t-mohn-hover: #A8331F;
--t-rand: clamp(16px, 4vw, 56px);
font-family: 'Archivo', system-ui, sans-serif; font-stretch: 100%; font-size: 1.0625rem; line-height: 1.6;
}
.tl :focus-visible { outline: 2px solid var(--text); outline-offset: 3px; }
.t-imbue { font-family: 'Imbue', Georgia, serif; font-weight: 400; font-optical-sizing: auto; letter-spacing: -.01em; }
.t-label { font-size: .8125rem; font-weight: 600; font-stretch: 112%; letter-spacing: .08em; text-transform: uppercase; color: var(--t-leise); }
.t-leise { color: var(--t-leise); }
.t-knopf { display: inline-flex; align-items: center; justify-content: center; gap: 10px; min-height: 48px; padding: 0 22px; border: 0; border-radius: 2px; background: var(--t-mohn); color: #fff; font: 600 1rem/1 'Archivo', sans-serif; text-decoration: none; cursor: pointer; transition: background-color 250ms var(--t-kurve); }
.t-knopf:hover { background: var(--t-mohn-hover); }
.t-neben { display: inline-flex; align-items: center; min-height: 44px; padding: 0 18px; border-radius: 2px; color: var(--text); text-decoration: none; font-weight: 600; box-shadow: inset 0 0 0 1px var(--t-messing), inset 0 0 0 4px var(--grund), inset 0 0 0 5px var(--t-messing); }
.t-kopf { position: fixed; inset: 0 0 auto; z-index: 5; display: flex; align-items: center; gap: 16px 32px; padding: 14px var(--t-rand); background: var(--grund); color: var(--text); }
.t-marke { font-size: 1.75rem; line-height: 1; text-decoration: none; }
.t-marke small { display: block; margin-top: 4px; font: 600 .6875rem/1 'Archivo', sans-serif; font-stretch: 112%; letter-spacing: .08em; text-transform: uppercase; color: var(--t-leise); }
.t-nav { display: flex; gap: 26px; margin-left: auto; font-size: .9375rem; }
.t-nav a { text-decoration: none; background: linear-gradient(currentColor, currentColor) 0 100% / 0 1px no-repeat; padding-bottom: 3px; transition: background-size 400ms var(--t-kurve); }
.t-nav a:hover { background-size: 100% 1px; }
.t-kopf .t-knopf { min-height: 44px; }
.t-held { min-height: 100svh; padding: 132px var(--t-rand) 128px; display: grid; grid-template-columns: minmax(0, 1fr); align-content: end; gap: 28px; }
.t-held h1 { font-weight: 300; font-size: clamp(3rem, 1.6rem + 6vw, 7.5rem); line-height: .95; max-width: 13ch; text-wrap: balance; }
.t-held h1 em { font-style: normal; font-weight: 500; }
.t-held .t-unter { max-width: 52ch; }
.t-held-reihe { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 20rem); gap: 24px 48px; align-items: end; }
.t-frei ul { list-style: none; margin-top: 8px; border-top: 1px solid var(--t-linie); }
.t-frei li { display: flex; justify-content: space-between; align-items: center; gap: 12px; min-height: 58px; padding: 8px 0; border-bottom: 1px solid var(--t-linie); }
.t-frei li .t-imbue { font-size: 2rem; line-height: 1; }
.t-status { display: inline-flex; align-items: center; gap: 8px; font-size: .8125rem; font-weight: 600; font-stretch: 112%; letter-spacing: .08em; text-transform: uppercase; color: var(--t-leise); white-space: nowrap; }
.t-status.frei { color: var(--text); }
.t-status.frei::before { content: ''; width: 8px; height: 8px; border-radius: 50%; background: var(--t-mohn); }
.t-wunsch { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)) auto; gap: 0; align-items: end; border-top: 1px solid var(--t-linie); border-bottom: 1px solid var(--t-linie); }
.t-wunsch label { display: grid; gap: 4px; padding: 14px 18px 12px 0; }
.t-wunsch label + label { padding-left: 18px; border-left: 1px solid var(--t-linie); }
.t-wunsch input, .t-wunsch select { font: 400 1.0625rem/1.3 'Archivo', sans-serif; font-stretch: 87%; font-variant-numeric: tabular-nums; color: var(--text); background: transparent; border: 0; border-bottom: 1px solid transparent; padding: 4px 0; min-width: 0; width: 100%; }
.t-wunsch select option { color: #1D1A17; background: #F5F0E6; }
.t-wunsch input:focus-visible, .t-wunsch select:focus-visible, .t-wunsch input:focus, .t-wunsch select:focus { outline: 2px solid var(--text); outline-offset: 2px; }
.t-wunsch label:focus-within .t-label { color: var(--text); }
.t-wunsch .t-knopf { margin: 10px 0; }
.t-kapitel-kopf { padding: 72px var(--t-rand) 0; display: flex; justify-content: space-between; align-items: end; gap: 12px 32px; flex-wrap: wrap; }
.t-kapitel-kopf h2 { font-size: clamp(2.25rem, 1.6rem + 2.6vw, 4rem); line-height: 1; font-weight: 400; }
.t-kapitel-kopf p { max-width: 44ch; }
.t-stimmung { min-height: 100svh; padding: 96px var(--t-rand) 112px; display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: clamp(12px, 2vw, 32px); align-content: center; align-items: center; }
.t-text { grid-column: 1 / 6; display: grid; gap: 16px; align-content: center; }
.t-text .t-uhr { font-size: clamp(4.5rem, 2.8rem + 6vw, 9rem); line-height: .9; font-weight: 300; font-variant-numeric: tabular-nums lining-nums; }
.t-text h3 { font-size: clamp(2rem, 1.6rem + 1.4vw, 3rem); line-height: 1.05; font-weight: 400; }
.t-text p { max-width: 40ch; }
.t-text .t-fakten { display: flex; flex-wrap: wrap; gap: 6px 22px; margin-top: 6px; padding-top: 12px; border-top: 1px solid var(--t-linie); font-stretch: 87%; font-variant-numeric: tabular-nums; }
.t-bild { grid-column: 7 / -1; margin: 0; display: grid; gap: 10px; }
.t-bild svg { display: block; width: 100%; height: auto; border-radius: 2px; }
.t-bild figcaption { font-size: .8125rem; color: var(--t-leise); display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.t-stimmung.gespiegelt .t-text { grid-column: 8 / -1; grid-row: 1; }
.t-stimmung.gespiegelt .t-bild { grid-column: 1 / 7; grid-row: 1; }
.t-raeume { padding: 112px var(--t-rand) 96px; display: grid; grid-template-columns: minmax(0, 4fr) minmax(0, 8fr); gap: 32px clamp(24px, 4vw, 72px); }
.t-raeume h2 { font-size: clamp(2.25rem, 1.6rem + 2.6vw, 4rem); line-height: 1; font-weight: 400; }
.t-raeume table { width: 100%; border-collapse: collapse; font-stretch: 87%; font-variant-numeric: tabular-nums; }
.t-raeume th, .t-raeume td { text-align: left; padding: 14px 12px 14px 0; border-bottom: 1px solid var(--t-linie); vertical-align: baseline; }
.t-raeume thead th { font-size: .8125rem; font-weight: 600; font-stretch: 112%; letter-spacing: .08em; text-transform: uppercase; color: var(--t-leise); }
.t-raeume tbody th { font: 400 1.5rem/1.1 'Imbue', Georgia, serif; }
.t-raeume td, .t-raeume thead th:not(:first-child) { text-align: right; }
.t-raeume .t-zeile-unten { grid-column: 2; display: flex; flex-wrap: wrap; gap: 14px 24px; align-items: center; }
.t-fuss { background: #15171F; color: #F2ECE2; padding: 48px var(--t-rand) 120px; display: flex; flex-wrap: wrap; justify-content: space-between; gap: 16px 32px; font-size: .9375rem; }
.t-fuss b { font: 400 1.75rem/1 'Imbue', Georgia, serif; }
.t-fuss span { color: #B3AA9C; }
.t-stand { position: fixed; right: var(--t-rand); bottom: 16px; z-index: 6; text-align: right; background: var(--grund); padding: 6px 10px; border-radius: 4px; font: 400 .75rem/1.45 'Geist Mono', ui-monospace, monospace; letter-spacing: .02em; }
.t-stand b { font-weight: 400; }
@media (max-width: 1000px) { .t-nav { display: none; } .t-kopf .t-knopf { margin-left: auto; } }
@media (max-width: 900px) {
.t-stimmung { grid-template-columns: minmax(0, 1fr); padding-top: 88px; padding-bottom: 140px; row-gap: 22px; }
.t-stimmung > *, .t-stimmung.gespiegelt > * { grid-column: 1 / -1 !important; grid-row: auto !important; }
.t-bild { order: 2; }
.t-raeume { grid-template-columns: minmax(0, 1fr); }
.t-raeume .t-zeile-unten { grid-column: 1; }
.t-held-reihe { grid-template-columns: minmax(0, 1fr); }
.t-held { padding-bottom: 96px; }
.t-wunsch { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.t-wunsch label:nth-of-type(3) { padding-left: 0; border-left: 0; }
.t-wunsch label:nth-of-type(n+3) { border-top: 1px solid var(--t-linie); }
.t-wunsch .t-knopf { grid-column: 1 / -1; }
.t-stand { display: none; }
}
@media (max-width: 480px) {
.t-marke small { display: none; }
.t-held { padding-top: 104px; }
.t-wunsch { grid-template-columns: minmax(0, 1fr); }
.t-wunsch label + label { padding-left: 0; border-left: 0; border-top: 1px solid var(--t-linie); }
.t-raeume th, .t-raeume td { padding-right: 8px; }
.t-raeume tbody th { font-size: 1.25rem; }
.t-kopf .t-knopf { padding: 0 16px; }
}
@media (max-width: 360px) { .t-raeume table { font-size: .9375rem; } .t-raeume .t-flaeche { display: none; } }
.vorschau .tl .t-wunsch, .vorschau .tl .t-stand { display: none; }
@media (forced-colors: active) { .tl button, .tl input, .tl select, .tl .t-knopf, .steuer button { border: 1px solid ButtonText !important; } }
@media (max-width: 900px) {
.kopf { grid-template-columns: 1fr auto; }
.kopf__ort, .kopf nav a:not(.pille) { display: none; }
.kapitel { grid-template-columns: minmax(0, 1fr); padding-top: 92px; padding-bottom: 150px; align-content: center; }
.kapitel > * { grid-column: 1 / -1 !important; grid-row: auto !important; }
.info { order: 2; margin-top: 22px; gap: 12px; }
.info h2 { font-size: 2rem; }
.k-fulda .werk, .k-wald .werk { max-width: none; }
.bogen { margin-bottom: 40px; }
.k-start .intro { margin-bottom: 28px; }
.karte { grid-template-columns: 1fr 1fr; margin-top: 28px; }
.frei ol { grid-template-columns: repeat(6, minmax(0, 1fr)); }
.k-anfrage .text { max-width: none; }
.stand { display: none; }
.steuer { left: 12px; right: 12px; bottom: 12px; max-width: none; grid-template-columns: auto auto; justify-content: start; column-gap: 14px; }
.zeile { flex-wrap: nowrap; }
.zeile > span:first-child { display: none; }
.werte { grid-column: 1 / -1; }
}
@media (max-width: 480px) {
.kopfzeile span:last-child { display: none; }
.index a { grid-template-columns: 2rem 1fr 14px; }
.index a .mono:nth-of-type(2) { display: none; }
.lang { display: none; }
}
@media (hover: none) {
.steuer button { height: 32px; padding: 0 12px; }
.index a { padding: 14px 0 13px; }
}
</style>
</head>
<body>
<!-- Bildwelt: eigene SVG-Motive, einmal definiert und per <use> mehrfach gezeigt -->
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
<defs>
<linearGradient id="f-himmel" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#a9b5bc"/><stop offset=".62" stop-color="#d3d9d9"/><stop offset="1" stop-color="#e2e5e2"/></linearGradient>
<linearGradient id="f-wasser" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#cfd5d4"/><stop offset=".45" stop-color="#b6c0c3"/><stop offset="1" stop-color="#8996a0"/></linearGradient>
<linearGradient id="f-nebel" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#e6e9e6" stop-opacity="0"/><stop offset=".5" stop-color="#e6e9e6" stop-opacity=".78"/><stop offset="1" stop-color="#e6e9e6" stop-opacity="0"/></linearGradient>
<filter id="w2" x="-10%" y="-50%" width="120%" height="200%"><feGaussianBlur stdDeviation="2"/></filter>
<filter id="w5" x="-20%" y="-60%" width="140%" height="220%"><feGaussianBlur stdDeviation="5"/></filter>
<filter id="w10" x="-40%" y="-40%" width="180%" height="180%"><feGaussianBlur stdDeviation="10"/></filter>
<filter id="w24" x="-50%" y="-50%" width="200%" height="200%"><feGaussianBlur stdDeviation="24"/></filter>
<linearGradient id="n-grund" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#08090c"/><stop offset=".7" stop-color="#121014"/><stop offset="1" stop-color="#0a0a0b"/></linearGradient>
<linearGradient id="n-laden" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#ffd59a"/><stop offset="1" stop-color="#d9883c"/></linearGradient>
<radialGradient id="n-lampe"><stop offset="0" stop-color="#ffb35c" stop-opacity=".9"/><stop offset=".25" stop-color="#ff9a3c" stop-opacity=".35"/><stop offset="1" stop-color="#ff9a3c" stop-opacity="0"/></radialGradient>
<radialGradient id="w-luft" cx=".62" cy=".38" r=".8"><stop offset="0" stop-color="#f1f3ea"/><stop offset=".45" stop-color="#c3cab8"/><stop offset="1" stop-color="#6f7860"/></radialGradient>
<linearGradient id="w-boden" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#2a3023" stop-opacity="0"/><stop offset=".35" stop-color="#2a3023" stop-opacity=".75"/><stop offset="1" stop-color="#161a12"/></linearGradient>
<linearGradient id="w-dach" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#262d20" stop-opacity=".7"/><stop offset="1" stop-color="#262d20" stop-opacity="0"/></linearGradient>
<linearGradient id="w-dunst" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#e4e8dd" stop-opacity=".05"/><stop offset=".72" stop-color="#e4e8dd" stop-opacity=".62"/><stop offset="1" stop-color="#e4e8dd" stop-opacity=".25"/></linearGradient>
<symbol id="b-fulda" viewBox="0 0 1200 800">
<rect width="1200" height="446" fill="url(#f-himmel)"/>
<rect y="440" width="1200" height="360" fill="url(#f-wasser)"/>
<path data-ufer fill="#7d898e" filter="url(#w2)"/>
<path data-spiegel fill="#7d898e" opacity=".32" filter="url(#w5)"/>
<rect y="372" width="1200" height="120" fill="url(#f-nebel)"/>
<g data-schlieren fill="#eef1ef"></g>
<g fill="#465054">
<rect x="168" y="452" width="5" height="150"/><rect x="204" y="458" width="4" height="128"/><rect x="236" y="462" width="4" height="112"/>
</g>
<g fill="#465054" opacity=".28" filter="url(#w2)">
<rect x="167" y="602" width="7" height="70"/><rect x="203" y="586" width="6" height="58"/><rect x="235" y="574" width="6" height="48"/>
</g>
<g transform="translate(742 560)">
<path d="M-92 0 Q0 7 92 0 Q0 3 -92 0Z" fill="#262c2f"/>
<circle cx="-4" cy="-17" r="5.5" fill="#262c2f"/><path d="M-9 -12 L1 -12 L5 -1 L-12 -1Z" fill="#262c2f"/>
<path d="M-2 -8 L-58 13 M-2 -8 L46 15" stroke="#262c2f" stroke-width="2"/>
<g opacity=".3" filter="url(#w2)"><path d="M-92 3 Q0 9 92 3 L60 10 Q0 14 -60 10Z" fill="#262c2f"/><rect x="-10" y="6" width="12" height="18" fill="#262c2f"/></g>
</g>
</symbol>
<symbol id="b-nacht" viewBox="0 0 1200 500">
<rect width="1200" height="500" fill="url(#n-grund)"/>
<rect x="-10" y="30" width="730" height="320" fill="#121318"/>
<g data-fenster fill="#1a1b21"></g>
<rect x="333" y="196" width="46" height="62" fill="#e7a556"/>
<rect x="318" y="182" width="76" height="90" fill="#ffb361" opacity=".35" filter="url(#w10)"/>
<rect x="720" y="120" width="420" height="240" fill="#16161b"/>
<rect x="790" y="232" width="250" height="128" fill="url(#n-laden)"/>
<rect x="780" y="226" width="270" height="12" fill="#25252b"/>
<g opacity=".85"><rect x="806" y="250" width="40" height="80" fill="#c7843f"/><rect x="856" y="262" width="60" height="6" fill="#b5702f"/><rect x="856" y="290" width="60" height="6" fill="#b5702f"/><rect x="930" y="250" width="90" height="56" fill="#f9e2b8"/></g>
<rect x="840" y="190" width="150" height="22" rx="3" fill="#53d2c4" filter="url(#w10)" opacity=".9"/>
<rect x="846" y="196" width="138" height="10" rx="2" fill="#bff6ee"/>
<path d="M884 360 L884 298 Q884 286 893 282 L897 268 Q888 262 890 251 Q893 240 903 241 Q913 243 912 254 Q911 263 905 268 L909 282 Q919 286 919 298 L919 360Z" fill="#060607"/>
<rect y="358" width="1200" height="142" fill="#0b0b0d"/>
<g filter="url(#w10)">
<rect x="790" y="372" width="250" height="110" fill="#d9883c" opacity=".3"/>
<rect x="840" y="388" width="150" height="70" fill="#53d2c4" opacity=".18"/>
<rect x="333" y="378" width="46" height="90" fill="#e7a556" opacity=".14"/>
</g>
<line x1="1150" y1="40" x2="1150" y2="360" stroke="#1d1d22" stroke-width="7"/>
<circle cx="1132" cy="48" r="120" fill="url(#n-lampe)"/>
<g data-bokeh filter="url(#w10)"></g>
</symbol>
<symbol id="b-wald" viewBox="0 0 800 1000">
<rect width="800" height="1000" fill="url(#w-luft)"/>
<polygon points="80,-40 340,-40 760,1000 420,1000" fill="#f4f6ee" opacity=".22" filter="url(#w24)"/>
<g data-baeume></g>
<rect y="720" width="800" height="280" fill="url(#w-boden)"/>
<path d="M96 918 L548 842 L556 858 L104 940Z" fill="#12150f"/>
<path d="M548 842 L590 826 L594 838 L556 858Z" fill="#12150f"/>
<rect width="800" height="300" fill="url(#w-dach)"/>
</symbol>
</defs>
</svg>
<header class="kopf" data-welt="basis">
<a class="marke serif" href="#start">Lene Dörnberg</a>
<span class="kopf__ort mono leise">Fotografie & Film · Kassel</span>
<nav aria-label="Hauptnavigation">
<a href="#fulda">Serien</a><a href="#nacht">Film</a><a href="#start">Über mich</a><a class="pille" href="#anfrage">Anfrage</a>
</nav>
</header>
<main data-welt="basis">
<section class="kapitel k-start" id="start" data-kapitel="Auswahl 2020–2026" data-neutral="#f4f4f2 #141414" data-bild="#f4f4f2 #141414">
<figure class="bogen">
<a href="#fulda" aria-label="Zur Serie Fulda, sieben Uhr"><svg viewBox="0 0 1200 800" aria-hidden="true"><use href="#b-fulda"/></svg></a>
<a href="#nacht" aria-label="Zum Kurzfilm Nachtschicht"><svg viewBox="0 0 1200 500" aria-hidden="true"><use href="#b-nacht"/></svg></a>
<a href="#wald" aria-label="Zur Serie Reinhardswald"><svg viewBox="0 0 800 1000" aria-hidden="true"><use href="#b-wald"/></svg></a>
<figcaption class="mono"><span>Auswahl 2020–2026</span><span class="leise">3 von 41 Serien</span></figcaption>
</figure>
<h1 class="name serif">Lene <em>Dörnberg</em></h1>
<p class="intro">Ich arbeite in Serien, meist dort, wo ich wohne: am Fluss, in der Nordstadt, im Wald hinter Hann. Münden. Für Auftraggeber fotografiere ich Menschen bei der Arbeit und drehe kurze Filme.</p>
<ol class="index">
<li><a href="#fulda"><span class="mono leise">01</span><b>Fulda, sieben Uhr</b><span class="mono leise">Serie · 14 Bilder · 2026</span><span class="ton" data-ton="fulda"></span></a></li>
<li><a href="#nacht"><span class="mono leise">02</span><b>Nachtschicht</b><span class="mono leise">Kurzfilm · 9 Min. · 2025</span><span class="ton" data-ton="nacht"></span></a></li>
<li><a href="#wald"><span class="mono leise">03</span><b>Reinhardswald</b><span class="mono leise">Serie · 22 Bilder · 2024</span><span class="ton" data-ton="wald"></span></a></li>
<li><a href="#anfrage"><span class="mono leise">04</span><b>Anfragen 2027</b><span class="mono leise">Aufträge · Lizenzen</span><span class="ton" data-ton="anfrage"></span></a></li>
</ol>
</section>
<section class="kapitel k-fulda" id="fulda" data-kapitel="Fulda, sieben Uhr" data-neutral="#e6e6e6 #141414" data-bild="#d8dfe1 #141414">
<div class="kopfzeile mono"><span>01 · Serie</span><span class="leise">Februar bis April 2026 · Mittelformat</span></div>
<div class="info">
<h2 class="serif">Fulda, sieben Uhr</h2>
<p>Jeden Morgen dieselbe Stelle bei Wolfsanger, bevor die Ruderer ablegen. Der Nebel entscheidet, wie viel vom anderen Ufer übrig bleibt.</p>
<a class="mono linkpfeil" href="#fulda">Serie ansehen · 14 Bilder</a>
</div>
<figure class="werk">
<svg viewBox="0 0 1200 800" role="img" aria-label="Flusslandschaft im Morgennebel, ein Ruderboot auf ruhigem Wasser"><use href="#b-fulda"/></svg>
<figcaption class="mono"><span>03 / 14</span><span class="leise">Wolfsanger, 07:12 Uhr, 4. März 2026</span></figcaption>
</figure>
</section>
<section class="kapitel k-nacht" id="nacht" data-kapitel="Nachtschicht" data-neutral="#0e0e0e #f2f2f2" data-bild="#1a120c #f4ece2">
<div class="kopfzeile mono"><span>02 · Kurzfilm</span><span class="leise">9 Min. · 2025 · Super 16 auf 4K</span></div>
<div class="info">
<h2 class="serif">Nachtschicht</h2>
<p>Ein Kiosk an der Holländischen Straße, eine Nacht lang, vom Ladenschluss der anderen bis zum ersten Bus. Fast ohne Worte.</p>
<a class="mono film" href="#nacht"><i aria-hidden="true">▶</i>Film ansehen · 09:12</a>
</div>
<figure class="werk">
<svg viewBox="0 0 1200 500" role="img" aria-label="Filmstill: nächtlicher Kiosk mit warmem Licht, davor eine Person als Silhouette"><use href="#b-nacht"/></svg>
<figcaption class="mono"><span>Standbild 00:04:31</span><span class="leise">Holländische Straße, 2:40 Uhr</span></figcaption>
</figure>
</section>
<section class="kapitel k-wald" id="wald" data-kapitel="Reinhardswald" data-neutral="#d6d6d3 #141414" data-bild="#1f251d #e8eae1">
<div class="kopfzeile mono"><span>03 · Serie</span><span class="leise">Januar bis Dezember 2024 · als Buch erschienen</span></div>
<figure class="werk">
<svg viewBox="0 0 800 1000" role="img" aria-label="Lichter Wald im Nebel, Stämme in mehreren Tiefenebenen"><use href="#b-wald"/></svg>
<figcaption class="mono"><span>11 / 22</span><span class="leise">Bei Gottsbüren, 1. November 2024</span></figcaption>
</figure>
<div class="info">
<h2 class="serif">Reinhardswald</h2>
<p>Nach den Sturmjahren steht der Wald licht. Ich bin ein Jahr lang an jedem Monatsersten denselben Weg gegangen.</p>
<a class="mono linkpfeil" href="#wald">Serie ansehen · 22 Bilder</a>
</div>
</section>
<section class="kapitel k-anfrage" id="anfrage" data-kapitel="Anfragen 2027" data-neutral="#f4f4f2 #141414" data-bild="#ece6dc #1a1714">
<h2 class="serif">Anfragen <em>2027</em></h2>
<div class="text">
<p>Reportagen, Porträts für Unternehmen und kurze Filme. Ich antworte innerhalb von zwei Werktagen.</p>
<a class="mono linkpfeil" href="#anfrage">Anfrage schreiben</a>
</div>
<dl class="karte mono">
<div><dt>Studio</dt><dd>Friedrich-Ebert-Straße 112<br>34119 Kassel</dd></div>
<div><dt>Kontakt</dt><dd>0561 47 39 20<br>post@lenedoernberg.de</dd></div>
<div><dt>Bildlizenzen</dt><dd>Serien und Standbilder<br>auf Anfrage</dd></div>
</dl>
<div class="frei mono">
<p class="frei__kopf"><span>Termine 2027</span><span class="leise"><i class="m-voll"></i>vergeben <i class="m-wenig"></i>wenige Tage <i class="m-frei"></i>frei</span></p>
<ol aria-label="Verfügbarkeit 2027 nach Monaten"><li class="m-voll"><span>Jan</span></li><li class="m-voll"><span>Feb</span></li><li class="m-wenig"><span>Mär</span></li><li class="m-frei"><span>Apr</span></li><li class="m-wenig"><span>Mai</span></li><li class="m-voll"><span>Jun</span></li><li class="m-frei"><span>Jul</span></li><li class="m-frei"><span>Aug</span></li><li class="m-wenig"><span>Sep</span></li><li class="m-frei"><span>Okt</span></li><li class="m-frei"><span>Nov</span></li><li class="m-frei"><span>Dez</span></li></ol>
</div>
<footer class="fuss mono leise"><span>© 2026 Lene Dörnberg</span><span>Impressum · Datenschutz</span></footer>
</section>
</main>
<!-- ===================== Variante „tageslauf“: Eventlocation, Kapitel „Ein Tag bei uns“ ===================== -->
<div class="tl" data-welt="tageslauf" hidden>
<header class="t-kopf">
<a class="t-marke t-imbue" href="#t-start">Gut Wendhagen<small>Feiern · Tagen · bei Gadebusch</small></a>
<nav class="t-nav" aria-label="Hauptnavigation"><a href="#t-tag">Ein Tag bei uns</a><a href="#t-raeume">Räume</a><a href="#t-raeume">Pakete</a><a href="#t-raeume">Termine</a></nav>
<a class="t-knopf" href="#t-raeume">Anfragen</a>
</header>
<main>
<section class="t-held" id="t-start" data-licht="kalk" data-name="Willkommen" aria-labelledby="t-titel">
<p class="t-label">Hochzeiten und Feste für 40 bis 180 Gäste</p>
<div class="t-held-reihe">
<h1 class="t-imbue" id="t-titel">Ein Tag auf dem Gut, <em>bis in die Nacht.</em></h1>
<div class="t-frei">
<p class="t-label">Freie Samstage 2027</p>
<ul>
<li><span class="t-imbue">8. Mai</span><span class="t-status frei">frei</span></li>
<li><span class="t-imbue">12. Juni</span><span class="t-status">Option bis 30.11.</span></li>
<li><span class="t-imbue">4. September</span><span class="t-status frei">frei</span></li>
</ul>
</div>
</div>
<p class="t-unter">Trauung unter der Linde, Essen in der Scheune, Tanz in der Tenne. Wir richten pro Tag nur ein Fest aus.</p>
<form class="t-wunsch" aria-label="Wunschtermin prüfen" data-t-form>
<label><span class="t-label">Wunschdatum</span><input type="date" value="2027-06-12" name="datum"></label>
<label><span class="t-label">Anlass</span><select name="anlass"><option>Hochzeit</option><option>Geburtstag</option><option>Firmenfest</option><option>Tagung</option></select></label>
<label><span class="t-label">Gäste</span><input type="number" value="90" min="10" max="180" name="gaeste" inputmode="numeric"></label>
<button class="t-knopf" type="submit">Termin prüfen</button>
</form>
</section>
<div class="t-kapitel-kopf" id="t-tag">
<h2 class="t-imbue">Ein Tag bei uns</h2>
<p class="t-leise">So läuft ein Samstag im Juni. Die Uhrzeiten sind Vorschläge, den Ablauf planen wir mit Ihnen.</p>
</div>
<article class="t-stimmung" data-licht="tag" data-name="Tag · 13:30" aria-labelledby="t-s1">
<div class="t-text">
<p class="t-uhr t-imbue" aria-hidden="true">13:30</p>
<h3 class="t-imbue" id="t-s1"><span class="nur-sr">13:30 Uhr: </span>Trauung unter der Linde</h3>
<p>Freie Trauung im Lindenhof oder standesamtlich im Gutssaal. Bei Regen ziehen wir in die Scheune um, die Stühle stehen dort schon bereit.</p>
<p class="t-fakten t-leise"><span>bis 120 Plätze</span><span>Bänke aus Eiche</span><span>Schatten ab 14 Uhr</span></p>
</div>
<figure class="t-bild">
<svg viewBox="0 0 1200 800" role="img" aria-label="Der Lindenhof am Mittag: eine große Linde, davor Stuhlreihen auf dem Rasen, hinten das weiße Gutshaus mit rotem Dach">
<defs>
<linearGradient id="tg-himmel" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#bcd3e0"/><stop offset="1" stop-color="#f2efe6"/></linearGradient>
<linearGradient id="tg-rasen" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#a7b877"/><stop offset="1" stop-color="#7f9554"/></linearGradient>
</defs>
<rect width="1200" height="800" fill="url(#tg-himmel)"/>
<g fill="#8fa06c" opacity=".55"><ellipse cx="120" cy="470" rx="190" ry="60"/><ellipse cx="1120" cy="470" rx="220" ry="70"/></g>
<rect x="560" y="318" width="520" height="160" fill="#efe9dd"/>
<path d="M540 322 L640 236 L1000 236 L1100 322 Z" fill="#a9563d"/>
<rect x="790" y="196" width="26" height="54" fill="#8c4632"/>
<g fill="#6d7a83">
<rect x="598" y="352" width="30" height="44"/><rect x="664" y="352" width="30" height="44"/><rect x="730" y="352" width="30" height="44"/><rect x="886" y="352" width="30" height="44"/><rect x="952" y="352" width="30" height="44"/><rect x="1018" y="352" width="30" height="44"/>
<rect x="598" y="416" width="30" height="44"/><rect x="664" y="416" width="30" height="44"/><rect x="730" y="416" width="30" height="44"/><rect x="886" y="416" width="30" height="44"/><rect x="952" y="416" width="30" height="44"/><rect x="1018" y="416" width="30" height="44"/>
</g>
<path d="M796 478 L796 404 Q820 384 844 404 L844 478 Z" fill="#5b4a3b"/>
<rect y="470" width="1200" height="330" fill="url(#tg-rasen)"/>
<path d="M610 800 L690 480 L730 480 L810 800 Z" fill="#e3d8c2"/>
<ellipse cx="330" cy="610" rx="300" ry="54" fill="#5f733e" opacity=".45"/>
<path d="M318 612 C312 520 300 440 286 380 L330 380 C334 450 344 530 352 612 Z" fill="#5a4a3c"/>
<g fill="#6f8a4f"><circle cx="300" cy="250" r="150"/><circle cx="170" cy="320" r="110"/><circle cx="440" cy="320" r="118"/></g>
<g fill="#82a05c"><circle cx="250" cy="200" r="104"/><circle cx="380" cy="230" r="96"/><circle cx="320" cy="140" r="82"/></g>
<g fill="#5b7442"><circle cx="210" cy="380" r="70"/><circle cx="400" cy="390" r="66"/></g>
<rect x="650" y="530" width="120" height="10" fill="#7a5b3e"/><rect x="656" y="540" width="8" height="30" fill="#6a4e35"/><rect x="756" y="540" width="8" height="30" fill="#6a4e35"/>
<g data-tl-stuehle></g>
</svg>
<figcaption><span>Lindenhof, Juni, 13:30 Uhr</span><span>Foto: Gut Wendhagen</span></figcaption>
</figure>
</article>
<article class="t-stimmung gespiegelt" data-licht="abend" data-name="Abend · 18:30" aria-labelledby="t-s2">
<div class="t-text">
<p class="t-uhr t-imbue" aria-hidden="true">18:30</p>
<h3 class="t-imbue" id="t-s2"><span class="nur-sr">18:30 Uhr: </span>Essen in der Scheune</h3>
<p>Eine lange Tafel unter den alten Balken, gekocht wird in unserer eigenen Küche. Die Abendsonne steht dann genau im Westtor.</p>
<p class="t-fakten t-leise"><span>140 an einer Tafel</span><span>Menü oder Buffet</span><span>Küche im Haus</span></p>
</div>
<figure class="t-bild">
<svg viewBox="0 0 1200 800" role="img" aria-label="Die Scheune am Abend: eine lange gedeckte Tafel unter Holzbalken, am Ende das offene Tor mit tief stehender Sonne">
<defs>
<linearGradient id="ta-tor" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f3c27a"/><stop offset=".7" stop-color="#e98d5c"/><stop offset="1" stop-color="#b8664a"/></linearGradient>
<linearGradient id="ta-boden" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#5a3e2a"/><stop offset="1" stop-color="#2c1e15"/></linearGradient>
<radialGradient id="ta-birne"><stop offset="0" stop-color="#ffe2a8"/><stop offset=".35" stop-color="#ffc56e" stop-opacity=".55"/><stop offset="1" stop-color="#ffb055" stop-opacity="0"/></radialGradient>
</defs>
<rect width="1200" height="800" fill="#3a2a1f"/>
<path d="M0 0 L600 210 L1200 0 Z" fill="#2a1d15"/>
<g stroke="#22170f" stroke-width="22" fill="none"><path d="M0 120 L600 300 L1200 120"/><path d="M0 280 L600 360 L1200 280"/></g>
<g fill="#4a3627"><rect x="140" y="0" width="26" height="800"/><rect x="1034" y="0" width="26" height="800"/><rect x="380" y="250" width="16" height="400"/><rect x="804" y="250" width="16" height="400"/></g>
<rect x="480" y="300" width="240" height="250" fill="url(#ta-tor)"/>
<path d="M480 300 L600 262 L720 300 Z" fill="#2a1d15"/>
<ellipse cx="610" cy="470" rx="46" ry="46" fill="#ffe4b0" opacity=".9"/>
<rect x="480" y="470" width="240" height="80" fill="#7c8a5a" opacity=".55"/>
<path d="M480 550 L720 550 L1000 800 L200 800 Z" fill="#f0a865" opacity=".22"/>
<rect y="550" width="1200" height="250" fill="url(#ta-boden)"/>
<path d="M560 540 L640 540 L960 800 L240 800 Z" fill="#efe6d6"/>
<path d="M560 540 L640 540 L652 552 L548 552 Z" fill="#d9ccb5"/>
<g data-tl-tafel></g>
<g data-tl-birnen></g>
</svg>
<figcaption><span>Scheune, Tafel für 140, 18:30 Uhr</span><span>Foto: Gut Wendhagen</span></figcaption>
</figure>
</article>
<article class="t-stimmung" data-licht="nacht" data-name="Nacht · 22:00" aria-labelledby="t-s3">
<div class="t-text">
<p class="t-uhr t-imbue" aria-hidden="true">22:00</p>
<h3 class="t-imbue" id="t-s3"><span class="nur-sr">22:00 Uhr: </span>Tanz in der Tenne</h3>
<p>Die Tenne liegt weit genug vom Dorf, Musik ist bis 3 Uhr erlaubt. Wer bleiben möchte, schläft in einem der neun Zimmer im Gutshaus.</p>
<p class="t-fakten t-leise"><span>Musik bis 3 Uhr</span><span>9 Zimmer, 22 Betten</span><span>Shuttle nach Schwerin</span></p>
</div>
<figure class="t-bild">
<svg viewBox="0 0 1200 800" role="img" aria-label="Die Tenne in der Nacht: ein dunkler Scheunengiebel mit weit offenem, warm erleuchtetem Tor, davor fällt das Licht auf den Hof">
<defs>
<linearGradient id="tn-himmel" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#0d1020"/><stop offset="1" stop-color="#232a40"/></linearGradient>
<linearGradient id="tn-tor" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#ffd18a"/><stop offset="1" stop-color="#d9823a"/></linearGradient>
</defs>
<rect width="1200" height="800" fill="url(#tn-himmel)"/>
<g data-tl-sterne fill="#e9e6f2"></g>
<path d="M1012 120 a48 48 0 1 0 34 82 a40 40 0 1 1 -34 -82 Z" fill="#efe9d8"/>
<g fill="#0b0d14"><circle cx="150" cy="330" r="150"/><circle cx="60" cy="420" r="120"/><circle cx="270" cy="410" r="110"/><rect x="140" y="440" width="26" height="200"/></g>
<path d="M330 640 L330 380 L640 170 L950 380 L950 640 Z" fill="#141720"/>
<path d="M310 392 L640 156 L970 392" fill="none" stroke="#0a0b10" stroke-width="18"/>
<rect x="520" y="420" width="240" height="220" fill="url(#tn-tor)"/>
<path d="M520 420 L640 384 L760 420 Z" fill="#141720"/>
<rect x="452" y="420" width="64" height="220" fill="#2a2620" transform="skewY(-8)" transform-origin="516 420"/>
<rect x="764" y="420" width="64" height="220" fill="#2a2620" transform="skewY(8)" transform-origin="764 420"/>
<g fill="#ffc977"><rect x="400" y="470" width="44" height="54"/><rect x="836" y="470" width="44" height="54"/><circle cx="640" cy="300" r="26"/></g>
<rect y="640" width="1200" height="160" fill="#0d0f16"/>
<path d="M520 640 L760 640 L980 800 L300 800 Z" fill="#f2b866" opacity=".3"/>
<g fill="#2b1d12"><path d="M590 640 l6 -60 h12 l6 60 Z"/><circle cx="602" cy="568" r="10"/><path d="M660 640 l5 -54 h11 l5 54 Z"/><circle cx="671" cy="574" r="9"/></g>
</svg>
<figcaption><span>Tenne, 22:00 Uhr</span><span>Foto: Gut Wendhagen</span></figcaption>
</figure>
</article>
<section class="t-raeume" id="t-raeume" data-licht="kalk" data-name="Räume und Plätze" aria-labelledby="t-r">
<div>
<p class="t-label">Für 40 bis 180 Gäste</p>
<h2 class="t-imbue" id="t-r">Räume und Plätze</h2>
</div>
<table>
<caption class="nur-sr">Kapazitäten je Raum und Bestuhlung</caption>
<thead><tr><th scope="col">Raum</th><th scope="col">Tafel</th><th scope="col">Empfang</th><th scope="col" class="t-flaeche">Fläche</th></tr></thead>
<tbody>
<tr><th scope="row">Scheune</th><td>140</td><td>180</td><td class="t-flaeche">310 m²</td></tr>
<tr><th scope="row">Gutssaal</th><td>60</td><td>80</td><td class="t-flaeche">120 m²</td></tr>
<tr><th scope="row">Lindenhof</th><td>120 Trauung</td><td>220</td><td class="t-flaeche">900 m²</td></tr>
</tbody>
</table>
<p class="t-zeile-unten"><a class="t-knopf" href="#t-start">Wunschdatum prüfen</a><a class="t-neben" href="#t-raeume">Pakete ab 89 € pro Person</a></p>
</section>
</main>
<footer class="t-fuss"><b>Gut Wendhagen</b><span>Dorfstraße 4 · 19205 Wendhagen bei Gadebusch · 038886 412 70</span><span>Impressum · Datenschutz</span></footer>
<p class="t-stand" aria-live="polite"><span class="t-leise" data-t-nr>Ein Tag bei uns</span><br><b data-t-titel>Willkommen</b></p>
</div>
<p class="ecke stand mono" aria-live="polite" data-welt="basis"><span class="leise" data-nr>01 / 05</span><br><b data-titel>Auswahl 2020–2026</b></p>
<div class="ecke steuer mono">
<div class="zeile zeile-variante" role="group" aria-label="Variante der Demo"><span>Variante</span>
<button type="button" data-variante="basis" aria-pressed="true">Foto & Film</button>
<button type="button" data-variante="tageslauf" aria-pressed="false">Tageslauf</button>
</div>
<div class="zeile" role="group" aria-label="Übergang" data-welt="basis"><span>Übergang</span>
<button type="button" data-modus="schwelle" aria-pressed="true">Schwelle</button>
<button type="button" data-modus="gekoppelt" aria-pressed="false">Gekoppelt</button>
</div>
<div class="zeile" role="group" aria-label="Farbtöne" data-welt="basis"><span>Töne</span>
<button type="button" data-toene="neutral" aria-pressed="true">Neutral</button>
<button type="button" data-toene="bild" aria-pressed="false"><span class="lang">Aus dem </span>Bild</button>
</div>
<p class="werte" data-welt="tageslauf" hidden><span data-tw-stimmung>Tag</span> · <b data-tw-grund>#f5f0e6</b> / <b data-tw-text>#1d1a17</b> · <b data-tw-kontrast>15,3 : 1</b><br><span data-tw-art>725 ms · cubic-bezier(0.625, 0.05, 0, 1)</span><span class="lang"> · Kipppunkt ohne Band unter 4,5 : 1</span></p>
<p class="werte" data-welt="basis"><b data-w-grund>#f4f4f2</b> / <b data-w-text>#141414</b> · <b data-w-kontrast>16,7 : 1</b><br><span data-w-art>600 ms · cubic-bezier(0.33, 1, 0.68, 1)</span><span class="lang" data-w-zusatz> · Schwelle 50 %</span></p>
</div>
<script>
const DEMOS = {}; // je Variante { neustart, vorfuehren, pause, weiter }
(() => {
const html = document.documentElement;
const reduziert = () => html.classList.contains('reduziert');
const $ = s => document.querySelector(s);
const kapitel = [...document.querySelectorAll('[data-kapitel]')];
const themeMeta = $('meta[name="theme-color"]');
/* ---------- Bildwelt erzeugen (fester Zufall, damit jedes Laden gleich aussieht) ---------- */
const zufall = (s => () => { s |= 0; s = s + 0x6D2B79F5 | 0; let t = Math.imul(s ^ s >>> 15, 1 | s); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; })(1934);
const NS = 'http://www.w3.org/2000/svg';
const el = (tag, attr, ziel) => { const n = document.createElementNS(NS, tag); for (const k in attr) n.setAttribute(k, attr[k]); ziel && ziel.appendChild(n); return n; };
// Fulda: Baumreihe am anderen Ufer, Spiegelung, Lichtschlieren im Wasser
let ufer = 'M0 446 L0 430', spiegel = 'M0 446 L0 462';
for (let x = 0; x <= 1200; x += 8) {
const h = 10 + 6 * Math.sin(x / 61) + 5 * Math.sin(x / 23 + 1) + zufall() * 6 + (x > 860 && x < 1010 ? 14 * Math.sin((x - 860) / 150 * Math.PI) : 0);
ufer += ` L${x} ${(440 - h).toFixed(1)}`; spiegel += ` L${x} ${(446 + h * .8).toFixed(1)}`;
}
$('[data-ufer]').setAttribute('d', ufer + ' L1200 446Z');
$('[data-spiegel]').setAttribute('d', spiegel + ' L1200 446Z');
const schl = $('[data-schlieren]');
for (let i = 0; i < 46; i++) {
const y = 470 + Math.pow(zufall(), 1.4) * 320, w = 60 + zufall() * 380;
el('rect', { x: (zufall() * 1300 - 100).toFixed(0), y: y.toFixed(0), width: w.toFixed(0), height: (1 + (y - 470) / 160).toFixed(1), opacity: (.08 + zufall() * .22).toFixed(2) }, schl);
}
// Nacht: Fensterraster, Bokeh im Vordergrund
const fen = $('[data-fenster]');
for (let r = 0; r < 3; r++) for (let c = 0; c < 7; c++) el('rect', { x: 40 + c * 98, y: 70 + r * 92, width: 46, height: 62 }, fen);
const bok = $('[data-bokeh]');
[[70, 70, 46, .5], [180, 30, 30, .34], [1040, 40, 38, .4], [1190, 150, 54, .3], [560, 470, 40, .22], [640, 20, 22, .3], [20, 300, 34, .26]].forEach(([cx, cy, r, o], i) =>
el('circle', { cx, cy, r, fill: i % 3 === 2 ? '#ffe7c4' : '#ff9d46', opacity: o }, bok));
// Wald: vier Tiefenebenen, Nebel dazwischen
const baeume = $('[data-baeume]');
[[24, 3, 9, '#8f9888', .7], [13, 7, 16, '#626c58', .85], [6, 14, 28, '#3a4232', .95], [2, 38, 64, '#171b13', 1]].forEach(([n, wmin, wmax, farbe, o], ebene) => {
const g = el('g', { fill: farbe, opacity: o }, baeume);
for (let i = 0; i < n; i++) {
const x = ebene === 3 ? [70, 640][i] + zufall() * 40 : (i + .1 + zufall() * .8) / n * 860 - 30;
const w = wmin + zufall() * (wmax - wmin), schief = (zufall() - .5) * w * .7;
el('polygon', { points: `${(x + schief).toFixed(1)},-10 ${(x + schief + w * .74).toFixed(1)},-10 ${(x + w).toFixed(1)},1010 ${x.toFixed(1)},1010` }, g);
}
if (ebene < 3) el('rect', { width: 800, height: 1000, fill: 'url(#w-dunst)', opacity: [1, .7, .35][ebene] }, baeume);
});
/* ---------- Farbe: Hilfsfunktionen ---------- */
const hex2rgb = h => { const n = parseInt(h.slice(1), 16); return [n >> 16, n >> 8 & 255, n & 255]; };
const rgb2hex = c => '#' + c.map(v => Math.round(Math.min(255, Math.max(0, v))).toString(16).padStart(2, '0')).join('');
const lin = v => (v /= 255) <= .04045 ? v / 12.92 : ((v + .055) / 1.055) ** 2.4;
const delin = v => 255 * (v <= .0031308 ? v * 12.92 : 1.055 * v ** (1 / 2.4) - .055);
const toOklab = h => { const [r, g, b] = hex2rgb(h).map(lin);
const l = Math.cbrt(.4122214708 * r + .5363325363 * g + .0514459929 * b), m = Math.cbrt(.2119034982 * r + .6806995451 * g + .1073969566 * b), s = Math.cbrt(.0883024619 * r + .2817188376 * g + .6299787005 * b);
return [.2104542553 * l + .793617785 * m - .0040720468 * s, 1.9779984951 * l - 2.428592205 * m + .4505937099 * s, .0259040371 * l + .7827717662 * m - .808675766 * s]; };
const fromOklab = ([L, A, B]) => { const l = (L + .3963377774 * A + .2158037573 * B) ** 3, m = (L - .1055613458 * A - .0638541728 * B) ** 3, s = (L - .0894841775 * A - 1.291485548 * B) ** 3;
return rgb2hex([4.0767416621 * l - 3.3077115913 * m + .2309699292 * s, -1.2684380046 * l + 2.6097574011 * m - .3413193965 * s, -.0041960863 * l - .7034186147 * m + 1.707614701 * s].map(delin)); };
const mische = (a, b, p) => { const x = toOklab(a), y = toOklab(b); return fromOklab(x.map((v, i) => v + (y[i] - v) * p)); };
const leucht = h => { const [r, g, b] = hex2rgb(h).map(lin); return .2126 * r + .7152 * g + .0722 * b; };
const kontrast = (a, b) => { const x = leucht(a), y = leucht(b); return (Math.max(x, y) + .05) / (Math.min(x, y) + .05); };
/* ---------- Zustand ---------- */
let modus = 'schwelle', toene = 'neutral', aktiv = 0;
const paar = i => kapitel[i].dataset[toene].split(' ');
let jetzt = { grund: '#f4f4f2', text: '#141414' };
function setzeFarbe(grund, text) {
jetzt = { grund, text };
html.style.setProperty('--grund', grund);
html.style.setProperty('--text', text);
themeMeta.setAttribute('content', grund);
$('[data-w-grund]').textContent = grund;
$('[data-w-text]').textContent = text;
const k = kontrast(grund, text);
$('[data-w-kontrast]').textContent = k.toFixed(1).replace('.', ',') + ' : 1' + (k < 4.5 ? ' (unter AA)' : '');
}
function zeigeKapitel(i) {
if (i === aktiv && $('[data-titel]').textContent === kapitel[i].dataset.kapitel) return;
aktiv = i;
$('[data-nr]').textContent = String(i + 1).padStart(2, '0') + ' / ' + String(kapitel.length).padStart(2, '0');
$('[data-titel]').textContent = kapitel[i].dataset.kapitel;
}
function beschrifte() {
$('[data-w-art]').textContent = reduziert() ? '200 ms · linear (reduziert)'
: modus === 'schwelle' ? '600 ms · cubic-bezier(0.33, 1, 0.68, 1)' : 'gekoppelt · OKLab · smoothstep · Text 150 ms';
if (modus === 'schwelle') $('[data-w-zusatz]').textContent = ' · Schwelle 50 %';
}
function toenePunkte() {
document.querySelectorAll('[data-ton]').forEach(t => {
const k = kapitel.find(k => k.id === t.dataset.ton);
t.style.setProperty('--ton', k.dataset[toene].split(' ')[0]);
});
}
/* A) Schwelle: Kapitel wird aktiv, sobald es die Viewport-Mitte überquert */
const io = new IntersectionObserver(eintraege => {
eintraege.forEach(e => { if (e.isIntersecting) { const i = kapitel.indexOf(e.target); zeigeKapitel(i); if (modus === 'schwelle') setzeFarbe(...paar(i)); } });
}, { rootMargin: '-50% 0px -50% 0px' });
kapitel.forEach(k => io.observe(k));
/* B) Gekoppelt: Fläche mischt zwischen zwei Kapiteln, während die Grenze von 70 % auf 30 % der Höhe wandert */
let gemeldet = false;
function koppeln() {
gemeldet = false;
if (modus !== 'gekoppelt') return;
const vh = innerHeight, oben = vh * .7, unten = vh * .3;
let i = 0, p = 0;
for (let k = 1; k < kapitel.length; k++) {
const top = kapitel[k].getBoundingClientRect().top;
if (top < oben) { i = k; p = Math.min(1, (oben - top) / (oben - unten)); }
}
const [g0, t0] = paar(Math.max(0, i - 1)), [g1, t1] = paar(i);
const s = p * p * (3 - 2 * p); // smoothstep: Zwischentöne kurz halten
const grund = i === 0 ? g1 : mische(g0, g1, s);
// Text nicht mitmischen (Zwischenstufen hätten keinen Kontrast), sondern den besseren der beiden wählen
const text = i === 0 ? t1 : (kontrast(grund, t0) >= kontrast(grund, t1) ? t0 : t1);
setzeFarbe(grund, text);
$('[data-w-zusatz]').textContent = ' · Mischung ' + Math.round(i === 0 ? 0 : p * 100) + ' %';
}
addEventListener('scroll', () => { if (!gemeldet && modus === 'gekoppelt') { gemeldet = true; requestAnimationFrame(koppeln); } }, { passive: true });
addEventListener('resize', () => modus === 'gekoppelt' && koppeln());
function aktuellesKapitel() {
const mitte = innerHeight / 2;
let i = 0; kapitel.forEach((k, n) => { if (k.getBoundingClientRect().top <= mitte) i = n; }); return i;
}
function setzeModus(neu) {
modus = neu;
html.classList.toggle('gekoppelt', modus === 'gekoppelt');
document.querySelectorAll('[data-modus]').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.modus === modus)));
beschrifte();
if (modus === 'gekoppelt') koppeln(); else setzeFarbe(...paar(aktuellesKapitel()));
}
function setzeToene(neu) {
toene = neu;
document.querySelectorAll('[data-toene]').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.toene === toene)));
toenePunkte();
if (modus === 'gekoppelt') koppeln(); else setzeFarbe(...paar(aktuellesKapitel()));
}
document.querySelectorAll('[data-modus]').forEach(b => b.addEventListener('click', () => setzeModus(b.dataset.modus)));
document.querySelectorAll('[data-toene]').forEach(b => b.addEventListener('click', () => setzeToene(b.dataset.toene)));
new MutationObserver(beschrifte).observe(html, { attributes: true, attributeFilter: ['class'] });
toenePunkte(); beschrifte(); setzeFarbe(...paar(0));
// Ankerlinks: weich scrollen, außer bei reduzierter Bewegung
document.querySelectorAll('a[href^="#"]').forEach(a => a.addEventListener('click', e => {
const z = document.querySelector(a.getAttribute('href')); if (!z) return;
e.preventDefault(); pause(); z.scrollIntoView({ behavior: reduziert() ? 'auto' : 'smooth' });
}));
/* ---------- Vorführung: von Kapitel zu Kapitel, verweilen, weiter; Töne wechseln jede Runde ---------- */
let uhr = null, warte = null, runde = 0;
const VERWEIL = 1300, FAHRT = 1100;
const sanft = t => t < .5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2;
function fahre(von, nach, fertig) {
const t0 = performance.now();
const schritt = t => {
const q = Math.min(1, (t - t0) / FAHRT);
scrollTo(0, von + (nach - von) * sanft(q));
if (q < 1) uhr = requestAnimationFrame(schritt); else fertig();
};
uhr = requestAnimationFrame(schritt);
}
function ziel(i) { return Math.max(0, kapitel[i].offsetTop + (kapitel[i].offsetHeight - innerHeight) / 2); }
function station(i) {
warte = setTimeout(() => {
const n = (i + 1) % kapitel.length;
if (n === 0) { runde++; setzeToene(runde % 2 ? 'bild' : 'neutral'); }
if (reduziert() || n === 0) { scrollTo(0, ziel(n)); station(n); }
else fahre(scrollY, ziel(n), () => station(n));
}, VERWEIL);
}
function pause() { cancelAnimationFrame(uhr); clearTimeout(warte); uhr = warte = null; }
function neustart() { pause(); runde = 0; scrollTo(0, 0); setzeModus('schwelle'); setzeToene('neutral'); }
function vorfuehren() { neustart(); station(0); }
function weiter() { if (!uhr && !warte) station(aktuellesKapitel()); }
DEMOS.basis = { neustart, vorfuehren, pause, weiter };
})();
/* ===================== Variante „tageslauf“: drei Lichtstimmungen im Kapitel „Ein Tag bei uns“ ===================== */
(() => {
const html = document.documentElement;
const reduziert = () => html.classList.contains('reduziert');
const $ = s => document.querySelector(s);
const welt = $('[data-welt="tageslauf"]');
const stationen = [...welt.querySelectorAll('[data-licht]')];
const themeMeta = $('meta[name="theme-color"]');
const NS = 'http://www.w3.org/2000/svg';
const el = (tag, attr, ziel) => { const n = document.createElementNS(NS, tag); for (const k in attr) n.setAttribute(k, attr[k]); ziel && ziel.appendChild(n); return n; };
/* Bildwelt: Stuhlreihen, Tafel, Glühbirnen, Sterne (fester Zufall) */
const zufall = (s => () => (s = (s * 16807) % 2147483647) / 2147483647)(2027);
const stuehle = welt.querySelector('[data-tl-stuehle]');
for (let r = 0; r < 5; r++) {
const y = 560 + r * 44, h = 18 + r * 4, w = 14 + r * 3, abst = 22 + r * 5;
for (const seite of [-1, 1]) for (let c = 0; c < 4; c++) {
const x = 710 + seite * (52 + r * 14 + c * abst) - (seite < 0 ? w : 0);
el('rect', { x: x.toFixed(1), y, width: w, height: h, rx: 2, fill: '#f7f4ee' }, stuehle);
el('rect', { x: x.toFixed(1), y: y + h, width: w, height: 3 + r, fill: '#5f733e', opacity: .5 }, stuehle);
}
}
const tafel = welt.querySelector('[data-tl-tafel]');
for (let i = 0; i < 9; i++) {
const t = i / 8, y = 556 + Math.pow(t, 1.6) * 230, halb = 46 + t * 330, g = 6 + t * 16;
for (const seite of [-1, 1]) {
el('ellipse', { cx: (600 + seite * (halb - g * 1.6)).toFixed(1), cy: y.toFixed(1), rx: (g * 1.3).toFixed(1), ry: (g * .45).toFixed(1), fill: '#fbf8f1', stroke: '#cdbfa8', 'stroke-width': 1 }, tafel);
el('rect', { x: (600 + seite * (halb + g * .6) - g * .7).toFixed(1), y: (y - g * 2.2).toFixed(1), width: (g * 1.4).toFixed(1), height: (g * 2.6).toFixed(1), rx: 2, fill: '#24180f' }, tafel);
}
}
const birnen = welt.querySelector('[data-tl-birnen]');
for (let k = 0; k < 2; k++) for (let i = 0; i <= 10; i++) {
const t = i / 10, x = 170 + t * 860, y = 210 + k * 70 + Math.sin(t * Math.PI) * (60 - k * 10);
el('circle', { cx: x.toFixed(1), cy: y.toFixed(1), r: 22, fill: 'url(#ta-birne)' }, birnen);
el('circle', { cx: x.toFixed(1), cy: y.toFixed(1), r: 4, fill: '#fff1cf' }, birnen);
}
const sterne = welt.querySelector('[data-tl-sterne]');
for (let i = 0; i < 38; i++) el('circle', { cx: (zufall() * 1200).toFixed(0), cy: (zufall() * 300).toFixed(0), r: (zufall() * 1.4 + .5).toFixed(1), opacity: (.35 + zufall() * .55).toFixed(2) }, sterne);
/* Lichtstimmungen (Rezept eventlocation-hochzeit). Räume, Preise und Anfrage stehen auf Kalk = Tag */
const LICHT = {
tag: { name: 'Tag', grund: '#f5f0e6', text: '#1d1a17', leise: '#5c574e', linie: '#d8cfc0', messing: '#8c6a3c' },
abend: { name: 'Abend', grund: '#e9dcc8', text: '#1d1a17', leise: '#524c43', linie: '#cdbea6', messing: '#7a5a30' },
nacht: { name: 'Nacht', grund: '#15171f', text: '#f2ece2', leise: '#b3aa9c', linie: '#343745', messing: '#c9a66b' },
};
LICHT.kalk = { ...LICHT.tag, name: 'Kalk' };
const DAUER = 725, DAUER_RED = 200;
/* Farbrechnung: OKLab mischen, WCAG-Kontrast */
const hex2rgb = h => { const n = parseInt(h.slice(1), 16); return [n >> 16, n >> 8 & 255, n & 255]; };
const rgb2hex = c => '#' + c.map(v => Math.round(Math.min(255, Math.max(0, v))).toString(16).padStart(2, '0')).join('');
const lin = v => (v /= 255) <= .04045 ? v / 12.92 : ((v + .055) / 1.055) ** 2.4;
const delin = v => 255 * (v <= .0031308 ? v * 12.92 : 1.055 * v ** (1 / 2.4) - .055);
const toOklab = h => { const [r, g, b] = hex2rgb(h).map(lin);
const l = Math.cbrt(.4122214708 * r + .5363325363 * g + .0514459929 * b), m = Math.cbrt(.2119034982 * r + .6806995451 * g + .1073969566 * b), s = Math.cbrt(.0883024619 * r + .2817188376 * g + .6299787005 * b);
return [.2104542553 * l + .793617785 * m - .0040720468 * s, 1.9779984951 * l - 2.428592205 * m + .4505937099 * s, .0259040371 * l + .7827717662 * m - .808675766 * s]; };
const fromOklab = ([L, A, B]) => { const l = (L + .3963377774 * A + .2158037573 * B) ** 3, m = (L - .1055613458 * A - .0638541728 * B) ** 3, s = (L - .0894841775 * A - 1.291485548 * B) ** 3;
return rgb2hex([4.0767416621 * l - 3.3077115913 * m + .2309699292 * s, -1.2684380046 * l + 2.6097574011 * m - .3413193965 * s, -.0041960863 * l - .7034186147 * m + 1.707614701 * s].map(delin)); };
const mische = (a, b, p) => { const x = toOklab(a), y = toOklab(b); return fromOklab(x.map((v, i) => v + (y[i] - v) * p)); };
const leucht = h => { const [r, g, b] = hex2rgb(h).map(lin); return .2126 * r + .7152 * g + .0722 * b; };
const kontrast = (a, b) => { const x = leucht(a), y = leucht(b); return (Math.max(x, y) + .05) / (Math.min(x, y) + .05); };
// Kurve token-studio-signatur als Bézier (x per Bisektion)
const bezier = (x1, y1, x2, y2) => { const f = (t, a, b) => 3 * a * (1 - t) ** 2 * t + 3 * b * (1 - t) * t * t + t ** 3;
return x => { let lo = 0, hi = 1; for (let i = 0; i < 24; i++) { const m = (lo + hi) / 2; if (f(m, x1, x2) < x) lo = m; else hi = m; } return f((lo + hi) / 2, y1, y2); }; };
const kurve = bezier(0.625, 0.05, 0, 1);
/* Zustand und Tween: Fläche mischt, Text kippt am Kipppunkt; das Band unter 4,5 : 1 wird übersprungen */
let ist = { ...LICHT.kalk }, ziel = 'kalk', tween = null, band = null, von = null;
function setze(grund, satz) {
ist = { ...satz, grund };
const st = html.style;
st.setProperty('--grund', grund); st.setProperty('--text', satz.text);
st.setProperty('--t-leise', satz.leise); st.setProperty('--t-linie', satz.linie); st.setProperty('--t-messing', satz.messing);
themeMeta.setAttribute('content', grund);
$('[data-tw-grund]').textContent = grund; $('[data-tw-text]').textContent = satz.text;
$('[data-tw-kontrast]').textContent = kontrast(grund, satz.text).toFixed(1).replace('.', ',') + ' : 1';
}
function bandVon(a, b) { // Mischbereich, in dem keiner der beiden Texttöne 4,5 : 1 schafft
let lo = null, hi = null;
for (let i = 0; i <= 200; i++) { const p = i / 200, g = mische(a.grund, b.grund, p); if (Math.max(kontrast(g, a.text), kontrast(g, b.text)) < 4.5) { if (lo === null) lo = p; hi = p; } }
return lo === null ? null : [Math.max(0, lo - .005), Math.min(1, hi + .005)];
}
function geheZu(name, sofort) {
if (name === ziel && !sofort) return;
ziel = name; cancelAnimationFrame(tween);
const nach = LICHT[name];
$('[data-tw-stimmung]').textContent = nach.name;
if (sofort) { setze(nach.grund, nach); return; }
von = { ...ist }; band = bandVon(von, nach);
const dauer = reduziert() ? DAUER_RED : DAUER, t0 = performance.now();
const schritt = t => {
const q = Math.min(1, (t - t0) / dauer);
let p = reduziert() ? q : kurve(q);
if (band && p > band[0] && p < band[1]) p = p < (band[0] + band[1]) / 2 ? band[0] : band[1]; // Kipppunkt
const grund = q >= 1 ? nach.grund : mische(von.grund, nach.grund, p);
const satz = kontrast(grund, von.text) >= kontrast(grund, nach.text) ? von : nach;
setze(grund, satz);
if (q < 1) tween = requestAnimationFrame(schritt);
};
tween = requestAnimationFrame(schritt);
}
function beschrifte() {
$('[data-tw-art]').textContent = reduziert() ? '200 ms · linear (reduziert)' : '725 ms · cubic-bezier(0.625, 0.05, 0, 1)';
}
new MutationObserver(beschrifte).observe(html, { attributes: true, attributeFilter: ['class'] });
/* Schwelle: Station wird aktiv, wenn sie die Viewport-Mitte überquert */
let aktiv = 0;
function zeigeStation(i) {
aktiv = i;
const s = stationen[i];
$('[data-t-titel]').textContent = s.dataset.name;
$('[data-t-nr]').textContent = s.dataset.licht === 'kalk' ? 'Gut Wendhagen' : 'Ein Tag bei uns · ' + (['tag', 'abend', 'nacht'].indexOf(s.dataset.licht) + 1) + ' / 3';
geheZu(s.dataset.licht);
}
const io = new IntersectionObserver(es => es.forEach(e => { if (e.isIntersecting) zeigeStation(stationen.indexOf(e.target)); }), { rootMargin: '-50% 0px -50% 0px' });
stationen.forEach(s => io.observe(s));
welt.querySelector('[data-t-form]').addEventListener('submit', e => { e.preventDefault(); pause(); $('#t-raeume').scrollIntoView({ behavior: reduziert() ? 'auto' : 'smooth' }); });
welt.querySelectorAll('a[href^="#"]').forEach(a => a.addEventListener('click', () => pause()));
/* Vorführung: von Station zu Station, verweilen, weiter */
let uhr = null, warte = null;
const VERWEIL = 1300, FAHRT = 1100;
const sanft = t => t < .5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2;
const zielY = i => Math.max(0, stationen[i].offsetTop + (stationen[i].offsetHeight - innerHeight) / 2);
function fahre(a, b, fertig) {
const t0 = performance.now();
const schritt = t => { const q = Math.min(1, (t - t0) / FAHRT); scrollTo(0, a + (b - a) * sanft(q)); if (q < 1) uhr = requestAnimationFrame(schritt); else fertig(); };
uhr = requestAnimationFrame(schritt);
}
function station(i) {
warte = setTimeout(() => {
const n = (i + 1) % stationen.length;
if (reduziert() || n === 0) { scrollTo(0, n === 0 ? 0 : zielY(n)); station(n); }
else fahre(scrollY, zielY(n), () => station(n));
}, VERWEIL);
}
function aktuelle() { const m = innerHeight / 2; let i = 0; stationen.forEach((s, n) => { if (s.getBoundingClientRect().top <= m) i = n; }); return i; }
function pause() { cancelAnimationFrame(uhr); clearTimeout(warte); uhr = warte = null; }
function neustart() { pause(); scrollTo(0, 0); zeigeStation(0); geheZu('kalk', true); beschrifte(); }
function vorfuehren() { neustart(); station(0); }
function weiter() { if (!uhr && !warte) station(aktuelle()); }
beschrifte();
DEMOS.tageslauf = { neustart, vorfuehren, pause, weiter, an: () => { html.classList.add('tl-an'); geheZu(LICHT[stationen[aktuelle()].dataset.licht] ? stationen[aktuelle()].dataset.licht : 'kalk', true); }, aus: () => { cancelAnimationFrame(tween); html.classList.remove('tl-an'); } };
})();
/* ===================== Variante wählen (Regel 13: ?v=, Umschalter schreibt die URL) ===================== */
const VARIANTEN = ['basis', 'tageslauf'];
let variante = (() => { const v = new URLSearchParams(location.search).get('v'); return VARIANTEN.includes(v) ? v : VARIANTEN[0]; })();
function zeigeVariante(v, schreiben) {
if (v !== variante) DEMOS[variante].pause();
variante = v;
document.querySelectorAll('[data-welt]').forEach(w => { w.hidden = w.dataset.welt !== v; });
document.querySelectorAll('[data-variante]').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.variante === v)));
if (v === 'tageslauf') DEMOS.tageslauf.an(); else DEMOS.tageslauf.aus();
if (schreiben) {
const u = new URL(location.href);
u.searchParams.set('v', v); // andere Parameter (reduziert, vorschau) bleiben stehen
history.replaceState(history.state, '', u);
DEMOS[v].neustart();
}
}
document.querySelectorAll('[data-variante]').forEach(b => b.addEventListener('click', () => { if (b.dataset.variante !== variante) zeigeVariante(b.dataset.variante, true); }));
zeigeVariante(variante, false);
if (variante !== 'basis') DEMOS[variante].neustart();
</script>
</body>
</html>