Header verschwindet beim Runterscrollen, kehrt beim Hochscrollen zurück
Beim Lesen gleitet der Header aus dem Bild, bei der ersten Bewegung nach oben ist er wieder da.
- Am Seitenanfang steht der Header normal, oft durchsichtig über dem Hero.
- Sobald die Seite sich bewegt, bekommt er eine Fläche, damit er auf jedem Untergrund lesbar bleibt.
- Scrollt man nach unten, gleitet er um seine eigene Höhe nach oben aus dem Bild.
- Bei jeder Bewegung nach oben kommt er sofort zurück.
- Seit Chrome 144 geht das ohne Scroll-Listener in reinem CSS über scroll-state(scrolled).
Beim Lesen gehört der ganze Bildschirm dem Inhalt, die Navigation ist trotzdem nur einen Wisch entfernt. Das ist eine unsichtbare Qualität: Man bemerkt sie kaum, ihr Fehlen aber sofort. Der Wechsel der Fläche am Seitenanfang gibt dem Ganzen Feinschliff.
Wann einsetzen
Fast jede inhaltsreiche Seite mit fixem Header und langem Scroll: Fallstudien, Artikel, Journale, Produktseiten. Besonders wertvoll auf dem Telefon.
Wann nicht
Wenn der Header Aktionen trägt, die immer sichtbar sein müssen, etwa den Warenkorb im Checkout. Bei sehr kurzen Seiten. Wenn eine Smooth-Scroll-Bibliothek den nativen Scroll ersetzt, funktioniert die CSS-Lösung nicht, dann die JS-Variante mit deren Scroll-Ereignis nehmen.
Varianten
- Ganz weg: der ganze Header gleitet hoch (diese Demo, Werte von Instrument)
- Logo bleibt: Fläche und Links gehen, die Marke bleibt als Pille stehen (diese Demo, Werte von COLLINS)
- Nur CSS: scroll-state(scrolled: bottom) ohne Scroll-Listener, Chrome 144+ (diese Demo)
- Sticky statt fixed: Header erst nach Scrollbeginn kleben lassen, Regeln in @container not scroll-state(scrolled: none)
- Mit Schrumpfen und Schatten über eine scroll()-Timeline
- Klebe-Zustand gestalten über @container scroll-state(stuck: top)
- JS mit Schwelle für Lenis oder ScrollSmoother über deren Scroll-Ereignis
Umsetzung
Reines CSS (Chrome 144+): html { container-type: scroll-state } header { position: fixed; inset: 0 0 auto; transition: translate .2s ease-in-out } @container scroll-state(scrolled: bottom) { header { translate: 0 -100% } }. Die Fläche hängt an @container scroll-state(scrollable: top), also an „nicht ganz oben“. Ohne Unterstützung bleibt der Header einfach stehen, das ist ein guter Fallback.
JS-Variante für alle Browser: Scroll-Listener passiv, pro Bild einmal per requestAnimationFrame prüfen. Weg seit dem letzten Bild aufsummieren, bei Richtungswechsel auf null setzen. Erst ab 8 px in einer Richtung die Klasse für „weg“ setzen, sonst zittert der Header. Bis zur eigenen Höhe vom Seitenanfang immer zeigen. Nur falls ein Eigenprojekt Lenis nutzt (optional, standardmäßig aus; viele Rezepte schließen es aus): lenis.on('scroll', ({ direction }) => …).
Fläche als eigene Ebene (::before) anlegen, dann blendet sie mit eigener Dauer und Verzögerung ein, unabhängig vom Gleiten. Gemessen: Instrument translate 200 ms ease-in-out, Fläche 350 ms; COLLINS translate 450 ms cubic-bezier(.215,.61,.355,1), Fläche 400 ms mit 200 ms Verzögerung.
Fallstricke: Header nie verstecken, solange etwas darin Fokus hat (header:focus-within { translate: none }). Für Ankersprünge scroll-padding-top in Headerhöhe setzen. scroll-state(scrolled) reagiert nur auf relative Scrolls (Rad, Wisch, Tasten, scrollBy), nicht auf scrollTo.
Robust umsetzen
Telefon. Hier lohnt es sich am meisten, weil wenig Höhe da ist. Mit der dynamischen Adressleiste testen, Höhen in svh oder dvh angeben, oben die Safe-Area (env(safe-area-inset-top)) einrechnen.
Ohne Bewegung. Header dauerhaft sichtbar lassen und nur die Fläche wechseln, kurz überblendet. Alternativ die Transition entfernen, dann springt der Header.
Barrierefrei. Solange Tastaturfokus im Header liegt, nie verstecken (:focus-within). Skip-Link anbieten. Kernaktionen nicht ausschließlich im versteckbaren Header anbieten.
Code
<!DOCTYPE html>
<!--
Header verschwindet beim Runterscrollen, kehrt beim Hochscrollen zurück
Fundus · Navigation · https://cecodesign.de/lab/fundus/navigation/hide-on-scroll-header/
Eigenständige Datei. Bilder liegen hier unter bilder/ – eigene einsetzen.
-->
<html lang="de" data-variante="weg">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Header verschwindet beim Runterscrollen · 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'; 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; }
:root {
--papier: #eeebe4; --karte: #f8f6f1; --tinte: #161514; --leise: rgba(22, 21, 20, .66); --linie: rgba(22, 21, 20, .14);
--nacht: #1f2024; --hell: #eef0ec; --hell-leise: rgba(238, 240, 236, .6); --akzent: #dcb3a8;
--kopf-h: 76px;
/* Variante „Ganz weg“ (gemessen bei Instrument) */
--kurve: cubic-bezier(0.42, 0, 0.58, 1); /* ease-in-out */
--dauer: 200ms;
--grund-dauer: 350ms;
--grund-verz: 0ms;
}
/* Variante „Logo bleibt“ (gemessen bei COLLINS) */
[data-variante='logo'] { --kurve: cubic-bezier(0.215, 0.61, 0.355, 1); --dauer: 450ms; --grund-dauer: 400ms; --grund-verz: 200ms; }
* { box-sizing: border-box; margin: 0; }
html { background: var(--papier); color: var(--tinte); font-family: 'Instrument Sans', system-ui, sans-serif; -webkit-font-smoothing: antialiased; scroll-padding-top: calc(var(--kopf-h) + 16px); }
body { overflow-x: clip; }
img { display: block; max-width: 100%; height: auto; }
a { color: inherit; }
.mono { font: 500 11px/1.4 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; }
em, .serif { font-family: 'Instrument Serif', Georgia, serif; font-style: italic; font-weight: 400; letter-spacing: -.01em; }
.weg-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.skip { position: fixed; left: 16px; top: 12px; z-index: 30; padding: 10px 14px; border-radius: 999px; background: var(--tinte); color: var(--papier); text-decoration: none; translate: 0 -200%; }
.skip:focus-visible { translate: 0 0; outline: 2px solid var(--akzent); outline-offset: 2px; }
/* ---------- Der Header ---------- */
.kopf {
position: fixed; inset: 0 0 auto; z-index: 20; height: var(--kopf-h);
padding: env(safe-area-inset-top) clamp(16px, 3.4vw, 44px) 0;
display: flex; align-items: center; gap: clamp(16px, 3vw, 40px);
color: var(--hell);
transition: translate var(--dauer) var(--kurve), color var(--grund-dauer) var(--kurve) var(--grund-verz);
}
/* Hintergrund als eigene Ebene: blendet getrennt vom Gleiten ein */
.kopf::before {
content: ""; position: absolute; inset: 0; z-index: -1;
background: color-mix(in oklab, var(--papier) 90%, transparent);
backdrop-filter: blur(14px) saturate(1.2); -webkit-backdrop-filter: blur(14px) saturate(1.2);
box-shadow: inset 0 -1px 0 var(--linie);
opacity: 0;
transition: opacity var(--grund-dauer) var(--kurve) var(--grund-verz), translate var(--dauer) var(--kurve);
}
.kopf.klebt { color: var(--tinte); }
.kopf.klebt::before { opacity: 1; }
.marke { display: flex; align-items: center; gap: 10px; text-decoration: none; font-size: 19px; font-weight: 600; letter-spacing: -.02em; white-space: nowrap; border-radius: 999px; padding: 6px 12px 6px 6px; margin-left: -6px; transition: background-color var(--dauer) var(--kurve), box-shadow var(--dauer) var(--kurve), color var(--dauer) var(--kurve); }
.marke svg { width: 28px; height: 28px; flex: none; }
.marke em { font-size: 22px; margin-left: -2px; }
.nav { display: flex; gap: 4px; margin-left: auto; }
.nav a, .cta, .menue { font-size: 15px; font-weight: 500; text-decoration: none; }
.nav a { padding: 8px 14px; border-radius: 999px; opacity: .82; transition: opacity 150ms linear; }
.nav a:hover, .nav a[aria-current] { opacity: 1; }
.nav a[aria-current]::after { content: ""; display: block; height: 1px; background: currentColor; margin-top: 3px; }
.cta { display: inline-flex; align-items: center; gap: 8px; height: 40px; padding: 0 18px; border-radius: 999px; border: 1px solid currentColor; white-space: nowrap; transition: background-color 150ms linear, color 150ms linear; }
.cta:hover { background: var(--hell); color: var(--nacht); }
.kopf.klebt .cta:hover { background: var(--tinte); color: var(--papier); }
.menue { display: none; margin-left: auto; height: 40px; padding: 0 16px; border-radius: 999px; border: 1px solid currentColor; background: none; color: inherit; font-family: inherit; }
.kopf :focus-visible { outline: 2px solid var(--akzent); outline-offset: 3px; }
.nav, .cta, .menue { transition: translate var(--dauer) var(--kurve), opacity var(--dauer) var(--kurve); }
/* Zustand „weg“: Variante „Ganz weg“ schiebt den ganzen Header um seine Höhe hoch */
[data-variante='weg'] .kopf.weg { translate: 0 -100%; }
/* Variante „Logo bleibt“: Fläche und Links gehen, die Marke bleibt als Pille stehen */
[data-variante='logo'] .kopf.weg::before { translate: 0 -100%; }
[data-variante='logo'] .kopf.weg :is(.nav, .cta, .menue) { translate: 0 -120%; opacity: 0; pointer-events: none; }
[data-variante='logo'] .kopf.weg .marke { background: var(--karte); color: var(--tinte); box-shadow: 0 8px 28px rgba(22, 21, 20, .14), 0 0 0 1px var(--linie); }
/* Variante „Nur CSS“: scroll-state()-Container-Query, kein Scroll-Listener (Chrome 144+) */
[data-variante='css'] { container-type: scroll-state; }
@container scroll-state(scrollable: top) {
[data-variante='css'] .kopf { color: var(--tinte); --stand: klebt; }
[data-variante='css'] .kopf::before { opacity: 1; }
}
@container scroll-state(scrolled: bottom) {
[data-variante='css'] .kopf { translate: 0 -100%; --richtung: runter; }
}
@container scroll-state(scrolled: top) {
[data-variante='css'] .kopf { --richtung: hoch; }
}
/* Barrierefreiheit: solange etwas im Header Fokus hat, bleibt er da */
.kopf:focus-within, [data-variante] .kopf:focus-within { translate: none; }
.kopf:focus-within::before { translate: none; }
.kopf:focus-within :is(.nav, .cta, .menue) { translate: none; opacity: 1; pointer-events: auto; }
/* ---------- Seite: Fallstudie ---------- */
.held { position: relative; min-height: min(100svh, 980px); background: var(--nacht); color: var(--hell); display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); overflow: clip; }
.held__text { display: grid; align-content: end; gap: clamp(20px, 3vw, 32px); padding: calc(var(--kopf-h) + 40px) clamp(16px, 3.4vw, 44px) 112px; }
.held__text .mono { color: var(--hell-leise); }
.held h1 { font-size: clamp(46px, 7.2vw, 116px); font-weight: 600; line-height: .94; letter-spacing: -.045em; max-width: 9.5ch; text-wrap: balance; }
.held h1 em { color: var(--akzent); font-size: 1.08em; letter-spacing: -.02em; }
.held__lead { max-width: 36ch; font-size: clamp(17px, 1.5vw, 20px); line-height: 1.5; color: var(--hell-leise); }
.held__bild { position: relative; }
.held__bild img { position: absolute; inset: 6% 4% 4%; width: 92%; height: 90%; object-fit: contain; }
.held__fakten { display: flex; gap: clamp(20px, 3vw, 44px); flex-wrap: wrap; padding-top: 22px; border-top: 1px solid rgba(238, 240, 236, .16); }
.held__fakten dt { color: var(--hell-leise); margin-bottom: 6px; }
.held__fakten dd { font-size: 15px; }
.text { padding: clamp(64px, 10vw, 140px) clamp(16px, 3.4vw, 44px); display: grid; gap: clamp(56px, 8vw, 110px); }
.einstieg { font-size: clamp(26px, 3.4vw, 50px); line-height: 1.12; letter-spacing: -.03em; font-weight: 500; max-width: 24ch; text-wrap: pretty; }
.einstieg em { font-size: 1.06em; }
.abschnitt { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); gap: 24px clamp(24px, 4vw, 64px); }
.abschnitt h2 { font-size: 15px; font-weight: 600; letter-spacing: -.01em; position: sticky; top: calc(var(--kopf-h) + 20px); align-self: start; display: flex; gap: 14px; }
.abschnitt h2 .mono { color: var(--leise); padding-top: 3px; }
.abschnitt__inhalt { display: grid; gap: 1.1em; max-width: 62ch; font-size: clamp(17px, 1.35vw, 19px); line-height: 1.62; color: rgba(22, 21, 20, .82); }
.bildpaar { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr); gap: clamp(12px, 1.6vw, 20px); align-items: end; }
.bildpaar figure { display: grid; gap: 12px; }
.bildpaar img { width: 100%; border-radius: 4px; object-fit: contain; background: var(--nacht); }
.bildpaar figure:first-child img { aspect-ratio: 3 / 2; }
.farben { aspect-ratio: 4 / 5; display: grid; grid-template-rows: 1.4fr 1fr .6fr; border-radius: 4px; overflow: hidden; }
.farbe { background: var(--f); padding: clamp(12px, 1.4vw, 18px); display: flex; justify-content: space-between; align-items: end; gap: 8px; }
.farbe b { font-size: clamp(17px, 1.6vw, 22px); font-weight: 500; letter-spacing: -.02em; }
.farbe .mono { opacity: .9; }
.bildpaar figcaption { color: var(--leise); }
.zitat { display: grid; justify-items: center; gap: 24px; text-align: center; }
.zitat p { font-family: 'Instrument Serif', Georgia, serif; font-style: italic; font-size: clamp(32px, 4.4vw, 66px); line-height: 1.04; letter-spacing: -.02em; text-wrap: balance; max-width: 19ch; }
.zitat footer { color: var(--leise); }
.zahlen { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); border-top: 1px solid var(--linie); }
.zahlen div { padding: 22px 0 0; display: grid; gap: 10px; }
.zahlen div + div { padding-left: clamp(14px, 2vw, 28px); border-left: 1px solid var(--linie); }
.zahlen b { font-size: clamp(44px, 6vw, 88px); font-weight: 500; letter-spacing: -.05em; line-height: .9; }
.zahlen span { color: var(--leise); }
.weitere { display: grid; gap: 22px; }
.weitere__kopf { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; border-bottom: 1px solid var(--linie); padding-bottom: 14px; }
.weitere__kopf h2 { font-size: clamp(28px, 3vw, 44px); font-weight: 500; letter-spacing: -.035em; }
.weitere__liste { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(12px, 1.6vw, 20px); list-style: none; padding: 0; }
.weitere__liste a { display: grid; gap: 12px; text-decoration: none; }
.weitere__liste img { aspect-ratio: 4 / 5; width: 100%; object-fit: cover; border-radius: 4px; }
.weitere__liste a > span { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; font-size: 15px; font-weight: 500; }
.weitere__liste .mono { color: var(--leise); }
.fuss { background: var(--tinte); color: var(--papier); padding: clamp(56px, 8vw, 110px) clamp(16px, 3.4vw, 44px) 110px; display: grid; gap: 28px; }
.fuss p:first-child { font-size: clamp(34px, 5vw, 78px); font-weight: 500; letter-spacing: -.045em; line-height: .98; max-width: 14ch; }
.fuss .mono { color: rgba(238, 235, 228, .5); }
/* ---------- Pult: Varianten und Werte (gehört nicht zum Muster) ---------- */
.pult { position: fixed; z-index: 25; left: 16px; right: 16px; bottom: max(16px, env(safe-area-inset-bottom)); display: flex; justify-content: space-between; align-items: end; gap: 10px; pointer-events: none; }
.pult > * { pointer-events: auto; background: rgba(22, 21, 20, .84); color: var(--hell); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border: 1px solid rgba(238, 240, 236, .12); border-radius: 14px; }
.anzeige { display: grid; grid-template-columns: auto 1fr; align-items: center; gap: 4px 12px; padding: 10px 14px 10px 10px; min-width: 0; }
.mini { grid-row: span 2; width: 30px; height: 38px; border-radius: 5px; border: 1px solid rgba(238, 240, 236, .3); overflow: hidden; position: relative; }
.mini i { position: absolute; left: 0; right: 0; top: 0; height: 8px; background: var(--akzent); transition: translate var(--dauer) var(--kurve); }
.mini b { position: absolute; left: 5px; right: 5px; height: 2px; background: rgba(238, 240, 236, .28); border-radius: 1px; }
.anzeige[data-stand='weg'] .mini i { translate: 0 -100%; }
[data-variante='logo'] .anzeige[data-stand='weg'] .mini i { translate: 0; right: 62%; border-radius: 0 0 3px 0; }
.anzeige__stand { color: var(--hell); white-space: nowrap; }
.anzeige__stand b { display: inline-block; width: 1.1em; color: var(--akzent); font-weight: 600; }
.anzeige__werte { color: var(--hell-leise); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.varianten { display: flex; gap: 2px; padding: 4px; }
.varianten button { font: inherit; letter-spacing: inherit; text-transform: inherit; color: var(--hell-leise); background: none; border: 0; border-radius: 10px; height: 32px; padding: 0 12px; cursor: pointer; transition: background-color 150ms linear, color 150ms linear; }
.varianten button:hover { color: var(--hell); }
.varianten button[aria-pressed='true'] { background: var(--hell); color: var(--tinte); }
.varianten button:disabled { opacity: .35; cursor: not-allowed; }
.varianten button:focus-visible { outline: 2px solid var(--akzent); outline-offset: 1px; }
/* Vorschau: nur die Bühne, Werte groß genug zum Lesen */
.vorschau body { overflow: hidden; }
.vorschau .varianten { display: none; }
/* Reduzierte Bewegung: Header bleibt immer da, nur der Hintergrund wechselt (kurz überblendet) */
.reduziert .kopf, .reduziert .kopf.weg, .reduziert .kopf::before, .reduziert .kopf :is(.nav, .cta, .menue, .marke) { translate: none !important; transition: color 150ms linear, opacity 150ms linear, background-color 150ms linear !important; }
.reduziert .kopf.weg :is(.nav, .cta, .menue) { opacity: 1 !important; pointer-events: auto !important; }
.reduziert .kopf.weg .marke { background: none !important; box-shadow: none !important; color: inherit !important; }
.reduziert .mini i { transition: none; translate: none !important; }
@media (hover: none) {
.nav a:hover { opacity: .82; }
.cta:hover, .kopf.klebt .cta:hover { background: none; color: inherit; }
}
@media (max-width: 900px) {
.nav, .cta { display: none; }
.menue { display: inline-flex; align-items: center; }
}
@media (max-width: 720px) {
:root { --kopf-h: 64px; }
.marke { font-size: 17px; } .marke em { font-size: 20px; } .marke svg { width: 24px; height: 24px; }
.held { grid-template-columns: 1fr; grid-template-rows: auto 42svh; min-height: 0; }
.held__text { padding-top: calc(var(--kopf-h) + 36px); padding-bottom: 32px; }
.held__fakten { gap: 18px 26px; }
.abschnitt, .bildpaar { grid-template-columns: 1fr; }
.abschnitt h2 { position: static; }
.bildpaar figure:first-child img { aspect-ratio: 4 / 3; }
.zahlen { grid-template-columns: 1fr; }
.zahlen div + div { padding-left: 0; border-left: 0; border-top: 1px solid var(--linie); margin-top: 22px; }
.weitere__liste { grid-template-columns: 1fr 1fr; }
.weitere__liste li:nth-child(3) { display: none; }
.pult { flex-direction: column; align-items: stretch; gap: 6px; }
.varianten button { flex: 1; padding: 0 6px; }
.anzeige__werte { font-size: 10px; letter-spacing: .1em; white-space: normal; line-height: 1.45; }
.farben { aspect-ratio: 16 / 10; grid-template-rows: none; grid-template-columns: 1.4fr 1fr .8fr; }
.farbe { flex-direction: column; align-items: start; justify-content: end; }
}
@media (forced-colors: active) { button, [role=button], [role=tab], [role=switch] { border: 1px solid ButtonText !important; } }
</style>
</head>
<body>
<a class="skip mono" href="#inhalt">Zum Inhalt springen</a>
<header class="kopf" data-kopf>
<a class="marke" href="#oben" aria-label="Studio Wendel, Startseite">
<svg viewBox="0 0 28 28" aria-hidden="true"><circle cx="14" cy="14" r="13" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M7.5 10 11 19l3-7 3 7 3.5-9" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round"/></svg>
<span>Studio <em>Wendel</em></span>
</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="cta" href="#kontakt">Projekt anfragen <span aria-hidden="true">→</span></a>
<button class="menue" type="button" aria-expanded="false">Menü</button>
</header>
<main id="inhalt">
<section class="held" id="oben">
<div class="held__text">
<p class="mono">Fallstudie 07 · Marke & Leitsystem</p>
<h1>Ein Blatt mit <em>Eselsohr</em> und Kaffeerand.</h1>
<p class="held__lead">Wie aus einer Buchhandlung mit Siebträger ein Lesecafé mit eigener Marke wurde.</p>
<dl class="held__fakten">
<div><dt class="mono">Kunde</dt><dd>Eselsohr</dd></div>
<div><dt class="mono">Leistung</dt><dd>Marke, Leitsystem, Website</dd></div>
<div><dt class="mono">Jahr</dt><dd>2021</dd></div>
</dl>
</div>
<div class="held__bild"><img src="bilder/marke-eselsohr-hoch.jpg" alt="Wortbildmarke Eselsohr: ein Blatt mit rosa umgeknickter Ecke über kursivem Schriftzug, Rosé und Creme auf Anthrazit" width="800" height="1000"></div>
</section>
<article class="text" id="arbeiten">
<p class="einstieg">Neben der Kasse stand irgendwann eine Espressomaschine. Aus Stöbern wurde <em>Aufenthaltszeit</em>, und die Buchhandlung brauchte ein Gesicht.</p>
<section class="abschnitt">
<h2><span class="mono">01</span>Ausgangslage</h2>
<div class="abschnitt__inhalt">
<p>Hanna und Ole Rieck verkaufen seit 1987 Bücher in einem schmalen Eckladen am Rand der Altstadt. Erst kam der Kaffee für die Stöbernden, dann ein zweiter Sessel, dann Stammgäste, die nur zum Lesen kamen.</p>
<p>Der Name war schnell gefunden. Was fehlte, war ein Zeichen, das beides trägt: Buchhandlung und Café, Papier und Milchschaum. Und das auf einem Lesezeichen genauso funktioniert wie zwei Meter breit an der Fassade.</p>
</div>
</section>
<div class="bildpaar">
<figure><img src="bilder/marke-eselsohr.jpg" alt="Wortbildmarke Eselsohr mit dem Zusatz Buchhandlung und Lesecafé, quer gesetzt auf Anthrazit" width="1200" height="800"><figcaption class="mono">Wortmarke und Zeichen, Fassung dunkel</figcaption></figure>
<figure>
<div class="farben" role="img" aria-label="Farbkarte: Rosé #DCB3A8 auf Anthrazit #1F2024, dazu Creme #F1E9DC">
<span class="farbe" style="--f:#dcb3a8;color:#1f2024"><b>Rosé</b><span class="mono">#DCB3A8</span></span>
<span class="farbe" style="--f:#1f2024;color:#f1e9dc"><b>Anthrazit</b><span class="mono">#1F2024</span></span>
<span class="farbe" style="--f:#f1e9dc;color:#1f2024"><b>Creme</b><span class="mono">#F1E9DC</span></span>
</div>
<figcaption class="mono">Eine Farbe, zwei Grundtöne</figcaption>
</figure>
</div>
<section class="abschnitt" id="studio">
<h2><span class="mono">02</span>Idee</h2>
<div class="abschnitt__inhalt">
<p>Wir haben kein Buch gezeichnet, sondern eine einzelne Seite. Drei Zeilen deuten den Text an, oben rechts knickt die Ecke um, als hätte jemand die Stelle markiert. Eine Strichstärke, ein Akzent: Rosé auf Anthrazit, wie die Polster der Lesesessel.</p>
<p>Die Wortmarke setzt „Eselsohr“ in einer weichen Kursive, „Buchhandlung & Lesecafé“ steht klein und gesperrt darunter. Tüten, Lesezeichen und Fassade folgen demselben Raster.</p>
</div>
</section>
<blockquote class="zitat">
<p>„Seit dem neuen Schild fragt niemand mehr, ob man hier auch nur Kaffee trinken darf.“</p>
<footer class="mono">Hanna Rieck, Inhaberin</footer>
</blockquote>
<section class="abschnitt" id="journal">
<h2><span class="mono">03</span>Website</h2>
<div class="abschnitt__inhalt">
<p>Die Website erzählt vor allem Geschichten aus dem Laden: lange Texte, viele Bilder, Öffnungszeiten ganz unten. Der Header hält sich deshalb zurück. Beim Lesen geht er aus dem Weg, bei der ersten Bewegung nach oben ist er wieder da.</p>
<p>Am Seitenanfang liegt er durchsichtig über dem Bild. Erst wenn die Seite sich bewegt, bekommt er eine helle Fläche, damit die Links auf jedem Untergrund lesbar bleiben.</p>
</div>
</section>
<div class="zahlen">
<div><b>3</b><span class="mono">Wochen vom Briefing bis zur Fassade</span></div>
<div><b>12</b><span class="mono">Anwendungen, ein Raster</span></div>
<div><b>1</b><span class="mono">Farbe, zwei Schnitte</span></div>
</div>
<section class="weitere">
<div class="weitere__kopf"><h2>Weitere Arbeiten</h2><a class="mono" href="#arbeiten">Alle ansehen →</a></div>
<ul class="weitere__liste">
<li><a href="#krume"><img src="bilder/marke-krume-hoch.jpg" alt="Wortmarke krume in fetten dunkelbraunen Kleinbuchstaben unter einem Brotlaib auf Senfgelb" width="800" height="1000"><span>Krume <span class="mono">2023</span></span></a></li>
<li><a href="#roestkontor"><img src="bilder/marke-roestkontor-hoch.jpg" alt="Wortbildmarke Röstkontor: Kaffeebohne im Kreis über cremefarbenen Versalien auf Ziegelrot" width="800" height="1000"><span>Röstkontor <span class="mono">2024</span></span></a></li>
<li><a href="#licht"><img src="bilder/typo-plakat-hoch.jpg" alt="Plakat in Neongelb mit riesigen schwarzen Versalien LICHT und orangem Kreis" width="800" height="1000"><span>Werkschau Licht <span class="mono">2025</span></span></a></li>
</ul>
</section>
</article>
</main>
<footer class="fuss" id="kontakt">
<p>Ein Projekt im Kopf? <em>Erzählen Sie.</em></p>
<p class="mono">Studio Wendel · Marken, Leitsysteme, Websites · hallo@studio-wendel.example</p>
</footer>
<div class="pult mono" aria-label="Demo-Steuerung">
<div class="anzeige" data-anzeige data-stand="oben" aria-live="off">
<span class="mini" aria-hidden="true"><i></i><b style="top:14px"></b><b style="top:20px;right:9px"></b><b style="top:26px"></b><b style="top:32px;right:12px"></b></span>
<span class="anzeige__stand" data-stand-text><b>·</b> Seitenanfang · Header klar</span>
<span class="anzeige__werte" data-werte>translate 0 → -100 % · 200 ms ease-in-out</span>
</div>
<div class="varianten" role="group" aria-label="Variante">
<button type="button" data-v="weg" aria-pressed="true">Ganz weg</button>
<button type="button" data-v="logo" aria-pressed="false">Logo bleibt</button>
<button type="button" data-v="css" aria-pressed="false">Nur CSS</button>
</div>
</div>
<script>
(() => {
const html = document.documentElement;
const reduziert = () => html.classList.contains('reduziert');
const kopf = document.querySelector('[data-kopf]');
const anzeige = document.querySelector('[data-anzeige]');
const standText = document.querySelector('[data-stand-text]');
const werteText = document.querySelector('[data-werte]');
const kannCss = CSS.supports('container-type: scroll-state');
const SCHWELLE = 8; // px in einer Richtung, bevor der Header reagiert (gegen Zittern)
const WERTE = {
weg: 'translateY 0 → -100 % · 200 ms ease-in-out · Fläche 350 ms · Schwelle 8 px',
logo: 'Fläche + Links -100 % · 450 ms cubic-bezier(.215,.61,.355,1) · Fläche 400 ms, +200 ms',
css: 'scroll-state(scrolled: bottom) · translate 200 ms ease-in-out · ohne JS'
};
let variante = 'weg';
// ---------- Das Muster (JS-Variante): Richtung mit Schwelle, per rAF gedrosselt ----------
let letzteY = scrollY, summe = 0, richtung = 0, weg = false, geplant = false;
function pruefen() {
geplant = false;
const y = Math.max(0, scrollY), d = y - letzteY;
letzteY = y;
if (d !== 0) {
if (Math.sign(d) !== richtung) { richtung = Math.sign(d); summe = 0; }
summe += Math.abs(d);
}
const kopfH = kopf.offsetHeight;
if (variante !== 'css') {
kopf.classList.toggle('klebt', y > 0);
if (y <= kopfH) weg = false; // ganz oben immer sichtbar
else if (summe >= SCHWELLE) weg = richtung > 0;
if (reduziert()) weg = false;
kopf.classList.toggle('weg', weg && !kopf.matches(':focus-within'));
}
melden(y);
// scroll-state() wird erst im nächsten Bild ausgewertet: Anzeige kurz danach noch einmal lesen
if (variante === 'css') { requestAnimationFrame(() => requestAnimationFrame(() => melden(Math.max(0, scrollY)))); setTimeout(() => melden(Math.max(0, scrollY)), 120); }
}
addEventListener('scroll', () => { if (!geplant) { geplant = true; requestAnimationFrame(pruefen); } }, { passive: true });
// ---------- Anzeige (Demo-Beiwerk): liest den echten Zustand ----------
function melden(y) {
let stand, pfeil, text;
if (variante === 'css') {
const cs = getComputedStyle(kopf);
const runter = cs.getPropertyValue('--richtung').trim() === 'runter';
stand = reduziert() ? (y > 0 ? 'klebt' : 'oben') : runter ? 'weg' : (cs.getPropertyValue('--stand').trim() === 'klebt' ? 'klebt' : 'oben');
pfeil = runter ? '↓' : y > 0 ? '↑' : '·';
} else {
stand = kopf.classList.contains('weg') ? 'weg' : kopf.classList.contains('klebt') ? 'klebt' : 'oben';
pfeil = y <= 0 ? '·' : richtung > 0 ? '↓' : '↑';
}
text = stand === 'weg' ? (variante === 'logo' ? 'Runter · nur Logo bleibt' : 'Runter · Header weg')
: stand === 'klebt' ? (reduziert() ? 'Header bleibt · reduziert' : pfeil === '↓' ? 'Runter · unter Schwelle' : 'Hoch · Header zurück')
: 'Seitenanfang · Header klar';
const werte = reduziert() ? 'Header bleibt stehen · Fläche 150 ms linear' : WERTE[variante];
if (werteText.textContent !== werte) werteText.textContent = werte;
if (anzeige.dataset.stand !== stand || standText.dataset.t !== pfeil + text) {
anzeige.dataset.stand = stand;
standText.dataset.t = pfeil + text;
standText.innerHTML = `<b>${pfeil}</b> ${text}`;
}
}
// ---------- Varianten ----------
function setzen(v) {
variante = v;
html.dataset.variante = v;
kopf.classList.remove('weg');
weg = false;
document.querySelectorAll('[data-v]').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.v === v)));
requestAnimationFrame(pruefen);
}
document.querySelectorAll('[data-v]').forEach(b => {
if (b.dataset.v === 'css' && !kannCss) { b.disabled = true; b.title = 'Dieser Browser kennt scroll-state()-Container-Queries noch nicht'; }
b.addEventListener('click', () => { pause(); html.classList.remove('vorfuehrung'); setzen(b.dataset.v); });
});
kopf.addEventListener('focusin', () => kopf.classList.remove('weg'));
document.querySelectorAll('a[href^="#"]').forEach(a => a.addEventListener('click', e => {
const ziel = document.getElementById(a.getAttribute('href').slice(1));
if (!ziel) e.preventDefault();
}));
// ---------- Vorführung: die Seite scrollt von selbst, runter und wieder hoch ----------
// Relative Schritte (scrollBy), damit auch scroll-state(scrolled) die Richtung erkennt.
const PLAN = [ // [Dauer ms, Strecke als Anteil der Höhe des Fensters]
[500, 0], [1300, 1.4], [450, 0], [700, -0.3], [1500, 0], [1500, 1.1], [600, 0], [650, -0.25], [1300, 0],
[1100, 0.5], [500, 0], [1500, -2.45], [1300, 0]
];
const RUNDE = PLAN.reduce((s, p) => s + p[0], 0);
const glatt = q => 0.5 - 0.5 * Math.cos(q * Math.PI);
let uhr = null, t0 = 0, soll = 0, ist = 0;
function sollBei(ms) {
let t = 0, pos = 0;
for (const [dauer, strecke] of PLAN) {
if (ms < t + dauer) return pos + strecke * glatt((ms - t) / dauer);
t += dauer; pos += strecke;
}
return pos;
}
function vorfuehren() {
pause();
html.classList.add('vorfuehrung');
scrollTo(0, 0); ist = 0;
t0 = performance.now();
const schritt = t => {
const ms = (t - t0) % RUNDE;
if (t - t0 >= RUNDE) { t0 = t - ms; scrollTo(0, 0); ist = 0; }
soll = Math.max(0, sollBei(ms) * innerHeight);
const d = Math.round(soll - ist);
if (d) { scrollBy(0, d); ist += d; }
uhr = requestAnimationFrame(schritt);
};
uhr = requestAnimationFrame(schritt);
}
function pause() { cancelAnimationFrame(uhr); uhr = null; }
function neustart() { pause(); html.classList.remove('vorfuehrung'); scrollTo(0, 0); setzen(variante); }
setzen(variante);
})();
</script>
</body>
</html>