Frosted Glass mit erweitertem Backdrop
Sticky Header aus getöntem Glas, dessen Backdrop doppelt so hoch ist: Farbe von unten fließt schon vor der Kante ein, eine 4-px-Lichtkante gibt Tiefe ohne Schatten.
Glasfläche für Sticky Header und schwebende Navigation, deren Unschärfe auch Inhalt knapp unterhalb der Kante einbezieht. Ein Kindelement trägt den backdrop-filter und ist doppelt so hoch wie der Kopf. Eine Maske schneidet die untere Hälfte weg. Farbe und Kontur von unten kommen so schon an, bevor sie die Kante erreichen, wie bei echtem Glas. Eine 3 bis 4 px hohe zweite Fläche unter dem Kopf mit kleinerem Blur und mehr Helligkeit bildet die Lichtkante.
Der Blur sieht dicht und körperhaft aus, nicht wie ein milchiger Filter. Die Lichtkante gibt Tiefe ohne Schlagschatten. Beim Scrollen bleibt der Inhalt unter der Navigation ahnbar, die Navigation wirkt leicht.
Wann einsetzen
Sticky Header, Bottom-Bars, Dock-Navigation, Overlays über Bild- oder Farbflächen, Medien-Controls über Video.
Wann nicht
Über ruhigem, einfarbigem Grund (Effekt unsichtbar, nur Kosten). Für große Textflächen. Bei mehreren gestapelten Glasebenen (Performance, Matsch).
Varianten
- Klar getönt mit saturate(150–180 %) gegen Grauschleier
- Dunkles Rauchglas für Dark Mode, stärkerer Blur (bis 30px)
- Pill-Navigation, die beim Scrollen schrumpft (Anleihe an die Tabbar von iOS 26)
- Glas mit Grain-Overlay gegen Banding
Umsetzung
Header: position: sticky, halbtransparenter Hintergrund für die Lesbarkeit, kein Filter direkt darauf. Kind .backdrop: position: absolute; inset: 0; height: 200%; backdrop-filter: blur(16px); mask-image: linear-gradient(to bottom, black 0% 50%, transparent 50% 100%); pointer-events: none. Gegen Flackern am Viewport-Rand bekommt .backdrop einen Verlauf von der Seitenfarbe (oben deckend) bis transparent bei 50 %. Kind .backdrop-edge: inset: 0; height: 100%; transform: translateY(100%); kleinerer Blur mit brightness/saturate, maskiert auf die Kantendicke. Header-Inhalt mit position: relative und z-index darüber. Kein Vorfahre darf filter, opacity < 1, mask oder clip-path tragen, sonst sieht der Backdrop nichts. Gate: @supports (backdrop-filter: blur(16px)) or (-webkit-backdrop-filter: blur(16px)); Fallback fast deckend, z. B. hsl(0 0% 100% / .95). Runde Ecken: clip-path: inset(0 0 50% 0 round …) oder eine SVG-Maske mit rect rx/ry statt der Verlaufsmaske. Fallstricke: overflow: hidden statt Maske klappt erst ab Chrome 142. Firefox hat einen Bug bei position: sticky mit Vorfahre overflow plus border-radius. overscroll-behavior: none verhindert Flackern in Firefox.
Robust umsetzen
Telefon. Funktioniert. Auf schwachen Android-Geräten Blur auf 8 bis 10px senken und nur ein Glaselement gleichzeitig zeigen.
Ohne Bewegung. Der Kern hat keine Bewegung. Eine schrumpfende Navigation bleibt bei reduce statisch.
Barrierefrei. Kontrast Text zu Glas gegen den ungünstigsten Hintergrund prüfen (4.5:1). Bei prefers-reduced-transparency: reduce deckenden Hintergrund zeigen. Der Fokusring muss auf Glas sichtbar bleiben.
Code
<!DOCTYPE html>
<!--
Frosted Glass mit erweitertem Backdrop
Fundus · Glas & Material · https://cecodesign.de/lab/fundus/glas-material/frosted-glass-erweitert/
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>Frosted Glass mit erweitertem Backdrop · 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: '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: 'Instrument Serif'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/instrument-serif@latest/latin-400-italic.woff2') format('woff2'); font-weight: 400; font-style: italic; font-display: swap; }
/* ---------- Seite ---------- */
:root {
--kurve: cubic-bezier(0.16, 1, 0.3, 1); /* expo.out: Pill schrumpft, 600 ms */
--ui: 150ms;
--rand: clamp(16px, 4vw, 56px);
--kopf-h: 68px;
}
body {
/* Variante „Klar“: getöntes Glas, Sättigungsboost gegen Grauschleier */
--seite: #f1eee8; --seite-rgb: 241 238 232; --tinte: #151514; --leise: rgb(21 21 20 / .66); --linie: rgb(21 21 20 / .14);
--glas-grund: rgb(var(--seite-rgb) / .5);
--glas-filter: blur(16px) saturate(180%);
--kante-filter: blur(8px) brightness(1.06) saturate(1.6);
--kante-grund: transparent;
--kante: 4px;
--band: #1f3fd8;
}
body[data-variante="rauch"] {
/* Dunkles Rauchglas */
--seite: #121311; --seite-rgb: 18 19 17; --tinte: #eeebe4; --leise: rgb(238 235 228 / .56); --linie: rgb(238 235 228 / .14);
--glas-grund: rgb(69 71 66 / .55);
--glas-filter: blur(30px) saturate(140%);
--kante-filter: blur(8px) brightness(1.6) saturate(1.4);
--kante-grund: rgb(255 255 255 / .06);
--band: #e8834e;
}
body[data-variante="pill"] {
/* Schwebende Pill, schrumpft beim Scrollen */
--glas-grund: rgb(255 255 255 / .55);
--glas-filter: blur(12px) saturate(300%);
}
* { box-sizing: border-box; margin: 0; }
html { background: #f1eee8; -webkit-font-smoothing: antialiased; overscroll-behavior: none; }
body {
background: var(--seite); color: var(--tinte);
font: 400 16px/1.55 'Instrument Sans', system-ui, sans-serif;
transition: background-color .6s var(--kurve), color .6s var(--kurve);
}
img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
.mono { font: 500 11px/1.3 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; color: var(--leise); }
em { font-family: 'Instrument Serif', Georgia, serif; font-style: italic; font-weight: 400; letter-spacing: -.01em; }
:focus-visible { outline: 2px solid var(--tinte); outline-offset: 3px; border-radius: 4px; }
/* ---------- Der Kopf: Glas mit erweitertem Backdrop ---------- */
.kopf {
position: sticky; top: 0; z-index: 10;
height: var(--kopf-h);
background: var(--glas-grund); /* Lesbarkeit separat steuern */
transition: width .6s var(--kurve), height .6s var(--kurve), top .6s var(--kurve), border-radius .6s var(--kurve), background-color .6s var(--kurve);
}
.kopf__innen {
position: relative; z-index: 2; height: 100%;
display: flex; align-items: center; gap: 28px;
max-width: 1320px; margin: 0 auto; padding: 0 var(--rand);
}
/* 1 · Backdrop doppelt so hoch wie der Kopf: Inhalt unter der Kante fließt in den Blur ein */
.backdrop {
position: absolute; inset: 0; height: 200%; z-index: 0;
-webkit-backdrop-filter: var(--glas-filter); backdrop-filter: var(--glas-filter);
/* 2 · untere Hälfte wegschneiden */
-webkit-mask-image: linear-gradient(to bottom, black 0% 50%, transparent 50% 100%);
mask-image: linear-gradient(to bottom, black 0% 50%, transparent 50% 100%);
/* 3 · gegen Flackern am Viewport-Rand: oben deckend in Seitenfarbe, bis 50 % transparent */
background: linear-gradient(to bottom, var(--seite) 0%, transparent 50%);
pointer-events: none;
}
/* 4 · Lichtkante: 4 px unter dem Kopf, kleinerer Blur plus Helligkeit */
.kante {
position: absolute; inset: 0; height: 100%; z-index: 1;
transform: translateY(100%);
background: var(--kante-grund);
-webkit-backdrop-filter: var(--kante-filter); backdrop-filter: var(--kante-filter);
-webkit-mask-image: linear-gradient(to bottom, black 0 var(--kante), transparent var(--kante));
mask-image: linear-gradient(to bottom, black 0 var(--kante), transparent var(--kante));
pointer-events: none;
}
/* Vergleich: einfacher Backdrop (nur Kopfhöhe) */
.backdrop--einfach { display: none; height: 100%; -webkit-mask-image: none; mask-image: none; }
body[data-backdrop="einfach"] .backdrop--erweitert,
body[data-backdrop="einfach"] .kante { display: none; }
body[data-backdrop="einfach"] .backdrop--einfach { display: block; }
body[data-backdrop="vergleich"] .backdrop--einfach { display: block; clip-path: inset(0 50% 0 0); }
body[data-backdrop="vergleich"] .backdrop--erweitert { clip-path: inset(0 0 0 50%); }
body[data-backdrop="vergleich"] .kante { clip-path: inset(0 0 0 50%); }
.vergleich { display: none; }
body[data-backdrop="vergleich"] .vergleich { display: block; }
.vergleich__strich { position: absolute; z-index: 3; left: 50%; top: 0; height: calc(100% + 34px); width: 1px; background: var(--tinte); opacity: .5; pointer-events: none; }
.vergleich__etikett {
position: absolute; z-index: 3; top: calc(100% + 12px); pointer-events: none;
padding: 5px 9px 4px; border-radius: 99px; background: var(--tinte); color: var(--seite);
font: 500 10px/1.2 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase;
}
.vergleich__etikett--l { right: calc(50% + 10px); }
.vergleich__etikett--r { left: calc(50% + 10px); }
/* Inhalt des Kopfs */
.marke { display: flex; align-items: center; gap: 10px; font-weight: 600; font-size: 19px; letter-spacing: -.03em; white-space: nowrap; }
.marke svg { width: 22px; height: 22px; flex: none; }
.nav { display: flex; gap: 26px; margin-left: auto; font-size: 15px; }
.nav a { position: relative; padding: 6px 0; transition: opacity var(--ui) linear; }
.nav:hover a { opacity: .55; }
.nav a:hover { opacity: 1; }
.nav a[aria-current] { font-weight: 600; }
.anfrage {
display: inline-flex; align-items: center; gap: 8px; white-space: nowrap;
padding: 9px 16px; border-radius: 99px; font-size: 14px; font-weight: 500;
background: var(--tinte); color: var(--seite);
transition: background-color .6s var(--kurve), color .6s var(--kurve);
}
.menue { display: none; font: inherit; font-size: 15px; font-weight: 500; color: inherit; background: none; border: 1px solid var(--linie); border-radius: 99px; padding: 8px 16px; margin-left: auto; min-height: 40px; }
/* Variante Pill: schwebend, runde Ecken, schrumpft beim Scrollen */
body[data-variante="pill"] .kopf {
top: 14px; margin: 14px auto 0; width: min(1080px, calc(100% - 2 * var(--rand))); height: 64px; border-radius: 99px;
box-shadow: 0 10px 30px -18px rgb(21 21 20 / .45);
}
body[data-variante="pill"] .kopf__innen { padding: 0 10px 0 22px; max-width: none; }
body[data-variante="pill"] .backdrop {
background: none; /* schwebt frei, kein Viewport-Rand */
-webkit-mask-image: none; mask-image: none;
clip-path: inset(0 0 50% 0 round 999px); /* runde Ecken: abschneiden statt maskieren */
}
body[data-variante="pill"] .backdrop--einfach { clip-path: inset(0 round 999px); }
body[data-variante="pill"][data-backdrop="vergleich"] .backdrop--einfach { clip-path: inset(0 50% 0 0 round 999px 0 0 999px); }
body[data-variante="pill"][data-backdrop="vergleich"] .backdrop--erweitert { clip-path: inset(0 0 50% 50% round 0 999px 999px 0); }
body[data-variante="pill"] .kante { display: none; }
body[data-variante="pill"] .kopf::after { /* Lichtkante als Ring über dem Glas */
content: ""; position: absolute; inset: 0; z-index: 1; border-radius: inherit; pointer-events: none;
box-shadow: inset 0 0 0 1px rgb(255 255 255 / .55), inset 0 1px 0 rgb(255 255 255 / .9);
}
body[data-variante="pill"] .kopf.klein { width: min(560px, calc(100% - 2 * var(--rand))); height: 54px; }
body[data-variante="pill"] .kopf.klein .anfrage { display: none; }
body[data-variante="pill"] .kopf.klein .nav { gap: 18px; }
body[data-variante="pill"] .vergleich__strich { height: 100%; }
body[data-variante="pill"] .nav { margin-left: auto; }
/* Ohne Unterstützung bzw. bei reduzierter Transparenz: fast deckend */
@supports not ((backdrop-filter: blur(16px)) or (-webkit-backdrop-filter: blur(16px))) {
.kopf { background: rgb(var(--seite-rgb) / .95); }
}
@media (prefers-reduced-transparency: reduce) {
.kopf { background: var(--seite); }
.backdrop, .kante { display: none !important; }
}
/* ---------- Inhalt ---------- */
.wrap { max-width: 1320px; margin: 0 auto; padding: 0 var(--rand); }
.held { padding-block: clamp(56px, 9vw, 120px) clamp(40px, 6vw, 72px); }
.held .mono { margin-bottom: 26px; }
.held h1 { font-size: clamp(42px, 7.4vw, 112px); line-height: .96; font-weight: 500; letter-spacing: -.045em; max-width: 13em; }
.held h1 em { font-size: 1.06em; }
.held__fuss { display: flex; justify-content: space-between; align-items: end; gap: 32px; margin-top: clamp(32px, 4vw, 52px); }
.held__fuss p:not(.mono) { max-width: 30em; font-size: 17px; color: var(--leise); }
.arbeiten__kopf { display: flex; justify-content: space-between; padding: 14px 0; border-top: 1px solid var(--linie); }
.raster { display: grid; grid-template-columns: repeat(12, 1fr); column-gap: clamp(16px, 2vw, 28px); row-gap: clamp(40px, 5vw, 72px); padding: 18px 0 clamp(72px, 9vw, 128px); }
.kachel:nth-child(1) { grid-column: 1 / 8; }
.kachel:nth-child(2) { grid-column: 8 / 13; margin-top: 18%; }
.kachel:nth-child(3) { grid-column: 1 / 6; }
.kachel:nth-child(4) { grid-column: 6 / 13; margin-top: -8%; }
.kachel:nth-child(5) { grid-column: 1 / 7; }
.kachel:nth-child(6) { grid-column: 7 / 13; }
.kachel img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; border-radius: 4px; }
.kachel figcaption { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding-top: 12px; }
.kachel b { font-weight: 500; font-size: 19px; letter-spacing: -.02em; }
.band { background: var(--band); color: #f6f4ef; padding: clamp(72px, 10vw, 140px) 0; transition: background-color .6s var(--kurve); }
.band .mono { color: rgb(246 244 239 / .8); margin-bottom: 24px; }
/* Rauchglas: Band in Orange trägt dunkle Schrift (Weiß hätte 2,5:1) */
body[data-variante="rauch"] .band { color: #121311; }
body[data-variante="rauch"] .band .mono { color: rgb(18 19 17 / .75); }
.band blockquote { font-size: clamp(32px, 5vw, 72px); line-height: 1.02; letter-spacing: -.04em; font-weight: 500; max-width: 15em; }
.kontakt { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; padding-block: clamp(64px, 8vw, 112px) clamp(140px, 14vw, 180px); }
.kontakt h2 { font-size: clamp(30px, 4vw, 52px); font-weight: 500; letter-spacing: -.04em; line-height: 1.02; }
.kontakt dl { display: grid; grid-template-columns: auto 1fr; gap: 10px 28px; align-content: start; font-size: 17px; }
.kontakt dt { padding-top: 4px; }
/* ---------- Bedienung: Varianten und Werte ---------- */
.pult {
position: fixed; left: 16px; bottom: 16px; z-index: 20; width: min(440px, calc(100% - 32px));
padding: 12px 14px; border-radius: 10px;
background: #faf8f4; color: #151514; border: 1px solid rgb(21 21 20 / .12);
box-shadow: 0 12px 32px -20px rgb(21 21 20 / .5);
display: grid; gap: 9px;
}
.pult .mono { color: rgb(21 21 20 / .66); }
.pult__zeile { display: grid; grid-template-columns: 74px 1fr; align-items: center; gap: 10px; }
.wahl { display: flex; gap: 2px; padding: 2px; border-radius: 7px; background: rgb(21 21 20 / .06); }
.wahl button {
flex: 1; border: 0; background: none; color: inherit; cursor: pointer; border-radius: 5px;
font: 500 10.5px/1 'Geist Mono', ui-monospace, monospace; letter-spacing: .1em; text-transform: uppercase;
padding: 8px 6px; transition: background-color var(--ui) linear, color var(--ui) linear;
}
.wahl button[aria-pressed="true"] { background: #151514; color: #faf8f4; }
.wahl button:focus-visible { outline: 2px solid #151514; outline-offset: 1px; }
.werte { font: 400 10.5px/1.5 'Geist Mono', ui-monospace, monospace; color: rgb(21 21 20 / .7); border-top: 1px solid rgb(21 21 20 / .1); padding-top: 9px; }
.werte b { font-weight: 500; color: #151514; }
/* Vorschau: nur die Bühne, Pult als schmales Etikett */
.vorschau body { overflow: hidden; }
.vorschau .pult { width: auto; padding: 8px 12px; }
.vorschau .pult__zeile { display: none; }
.vorschau .werte { border: 0; padding: 0; }
/* Touch: größere Schalter, keine Hover-Dämpfung */
@media (hover: none) {
.wahl button { padding: 11px 4px; }
.nav:hover a { opacity: 1; }
}
@media (max-width: 760px) {
:root { --kopf-h: 60px; }
.nav, .anfrage { display: none; }
.menue { display: block; }
body[data-variante="pill"] .kopf { height: 58px; }
body[data-variante="pill"] .kopf.klein { width: min(230px, calc(100% - 2 * var(--rand))); height: 50px; }
body[data-variante="pill"] .kopf__innen { padding: 0 8px 0 18px; }
.raster { grid-template-columns: 1fr; }
.raster .kachel { grid-column: 1 / -1 !important; margin-top: 0 !important; }
.held__fuss { flex-direction: column; align-items: start; }
.kontakt { grid-template-columns: 1fr; }
.pult { left: 12px; bottom: 12px; width: calc(100% - 24px); }
.pult { padding: 8px; gap: 6px; }
.pult__zeile { grid-template-columns: 1fr; }
.pult__zeile > .mono { display: none; }
.werte { padding: 6px 4px 0; }
.werte .lang { display: none; }
}
/* Reduzierte Bewegung: keine Übergänge, Pill bleibt groß */
.reduziert body, .reduziert .kopf, .reduziert .band, .reduziert .anfrage { transition: none; }
.reduziert body[data-variante="pill"] .kopf.klein { width: min(1080px, calc(100% - 2 * var(--rand))); height: 64px; }
@media (min-width: 761px) { .reduziert body[data-variante="pill"] .kopf.klein .anfrage { display: inline-flex; } }
@media (forced-colors: active) { button, [role=button], [role=tab], [role=switch] { border: 1px solid ButtonText !important; } }
</style>
</head>
<body data-variante="klar" data-backdrop="erweitert">
<header class="kopf" data-kopf>
<div class="backdrop backdrop--erweitert" aria-hidden="true"></div>
<div class="backdrop backdrop--einfach" aria-hidden="true"></div>
<div class="kante" aria-hidden="true"></div>
<div class="vergleich" aria-hidden="true">
<span class="vergleich__strich"></span>
<span class="vergleich__etikett vergleich__etikett--l">Einfach</span>
<span class="vergleich__etikett vergleich__etikett--r">Erweitert</span>
</div>
<div class="kopf__innen">
<a class="marke" href="#oben" aria-label="Studio Feldweg, Startseite">
<svg viewBox="0 0 22 22" aria-hidden="true"><circle cx="11" cy="11" r="10" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M4 15.5c3.5-1.2 5-4.5 7-9 2 4.5 3.5 7.8 7 9" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
Feldweg
</a>
<nav class="nav" aria-label="Hauptnavigation">
<a href="#arbeiten" aria-current="page">Arbeiten</a>
<a href="#studio">Studio</a>
<a href="#journal">Journal</a>
<a href="#kontakt">Kontakt</a>
</nav>
<a class="anfrage" href="#kontakt">Projekt anfragen</a>
<button class="menue" type="button">Menü</button>
</div>
</header>
<main id="oben">
<section class="held wrap">
<p class="mono">Studio für Marken & Websites · Köln-Ehrenfeld</p>
<h1>Marken, die man <em>wiedererkennt.</em> Seiten, die Ruhe haben.</h1>
<div class="held__fuss">
<p>Wir entwickeln Erscheinungsbilder und Websites für Handwerk, Gastronomie und Mittelstand. Klein im Team, gründlich im Detail.</p>
<p class="mono">Seit 2014 · 140 Projekte</p>
</div>
</section>
<section class="wrap" id="arbeiten">
<div class="arbeiten__kopf"><p class="mono">Ausgewählte Arbeiten</p><p class="mono">(06)</p></div>
<div class="raster">
<figure class="kachel"><img src="bilder/marke-roestkontor.jpg" alt="Wortmarke Röstkontor in Creme auf Ziegelrot mit Kaffeebohne als Signet" width="1200" height="800"><figcaption><b>Röstkontor</b><span class="mono">Marke & Verpackung · 2024</span></figcaption></figure>
<figure class="kachel"><img src="bilder/flakon.jpg" alt="Bernsteinfarbener Flakon auf hellen Sockeln vor kräftigem Kobaltblau" width="1200" height="800"><figcaption><b>Ambra Nord</b><span class="mono">Kampagne · 2025</span></figcaption></figure>
<figure class="kachel"><img src="bilder/magazin-doppelseite.jpg" alt="Aufgeschlagenes Magazin mit Stillleben links und der Überschrift „Die Ruhe der Dinge“ rechts" width="1200" height="800" loading="lazy"><figcaption><b>Gefüge</b><span class="mono">Editorial · 2025</span></figcaption></figure>
<figure class="kachel"><img src="bilder/marke-krume.jpg" alt="Wortmarke krume in Dunkelbraun auf Senfgelb, darüber ein Brotlaib" width="1200" height="800" loading="lazy"><figcaption><b>Krume</b><span class="mono">Marke · 2023</span></figcaption></figure>
<figure class="kachel"><img src="bilder/typo-plakat.jpg" alt="Plakat mit schwarzen Versalien LICHT und orangem Kreis auf Neongelb" width="1200" height="800" loading="lazy"><figcaption><b>Licht, Halle 4</b><span class="mono">Plakat · 2025</span></figcaption></figure>
<figure class="kachel"><img src="bilder/zitronen-tisch.jpg" alt="Zitronen und ein weißer Teller auf dunkelgrauem Tisch im harten Licht" width="1200" height="800" loading="lazy"><figcaption><b>Limonaia</b><span class="mono">Website · 2026</span></figcaption></figure>
</div>
</section>
<section class="band" id="studio">
<div class="wrap">
<p class="mono">Arbeitsweise</p>
<blockquote>Wir arbeiten mit wenigen Kunden. Dafür <em>lange</em> und ohne Umwege.</blockquote>
</div>
</section>
<section class="kontakt wrap" id="kontakt">
<h2>Neues Projekt? <em>Erzählen Sie.</em></h2>
<dl>
<dt class="mono">Mail</dt><dd>hallo@studio-feldweg.de</dd>
<dt class="mono">Telefon</dt><dd>0221 5874 210</dd>
<dt class="mono">Adresse</dt><dd>Lichtstraße 41, 50825 Köln</dd>
</dl>
</section>
</main>
<aside class="pult" aria-label="Varianten der Demo">
<div class="pult__zeile">
<span class="mono">Variante</span>
<div class="wahl" data-wahl="variante">
<button type="button" data-wert="klar" aria-pressed="true">Klar</button>
<button type="button" data-wert="rauch" aria-pressed="false">Rauchglas</button>
<button type="button" data-wert="pill" aria-pressed="false">Pill</button>
</div>
</div>
<div class="pult__zeile">
<span class="mono">Backdrop</span>
<div class="wahl" data-wahl="backdrop">
<button type="button" data-wert="erweitert" aria-pressed="true">Erweitert</button>
<button type="button" data-wert="einfach" aria-pressed="false">Einfach</button>
<button type="button" data-wert="vergleich" aria-pressed="false">Vergleich</button>
</div>
</div>
<p class="werte" data-werte aria-live="polite"></p>
</aside>
<script>
(() => {
const html = document.documentElement, body = document.body;
const reduziert = () => html.classList.contains('reduziert');
const kopf = document.querySelector('[data-kopf]');
const werteEl = document.querySelector('[data-werte]');
// Tatsächlich verwendete Werte je Variante (identisch mit dem CSS oben)
const WERTE = {
klar: { filter: 'blur(16px) saturate(180%)', grund: 'rgb(241 238 232 / .5)', kante: 'Kante 4px · blur(8px) brightness(1.06) saturate(1.6)' },
rauch: { filter: 'blur(30px) saturate(140%)', grund: 'rgb(69 71 66 / .55)', kante: 'Kante 4px · blur(8px) brightness(1.6) saturate(1.4)' },
pill: { filter: 'blur(12px) saturate(300%)', grund: 'rgb(255 255 255 / .55)', kante: 'Ring 1px · Schrumpfen 600 ms expo.out' },
};
const BACKDROP = { erweitert: 'Höhe 200 % · Maske 50 %', einfach: 'Höhe 100 % · ohne Maske', vergleich: 'links 100 % · rechts 200 %' };
function setze(art, wert) {
body.dataset[art] = wert;
document.querySelectorAll(`[data-wahl="${art}"] button`).forEach(b => b.setAttribute('aria-pressed', String(b.dataset.wert === wert)));
const w = WERTE[body.dataset.variante];
werteEl.innerHTML = `<b>${w.filter}</b> · ${BACKDROP[body.dataset.backdrop]}<span class="lang"> · ${w.kante} · Grund ${w.grund}</span>`;
schrumpfen();
}
document.querySelectorAll('[data-wahl]').forEach(gruppe => {
gruppe.addEventListener('click', e => {
const b = e.target.closest('button'); if (!b) return;
stopVorfuehrung();
setze(gruppe.dataset.wahl, b.dataset.wert);
});
});
document.querySelectorAll('a[href^="#"]').forEach(a => a.addEventListener('click', e => e.preventDefault()));
// Pill schrumpft ab 40 px Scrollweg (bei reduzierter Bewegung bleibt sie groß, siehe CSS)
function schrumpfen() { kopf.classList.toggle('klein', scrollY > 40); }
addEventListener('scroll', schrumpfen, { passive: true });
// Vorführung: Seite scrollt selbst auf und ab, pro Runde eine Einstellung
const RUNDEN = [
['klar', 'vergleich'],
['klar', 'erweitert'],
['rauch', 'erweitert'],
['pill', 'erweitert'],
];
const RUNDE_MS = 7000;
let uhr = null, verstrichen = 0, letzte = 0, runde = -1;
function tiefe() {
// so weit, dass Bilder und das Farbband unter dem Kopf durchlaufen
const band = document.querySelector('.band');
return Math.min(document.documentElement.scrollHeight - innerHeight, band.offsetTop + 80);
}
function schritt(t) {
verstrichen += Math.min(64, t - letzte); letzte = t;
const n = Math.floor(verstrichen / RUNDE_MS), p = (verstrichen % RUNDE_MS) / RUNDE_MS;
const r = RUNDEN[n % RUNDEN.length];
if (n !== runde) { runde = n; setze('variante', r[0]); setze('backdrop', r[1]); }
if (reduziert()) {
// ohne Gleiten: feste Stelle, an der eine Bildkante unter dem Kopf liegt
const kachel = document.querySelector('.kachel');
scrollTo(0, kachel.offsetTop + kachel.offsetHeight * 0.45);
} else {
// weich hinab und zurück (Kosinus), oben kurz verweilen
const q = Math.min(1, Math.max(0, (p - 0.06) / 0.88));
scrollTo(0, tiefe() * (0.5 - 0.5 * Math.cos(q * 2 * Math.PI)));
}
uhr = requestAnimationFrame(schritt);
}
function vorfuehren() {
pause();
html.classList.add('vorfuehrung');
verstrichen = 0; runde = -1;
letzte = performance.now();
uhr = requestAnimationFrame(schritt);
}
function pause() { cancelAnimationFrame(uhr); uhr = null; }
function weiter() {
if (uhr || !html.classList.contains('vorfuehrung')) return;
letzte = performance.now(); uhr = requestAnimationFrame(schritt);
}
function stopVorfuehrung() { if (html.classList.contains('vorfuehrung') && !html.classList.contains('vorschau')) { pause(); html.classList.remove('vorfuehrung'); } }
function neustart() {
pause(); html.classList.remove('vorfuehrung');
setze('variante', 'klar'); setze('backdrop', 'erweitert');
scrollTo(0, 0); schrumpfen();
}
// Wer selbst scrollt, übernimmt
['wheel', 'touchstart', 'keydown'].forEach(ev => addEventListener(ev, stopVorfuehrung, { passive: true }));
setze('variante', 'klar'); setze('backdrop', 'erweitert');
})();
</script>
</body>
</html>