Schwebende Pill-Navigation mit Glas bzw. Progressive Blur
Die Navigation schwebt als Glaskapsel über dem Inhalt, wird beim Scrollen schmaler und nimmt die Farbe des Abschnitts darunter an.
- Die Navigation ist keine volle Leiste, sondern eine abgelöste Kapsel mit Abstand zum Rand, oben oder unten mittig, mit border-radius 999px.
- Die Fläche ist halbtransparent und mit backdrop-filter unscharf und gesättigt. Eine feine 1px-Innenkante mit niedriger Deckkraft erzeugt den Glaseindruck.
- Beim Scrollen wird die Kapsel schmaler: Wortmarke und CTA-Text klappen weg.
- Ihre Farbe folgt dem Abschnitt, der gerade unter ihr liegt.
- Ein gleitender Indikator markiert den aktiven Punkt.
- Variante ohne Kapsel: Progressive Blur, bei dem die Unschärfe zum Rand hin in mehreren Stufen zunimmt.
Die Kapsel wirkt leicht und schwebend und bündelt die Navigation in einem klaren Objekt. Der Inhalt bleibt unter ihr sichtbar, die Seite wirkt dadurch größer. Das Material knüpft an Apples Liquid Glass (seit 2025) an und liest sich deshalb sofort als zeitgemäß.
Wann einsetzen
Landingpages, SaaS, Studios und Portfolios mit drei bis sechs Punkten. Auf dem Telefon als Dock unten.
Wann nicht
Wenn der Kontrast über wechselnden Bildern nicht sicher ist. Bei vielen Menüpunkten oder tiefen Menüs. Bei einer brutalen oder flachen Marke: dort wirkt Glas generisch.
Varianten
- Oben mittig, beim Scrollen schmaler werdend (diese Demo, „Glas oben“)
- Unten mittig als Dock, auf dem Telefon in der Daumenzone (diese Demo, „Dock unten“)
- Progressive Blur statt Glaskapsel mit 8 gestaffelten Unschärfe-Ebenen (diese Demo)
- Farbwechsel je Abschnitt über data-thema der Sektion unter der Pille (in allen drei Varianten aktiv)
Umsetzung
position: fixed; inset-inline: 0; margin-inline: auto; width: fit-content; border-radius: 999px; background-color: color-mix(in oklab, var(--grund) 60%, transparent); backdrop-filter: blur(16px) saturate(1.4); box-shadow: inset 0 0 0 1px rgb(255 255 255 / .12). Antinomy nutzt deckender 80 % Fläche mit blur(12px) und wechselt die Farbe mit 300 ms.
Schmaler werden: Wortmarke und CTA-Text in einem Grid mit grid-template-columns 1fr, im kompakten Zustand 0fr. So animiert die Breite ohne feste Pixelwerte. Den Zustand setzt eine Scroll-Schwelle (hier 40px) oder CSS mit animation-timeline: scroll() bzw. scroll-state(stuck).
Progressive Blur: 8 überlagerte Ebenen mit backdrop-filter 0,5 / 1 / 2 / 4 / 8 / 16 / 32 / 64 px. Jede Ebene bekommt eine linear-gradient-Maske über ein Band von 37,5 % mit 12,5 % Überlappung, pointer-events: none. Darüber eine Tönung in der Grundfarbe für den Kontrast.
Fallstricke: backdrop-filter braucht sichtbaren Inhalt dahinter, über einfarbiger Fläche sieht man nichts. Ohne Unterstützung (@supports not) eine fast deckende Fläche verwenden. Kontrast mit höherer Deckkraft absichern. Ein Indikator-Element gehört nicht direkt in die ul, sondern daneben in einen gemeinsamen, positionierten Rahmen.
Robust umsetzen
Telefon. Unten mittig in die Daumenzone setzen und env(safe-area-inset-bottom) addieren. Links mindestens 44px hoch. Wortmarke und CTA weglassen, wenn die Breite nicht reicht. Für Progressive Blur 4 statt 8 Ebenen.
Ohne Bewegung. Keine Breitenänderung beim Scrollen, die Pille bleibt statisch offen. Der Indikator springt. Bei prefers-reduced-transparency eine deckende Fläche ohne backdrop-filter.
Barrierefrei. Kontrast von mindestens 4,5:1 über allen Hintergründen prüfen, auch über Bildern. aria-current am aktiven Link. Glas und Indikator nie als einzigen Zustandshinweis nutzen; der aktive Link wird zusätzlich fetter. Das CTA-Symbol braucht im kompakten Zustand ein aria-label. Sichtbarer Fokusrahmen auf allen Links.
Code
<!DOCTYPE html>
<!--
Schwebende Pill-Navigation mit Glas bzw. Progressive Blur
Fundus · Navigation · https://cecodesign.de/lab/fundus/navigation/schwebende-pill-navigation/
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, viewport-fit=cover">
<title>Schwebende Pill-Navigation mit Glas bzw. Progressive Blur · 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; }
:root {
--papier: #eeebe4; --tinte: #141413; --nacht: #141413; --blau: #1a3cbf;
--farbe: cubic-bezier(0, 0, 0.58, 1); /* Farbwechsel: ease-out, 300 ms (Antinomy) */
--breite: cubic-bezier(0.2, 0.8, 0.2, 1); /* Breitenänderung, 500 ms */
--abstand: 16px; /* Abstand der Pille zum Rand */
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { background: var(--papier); color: var(--tinte); font-family: 'Instrument Sans', system-ui, sans-serif; -webkit-font-smoothing: antialiased; scroll-behavior: smooth; }
:root.reduziert, :root.vorfuehrung { scroll-behavior: auto; }
/* Tastatur: Fokus springt sofort ins Bild, Mausklicks auf Anker gleiten weiter */
:root:has(:focus-visible) { scroll-behavior: auto; }
a { color: inherit; text-decoration: none; }
img { display: block; max-width: 100%; }
main img { height: auto; outline: 1px solid var(--s); outline-offset: -1px; }
.mono { font: 500 11px/1.45 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; }
.serif { font-family: 'Instrument Serif', Georgia, serif; font-style: italic; font-weight: 400; letter-spacing: -.01em; }
/* ---------- Abschnitte mit eigenem Thema (die Pille liest data-thema) ---------- */
[data-thema='hell'] { --g: #eeebe4; --t: #141413; --l: rgba(20, 20, 19, .64); --s: rgba(20, 20, 19, .14); }
[data-thema='dunkel'] { --g: #141413; --t: #f1eee7; --l: rgba(241, 238, 231, .56); --s: rgba(241, 238, 231, .14); }
[data-thema='blau'] { --g: #1a3cbf; --t: #ffffff; --l: rgba(255, 255, 255, .66); --s: rgba(255, 255, 255, .2); }
section { background: var(--g); color: var(--t); padding: clamp(96px, 14vh, 160px) clamp(20px, 4vw, 56px); }
.leise { color: var(--l); }
/* Start */
.start { padding-top: clamp(112px, 15vh, 148px); padding-bottom: clamp(48px, 8vh, 96px); }
.start__zeile { display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.start h1 { font-size: clamp(28px, calc(7.4vw + 10px), 112px); font-weight: 560; letter-spacing: -.045em; line-height: .94; max-width: 13ch; margin-top: clamp(28px, 5vh, 56px); }
.start h1 .serif { font-size: 1.08em; letter-spacing: -.02em; }
.start__fuss { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); gap: 32px clamp(24px, 5vw, 80px); align-items: end; margin-top: clamp(28px, 5vh, 52px); }
.start__fuss p.text { font-size: clamp(16px, 1.25vw, 19px); line-height: 1.5; max-width: 36ch; }
/* Bedienung: Variante und Werte, dezent */
.steuer { display: grid; gap: 12px; justify-items: end; text-align: right; }
.modi { display: flex; gap: 4px; flex-wrap: wrap; justify-content: flex-end; }
.modi button { font: inherit; letter-spacing: inherit; text-transform: inherit; color: var(--l); background: none; border: 1px solid var(--s); border-radius: 999px; height: 30px; padding: 0 12px; cursor: pointer; transition: background-color .15s, color .15s, border-color .15s; }
.modi button[aria-pressed='true'] { background: var(--t); color: var(--g); border-color: var(--t); }
.modi button:focus-visible { outline: 2px solid var(--t); outline-offset: 2px; }
.werte { color: var(--l); display: grid; gap: 4px; }
.werte span { display: block; }
.werte i { font-style: normal; white-space: nowrap; }
.werte b { color: var(--t); font-weight: 500; }
.streifen { display: grid; grid-template-columns: 1.6fr .9fr .9fr; gap: clamp(10px, 1.2vw, 16px); margin-top: clamp(40px, 7vh, 72px); align-items: start; }
.streifen figure { display: grid; gap: 10px; }
.streifen img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; border-radius: 4px; }
.streifen figure:nth-child(2) img { aspect-ratio: 4 / 5; }
.streifen figure:nth-child(3) { margin-top: 24%; }
.streifen figure:nth-child(3) img { aspect-ratio: 4 / 5; }
.streifen figcaption { display: flex; justify-content: space-between; gap: 8px; color: var(--l); }
/* Arbeiten */
.kopf { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; border-bottom: 1px solid var(--s); padding-bottom: 14px; margin-bottom: clamp(28px, 4vw, 48px); }
.kopf h2 { font-size: clamp(30px, 3.6vw, 52px); font-weight: 540; letter-spacing: -.035em; line-height: 1; }
.raster { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: clamp(36px, 6vw, 88px) clamp(12px, 1.6vw, 24px); }
.raster { align-items: start; }
.arbeit { display: grid; gap: 12px; align-content: start; }
.arbeit img { width: 100%; object-fit: cover; border-radius: 4px; aspect-ratio: 3 / 2; }
.arbeit:nth-child(1) { grid-column: 1 / span 7; }
.arbeit:nth-child(2) { grid-column: 9 / span 4; margin-top: 22%; }
.arbeit:nth-child(2) img, .arbeit:nth-child(3) img { aspect-ratio: 4 / 5; }
.arbeit img.arbeit__ganz { object-fit: contain; background: #f4f1ea; padding: 0 6%; }
.arbeit:nth-child(3) { grid-column: 2 / span 4; }
.arbeit:nth-child(4) { grid-column: 7 / span 6; margin-top: 12%; }
.arbeit__zeile { display: flex; justify-content: space-between; gap: 4px 12px; align-items: baseline; flex-wrap: wrap; }
.arbeit h3 { white-space: nowrap; }
.arbeit h3 { font-size: clamp(19px, 1.6vw, 24px); font-weight: 520; letter-spacing: -.02em; }
/* Studio */
.studio__satz { font-size: clamp(32px, 4.8vw, 76px); font-weight: 520; letter-spacing: -.04em; line-height: 1.02; max-width: 17ch; margin-top: 28px; }
.studio__unten { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr); gap: 40px clamp(24px, 6vw, 96px); margin-top: clamp(48px, 8vh, 96px); align-items: end; }
.studio__unten p { font-size: clamp(16px, 1.2vw, 18px); line-height: 1.55; max-width: 40ch; }
.zahlen { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); border-top: 1px solid var(--s); }
.zahlen div { padding: 18px 12px 0 0; display: grid; gap: 8px; }
.zahlen strong { font-size: clamp(36px, 4vw, 64px); font-weight: 480; letter-spacing: -.04em; line-height: 1; }
.studio__bild { margin-top: clamp(48px, 8vh, 96px); width: 100%; aspect-ratio: 21 / 9; object-fit: cover; border-radius: 4px; }
/* Journal */
.journal ol { list-style: none; border-bottom: 1px solid var(--s); }
.journal li a { display: grid; grid-template-columns: 7em minmax(0, 1fr) auto; gap: 16px; align-items: baseline; padding: clamp(18px, 2.4vw, 30px) 0; border-top: 1px solid var(--s); }
.journal li:first-child a { border-top: 0; }
.journal h3 { font-size: clamp(20px, 2.4vw, 36px); font-weight: 500; letter-spacing: -.03em; line-height: 1.1; }
/* Kontakt */
.kontakt { min-height: 88svh; display: grid; align-content: space-between; gap: 64px; }
.kontakt h2 { font-size: clamp(44px, 8vw, 128px); font-weight: 540; letter-spacing: -.05em; line-height: .92; max-width: 12ch; }
.kontakt__fuss { display: flex; justify-content: space-between; align-items: end; gap: 24px; flex-wrap: wrap; }
.knopf { display: inline-flex; align-items: center; gap: 10px; height: 52px; padding: 0 24px; border-radius: 999px; background: var(--t); color: var(--g); font-size: 16px; font-weight: 560; }
/* =================================================================
DIE SCHWEBENDE PILLE
================================================================= */
.leiste {
position: fixed; inset-inline: 0; top: var(--abstand); z-index: 20;
display: flex; justify-content: center; pointer-events: none;
padding-inline: var(--abstand);
}
.pille {
pointer-events: auto; position: relative;
display: flex; align-items: center; gap: 4px;
width: fit-content; max-width: 100%;
padding: 6px; border-radius: 999px;
color: var(--t);
/* Glas: halbtransparente Fläche + Unschärfe + Sättigung */
background-color: color-mix(in oklab, var(--g) 60%, transparent);
-webkit-backdrop-filter: blur(16px) saturate(1.4);
backdrop-filter: blur(16px) saturate(1.4);
/* feine Innenkante, Lichtkante oben, weicher Schatten */
box-shadow: inset 0 0 0 1px var(--kante), inset 0 1px 0 var(--licht), 0 10px 32px -14px rgba(0, 0, 0, .32);
transition: background-color .3s var(--farbe), color .3s var(--farbe), box-shadow .3s var(--farbe), padding .5s var(--breite);
}
.leiste[data-thema='hell'] { --kante: rgba(20, 20, 19, .07); --licht: rgba(255, 255, 255, .55); }
.leiste[data-thema='dunkel'] { --kante: rgba(255, 255, 255, .12); --licht: rgba(255, 255, 255, .08); }
.leiste[data-thema='blau'] { --kante: rgba(255, 255, 255, .2); --licht: rgba(255, 255, 255, .16); }
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { .pille { background-color: color-mix(in oklab, var(--g) 94%, transparent); } }
@media (prefers-reduced-transparency: reduce) { .pille { background-color: var(--g); -webkit-backdrop-filter: none; backdrop-filter: none; } }
/* Klappteile: Breite per grid-template-columns 1fr ↔ 0fr */
.klapp { display: grid; grid-template-columns: 1fr; transition: grid-template-columns .5s var(--breite), opacity .3s var(--farbe); }
.klapp > span { min-width: 0; overflow: hidden; white-space: nowrap; }
.marke { display: flex; align-items: center; height: var(--h); padding: 0 6px 0 8px; border-radius: 999px; font-weight: 600; font-size: 15px; letter-spacing: -.02em; }
.marke svg { width: 26px; height: 26px; flex: none; }
.wort { display: inline-block; }
.marke .wort { padding: 0 6px 0 9px; }
.punkte { position: relative; isolation: isolate; }
.punkte ul { display: flex; list-style: none; }
.punkte a { position: relative; display: flex; align-items: center; height: var(--h); padding: 0 14px; border-radius: 999px; font-size: 14.5px; font-weight: 500; transition: color .3s var(--farbe), height .5s var(--breite); }
.punkte a:focus-visible, .marke:focus-visible, .cta:focus-visible { outline: 2px solid currentColor; outline-offset: -2px; }
/* gleitender Indikator für den aktiven Punkt */
.zeiger {
position: absolute; z-index: -1; left: 0; top: 50%; height: var(--h); width: 0; border-radius: 999px;
background: color-mix(in oklab, var(--t) 11%, transparent);
translate: 0 -50%; opacity: 0;
transition: transform .5s var(--breite), width .5s var(--breite), height .5s var(--breite), opacity .3s var(--farbe), background-color .3s var(--farbe);
}
.punkte a[aria-current] { font-weight: 560; }
.cta { display: flex; align-items: center; height: var(--h); padding: 0 6px 0 16px; border-radius: 999px; background: var(--t); color: var(--g); font-size: 14px; font-weight: 560; transition: background-color .3s var(--farbe), color .3s var(--farbe), height .5s var(--breite), padding .5s var(--breite); }
.cta .wort { padding-right: 8px; }
.cta svg { width: 26px; height: 26px; flex: none; }
/* Zustände: offen (oben) und kompakt (nach 40 px Scroll) */
.leiste { --h: 40px; }
.leiste.kompakt { --h: 34px; }
.leiste.kompakt .pille { padding: 4px; }
.leiste.kompakt .marke .klapp { grid-template-columns: 0fr; opacity: 0; }
.leiste.kompakt .cta { padding: 0 4px; }
.leiste.kompakt .cta .klapp { grid-template-columns: 0fr; opacity: 0; }
@media (hover: hover) {
.punkte a:hover { color: color-mix(in oklab, var(--t) 70%, transparent); }
.punkte a[aria-current]:hover { color: var(--t); }
}
/* ---------- Variante B: unten als Dock ---------- */
[data-variante='dock'] .leiste { top: auto; bottom: calc(var(--abstand) + env(safe-area-inset-bottom)); }
/* ---------- Variante C: Progressive Blur statt Kapsel ---------- */
.band { display: none; position: absolute; inset: calc(-1 * var(--abstand)) 0 auto; height: 136px; pointer-events: none; z-index: -1; }
.band i { position: absolute; inset: 0; -webkit-backdrop-filter: blur(var(--b)); backdrop-filter: blur(var(--b));
-webkit-mask-image: linear-gradient(to top, transparent var(--m0), #000 var(--m1), #000 var(--m2), transparent var(--m3));
mask-image: linear-gradient(to top, transparent var(--m0), #000 var(--m1), #000 var(--m2), transparent var(--m3)); }
/* 8 Ebenen, je ein Band von 37,5 % mit 12,5 % Überlappung; Unschärfe verdoppelt sich zum Rand hin */
.band i:nth-child(1) { --b: .5px; --m0: 0%; --m1: 12.5%; --m2: 25%; --m3: 37.5%; }
.band i:nth-child(2) { --b: 1px; --m0: 12.5%; --m1: 25%; --m2: 37.5%; --m3: 50%; }
.band i:nth-child(3) { --b: 2px; --m0: 25%; --m1: 37.5%; --m2: 50%; --m3: 62.5%; }
.band i:nth-child(4) { --b: 4px; --m0: 37.5%; --m1: 50%; --m2: 62.5%; --m3: 75%; }
.band i:nth-child(5) { --b: 8px; --m0: 50%; --m1: 62.5%; --m2: 75%; --m3: 87.5%; }
.band i:nth-child(6) { --b: 16px; --m0: 62.5%; --m1: 75%; --m2: 87.5%; --m3: 100%; }
.band i:nth-child(7) { --b: 32px; --m0: 75%; --m1: 87.5%; --m2: 100%; --m3: 100%; }
.band i:nth-child(8) { --b: 64px; --m0: 87.5%; --m1: 100%; --m2: 100%; --m3: 100%; }
/* Tönung für Kontrast: Grundfarbe 72 % oben, 0 % unten */
.band b { position: absolute; inset: 0; background-color: var(--g);
-webkit-mask-image: linear-gradient(to bottom, rgba(0, 0, 0, .72), rgba(0, 0, 0, .3) 55%, transparent);
mask-image: linear-gradient(to bottom, rgba(0, 0, 0, .72), rgba(0, 0, 0, .3) 55%, transparent);
transition: background-color .3s var(--farbe); }
[data-variante='blur'] .band { display: block; }
[data-variante='blur'] .pille { background-color: transparent; -webkit-backdrop-filter: none; backdrop-filter: none; box-shadow: none; }
/* Vorschau-Marke: welche Variante gerade läuft */
.marker { display: none; position: fixed; left: 20px; bottom: 18px; z-index: 21; padding: 7px 11px; border-radius: 999px; background: rgba(20, 20, 19, .82); color: #f1eee7; }
.vorschau .marker { display: block; }
.vorschau body { overflow: hidden; }
.vorschau .steuer { visibility: hidden; }
[data-variante='dock'] .marker { bottom: auto; top: 18px; }
/* ---------- Reduzierte Bewegung: kein Schrumpfen, Indikator springt ---------- */
.reduziert .klapp, .reduziert .pille, .reduziert .punkte a, .reduziert .cta { transition-property: background-color, color, box-shadow; transition-duration: .15s; }
.reduziert .zeiger { transition: opacity .15s linear, background-color .15s linear; }
/* ---------- Schmal und Touch ---------- */
@media (max-width: 720px) {
.start__fuss, .studio__unten { grid-template-columns: minmax(0, 1fr); }
.werte i { white-space: normal; }
.steuer { justify-items: start; text-align: left; }
.modi { justify-content: flex-start; }
.streifen { grid-template-columns: 1fr 1fr; }
.streifen figure:nth-child(1) { grid-column: 1 / -1; }
.streifen figure:nth-child(3) { margin-top: 0; }
.raster { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.arbeit:nth-child(n) { grid-column: auto; margin-top: 0; }
.arbeit:nth-child(1), .arbeit:nth-child(4) { grid-column: 1 / -1; }
.zahlen { grid-template-columns: 1fr 1fr; row-gap: 20px; }
.journal li a { grid-template-columns: 1fr; gap: 6px; }
.journal li a .pfeil { display: none; }
.studio__bild { aspect-ratio: 4 / 3; }
}
@media (max-width: 560px) {
:root { --abstand: 12px; }
.start { padding-top: 88px; }
.start__zeile { display: grid; gap: 4px; }
.cta-li { display: none; }
.punkte a { padding: 0 11px; font-size: 14px; }
.marke { padding: 0 4px 0 6px; }
.marke .klapp { grid-template-columns: 0fr; opacity: 0; }
.band { height: 104px; }
/* 4 statt 8 Ebenen, Band 50 %, Überlappung 25 % */
.band i:nth-child(odd) { display: none; }
.band i:nth-child(2) { --b: 2px; --m0: 0%; --m1: 25%; --m2: 50%; --m3: 75%; }
.band i:nth-child(4) { --b: 6px; --m0: 25%; --m1: 50%; --m2: 75%; --m3: 100%; }
.band i:nth-child(6) { --b: 16px; --m0: 50%; --m1: 75%; --m2: 100%; --m3: 100%; }
.band i:nth-child(8) { --b: 40px; --m0: 75%; --m1: 100%; --m2: 100%; --m3: 100%; }
}
@media (hover: none) { .leiste { --h: 44px; } .leiste.kompakt { --h: 40px; } }
/* Sehr schmal (320 px): Pille und Titel passen ohne Querscrollen */
@media (max-width: 360px) {
.punkte a { padding: 0 8px; font-size: 13.5px; }
.arbeit h3 { white-space: normal; }
}
/* Kontrastmodus: Knöpfe mit Rahmen */
@media (forced-colors: active) { button, [role=button], [role=tab], [role=switch] { border: 1px solid ButtonText !important; } }
</style>
</head>
<body data-variante="oben">
<header class="leiste" data-leiste data-thema="hell">
<div class="band" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><b></b></div>
<nav class="pille" aria-label="Hauptnavigation">
<a class="marke" href="#start" aria-label="Studio Feldweg, zum Anfang">
<svg viewBox="0 0 26 26" aria-hidden="true"><circle cx="13" cy="13" r="12" fill="currentColor"/><path d="M7 18.5c3.2-1.4 4.6-4.7 6-11 1.4 6.3 2.8 9.6 6 11" fill="none" style="stroke: var(--g)" stroke-width="1.8" stroke-linecap="round"/></svg>
<span class="klapp"><span><span class="wort">Feldweg</span></span></span>
</a>
<div class="punkte"><ul>
<li><a href="#arbeiten" data-ziel="arbeiten">Arbeiten</a></li>
<li><a href="#studio" data-ziel="studio">Studio</a></li>
<li><a href="#journal" data-ziel="journal">Journal</a></li>
<li><a href="#kontakt" data-ziel="kontakt">Kontakt</a></li>
</ul><span class="zeiger" data-zeiger aria-hidden="true"></span></div>
<span class="cta-li"><a class="cta" href="#kontakt" aria-label="Projekt anfragen">
<span class="klapp"><span><span class="wort">Projekt anfragen</span></span></span>
<svg viewBox="0 0 26 26" aria-hidden="true"><circle cx="13" cy="13" r="13" style="fill: var(--g)" opacity=".16"/><path d="M9 17 17 9M11 9h6v6" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a></span>
</nav>
</header>
<main>
<section class="start" id="start" data-thema="hell">
<div class="start__zeile mono leise"><span>Studio Feldweg · Köln-Ehrenfeld</span><span>Marken und Erscheinungsbilder seit 2014</span></div>
<h1>Erscheinungsbilder, die <span class="serif">leise</span> bleiben und lange halten.</h1>
<div class="start__fuss">
<p class="text">Wir entwickeln Marken für Cafés, Handwerk und Mittelstand. Vom ersten Zeichen bis zur Website, in einem kleinen Team mit kurzen Wegen.</p>
<div class="steuer">
<div class="modi mono" role="group" aria-label="Variante der Navigation">
<button type="button" data-v="oben" aria-pressed="true">Glas oben</button>
<button type="button" data-v="dock" aria-pressed="false">Dock unten</button>
<button type="button" data-v="blur" aria-pressed="false">Progressive Blur</button>
</div>
<p class="werte mono" data-werte aria-live="polite"></p>
</div>
</div>
<div class="streifen">
<figure><img src="bilder/marke-roestkontor.jpg" alt="Wortbildmarke Röstkontor: Kaffeebohne im Kreis über cremefarbenen Versalien auf Ziegelrot" width="1200" height="800"><figcaption class="mono"><span>Röstkontor</span><span>Markenkonzept · 2025</span></figcaption></figure>
<figure><img src="bilder/marke-krume-hoch.jpg" alt="Wortmarke krume mit Brotlaib, dunkelbraun auf Sonnengelb" width="800" height="1000"><figcaption class="mono"><span>Krume</span><span>Marke · 2024</span></figcaption></figure>
<figure><img src="bilder/stoff-stapel-hoch.jpg" alt="Stapel gefalteter Leinentücher in Rosa, Weiß und Senfgelb auf Holz" width="800" height="1000"><figcaption class="mono"><span>Tuchwerk</span><span>2023</span></figcaption></figure>
</div>
</section>
<section id="arbeiten" data-thema="hell" style="padding-top: 24px">
<div class="kopf"><h2>Ausgewählte Arbeiten</h2><span class="mono leise">(04 von 38)</span></div>
<div class="raster">
<article class="arbeit"><img src="bilder/marke-eselsohr.jpg" alt="Wortbildmarke Eselsohr, Buchhandlung und Lesecafé, in Weiß und Rosa auf Anthrazit" width="1200" height="800" loading="lazy"><div class="arbeit__zeile"><h3>Eselsohr</h3><span class="mono leise">Marke & Print · 2024</span></div></article>
<article class="arbeit"><img src="bilder/zitronen-tisch-hoch.jpg" alt="Zitronen und ein heller Teller mit einer halben Zitrone auf dunklem Tisch" width="800" height="1000" loading="lazy"><div class="arbeit__zeile"><h3>Café Zitronengarten</h3><span class="mono leise">Marke · Leitsystem · 2023</span></div></article>
<article class="arbeit"><img src="bilder/beton-treppe-hoch.jpg" alt="Freitragende Treppe mit schmalem Handlauf an einer hellen Betonwand, harte Schatten" width="800" height="1000" loading="lazy"><div class="arbeit__zeile"><h3>Treppenwerk</h3><span class="mono leise">Logo · 2021</span></div></article>
<article class="arbeit"><img src="bilder/magazin-doppelseite.jpg" alt="Aufgeschlagenes Magazin Gefüge mit Keramik-Stillleben und der Überschrift Die Ruhe der Dinge" width="1200" height="800" loading="lazy"><div class="arbeit__zeile"><h3>Gefüge</h3><span class="mono leise">Editorial · 2025</span></div></article>
</div>
</section>
<section id="studio" data-thema="dunkel">
<p class="mono leise">Studio</p>
<p class="studio__satz">Vier Menschen, ein langer Tisch und <span class="serif">keine</span> Agenturschleifen.</p>
<div class="studio__unten">
<p class="leise">Wer bei uns anfragt, spricht vom ersten Termin bis zur Druckfreigabe mit denselben Menschen. Wir arbeiten an wenigen Projekten gleichzeitig und dafür gründlich.</p>
<div class="zahlen">
<div><strong>12</strong><span class="mono leise">Jahre</span></div>
<div><strong>140</strong><span class="mono leise">Marken</span></div>
<div><strong>4</strong><span class="mono leise">Menschen</span></div>
<div><strong>1</strong><span class="mono leise">Ansprechpartner</span></div>
</div>
</div>
<img class="studio__bild" src="bilder/fenster-licht.jpg" alt="Ruhiger Raum im Studio: Vase mit Zweigen auf einer Holzbank im Fensterlicht vor salbeigrüner Wand" width="1200" height="800" loading="lazy">
</section>
<section id="journal" class="journal" data-thema="hell">
<div class="kopf"><h2>Journal</h2><span class="mono leise">Notizen aus dem Studio</span></div>
<ol>
<li><a href="#journal"><span class="mono leise">Okt 2026</span><h3>Warum ein neues Logo selten das eigentliche Problem löst</h3><span class="mono leise pfeil">8 Min</span></a></li>
<li><a href="#journal"><span class="mono leise">Sep 2026</span><h3>Ein Café, drei Schilder: das Leitsystem für den Zitronengarten</h3><span class="mono leise pfeil">5 Min</span></a></li>
<li><a href="#journal"><span class="mono leise">Aug 2026</span><h3>Rosa drucken, Rosa zeigen: eine Marke zwischen Papier und Bildschirm</h3><span class="mono leise pfeil">6 Min</span></a></li>
</ol>
</section>
<section id="kontakt" class="kontakt" data-thema="blau">
<p class="mono leise">Kontakt</p>
<h2>Erzählen Sie uns von Ihrem <span class="serif">Vorhaben.</span></h2>
<div class="kontakt__fuss">
<a class="knopf" href="#kontakt">Erstgespräch vereinbaren <span aria-hidden="true">→</span></a>
<p class="mono leise">Antwort innerhalb von zwei Werktagen<br>Studio Feldweg · Köln-Ehrenfeld</p>
</div>
</section>
</main>
<p class="marker mono" data-marker aria-hidden="true">Glas oben</p>
<script>
(() => {
const html = document.documentElement;
const reduziert = () => html.classList.contains('reduziert');
const leiste = document.querySelector('[data-leiste]');
const pille = leiste.querySelector('.pille');
const zeiger = document.querySelector('[data-zeiger]');
const links = [...document.querySelectorAll('[data-ziel]')];
const abschnitte = [...document.querySelectorAll('main section')];
const ziele = links.map(a => document.getElementById(a.dataset.ziel));
const werte = document.querySelector('[data-werte]');
const marker = document.querySelector('[data-marker]');
const SCHWELLE = 40; // ab hier kompakt
const NAMEN = { oben: 'Glas oben', dock: 'Dock unten', blur: 'Progressive Blur' };
// Werte-Anzeige: Zeilen aus unteilbaren Teilen (erstes Teil fett)
const WERTE = {
oben: [['Glas', 'Grund 60 %', 'blur(16px) saturate(1.4)', 'Kante 1px', 'Radius 999px'], ['Kompakt ab 40px', 'Breite 500 ms cubic-bezier(.2,.8,.2,1)', 'Farbe 300 ms ease-out']],
dock: [['Dock', 'bottom: calc(16px + env(safe-area-inset-bottom))'], ['Glas', 'wie oben', 'Farbe aus dem Abschnitt unter der Pille']],
blur: [['Progressive Blur', '8 Ebenen 0,5 / 1 / 2 / 4 / 8 / 16 / 32 / 64 px'], ['Masken', 'Band 37,5 %', 'Überlappung 12,5 %', 'Höhe 136px', 'Tönung 72 → 0 %']]
};
const zeile = teile => '<span><b>' + teile[0] + '</b> ' + teile.slice(1).map(t => '<i>' + t + '</i>').join(' · ') + '</span>';
let variante = matchMedia('(hover: none), (max-width: 560px)').matches ? 'dock' : 'oben';
let gewaehlt = variante, aktiv = -2;
function setzeVariante(v) {
variante = v;
document.body.dataset.variante = v;
document.querySelectorAll('[data-v]').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.v === v)));
werte.innerHTML = WERTE[v].map(zeile).join('');
marker.textContent = NAMEN[v];
pruefen();
}
// Indikator: liegt in der Linkliste, folgt dem aktiven Link
function zeigerSetzen(sofort) {
const a = links[aktiv];
if (sofort || reduziert()) { zeiger.style.transition = reduziert() ? '' : 'none'; }
if (a) {
zeiger.style.transform = `translateX(${a.offsetLeft}px)`;
zeiger.style.width = a.offsetWidth + 'px';
zeiger.style.opacity = 1;
} else zeiger.style.opacity = 0;
if (sofort && !reduziert()) { zeiger.offsetWidth; zeiger.style.transition = ''; }
}
function pruefen() {
const y = scrollY, h = innerHeight;
// 1) Zustand: offen oben, kompakt nach 40 px (bei reduzierter Bewegung statisch offen)
leiste.classList.toggle('kompakt', y > SCHWELLE && !reduziert());
// 2) Thema: der Abschnitt unter der Mitte der Pille bestimmt die Farbe
const r = pille.getBoundingClientRect();
const probe = variante === 'blur' ? 40 : r.top + r.height / 2;
const unter = abschnitte.find(s => { const b = s.getBoundingClientRect(); return b.top <= probe && b.bottom > probe; });
if (unter && leiste.dataset.thema !== unter.dataset.thema) leiste.dataset.thema = unter.dataset.thema;
// 3) Aktiver Punkt: letzter Abschnitt, dessen Oberkante über 45 % der Höhe liegt
let i = -1;
ziele.forEach((z, j) => { if (z.getBoundingClientRect().top <= h * 0.45) i = j; });
if (y >= document.documentElement.scrollHeight - h - 2) i = ziele.length - 1;
if (i !== aktiv) {
const vorher = aktiv; aktiv = i;
links.forEach((a, j) => j === i ? a.setAttribute('aria-current', 'true') : a.removeAttribute('aria-current'));
zeigerSetzen(vorher < 0);
}
}
let wartet = false;
addEventListener('scroll', () => { if (!wartet) { wartet = true; requestAnimationFrame(() => { wartet = false; pruefen(); }); } }, { passive: true });
addEventListener('resize', () => { pruefen(); zeigerSetzen(true); });
document.fonts && document.fonts.ready.then(() => zeigerSetzen(true));
document.querySelectorAll('[data-v]').forEach(b => b.addEventListener('click', () => { pause(); html.classList.remove('vorfuehrung'); gewaehlt = b.dataset.v; setzeVariante(gewaehlt); }));
setzeVariante(variante);
// Vorführung: die Seite gleitet von Halt zu Halt (Bilder unter dem Glas, dunkles Studio,
// blauer Kontakt) und wieder hinauf; jede Runde zeigt eine Variante
const FOLGE = ['oben', 'blur', 'dock'], GLEITEN = 900, HALTEN = 750, ZURUECK = 1300;
let uhr = null, sprung = null, t0 = 0, runde = -1, plan = null, angehalten = 0;
const sanft = q => 0.5 - 0.5 * Math.cos(Math.min(1, Math.max(0, q)) * Math.PI);
const oben = sel => document.querySelector(sel).getBoundingClientRect().top + scrollY;
function halte() {
const max = document.documentElement.scrollHeight - innerHeight;
return [0, oben('.streifen') - 34, oben('.arbeit') - 30, oben('#studio') - 6, oben('#journal') + 20, oben('#kontakt') - 6]
.map(y => Math.round(Math.min(max, Math.max(0, y))));
}
function planen() {
const h = halte(), seg = [];
let t = 0;
for (let i = 1; i < h.length; i++) { seg.push({ t, d: GLEITEN, von: h[i - 1], nach: h[i] }); t += GLEITEN + HALTEN; }
seg.push({ t, d: ZURUECK, von: h[h.length - 1], nach: 0 }); t += ZURUECK + HALTEN;
return { seg, dauer: t };
}
function vorfuehren() {
pause();
html.classList.add('vorfuehrung');
scrollTo(0, 0); runde = -1;
if (reduziert()) { // ohne Gleiten: von Halt zu Halt springen
let k = 0;
const weiterSpringen = () => {
const h = halte();
if (k % h.length === 0) setzeVariante(FOLGE[(k / h.length) % FOLGE.length]);
scrollTo(0, h[k % h.length]); k++;
sprung = setTimeout(weiterSpringen, 1600);
};
weiterSpringen(); return;
}
t0 = performance.now();
laufen();
}
function laufen() {
const schritt = t => {
if (!plan) plan = planen();
const n = Math.floor((t - t0) / plan.dauer), p = (t - t0) % plan.dauer;
if (n !== runde) { runde = n; plan = planen(); setzeVariante(FOLGE[n % FOLGE.length]); }
let y = 0;
for (const s of plan.seg) { if (p >= s.t) y = s.von + (s.nach - s.von) * sanft((p - s.t) / s.d); }
scrollTo(0, y);
uhr = requestAnimationFrame(schritt);
};
uhr = requestAnimationFrame(schritt);
}
function pause() { if (uhr) angehalten = performance.now() - t0; cancelAnimationFrame(uhr); clearTimeout(sprung); uhr = sprung = null; }
function neustart() { pause(); html.classList.remove('vorfuehrung'); scrollTo(0, 0); setzeVariante(gewaehlt); }
function weiter() {
if (uhr || sprung || !html.classList.contains('vorfuehrung')) return;
if (reduziert()) return vorfuehren();
t0 = performance.now() - angehalten; laufen();
}
})();
</script>
</body>
</html>