Logo-Wand / Social-Proof-Band
Kundenlogos eines Hamburger Studios unter dem Hero: als ziehbares Laufband, als Raster mit gestaffelt umklappenden Kacheln oder als Haarlinien-Raster mit Kennzahl beim Hover.
Agentur, Studio, SaaS mit vielen bekannten Kunden: Band mit Pause-Knopf (WCAG 2.2.2), dazu Klapp-Raster und Haarlinien-Raster als Ansichten.
Direkt unter dem Hero steht ein Band oder Raster mit Kunden- und Partnerlogos. Die Logos sind einfarbig (Graustufe oder Textfarbe), darüber steht ein kurzer Satz wie „Vertraut von …“. Bewegung ist ruhig oder fehlt ganz: ein endloses Laufband, das sich auch ziehen lässt, oder ein festes Raster, in dem die Kacheln in Abständen auf das nächste Logo umklappen. Beim Hover bekommt das Logo seine Markenfarbe, die anderen treten zurück. Jedes Logo verlinkt auf die passende Fallstudie.
Fremde Marken leihen Vertrauen, bevor der Besucher eine Zeile Text gelesen hat. Ein Raster mit Umklapp-Wechsel zeigt viele Logos auf wenig Fläche, ohne dass etwas ständig läuft. Verlinkte Logos machen aus Dekoration einen Weg zu den Referenzen.
Wann einsetzen
B2B-, SaaS- und Agenturseiten, Portfolios von Freelancern mit bekannten Kunden und Template-Startseiten. Der Platz eignet sich auch für „Bekannt aus“, Partner, Zertifikate, Verbände und Förderer.
Wann nicht
Mit weniger als etwa 5 Logos. Mit unbekannten Logos ohne Bezug zum Angebot. Mit Logos ohne Nutzungserlaubnis. Als dritte Laufband-Zeile auf derselben Seite. Mit farbigen Original-Logos in voller Sättigung, die das Layout zerreißen. Keine selbst gestalteten Siegel mit Umwelt-, Bio-, Regional- oder Sozialaussage: Eigensiegel ohne Zertifizierungssystem sind seit 27.09.2026 unzulässig (UWG Anhang Nr. 2a), vom Kunden prüfen lassen. Auszeichnungen nur als Preis eines Vergebers mit Wettbewerb und Jahr; echte Siegel mit Nachweis zeigt die Variante „Siegel mit Nachweis“.
Varianten
- Laufband mit Haarlinien, per Ziehen greifbar (cursor: grab), Logos verlinkt (Stripe)
- Festes Raster mit 4/6/8 Spalten, Kacheln klappen gestaffelt per rotateX auf das nächste Logo (Cursor)
- Statisches Raster mit Haarlinien-Rahmen, zeitlos und ohne JS
- Hover gibt dem Logo die Markenfarbe, Nachbarn dimmen (passt zu graustufen-zu-farbe und nachbarn-reagieren-hover)
- Logo plus Kennzahl oder Kurzzitat beim Hover
- Ruhiges Raster: Logos in natürlicher Breite, zentriert, Reihen per flex-wrap: balance gleich lang (Chrome 150), sonst per Messung
- Auszeichnungen mit Jahr: Jahreszahl, Grafik des Vergebers, Titel, Wettbewerb oder Guide mit Jahr, neueste zuerst (Gastro, Kanzlei); keine Eigensiegel mit Umwelt- oder Sozialaussage
- Siegel mit Nachweis: je Zeichen Träger, Art, Zertifikatsnummer, Gültigkeit und Link zu den Kriterien als dl, Logo nur Begleitung; ohne vollständige Daten erscheint das Zeichen nicht
Umsetzung
Markup: <section aria-labelledby="kunden"><h2 id="kunden">Vertraut von …</h2><ul><li><a href="/referenzen/kunde" aria-label="Kunde, Fallstudie"><svg …></svg></a></li>…</ul></section>. Logos als SVG mit fill: currentColor, damit eine Farbe für alle gilt und der Hover nur color ändert (300 ms cubic-bezier(.25, 1, .5, 1)). Feste Kachelbreite (Stripe: 172px) statt Logobreite, optischer Ausgleich über die Höhe je Logo. Laufband: Liste doppelt rendern (zweite Kopie aria-hidden="true", Links mit tabindex="-1"), per CSS @keyframes von translateX(0) auf translateX(-50%), linear, 30 bis 60 s, Hover pausiert (animation-play-state). Mit Ziehen besser per requestAnimationFrame und Pointer Events. Klapp-Raster: pro Kachel Vorder- und Rückseite, .flipped { transform: rotateX(180deg) } mit 600 ms ease-in-out, gestaffelt um 160 ms, alle 5 s; danach die Logos ohne Übergang tauschen und zurücksetzen. Logos nur mit Freigabe des Kunden zeigen.
Robust umsetzen
Telefon. Raster auf 4 Spalten und weniger Logos (Cursor blendet ab Kachel 5 aus). Das Laufband bleibt, läuft aber langsamer. Tippen statt Hover.
Ohne Bewegung. Laufband anhalten oder als statisches Raster zeigen. Kein Umklappen (so macht es Cursor). Farbwechsel beim Hover ohne Übergang.
Barrierefrei. Jedes Logo braucht einen Namen (alt oder aria-label mit dem Firmennamen). Die doppelte Laufband-Kopie ist aria-hidden und enthält keine Fokusziele. Ein bewegtes Band muss sich anhalten lassen (WCAG 2.2.2); Pause beim Hover reicht auf Touch nicht, also einen sichtbaren Pause-Knopf anbieten. Fokussierte Logos ins Sichtfeld holen und hervorheben. Die einfarbigen Logos brauchen genug Kontrast zum Grund.
Code
<!DOCTYPE html>
<!--
Logo-Wand / Social-Proof-Band
Fundus · Hero & Section-Layouts · https://cecodesign.de/lab/fundus/hero-layouts/logo-wand-social-proof/
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>Logo-Wand / Social-Proof-Band · Fundus-Demo</title>
<script>document.documentElement.classList.add('js');if(matchMedia('(prefers-reduced-motion: reduce)').matches)document.documentElement.classList.add('reduziert')</script>
<style>
/* Hausschriften des Studios */
@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-italic.woff2') format('woff2'); font-weight: 400; font-style: italic; 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-style: normal; 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; }
/* Schriften der (erfundenen) Kundenlogos: jede Marke hat ihre eigene Wortmarke */
@font-face { font-family: 'Fraunces'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/fraunces@latest/latin-300-italic.woff2') format('woff2'); font-weight: 300; font-style: italic; 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: 'Cormorant Garamond'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/cormorant-garamond@latest/latin-600-normal.woff2') format('woff2'); font-weight: 600; font-style: normal; font-display: swap; }
@font-face { font-family: 'Cormorant Garamond'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/cormorant-garamond@latest/latin-500-italic.woff2') format('woff2'); font-weight: 500; font-style: italic; font-display: swap; }
@font-face { font-family: 'Michroma'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/michroma@latest/latin-400-normal.woff2') format('woff2'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'Questrial'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/questrial@latest/latin-400-normal.woff2') format('woff2'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'Jost'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/jost@latest/latin-500-normal.woff2') format('woff2'); font-weight: 500; font-display: swap; }
@font-face { font-family: 'Jost'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/jost@latest/latin-700-normal.woff2') format('woff2'); font-weight: 700; font-display: swap; }
@font-face { font-family: 'Bricolage Grotesque'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/bricolage-grotesque@latest/latin-500-normal.woff2') format('woff2'); font-weight: 500; font-display: swap; }
@font-face { font-family: 'Bricolage Grotesque'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/bricolage-grotesque@latest/latin-800-normal.woff2') format('woff2'); font-weight: 800; font-display: swap; }
@font-face { font-family: 'Source Serif 4'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/source-serif-4@latest/latin-400-normal.woff2') format('woff2'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'Fraunces'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/fraunces:vf@latest/latin-full-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-style: normal; font-display: swap; }
@font-face { font-family: 'Fraunces'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/fraunces:vf@latest/latin-wght-italic.woff2') format('woff2-variations'); font-weight: 100 900; font-style: italic; font-display: swap; }
@font-face { font-family: 'Commissioner'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/commissioner:vf@latest/latin-full-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-display: swap; }
@font-face { font-family: 'Atkinson Hyperlegible Next'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/atkinson-hyperlegible-next:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 200 800; font-display: swap; }
@font-face { font-family: 'EB Garamond'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/eb-garamond:vf@latest/latin-wght-normal.woff') format('woff'); font-weight: 400 800; font-style: normal; font-display: swap; }
@font-face { font-family: 'EB Garamond'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/eb-garamond:vf@latest/latin-wght-italic.woff2') format('woff2-variations'); font-weight: 400 800; font-style: italic; font-display: swap; }
@font-face { font-family: 'DM Mono'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/dm-mono@latest/latin-500-normal.woff2') format('woff2'); font-weight: 500; font-display: swap; }
@font-face { font-family: 'Unbounded'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/unbounded@latest/latin-900-normal.woff2') format('woff2'); font-weight: 900; font-display: swap; }
:root {
--papier: #efece6; --kachel: #e6e2da; --tinte: #151514; --leise: rgba(21, 21, 20, .66); --linie: rgba(21, 21, 20, .15);
--grau: #6c6963; /* Logos einfarbig, 4,6 : 1 auf Papier */
--akzent: #2d5b43;
/* Gemessen (Stripe, Cursor) */
--fill: cubic-bezier(0.25, 1, 0.5, 1); /* Logo-Farbwechsel 300 ms */
--klapp: cubic-bezier(0.42, 0, 0.58, 1); /* ease-in-out, Flip 600 ms */
--kachel-b: 172px; /* Stripe: Laufband-Kachel */
--dimm: .3;
}
* { 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; }
body { overflow-x: hidden; }
button { font: inherit; color: inherit; }
a { color: inherit; }
.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: -.02em; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
/* ---------- Seite ---------- */
.buehne { min-height: 100svh; max-width: 1440px; margin: 0 auto; padding: clamp(16px, 2.6vw, 34px) clamp(16px, 3.4vw, 48px) clamp(16px, 2.6vw, 30px); display: grid; grid-template-columns: minmax(0, 1fr); grid-template-rows: auto 1fr auto auto; }
.nav { display: flex; align-items: center; justify-content: space-between; gap: 20px; }
.marke { display: flex; align-items: center; gap: 10px; font-weight: 600; font-size: 19px; letter-spacing: -.03em; text-decoration: none; }
.marke svg { width: 22px; height: 22px; }
.nav ul { display: flex; gap: clamp(18px, 2.6vw, 36px); list-style: none; padding: 0; }
.nav ul a { text-decoration: none; transition: color 150ms linear; }
.nav ul a:hover { color: var(--tinte); }
.knopf { display: inline-flex; align-items: center; gap: 10px; height: 40px; padding: 0 18px; border-radius: 999px; background: var(--tinte); color: var(--papier); text-decoration: none; font-size: 14px; font-weight: 500; transition: background 150ms linear; }
.knopf:hover { background: var(--akzent); }
.knopf.leer { background: none; color: var(--tinte); padding: 0 4px; }
.knopf.leer:hover { color: var(--akzent); }
.nav .menue { display: none; }
.held { display: grid; grid-template-columns: repeat(12, 1fr); column-gap: clamp(16px, 2vw, 28px); row-gap: 26px; grid-template-rows: 1fr; padding-top: clamp(40px, 9vh, 110px); }
.held h1 { grid-column: 1 / 9; grid-row: 1; align-self: start; font-size: clamp(40px, 6.3vw, 98px); font-weight: 500; line-height: .96; letter-spacing: -.048em; }
.held h1 em { color: var(--akzent); letter-spacing: -.03em; }
/* Bild im Satz: Pille mit Arbeitsprobe */
/* Arbeitsprobe als Miniatur einer Magazin-Doppelseite (CSS statt Foto) */
.held .pille { display: inline-block; width: 1.75em; height: .74em; border-radius: 999px; vertical-align: -.02em; margin: 0 .04em;
background: linear-gradient(90deg, transparent 49.4%, rgba(0, 0, 0, .14) 50%, transparent 50.6%),
repeating-linear-gradient(to bottom, rgba(21, 21, 20, .34) 0 .028em, transparent .028em .075em) 72% 50% / 24% 44% no-repeat,
linear-gradient(#2d5b43, #2d5b43) 29% 50% / 18% 52% no-repeat,
linear-gradient(90deg, #e6e2da, #f6f3ec 46%, #e9e5dc 50%, #f9f7f2 54%, #e6e2da); }
.held .neben { grid-column: 9 / 13; grid-row: 1; align-self: end; display: grid; gap: 22px; }
.held .neben p:not(.mono) { font-size: clamp(15px, 1.15vw, 17px); line-height: 1.5; color: var(--leise); max-width: 34ch; }
.held .neben p b { color: var(--tinte); font-weight: 500; }
.held .tasten { display: flex; gap: 10px; flex-wrap: wrap; }
/* ---------- Social-Proof ---------- */
.beweis { margin-top: clamp(36px, 7vh, 80px); }
.beweis-kopf { display: flex; justify-content: space-between; align-items: baseline; gap: 12px 24px; padding-bottom: 16px; }
.beweis h2 { font-size: 15px; font-weight: 500; letter-spacing: -.01em; }
.beweis h2 span { color: var(--leise); font-weight: 400; }
.kopf-rechts { display: flex; gap: 16px; align-items: baseline; min-width: 0; }
.fallzeile { display: flex; gap: 10px; align-items: baseline; white-space: nowrap; text-decoration: none; min-width: 0; }
.fallzeile b { color: var(--tinte); font-weight: 500; }
.fallzeile .pfeil { display: inline-block; transition: translate 300ms var(--fill); }
.fallzeile.hat .pfeil { translate: 3px -3px; }
.fallzeile .text { overflow: hidden; text-overflow: ellipsis; }
.fallzeile .text.neu { animation: neu 300ms var(--fill); }
@keyframes neu { from { opacity: 0; translate: 0 5px; } }
/* Ein Logo: einfarbig (currentColor), Hover gibt Markenfarbe, Nachbarn dimmen */
.logo { position: relative; display: grid; place-items: center; width: 100%; height: 100%; color: var(--grau); text-decoration: none; outline: none; -webkit-tap-highlight-color: transparent;
transition: color 300ms var(--fill), opacity 150ms linear; }
.logo svg { display: block; height: calc(var(--h, 26px) * var(--s, 1)); width: auto; max-width: 80%; overflow: visible; pointer-events: none; }
.logo:focus-visible { color: var(--marke); }
.logo:focus-visible::after { content: ''; position: absolute; inset: 6px; border: 1.5px solid var(--marke); border-radius: 6px; }
.logo.an { color: var(--marke); }
@media (hover: hover) { .logo:hover { color: var(--marke); } }
.wand.aktiv .logo:not(.an), .wand:has(.logo:focus-visible) .logo:not(:focus-visible) { opacity: var(--dimm); }
.ansicht[hidden] { display: none; }
/* Variante 1: Laufband mit Haarlinien, per Ziehen greifbar (Stripe) */
.band { position: relative; border-block: 1px solid var(--linie); overflow: hidden; cursor: grab; user-select: none; -webkit-user-select: none; touch-action: pan-y;
mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent); }
.band.zieht { cursor: grabbing; }
.spur { display: flex; width: max-content; will-change: transform; }
.spur ul { display: flex; list-style: none; padding: 0; }
.spur li { flex: none; width: var(--kachel-b); height: 108px; }
.band.zieht .logo { pointer-events: none; }
.halt { display: none; place-items: center; flex: none; width: 26px; height: 26px; padding: 0; border: 1px solid var(--linie); border-radius: 50%; background: none; cursor: pointer; color: var(--leise); transition: color 150ms linear, border-color 150ms linear; align-self: center; }
.halt:hover, .halt:focus-visible { color: var(--tinte); border-color: rgba(21, 21, 20, .4); }
.halt svg { width: 10px; height: 10px; }
[data-v='band'] .halt { display: grid; }
/* Variante 2: festes Raster, Kacheln klappen gestaffelt um (Cursor) */
.klapp { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; list-style: none; padding: 0; }
.karte { height: 4rem; perspective: 1000px; }
.karte:nth-child(n+5) { display: none; }
.innen { position: relative; height: 100%; transform-style: preserve-3d; transition: transform 600ms var(--klapp); }
.karte.flipped .innen { transform: rotateX(180deg); }
.innen.sofort { transition: none; }
.seite { position: absolute; inset: 0; display: grid; backface-visibility: hidden; -webkit-backface-visibility: hidden; background: var(--kachel); border-radius: 10px; }
.seite.hinten { transform: rotateX(180deg); }
@media (min-width: 660px) { .klapp { grid-template-columns: repeat(6, 1fr); } .karte { height: 4.5rem; } .karte:nth-child(n+5) { display: block; } .karte:nth-child(n+7) { display: none; } }
@media (min-width: 900px) { .klapp { grid-template-columns: repeat(8, 1fr); } .karte { height: 6.25rem; } .karte:nth-child(n+7) { display: block; } }
/* Variante 3: statisches Raster mit Haarlinien, Hover zeigt Kennzahl */
.linien { display: grid; grid-template-columns: repeat(8, 1fr); list-style: none; padding: 0; border-top: 1px solid var(--linie); border-left: 1px solid var(--linie); }
.linien li { height: 116px; border-right: 1px solid var(--linie); border-bottom: 1px solid var(--linie); }
.linien .logo svg { transition: translate 300ms var(--fill); }
.linien .logo .zahl, .linien .logo .ecke { position: absolute; opacity: 0; transition: opacity 150ms linear, translate 300ms var(--fill); }
.linien .logo .zahl { left: 12px; right: 12px; bottom: 10px; translate: 0 6px; font-size: 9.5px; letter-spacing: .1em; line-height: 1.35; }
.linien .logo .zahl b { display: block; color: var(--tinte); font-weight: 500; }
.linien .logo .ecke { top: 10px; right: 10px; width: 10px; height: 10px; translate: -4px 4px; color: var(--marke); }
.linien .logo.an svg[data-logo], .linien .logo:focus-visible svg[data-logo] { translate: 0 -14px; }
.linien .logo.an .zahl, .linien .logo.an .ecke, .linien .logo:focus-visible .zahl, .linien .logo:focus-visible .ecke { opacity: 1; translate: 0 0; }
/* Demo-Bedienung */
.steuerung { display: flex; justify-content: space-between; align-items: center; gap: 12px 24px; flex-wrap: wrap; padding-top: 22px; }
.modi { display: flex; gap: 4px; flex-wrap: wrap; }
.modi button { letter-spacing: inherit; text-transform: inherit; color: var(--leise); background: none; border: 1px solid var(--linie); border-radius: 999px; height: 30px; padding: 0 12px; cursor: pointer; transition: color 150ms linear, background 150ms linear, border-color 150ms linear; }
.modi button:hover { color: var(--tinte); border-color: rgba(21, 21, 20, .4); }
.modi button[aria-pressed='true'] { background: var(--tinte); color: var(--papier); border-color: var(--tinte); }
.werte { display: flex; flex-wrap: wrap; gap: 4px 16px; justify-content: flex-end; max-width: 900px; }
.werte b { color: var(--tinte); font-weight: 500; }
/* Vorführung: gezeichneter Zeiger */
.zeiger { position: fixed; left: 0; top: 0; z-index: 9; width: 18px; height: 18px; pointer-events: none; display: none; }
.zeiger .faust { display: none; }
.zeiger.greift .pfeilform { display: none; }
.zeiger.greift .faust { display: block; }
.vorfuehrung .zeiger { display: block; }
/* Reduzierte Bewegung: Band steht als zentriertes Raster (Stripe --centered), kein Flip */
.reduziert .band { mask-image: none; cursor: default; }
.reduziert .spur { width: auto; transform: none !important; }
.reduziert .spur ul { display: grid; grid-template-columns: repeat(8, 1fr); width: 100%; }
.reduziert .spur li { width: auto; }
.reduziert .spur ul[aria-hidden] { display: none; }
.reduziert [data-v='band'] .halt { display: none; }
.reduziert .logo, .reduziert .logo svg, .reduziert .linien .logo .zahl, .reduziert .linien .logo .ecke, .reduziert .innen, .reduziert .fallzeile .pfeil { transition: none; }
.reduziert .fallzeile .text.neu { animation: none; }
.reduziert .linien .logo.an svg, .reduziert .linien .logo:focus-visible svg { translate: none; }
.reduziert .linien .logo .zahl, .reduziert .linien .logo .ecke { translate: none; }
.reduziert .linien .logo svg { translate: 0 0; }
/* Mittlere Breiten */
@media (max-width: 1100px) {
.held { grid-template-rows: auto 1fr; }
.held h1 { grid-column: 1 / -1; }
.held .neben { grid-column: 1 / -1; grid-row: 2; grid-template-columns: minmax(0, 1fr) auto; align-items: end; padding-bottom: 0; }
.held .neben .mono { grid-column: 1 / -1; }
.linien li { height: 100px; }
.linien .logo .zahl span { display: none; }
.logo { --s: .88; }
}
@media (max-width: 899px) {
.linien { grid-template-columns: repeat(4, 1fr); }
.linien li:nth-child(n+13) { display: none; }
}
/* Telefon */
@media (max-width: 659px) {
.nav ul, .nav .knopf { display: none; }
.nav .menue { display: inline-flex; align-items: center; height: 34px; padding: 0 14px; border: 1px solid var(--linie); border-radius: 999px; background: none; }
.buehne { grid-template-rows: auto auto auto auto; }
.held { padding-top: 44px; grid-template-rows: auto auto; }
.held h1 { font-size: clamp(38px, 11vw, 56px); }
.held .neben { grid-template-columns: 1fr; }
.beweis { margin-top: 48px; }
.beweis-kopf { flex-direction: column; gap: 6px; }
.beweis h2 span { display: block; }
:root { --kachel-b: 136px; }
.spur li { height: 84px; }
.logo { --s: .82; }
.reduziert .spur ul { grid-template-columns: repeat(4, 1fr); }
.klapp .logo { --s: .7; }
.klapp .logo svg, .linien .logo svg { max-width: 86%; }
.linien .logo { --s: .66; }
.linien li { height: 86px; }
.linien .logo .zahl { left: 7px; right: 7px; bottom: 6px; font-size: 8px; letter-spacing: .06em; }
.linien .logo .zahl, .linien .logo .ecke { display: none; }
.linien .logo.an svg[data-logo] { translate: none; }
.werte { justify-content: flex-start; }
}
.vorschau body { overflow: hidden; }
.vorschau .buehne { height: 100svh; }
.vorschau .werte { display: none; }
.vorschau .held { padding-top: 34px; }
.vorschau .beweis { margin-top: 24px; }
.vorschau .linien li { height: 88px; }
.vorschau .linien .logo.an svg[data-logo] { translate: 0 -12px; }
/* ===================== Varianten (Regel 13: ?v=laufband | raster | auszeichnungen | siegel-nachweis) ===================== */
[data-v-sektion][hidden] { display: none !important; }
.vwahl { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.vwahl > span, .modi > span { margin-right: 6px; min-width: 64px; }
.pultspalte .modi { align-items: center; }
.ruhig h2 span, .preise h2 span { color: var(--leise); font-weight: 400; }
.vwahl button { letter-spacing: inherit; text-transform: inherit; color: var(--leise); background: none; border: 1px solid var(--linie); border-radius: 999px; height: 30px; padding: 0 12px; cursor: pointer; transition: color 150ms linear, background 150ms linear, border-color 150ms linear; }
.vwahl button:hover { color: var(--tinte); border-color: var(--tinte); }
.vwahl button[aria-pressed='true'] { background: var(--tinte); color: var(--papier); border-color: var(--tinte); }
.vwahl button:focus-visible, .modi button:focus-visible { outline: 2px solid var(--akzent); outline-offset: 2px; }
.pultspalte { display: grid; gap: 8px; }
html:not([data-v='laufband']) .modi[data-ansichten] { display: none; }
.weiche { display: none; }
html[data-v='raster'] .weiche { display: flex; }
/* ---------- Welt Handwerk (Rezept handwerk-werkstatt-klartext): ruhiges Raster ---------- */
html[data-v='raster'] { --papier: #efedea; --kachel: #d3cdc2; --tinte: #1a1a1a; --leise: rgba(26, 26, 26, .62); --linie: rgba(26, 26, 26, .16); --akzent: #fb6100; --grau: #4f4b46;
font-family: 'Atkinson Hyperlegible Next', system-ui, sans-serif; }
html[data-v='raster'] .marke { font: 700 20px/1 'Archivo', sans-serif; font-stretch: 75%; letter-spacing: .02em; text-transform: uppercase; }
html[data-v='raster'] .held h1 { font: 700 clamp(42px, 6.2vw, 96px)/.98 'Archivo', sans-serif; font-stretch: 75%; letter-spacing: -.01em; text-transform: uppercase; }
html[data-v='raster'] .held h1 em { font: inherit; color: var(--akzent); letter-spacing: inherit; }
html[data-v='raster'] .knopf { background: var(--akzent); color: #1a1a1a; font-weight: 700; transition: background 150ms cubic-bezier(0.4, 0, 0.2, 1); }
html[data-v='raster'] .knopf:hover { background: #ff7a26; }
html[data-v='raster'] .knopf.leer { background: none; }
html[data-v='raster'] .nav .knopf { background: var(--tinte); color: var(--papier); }
html[data-v='raster'] .held .pille { background: radial-gradient(circle at 50% 0, transparent 46%, rgba(0, 0, 0, .18) 47% 52%, transparent 53%) 0 0 / 18px 14px, radial-gradient(circle at 50% 0, transparent 46%, rgba(0, 0, 0, .18) 47% 52%, transparent 53%) 9px 7px / 18px 14px, linear-gradient(160deg, #c2552e, #9c3f22); }
html[data-v='raster'] .beweis h2 { font-size: 17px; font-weight: 700; }
.ruhig { margin-top: clamp(36px, 7vh, 80px); }
.ruhig .beweis-kopf { border-bottom: 1px solid var(--linie); margin-bottom: clamp(26px, 4vh, 44px); }
/* Das Muster: Logos in natürlicher Breite, zentriert, Reihen gleich lang */
.ruhig-liste { list-style: none; padding: 0; margin: 0 auto; display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: clamp(22px, 3.4vh, 36px) clamp(32px, 4.4vw, 64px); max-inline-size: var(--breite-max, 100%); }
@supports (flex-wrap: balance) {
.ruhig-liste:not(.ohne-balance) { flex-wrap: balance; }
}
.ruhig-liste li { flex: none; }
.ruhig-liste a { display: block; color: var(--grau); text-decoration: none; opacity: .78; transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1), color 150ms cubic-bezier(0.4, 0, 0.2, 1); outline: none; }
.ruhig-liste a:hover, .ruhig-liste a.an { opacity: 1; color: var(--tinte); }
.ruhig-liste a:focus-visible { opacity: 1; color: var(--tinte); outline: 2px solid var(--akzent); outline-offset: 8px; border-radius: 2px; }
.ruhig-liste svg { display: block; height: var(--h, 40px); width: auto; overflow: visible; }
.ruhig-fuss { display: flex; justify-content: center; margin-top: clamp(24px, 4vh, 40px); }
.ruhig-fuss p { font-size: 15px; color: var(--leise); text-align: center; max-width: 60ch; }
/* ---------- Welt Gastro (Rezept gastro-warm-appetitlich): Auszeichnungen mit Jahr ---------- */
html[data-v='auszeichnungen'] { --papier: #f0ece0; --kachel: #e6dfcd; --tinte: #353839; --leise: rgba(53, 56, 57, .62); --linie: rgba(53, 56, 57, .16); --akzent: #293e3a; --grau: #4e5152; --gastro: cubic-bezier(0.16, 1, 0.3, 1);
font-family: 'Commissioner', system-ui, sans-serif; }
html[data-v='auszeichnungen'] .marke { font: 600 22px/1 'Fraunces', Georgia, serif; font-variation-settings: 'SOFT' 100, 'opsz' 30; letter-spacing: -.01em; }
html[data-v='auszeichnungen'] .held h1 { font: 520 clamp(40px, 6vw, 92px)/1.02 'Fraunces', Georgia, serif; font-variation-settings: 'SOFT' 80; letter-spacing: -.01em; }
html[data-v='auszeichnungen'] .held h1 em { font: italic 420 1em/1 'Fraunces', Georgia, serif; color: #6f8a68; letter-spacing: -.01em; }
html[data-v='auszeichnungen'] .knopf { background: var(--akzent); color: var(--papier); transition: background 200ms var(--gastro); }
html[data-v='auszeichnungen'] .knopf:hover { background: #1d2d2a; }
html[data-v='auszeichnungen'] .knopf.leer { background: none; color: var(--tinte); }
html[data-v='auszeichnungen'] .held .pille { background: radial-gradient(circle at 50% 50%, #f6f1e4 0 22%, #e9e1cd 23% 31%, transparent 32%), radial-gradient(circle at 30% 40%, #8a5a3c 0 6%, transparent 7%), radial-gradient(circle at 68% 62%, #7f937a 0 8%, transparent 9%), #eac16e; }
.preise { margin-top: clamp(36px, 7vh, 80px); }
.preise .beweis-kopf { padding-bottom: 18px; }
.preise h2 { font: 520 21px/1.15 'Fraunces', Georgia, serif; font-variation-settings: 'SOFT' 60; }
.preise h2 span { font: 400 15px/1.4 'Commissioner', sans-serif; }
.preise-liste { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); border-top: 1px solid var(--linie); border-bottom: 1px solid var(--linie); }
.preise-liste li + li { border-left: 1px solid var(--linie); }
.preis { position: relative; display: grid; grid-template-rows: auto auto 1fr; gap: 14px; height: 100%; padding: 22px clamp(14px, 1.6vw, 24px) 24px; color: var(--tinte); text-decoration: none; outline: none; transition: opacity 200ms var(--gastro); }
.preis-jahr { font: 560 clamp(30px, 2.8vw, 44px)/1 'Fraunces', Georgia, serif; font-variation-settings: 'SOFT' 50; font-variant-numeric: lining-nums tabular-nums; letter-spacing: -.01em; display: flex; justify-content: space-between; align-items: baseline; }
.preis-jahr i { font: 500 10.5px/1 'Geist Mono', ui-monospace, monospace; font-style: normal; letter-spacing: .14em; text-transform: uppercase; color: var(--leise); }
.preis .siegel { width: clamp(58px, 5.2vw, 76px); height: auto; color: var(--grau); transition: color 200ms var(--gastro), rotate 700ms var(--gastro); }
.preis-text { display: grid; gap: 6px; align-content: start; }
.preis-text b { font: 520 18px/1.2 'Fraunces', Georgia, serif; font-variation-settings: 'SOFT' 60; }
.preis-text span { font-size: 14px; line-height: 1.45; color: var(--leise); }
.preise-liste:has(.preis.an, .preis:focus-visible) .preis:not(.an, :focus-visible) { opacity: .42; }
.preis.an .siegel, .preis:focus-visible .siegel { color: var(--marke); rotate: -8deg; }
@media (hover: hover) { .preis:hover .siegel { color: var(--marke); rotate: -8deg; } .preise-liste:has(.preis:hover) .preis:not(:hover) { opacity: .42; } }
.preis:focus-visible::after { content: ''; position: absolute; inset: 4px; border: 1.5px solid var(--marke); border-radius: 4px; }
.reduziert .preis, .reduziert .preis .siegel, .reduziert .ruhig-liste a { transition: none; }
.reduziert .preis .siegel { rotate: none !important; }
.vorschau .ruhig, .vorschau .preise { margin-top: 26px; }
.vorschau .ruhig-fuss { display: none; }
.vorschau .preis { gap: 10px; padding-top: 16px; padding-bottom: 16px; }
.vorschau .preis-text span { font-size: 13px; }
/* ---------- Welt Weingut (Rezept weingut-hofladen-herkunft): Siegel mit Nachweis ---------- */
html[data-v='siegel-nachweis'] { --papier: #f1ece2; --kachel: #e6dccb; --tinte: #22211f; --leise: #5a564f; --linie: #d8cfc0; --akzent: #8a3a2b; --grau: #5a564f;
--wein-ein: cubic-bezier(0.16, 1, 0.3, 1); --wein-aus: cubic-bezier(0.7, 0, 0.84, 0);
font-family: 'EB Garamond', Georgia, serif; }
html[data-v='siegel-nachweis'] .mono { font-family: 'DM Mono', ui-monospace, monospace; }
html[data-v='siegel-nachweis'] .marke { font: 500 23px/1 'EB Garamond', Georgia, serif; letter-spacing: .01em; }
html[data-v='siegel-nachweis'] .held h1 { font: 400 clamp(2.75rem, 1.6rem + 5vw, 6rem)/1 'EB Garamond', Georgia, serif; letter-spacing: -.015em; }
html[data-v='siegel-nachweis'] .held h1 em { font-style: italic; color: var(--tinte); letter-spacing: -.01em; }
html[data-v='siegel-nachweis'] .held .neben p:not(.mono) { font-size: 1.25rem; line-height: 1.5; color: var(--grau); }
html[data-v='siegel-nachweis'] .knopf { font: 500 .8125rem/1 'DM Mono', ui-monospace, monospace; letter-spacing: .08em; text-transform: uppercase; border-radius: 0; background: var(--akzent); color: #fff; transition: background 150ms var(--wein-ein); }
html[data-v='siegel-nachweis'] .knopf:hover { background: #6f2e22; }
html[data-v='siegel-nachweis'] .knopf.leer { background: none; color: var(--tinte); }
html[data-v='siegel-nachweis'] .nav .knopf { background: none; color: var(--tinte); box-shadow: inset 0 0 0 1px var(--tinte); }
html[data-v='siegel-nachweis'] .held .pille { border-radius: 0; background: url('bilder/lagen-steckbrief-karte-sonnwendberg.webp') 50% 60% / cover, #8f8a6c; }
.nachweise { margin-top: clamp(36px, 7vh, 80px); }
.nachweise .beweis-kopf { padding-bottom: 18px; }
.nachweise h2 { font: 400 clamp(1.5rem, 1.2rem + 1vw, 2rem)/1.1 'EB Garamond', Georgia, serif; }
.nachweise h2 span { display: block; margin-top: 4px; font: 500 .75rem/1.4 'DM Mono', ui-monospace, monospace; letter-spacing: .1em; text-transform: uppercase; color: var(--leise); }
/* Das Muster: je Zeichen eine dl mit Träger, Art, Nummer, Gültigkeit und Kriterien. Das Logo ist nur Begleitung. */
.nachweis-liste[data-anzahl='3'] { --spalten: 3; }
.nachweis-liste { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(var(--spalten, 4), minmax(0, 1fr)); border-top: 1px solid var(--tinte); border-bottom: 1px solid var(--linie); }
.nachweis { position: relative; display: grid; grid-template-rows: auto auto 1fr; gap: 14px; padding: 20px clamp(14px, 1.6vw, 24px) 22px; border-left: 1px solid var(--linie); transition: background-color 150ms var(--wein-ein); }
.nachweis:first-child { border-left: 0; padding-left: 0; }
.nachweis.an { background: rgba(230, 220, 203, .55); }
.nachweis.geht { opacity: 0; translate: 0 8px; transition: opacity 200ms var(--wein-aus), translate 200ms var(--wein-aus); }
.nachweis.neu { animation: n-rein 450ms var(--wein-ein) both; }
@keyframes n-rein { from { opacity: 0; translate: 0 12px; } }
.nachweis-kopf { display: flex; align-items: center; gap: 12px; }
.nachweis .zeichen { flex: none; width: 40px; height: 40px; color: var(--grau); transition: color 150ms var(--wein-ein); }
.nachweis.an .zeichen { color: var(--akzent); }
.nachweis h3 { font: italic 400 1.375rem/1.15 'EB Garamond', Georgia, serif; letter-spacing: -.005em; text-wrap: balance; }
.nachweis dl { display: grid; gap: 0; margin: 0; align-content: start; }
.nachweis dl > div { display: grid; gap: 2px; padding: 7px 0 8px; border-top: 1px solid var(--linie); }
.nachweis dt { font: 500 .6875rem/1.35 'DM Mono', ui-monospace, monospace; letter-spacing: .1em; text-transform: uppercase; color: var(--leise); }
.nachweis dd { margin: 0; font-size: 1.0625rem; line-height: 1.3; color: var(--tinte); overflow-wrap: break-word; hyphens: auto; }
.nachweis dd.nr, .nachweis dd time { font: 500 .8125rem/1.4 'DM Mono', ui-monospace, monospace; letter-spacing: .02em; }
.nachweis dd a { font: 500 .75rem/1.4 'DM Mono', ui-monospace, monospace; letter-spacing: .08em; text-transform: uppercase; color: var(--akzent); text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }
.nachweis dd a:hover { text-decoration-thickness: 2px; }
.nachweis dd a:focus-visible { outline: 2px solid var(--akzent); outline-offset: 3px; }
.nachweis-fuss { margin-top: 16px; font-size: 1.0625rem; color: var(--grau); max-width: 66ch; }
.pruefung { display: none; }
html[data-v='siegel-nachweis'] .pruefung { display: flex; }
.vorschau .nachweis-fuss { display: none; }
.vorschau .nachweise { margin-top: 26px; }
.vorschau .nachweis { padding-top: 14px; padding-bottom: 14px; gap: 10px; }
.vorschau .nachweis dl > div { padding: 4px 0; }
.vorschau[data-v='siegel-nachweis'] .held h1 { font-size: 3.6rem; }
.vorschau[data-v='siegel-nachweis'] .held .neben p:not(.mono) { display: none; }
.vorschau[data-v='siegel-nachweis'] .held { padding-top: 24px; }
@media (max-width: 1100px) { .nachweise .nachweis-liste { --spalten: 2; } .nachweis:nth-child(odd) { border-left: 0; padding-left: 0; } .nachweis:nth-child(n+3) { border-top: 1px solid var(--linie); } }
@media (max-width: 659px) {
.nachweise .nachweis-liste { --spalten: 1; }
.nachweis { border-left: 0; padding-inline: 0; }
.nachweis + .nachweis { border-top: 1px solid var(--linie); }
.nachweise h2 { font-size: 1.5rem; }
}
@media (hover: hover) { .nachweis:hover { background: rgba(230, 220, 203, .55); } .nachweis:hover .zeichen { color: var(--akzent); } }
.reduziert .nachweis, .reduziert .nachweis .zeichen { transition: none; }
.reduziert .nachweis.neu { animation: none; }
.reduziert .nachweis.geht { translate: none; }
@media (max-width: 900px) {
.preise-liste { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.preise-liste li:nth-child(4) { border-left: 0; }
.preise-liste li:nth-child(n+4) { border-top: 1px solid var(--linie); }
}
@media (max-width: 659px) {
.preise-liste { grid-template-columns: 1fr; }
.preise-liste li + li { border-left: 0; border-top: 1px solid var(--linie); }
.preis { grid-template-columns: 76px 56px 1fr; grid-template-rows: auto; align-items: center; gap: 14px; padding: 16px 0; }
.preis-jahr { font-size: 28px; display: block; }
.preis-jahr i { display: none; }
.preis .siegel { width: 52px; }
.preis-text b { font-size: 17px; }
.ruhig-liste { gap: 22px 30px; }
.ruhig h2 span, .preise h2 span { display: block; }
.ruhig-liste svg { height: calc(var(--h, 40px) * .8); }
}
@media (forced-colors: active) {
button, [role=button], [role=tab], [role=switch] { border: 1px solid ButtonText !important; }
}
</style>
</head>
<body>
<div class="buehne">
<header class="nav">
<a class="marke" href="#start" aria-label="Reede, zur Startseite">
<svg viewBox="0 0 22 22" aria-hidden="true"><path d="M1 1h9v9H1zM12 12h9v9h-9z" fill="currentColor"/><path d="M12 1.75h8.25V10H12zM1.75 12H10v8.25H1.75z" fill="none" stroke="currentColor" stroke-width="1.5"/></svg>
Reede
</a>
<ul class="mono">
<li><a href="#arbeiten">Arbeiten</a></li>
<li><a href="#studio">Studio</a></li>
<li><a href="#journal">Journal</a></li>
<li><a href="#kontakt">Kontakt</a></li>
</ul>
<a class="knopf" href="#kontakt">Projekt anfragen</a>
<button class="menue mono" type="button">Menü</button>
</header>
<section class="held" aria-labelledby="titel">
<h1 id="titel">Marken für Betriebe, die noch <span class="pille" role="img" aria-label="Arbeitsprobe: Magazin-Doppelseite"></span> selbst etwas <em>herstellen.</em></h1>
<div class="neben">
<p class="mono">Marken & Websites · Hamburg</p>
<p>Wir gestalten Erscheinungsbilder, Verpackungen und Websites für <b>Manufakturen, Handwerk und Mittelstand</b>. Seit 2014, aus einem Hinterhof an der Elbe.</p>
<div class="tasten"><a class="knopf" href="#kontakt">Projekt besprechen</a><a class="knopf leer" href="#arbeiten">Arbeiten ansehen ↗</a></div>
</div>
</section>
<section class="beweis" aria-labelledby="kunden" data-beweis data-v="band" data-v-sektion="laufband">
<div class="beweis-kopf">
<h2 id="kunden">Vertraut von 60 Betrieben <span>zwischen Flensburg und Freiburg</span></h2>
<div class="kopf-rechts mono">
<a class="fallzeile" href="#referenzen" data-fall><span class="text" data-falltext>Alle Referenzen</span><span class="pfeil" aria-hidden="true">↗</span></a>
<button class="halt" type="button" data-halt aria-pressed="false" aria-label="Laufband anhalten"><svg viewBox="0 0 10 10" aria-hidden="true"><path d="M2 1h2v8H2zM6 1h2v8H6z" fill="currentColor"/></svg></button>
</div>
</div>
<div class="ansicht wand" data-ansicht="band">
<div class="band" data-band>
<div class="spur" data-spur></div>
</div>
</div>
<div class="ansicht wand" data-ansicht="klapp" hidden>
<ul class="klapp" data-klapp></ul>
</div>
<div class="ansicht wand" data-ansicht="linien" hidden>
<ul class="linien" data-linien></ul>
</div>
</section>
<section class="ruhig" aria-labelledby="ruhig-titel" data-v-sektion="raster" hidden>
<div class="beweis-kopf">
<h2 id="ruhig-titel">Geprüft, zertifiziert, in der Innung <span>Mitgliedschaften und Herstellerpartner</span></h2>
<a class="fallzeile mono" href="#nachweise"><span class="text">Alle Nachweise als PDF</span><span class="pfeil" aria-hidden="true">↗</span></a>
</div>
<div class="ruhig-rahmen" data-ruhig-rahmen>
<ul class="ruhig-liste" data-ruhig aria-label="Partner und Nachweise"></ul>
</div>
<div class="ruhig-fuss"><p>Alle Zertifikate sind aktuell und auf Anfrage einsehbar. Herstellerpartner heißt: geschult, mit verlängerter Systemgarantie.</p></div>
</section>
<section class="preise" aria-labelledby="preise-titel" data-v-sektion="auszeichnungen" hidden>
<div class="beweis-kopf">
<h2 id="preise-titel">Ausgezeichnet <span>und trotzdem ein Gasthaus geblieben</span></h2>
<a class="fallzeile mono" href="#presse"><span class="text">Presse und Kritiken</span><span class="pfeil" aria-hidden="true">↗</span></a>
</div>
<ol class="preise-liste" data-preise></ol>
</section>
<section class="nachweise" aria-labelledby="nachweis-titel" data-v-sektion="siegel-nachweis" hidden>
<div class="beweis-kopf">
<h2 id="nachweis-titel">Geprüft von Dritten <span>Jedes Zeichen mit Prüfstelle, Nummer und Gültigkeit</span></h2>
<a class="fallzeile mono" href="#kontrollberichte"><span class="text">Kontrollberichte als PDF</span><span class="pfeil" aria-hidden="true">↗</span></a>
</div>
<ul class="nachweis-liste" data-nachweise aria-label="Zertifikate und Kontrollen"></ul>
<p class="nachweis-fuss">Zeichen ohne Prüfstelle, Zertifikatsnummer und gültigen Zeitraum zeigen wir nicht. Stand der Angaben: <time data-stand></time>.</p>
</section>
<div class="steuerung">
<div class="pultspalte">
<div class="vwahl mono" role="group" aria-label="Variante">
<span>Variante</span>
<button type="button" data-v-wahl="laufband" aria-pressed="true">Laufband</button>
<button type="button" data-v-wahl="raster" aria-pressed="false">Ruhiges Raster</button>
<button type="button" data-v-wahl="auszeichnungen" aria-pressed="false">Auszeichnungen</button>
<button type="button" data-v-wahl="siegel-nachweis" aria-pressed="false">Siegel mit Nachweis</button>
</div>
<div class="modi mono" role="group" aria-label="Ansicht" data-ansichten>
<span>Ansicht</span>
<button type="button" data-wahl="band" aria-pressed="true">Laufband</button>
<button type="button" data-wahl="klapp" aria-pressed="false">Klapp-Raster</button>
<button type="button" data-wahl="linien" aria-pressed="false">Haarlinien</button>
</div>
<div class="modi weiche mono" role="group" aria-label="Weiche">
<span>Weiche</span>
<button type="button" data-weiche="nativ" aria-pressed="true">flex-wrap: balance</button>
<button type="button" data-weiche="messung" aria-pressed="false">Fallback: Messung</button>
</div>
</div>
<div class="modi pruefung mono" role="group" aria-label="Datenprüfung">
<span>Prüfung</span>
<button type="button" data-nr-weg aria-pressed="false">Nummer fehlt</button>
</div>
</div>
<p class="werte mono" data-werte aria-live="polite"></p>
</div>
</div>
<svg class="zeiger" data-zeiger viewBox="0 0 18 18" aria-hidden="true">
<path class="pfeilform" d="M2 1.5 15.5 9.2l-6 1.3-2.9 5.6Z" fill="#151514" stroke="#efece6" stroke-width="1.2" stroke-linejoin="round"/>
<g class="faust"><circle cx="9" cy="9" r="7.4" fill="#151514" stroke="#efece6" stroke-width="1.2"/><path d="M5.6 9h6.8M5.6 9l2-2M5.6 9l2 2M12.4 9l-2-2M12.4 9l-2 2" stroke="#efece6" stroke-width="1.2" fill="none" stroke-linecap="round"/></g>
</svg>
<script>
var STEUER = {}; // je Variante ein Steuerobjekt { start, stopp, neustart, vorfuehren, pause, weiter }
/* ---------- Grundvariante „Laufband“ (Studio Reede, mit Klapp- und Haarlinien-Ansicht) ---------- */
(() => {
const html = document.documentElement;
const reduziert = () => html.classList.contains('reduziert');
const aktiv = () => !html.dataset.v || html.dataset.v === 'laufband';
const beruehrung = matchMedia('(hover: none)');
const $ = (s, r = document) => r.querySelector(s), $$ = (s, r = document) => [...r.querySelectorAll(s)];
/* ---------- Die Kunden: erfundene Betriebe, eigene Wortmarken ---------- */
// w: Breite der viewBox (Höhe 40, wird nach dem Laden der Schriften nachgemessen)
// h: optischer Ausgleich, Wortmarken mit Bildzeichen kleiner, schmale größer
const S = 'fill="none" stroke="currentColor"';
const LOGOS = [
{ id: 'habicht', name: 'Rösterei Habicht', farbe: '#8a4a26', w: 128, h: 30, fall: 'Marke & Shop', zahl: '+41 % Online-Bestellungen',
svg: `<circle cx="16" cy="20" r="13" ${S} stroke-width="2.2"/><path d="M16 8.5c-4.6 4-4.6 19 0 23-2.3-6-2.3-17 0-23z"/><text x="36" y="29" font-family="Fraunces" font-style="italic" font-weight="300" font-size="27" letter-spacing="-.5">Habicht</text>` },
{ id: 'nordwerk', name: 'Nordwerk', farbe: '#1f4f8f', w: 172, h: 27, fall: 'Website', zahl: '3× mehr Bewerbungen',
svg: `<path d="M3 7h23v26H3z" ${S} stroke-width="2.6"/><path d="M8.5 27V13l12 14V13" ${S} stroke-width="2.6"/><text x="35" y="28" font-family="Archivo" font-weight="800" font-size="20" style="font-stretch:125%" letter-spacing=".6">NORDWERK</text>` },
{ id: 'korn', name: 'Korn & Kruste', farbe: '#a87618', w: 168, h: 31, fall: 'Verpackung', zahl: '14 Filialen, ein Auftritt',
svg: `<path d="M13 35V9" ${S} stroke-width="1.8"/><path d="M13 13c-4-1-6-4-6-7 4 0 6 3 6 7zm0 0c4-1 6-4 6-7-4 0-6 3-6 7zm0 7c-4-1-6-4-6-7 4 0 6 3 6 7zm0 0c4-1 6-4 6-7-4 0-6 3-6 7zm0 7c-4-1-6-4-6-7 4 0 6 3 6 7zm0 0c4-1 6-4 6-7-4 0-6 3-6 7z"/><text x="27" y="28" font-family="Cormorant Garamond" font-weight="600" font-size="25">Korn <tspan font-style="italic" font-weight="500">&</tspan> Kruste</text>` },
{ id: 'volta', name: 'Volta Räder', farbe: '#cf4527', w: 138, h: 25, fall: 'Marke & Website', zahl: 'Start in 9 Wochen',
svg: `<circle cx="8.5" cy="25" r="7" ${S} stroke-width="2.4"/><circle cx="30" cy="25" r="7" ${S} stroke-width="2.4"/><path d="M8.5 25 15.5 13h10l4.5 12M13 13h5" ${S} stroke-width="2.4" stroke-linejoin="round"/><text x="46" y="31" font-family="Michroma" font-size="17" letter-spacing="3">VOLTA</text>` },
{ id: 'hafenkontor', name: 'Hafenkontor', farbe: '#0f6a70', w: 160, h: 30, fall: 'Erscheinungsbild', zahl: 'Partner seit 2017',
svg: `<path d="M2 14q4-4 8 0t8 0 8 0M2 21q4-4 8 0t8 0 8 0M2 28q4-4 8 0t8 0 8 0" ${S} stroke-width="2.2"/><text x="34" y="29" font-family="Instrument Serif" font-size="28" letter-spacing="-.3">Hafenkontor</text>` },
{ id: 'lumen', name: 'Lumen Optik', farbe: '#6a4bb3', w: 150, h: 27, fall: 'Website', zahl: '+62 % Terminbuchungen',
svg: `<circle cx="9.5" cy="21" r="7.2" ${S} stroke-width="2.4"/><circle cx="28.5" cy="21" r="7.2" ${S} stroke-width="2.4"/><path d="M16.7 20q2.3-2.6 4.6 0" ${S} stroke-width="2.4"/><text x="44" y="28" font-family="Questrial" font-size="25">lumen<tspan fill-opacity=".55"> optik</tspan></text>` },
{ id: 'stahl', name: 'Gebr. Stahl', farbe: '#36454f', w: 168, h: 27, fall: 'Messeauftritt', zahl: '2 Messen im Jahr',
svg: `<path d="M3 7h22v5h-8v16h8v5H3v-5h8V12H3z"/><text x="35" y="27" font-family="Jost" font-weight="700" font-size="17" letter-spacing="2.6">GEBR. STAHL</text>` },
{ id: 'kiezkraut', name: 'Kiezkraut', farbe: '#3c8436', w: 150, h: 29, fall: 'Verpackung', zahl: '23 Sorten im Regal',
svg: `<path d="M5 33C5 18 13 8 31 7c0 17-10 26-26 26zM5 33 21 17" ${S} stroke-width="2.4" stroke-linejoin="round"/><text x="37" y="28" font-family="Bricolage Grotesque" font-weight="800" font-size="25" letter-spacing="-.8">kiezkraut</text>` },
{ id: 'ahorn', name: 'Tischlerei Ahorn', farbe: '#a24f28', w: 118, h: 30, fall: 'Marke', zahl: 'Vierte Generation',
svg: `<path d="M15 4l9 13h-5l7 10H4l7-10H6z"/><path d="M15 27v8" ${S} stroke-width="2.8"/><text x="33" y="28" font-family="Jost" font-weight="500" font-size="25" letter-spacing="-.2">Ahorn</text>` },
{ id: 'muehlenbach', name: 'Mühlenbach', farbe: '#2b689a', w: 168, h: 29, fall: 'Etiketten', zahl: '8 Mehlsorten',
svg: `<circle cx="17" cy="20" r="13" ${S} stroke-width="2"/><path d="M17 7v26M4 20h26M7.8 10.8l18.4 18.4M26.2 10.8 7.8 29.2" ${S} stroke-width="1.6"/><circle cx="17" cy="20" r="4.2"/><text x="38" y="28" font-family="'Source Serif 4'" font-size="24">Mühlenbach</text>` },
{ id: 'pixelhof', name: 'Pixelhof', farbe: '#d23f73', w: 138, h: 28, fall: 'Website & Kurse', zahl: '1.800 Teilnehmende',
svg: `<path d="M3 8h10v10H3zM15 8h10v10H15zM3 20h10v10H3z"/><path d="M16 21h8v8h-8z" ${S} stroke-width="2"/><text x="34" y="27" font-family="Geist Mono" font-weight="600" font-size="20" letter-spacing="-.6">pixelhof</text>` },
{ id: 'elbsegel', name: 'Elbsegel', farbe: '#0b5ea6', w: 150, h: 27, fall: 'Buchung', zahl: '+55 % Direktbuchungen',
svg: `<path d="M15 4v25H3zM18 9l11 20H18z"/><path d="M1 34h30" ${S} stroke-width="2.4"/><text x="38" y="28" font-family="Unbounded" font-weight="900" font-size="16" letter-spacing=".4">ELBSEGEL</text>` },
{ id: 'kessler', name: 'Weingut Kessler', farbe: '#7a2140', w: 128, h: 31, fall: 'Etiketten', zahl: 'Jahrgang 2025',
svg: `<circle cx="9" cy="13" r="3.7"/><circle cx="17" cy="13" r="3.7"/><circle cx="25" cy="13" r="3.7"/><circle cx="13" cy="20" r="3.7"/><circle cx="21" cy="20" r="3.7"/><circle cx="17" cy="27" r="3.7"/><path d="M17 3v6" ${S} stroke-width="2"/><text x="35" y="28" font-family="Cormorant Garamond" font-style="italic" font-weight="500" font-size="30">Kessler</text>` },
{ id: 'ofenhaus', name: 'Ofenhaus', farbe: '#cc5f22', w: 142, h: 28, fall: 'Marke & Speisekarte', zahl: '3 Standorte',
svg: `<path d="M4 33V19a13 13 0 0 1 26 0v14M1 33h32" ${S} stroke-width="2.4"/><path d="M17 31c-4 0-6-3-5-6 1-2 3-3 3-6 3 2 6 5 6 8 0 2-2 4-4 4z"/><text x="41" y="28" font-family="Bricolage Grotesque" font-weight="500" font-size="24" letter-spacing="-.5">Ofenhaus</text>` },
{ id: 'brueckner', name: 'Brückner Bau', farbe: '#9a7414', w: 140, h: 27, fall: 'Bauschilder & Website', zahl: '120 Baustellen im Jahr',
svg: `<path d="M2 30h32M2 30V18M34 30V18M2 18q16-14 32 0M10 30v-9.5M18 30v-12M26 30v-9.5" ${S} stroke-width="2.2"/><text x="42" y="29" font-family="Archivo" font-weight="700" font-size="23" style="font-stretch:75%" letter-spacing=".3">BRÜCKNER</text>` },
{ id: 'wollwerk', name: 'Wollwerk', farbe: '#94478a', w: 142, h: 28, fall: 'Onlineshop', zahl: 'Versand in 12 Länder',
svg: `<circle cx="16" cy="20" r="13" ${S} stroke-width="2.2"/><path d="M6 14c6 2 14 9 16 18M10 9c6 4 13 12 15 19M5 22c5 1 10 5 12 10" ${S} stroke-width="1.8"/><text x="36" y="28" font-family="Instrument Sans" font-weight="600" font-size="25" letter-spacing="-.9">wollwerk</text>` },
];
const L = LOGOS.length;
const nachId = Object.fromEntries(LOGOS.map(l => [l.id, l]));
const logoSvg = l => `<svg data-logo="${l.id}" viewBox="0 0 ${l.w} 40" style="--h:${l.h}px;aspect-ratio:${l.w}/40" fill="currentColor" aria-hidden="true">${l.svg}</svg>`;
const logoLink = (l, { kopie = false, mehr = '' } = {}) =>
`<a class="logo" href="#fall-${l.id}" data-id="${l.id}" style="--marke:${l.farbe}" ${kopie ? 'tabindex="-1" aria-hidden="true"' : `aria-label="${l.name}, Fallstudie: ${l.fall}"`}>${logoSvg(l)}${mehr}</a>`;
/* ---------- Aufbau der drei Ansichten aus derselben Liste ---------- */
const beweis = $('[data-beweis]'), spur = $('[data-spur]'), band = $('[data-band]');
const klapp = $('[data-klapp]'), linien = $('[data-linien]');
const liste = kopie => `<ul${kopie ? ' aria-hidden="true"' : ''}>${LOGOS.map(l => `<li>${logoLink(l, { kopie })}</li>`).join('')}</ul>`;
spur.innerHTML = liste(false) + liste(true); // zweite Kopie für den nahtlosen Lauf
const KARTEN = 8;
klapp.innerHTML = Array.from({ length: KARTEN }, (_, i) =>
`<li class="karte"><div class="innen"><div class="seite vorn">${logoLink(LOGOS[i])}</div><div class="seite hinten"></div></div></li>`).join('');
const ecke = `<svg class="ecke" viewBox="0 0 10 10" aria-hidden="true"><path d="M1.5 8.5 8.5 1.5M3 1.5h5.5V7" fill="none" stroke="currentColor" stroke-width="1.3"/></svg>`;
linien.innerHTML = LOGOS.map(l => `<li>${logoLink(l, { mehr: `<span class="zahl mono" aria-hidden="true"><b>${l.zahl}</b><span>${l.fall}</span></span>${ecke}` })}</li>`).join('');
// Nach dem Laden der Schriften jede Wortmarke vermessen und die viewBox anpassen
function vermessen() {
LOGOS.forEach(l => {
const svg = $(`svg[data-logo="${l.id}"]`);
if (!svg || !svg.getBBox) return;
try {
const b = svg.getBBox();
if (b.width > 0) l.w = Math.ceil(b.x + b.width + 1);
} catch (e) {}
$$(`svg[data-logo="${l.id}"]`).forEach(s => { s.setAttribute('viewBox', `0 0 ${l.w} 40`); s.style.aspectRatio = `${l.w}/40`; });
});
bandMessen();
}
/* ---------- Fallzeile: Logo plus Kennzahl ---------- */
const fall = $('[data-fall]'), fallText = $('[data-falltext]');
let fallId = null;
function fallZeigen(id) {
if (id === fallId) return;
fallId = id;
const l = id && nachId[id];
fallText.innerHTML = l ? `<b>${l.name}</b> · ${l.zahl}` : 'Alle Referenzen';
fall.classList.toggle('hat', !!l);
fallText.classList.remove('neu'); void fallText.offsetWidth; fallText.classList.add('neu');
}
/* ---------- Hervorheben (Hover, Fokus, Tippen, Vorführung) ---------- */
let markiert = null;
function markieren(a) {
if (a === markiert) return;
if (markiert) markiert.classList.remove('an');
markiert = a || null;
$$('.wand').forEach(w => w.classList.toggle('aktiv', !!(a && w.contains(a))));
if (a) a.classList.add('an');
fallZeigen(a ? a.dataset.id : null);
}
beweis.addEventListener('pointerover', e => {
if (e.pointerType !== 'mouse' || vorfuehrt) return;
const a = e.target.closest('.logo');
if (a && !band.classList.contains('zieht')) markieren(a);
});
beweis.addEventListener('pointerout', e => {
if (e.pointerType !== 'mouse' || vorfuehrt) return;
const a = e.target.closest('.logo');
if (a && !a.contains(e.relatedTarget)) markieren(null);
});
beweis.addEventListener('focusin', e => { const a = e.target.closest('.logo'); if (a) fallZeigen(a.dataset.id); });
beweis.addEventListener('focusout', () => { if (!markiert) fallZeigen(null); });
// Klick: in der Demo keine Navigation. Touch: erstes Tippen hebt hervor, zweites würde öffnen.
beweis.addEventListener('click', e => {
const a = e.target.closest('.logo');
if (!a) return;
e.preventDefault();
if (gezogen) return;
if (beruehrung.matches) markieren(a === markiert ? null : a);
});
document.addEventListener('pointerdown', e => { if (beruehrung.matches && !e.target.closest('.logo')) markieren(null); });
/* ---------- Variante 1: Laufband mit Ziehen ---------- */
const TEMPO = 55, TEMPO_TOUCH = 35; // px/s → Runde ≈ 50 s (16 × 172 px) bzw. ≈ 62 s mobil
let x = 0, v = 0, wurf = 0, breite = 1, laeuft = false, letzte = 0, bandHover = false, angehalten = false;
let zieh = null, gezogen = false;
const tempo = () => beruehrung.matches ? TEMPO_TOUCH : TEMPO;
function bandMessen() { breite = spur.firstElementChild.getBoundingClientRect().width || 1; }
function bandMalen() { spur.style.transform = `translate3d(${x.toFixed(2)}px,0,0)`; }
function takt(t) {
const dt = Math.min(0.05, (t - letzte) / 1000); letzte = t;
if (variante !== 'band' || reduziert() || !aktiv()) { laeuft = false; return; }
if (!zieh) {
const ziel = (bandHover || angehalten || vorfuehrHalt) ? 0 : tempo();
v += (ziel - v) * (1 - Math.exp(-dt / 0.25)); // weich anhalten und anfahren
x -= v * dt;
x += wurf * dt; wurf *= Math.exp(-dt / 0.35); // Schwung nach dem Loslassen
}
x = ((x % breite) - breite) % breite; // immer in (−breite, 0]
bandMalen();
requestAnimationFrame(takt);
}
function bandStart() { if (!laeuft && variante === 'band' && !reduziert() && aktiv()) { laeuft = true; letzte = performance.now(); requestAnimationFrame(takt); } }
function ziehStart(px) { zieh = { px, x0: x, vx: 0, t: performance.now(), letztX: px }; gezogen = false; }
function ziehBewegen(px) {
if (!zieh) return;
if (Math.abs(px - zieh.px) > 4 && !gezogen) { gezogen = true; band.classList.add('zieht'); markieren(null); }
const t = performance.now(), dt = Math.max(1, t - zieh.t);
zieh.vx = zieh.vx * 0.6 + ((px - zieh.letztX) / dt * 1000) * 0.4;
zieh.t = t; zieh.letztX = px;
x = zieh.x0 + (px - zieh.px);
}
function ziehEnde() {
if (!zieh) return;
if (gezogen) { wurf = Math.max(-1600, Math.min(1600, zieh.vx)); v = 0; }
zieh = null; band.classList.remove('zieht');
setTimeout(() => { gezogen = false; }, 0);
}
band.addEventListener('pointerdown', e => {
if (reduziert() || vorfuehrt || e.button > 0) return;
ziehStart(e.clientX);
band.setPointerCapture(e.pointerId);
});
band.addEventListener('pointermove', e => { if (zieh && !vorfuehrt) ziehBewegen(e.clientX); });
band.addEventListener('pointerup', () => { if (!vorfuehrt) ziehEnde(); });
band.addEventListener('pointercancel', () => { if (!vorfuehrt) ziehEnde(); });
band.addEventListener('pointerenter', e => { if (e.pointerType === 'mouse' && !vorfuehrt) bandHover = true; });
band.addEventListener('pointerleave', e => { if (e.pointerType === 'mouse' && !vorfuehrt) bandHover = false; });
// Tastatur: Band hält an, das fokussierte Logo wird in die Mitte geholt (statt den Container zu scrollen)
band.addEventListener('focusin', e => {
bandHover = true;
band.scrollLeft = 0;
const a = e.target.closest('.logo'); if (!a) return;
const r = a.getBoundingClientRect(), br = band.getBoundingClientRect();
if (r.left < br.left + br.width * .1 || r.right > br.right - br.width * .1) { x += (br.left + br.width / 2) - (r.left + r.width / 2); x = Math.min(0, x); if (!reduziert()) bandMalen(); } /* nicht umbrechen: sonst steht die aria-hidden-Kopie in der Mitte und das fokussierte Original außerhalb */
});
band.addEventListener('scroll', () => { band.scrollLeft = 0; });
band.addEventListener('focusout', () => { bandHover = false; });
band.addEventListener('dragstart', e => e.preventDefault());
// Pausieren für alle (WCAG 2.2.2): Hover allein reicht auf Touch nicht
const halt = $('[data-halt]');
halt.addEventListener('click', () => {
angehalten = !angehalten;
halt.setAttribute('aria-pressed', angehalten);
halt.setAttribute('aria-label', angehalten ? 'Laufband fortsetzen' : 'Laufband anhalten');
halt.querySelector('path').setAttribute('d', angehalten ? 'M2.5 1 9 5l-6.5 4z' : 'M2 1h2v8H2zM6 1h2v8H6z');
});
/* ---------- Variante 2: Klapp-Raster (Cursor) ---------- */
const FLIP = 600, VERSATZ = 160, INTERVALL = 5000, ERSTER = 1200;
let basis = 0, klappUhren = [], klappHover = false;
const karten = $$('.karte', klapp);
const sichtbar = () => karten.filter(k => getComputedStyle(k).display !== 'none');
function klappStopp() { klappUhren.forEach(clearTimeout); klappUhren = []; }
function klappPlanen(ms) { klappStopp(); if (!reduziert()) klappUhren.push(setTimeout(umklappen, ms)); }
function umklappen() {
if (variante !== 'klapp' || reduziert() || !aktiv()) return;
if (klappHover && !vorfuehrt) { klappPlanen(INTERVALL); return; }
const k = sichtbar(), n = k.length;
k.forEach((karte, i) => {
const l = LOGOS[(basis + n + i) % L];
$('.hinten', karte).innerHTML = logoLink(l, { kopie: true });
klappUhren.push(setTimeout(() => karte.classList.add('flipped'), i * VERSATZ));
});
// nach (n − 1) × 160 + 600 + 50 ms: Logos ohne Übergang tauschen, Kacheln zurücksetzen
klappUhren.push(setTimeout(() => {
k.forEach((karte, i) => {
const innen = $('.innen', karte);
innen.classList.add('sofort');
$('.vorn', karte).innerHTML = logoLink(LOGOS[(basis + n + i) % L]);
$('.hinten', karte).innerHTML = '';
karte.classList.remove('flipped');
void innen.offsetWidth;
innen.classList.remove('sofort');
});
basis = (basis + n) % L;
if (markiert && !markiert.isConnected) markieren(null);
klappUhren.push(setTimeout(umklappen, INTERVALL - ((n - 1) * VERSATZ + FLIP + 50)));
}, (n - 1) * VERSATZ + FLIP + 50));
}
klapp.addEventListener('pointerenter', e => { if (e.pointerType === 'mouse') klappHover = true; });
klapp.addEventListener('pointerleave', () => { klappHover = false; });
/* ---------- Varianten ---------- */
let variante = 'band';
const werteEl = $('[data-werte]');
function werteZeigen() {
if (!aktiv()) return;
const sp = sichtbar().length || 8;
const W = {
band: reduziert()
? '<span>Reduziert: <b>Band steht als Raster, keine Kopie</b></span><span>Hover <b>Fill sofort</b></span>'
: `<span>Kachel <b>${parseInt(getComputedStyle(html).getPropertyValue('--kachel-b'))} px</b></span><span>Lauf <b>${tempo()} px/s linear · Runde ${Math.round(breite / tempo())} s</b></span><span>Fill <b>300 ms cubic-bezier(.25,1,.5,1)</b></span><span>Nachbarn <b>150 ms linear → .3</b></span><span>Ziehen <b>cursor: grab</b></span>`,
klapp: reduziert()
? `<span>${sp} Spalten</span><span>Reduziert: <b>kein Flip</b></span>`
: `<span>Spalten <b>${sp} (4/6/8)</b></span><span>Flip <b>rotateX 180° · 600 ms ease-in-out</b></span><span>Versatz <b>160 ms</b></span><span>Intervall <b>5000 ms</b></span><span>perspective <b>1000px</b></span>`,
linien: `<span>Haarlinie <b>1px · 15 %</b></span><span>Spalten <b>${getComputedStyle(linien).gridTemplateColumns.split(' ').length}</b></span><span>Hover <b>Fill 300 ms · Kennzahl 150 ms linear</b></span><span>kein JS nötig</span>`,
};
werteEl.innerHTML = W[variante];
}
function wechseln(neu) {
if (neu === variante) return;
variante = neu;
markieren(null); fallZeigen(null);
beweis.dataset.v = neu;
$$('[data-wahl]').forEach(b => b.setAttribute('aria-pressed', b.dataset.wahl === neu));
$$('[data-ansicht]').forEach(a => { a.hidden = a.dataset.ansicht !== neu; });
klappStopp();
if (neu === 'band') { bandMessen(); bandStart(); }
if (neu === 'klapp') klappPlanen(ERSTER);
werteZeigen();
}
$$('[data-wahl]').forEach(b => b.addEventListener('click', () => { if (vorfuehrt) neustart(); wechseln(b.dataset.wahl); }));
/* ---------- Vorführung: ein gedachter Zeiger, 21 s pro Runde ---------- */
const zeiger = $('[data-zeiger]');
const RUNDE = 21000;
let vorfuehrt = false, vorfuehrHalt = false, uhr = null, start = 0, pausiertBei = null, phase = '';
const sanft = p => p < .5 ? 2 * p * p : 1 - Math.pow(-2 * p + 2, 2) / 2;
// Zielpunkte relativ zu Elementen, zur Laufzeit gelesen
const mitte = el => { const r = el.getBoundingClientRect(); return [r.left + r.width / 2, r.top + r.height / 2]; };
const draussen = () => [innerWidth * .78, innerHeight + 30];
function bandPunkt(anteil) { const r = band.getBoundingClientRect(); return [r.left + r.width * anteil, r.top + r.height / 2]; }
function zelle(i) { const li = $$('li', linien).filter(l => getComputedStyle(l).display !== 'none'); return mitte(li[Math.min(i, li.length - 1)]); }
function weg(t, punkte) {
// punkte: [[ms, () => [x, y]], …]; zwischen zwei Punkten sanft interpolieren
if (t <= punkte[0][0]) return punkte[0][1]();
for (let i = 1; i < punkte.length; i++) {
if (t <= punkte[i][0]) {
const [t0, a] = punkte[i - 1], [t1, b] = punkte[i];
const p = sanft((t - t0) / (t1 - t0)), A = a(), B = b();
return [A[0] + (B[0] - A[0]) * p, A[1] + (B[1] - A[1]) * p];
}
}
return punkte[punkte.length - 1][1]();
}
let griffX = null;
const BAND_WEG = [[0, draussen], [500, draussen], [1300, () => bandPunkt(.46)], [3500, () => bandPunkt(.46)], [5100, () => bandPunkt(.22)], [5500, () => bandPunkt(.24)], [6300, draussen]];
const LINIEN_WEG = [[0, draussen], [400, draussen], [1100, () => zelle(2)], [2500, () => zelle(2)], [3100, () => zelle(5)], [4400, () => zelle(5)], [5000, () => zelle(9)], [6200, () => zelle(9)], [6900, draussen]];
function schritt(t) {
const r = (t - start) % RUNDE;
const ph = r < 6500 ? 'band' : r < 14000 ? 'klapp' : 'linien';
if (ph !== phase) { phase = ph; wechseln(ph); if (ph === 'band' && !reduziert()) { x = 0; v = TEMPO; } }
let p = null, greift = false;
if (ph === 'band') {
const q = r;
p = weg(q, BAND_WEG);
vorfuehrHalt = q > 1200 && q < 5700; // Hover hält das Band an
if (!reduziert()) {
if (q > 3500 && q < 5100) { // greifen und nach links ziehen
greift = true;
if (griffX === null) { ziehStart(p[0]); }
ziehBewegen(p[0]);
} else if (griffX !== null || zieh) { ziehEnde(); }
griffX = greift ? p[0] : null;
}
} else if (ph === 'linien') {
p = weg(r - 14000, LINIEN_WEG);
vorfuehrHalt = false;
} else { vorfuehrHalt = false; }
if (p) {
zeiger.style.display = '';
zeiger.classList.toggle('greift', greift);
zeiger.style.transform = `translate(${p[0] - (greift ? 9 : 2)}px, ${p[1] - (greift ? 9 : 1)}px)`;
const el = !greift && document.elementFromPoint(p[0], p[1]);
const a = el && el.closest && el.closest('.logo');
markieren(a && !a.closest('[hidden]') ? a : null);
} else { zeiger.style.display = 'none'; markieren(null); }
uhr = requestAnimationFrame(schritt);
}
function vorfuehren() {
pause();
vorfuehrt = true; html.classList.add('vorfuehrung');
start = performance.now() - (pausiertBei ?? 0); pausiertBei = null; phase = '';
uhr = requestAnimationFrame(schritt);
}
function pause() {
if (uhr) { cancelAnimationFrame(uhr); uhr = null; pausiertBei = (performance.now() - start) % RUNDE; }
}
function weiter() { if (!uhr && vorfuehrt) vorfuehren(); }
function neustart() {
if (uhr) cancelAnimationFrame(uhr);
uhr = null; pausiertBei = null; vorfuehrt = false; vorfuehrHalt = false; griffX = null;
html.classList.remove('vorfuehrung'); zeiger.classList.remove('greift');
if (zieh) ziehEnde();
markieren(null);
const alt = variante; variante = ''; wechseln(alt || 'band');
}
/* ---------- Start ---------- */
bandMessen(); werteZeigen(); bandStart();
(document.fonts ? document.fonts.ready : Promise.resolve()).then(() => { vermessen(); werteZeigen(); });
addEventListener('resize', () => { bandMessen(); werteZeigen(); });
// Schalter „reduzierte Bewegung“ der Detailseite setzt die Klasse nachträglich
new MutationObserver(() => {
if (reduziert()) { klappStopp(); karten.forEach(k => k.classList.remove('flipped')); spur.style.transform = ''; }
else { bandStart(); if (variante === 'klapp' && aktiv()) klappPlanen(ERSTER); }
werteZeigen();
}).observe(html, { attributes: true, attributeFilter: ['class'] });
function anfangen() { const alt = variante; variante = ''; wechseln(alt || 'band'); bandMessen(); werteZeigen(); }
function anhalten() { neustart(); klappStopp(); laeuft = false; }
STEUER.laufband = { start: anfangen, stopp: anhalten, neustart, vorfuehren, pause, weiter };
})();
</script>
<script>
/* ---------- Welten der Varianten: Kopf und Hero wechseln mit, damit das Muster in seiner Branche steht ---------- */
(() => {
const html = document.documentElement;
const $ = (s, r = document) => r.querySelector(s), $$ = (s, r = document) => [...r.querySelectorAll(s)];
const reduziert = () => html.classList.contains('reduziert');
const beruehrung = matchMedia('(hover: none)');
const held = $('.held'), marke = $('.nav .marke'), navListe = $('.nav ul'), navKnopf = $('.nav > .knopf');
const REEDE = { marke: marke.innerHTML, label: marke.getAttribute('aria-label'), nav: navListe.innerHTML, knopf: navKnopf.textContent, held: held.innerHTML, titel: document.title };
const WELTEN = {
raster: {
marke: `<svg viewBox="0 0 22 22" aria-hidden="true"><path d="M1 11 11 2l10 9" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linejoin="round"/><path d="M4 13.5h14M6.5 17h9M9 20.5h4" stroke="#fb6100" stroke-width="2.2"/></svg>Brömmel`,
label: 'Brömmel Bedachungen, zur Startseite',
nav: '<li><a href="#leistungen">Leistungen</a></li><li><a href="#referenzen">Referenzen</a></li><li><a href="#ausbildung">Ausbildung</a></li><li><a href="#kontakt">Kontakt</a></li>',
knopf: '04131 99 27 40',
held: `<h1 id="titel">Dächer, die <span class="pille" role="img" aria-label="Detail: Biberschwanz-Ziegel"></span> <em>hundert Winter</em> halten.</h1>
<div class="neben">
<p class="mono">Dachdeckermeister · Lüneburg</p>
<p>Steildach, Flachdach, Solar und Denkmal in Lüneburg und der Heide. <b>Meisterbetrieb in dritter Generation</b>, 14 Leute, eigene Klempnerei.</p>
<div class="tasten"><a class="knopf" href="#kontakt">Rückruf anfordern</a><a class="knopf leer" href="#leistungen">Leistungen ansehen ↗</a></div>
</div>`,
titel: 'Brömmel Bedachungen'
},
auszeichnungen: {
marke: `<svg viewBox="0 0 22 22" aria-hidden="true"><circle cx="11" cy="11" r="9.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M11 5.5v11M7.5 8.5c0 2 1.5 3 3.5 3s3.5-1 3.5-3" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>Hofküche Ellerbrook`,
label: 'Hofküche Ellerbrook, zur Startseite',
nav: '<li><a href="#karte">Karte</a></li><li><a href="#hofladen">Hofladen</a></li><li><a href="#feiern">Feiern</a></li><li><a href="#anfahrt">Anfahrt</a></li>',
knopf: 'Tisch reservieren',
held: `<h1 id="titel">Gekocht wird, <em>was der Hof</em> <span class="pille" role="img" aria-label="Teller mit Gemüse vom Hof"></span> heute hergibt.</h1>
<div class="neben">
<p class="mono">Restaurant & Hofladen · bei Lübeck</p>
<p>Mittwoch bis Sonntag ab 17 Uhr, sonntags auch mittags. <b>Gemüse vom eigenen Acker</b>, Fleisch von drei Höfen aus dem Dorf.</p>
<div class="tasten"><a class="knopf" href="#reservieren">Tisch reservieren</a><a class="knopf leer" href="#karte">Zur Karte ↗</a></div>
</div>`,
titel: 'Hofküche Ellerbrook'
},
'siegel-nachweis': {
marke: `<svg viewBox="0 0 22 22" aria-hidden="true"><path d="M2 20h18M4 20V9l7-6 7 6v11" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M8 20v-5.5h6V20M6.5 11.5h9" fill="none" stroke="currentColor" stroke-width="1.2"/></svg>Weingut Sennfeld`,
label: 'Weingut Sennfeld, zur Startseite',
nav: '<li><a href="#weine">Weine</a></li><li><a href="#lagen">Lagen</a></li><li><a href="#hofladen">Hofladen</a></li><li><a href="#besuch">Besuch</a></li>',
knopf: 'Hofladen',
held: `<h1 id="titel">Kalk, Löss und <span class="pille" role="img" aria-label="Lage Sonnwendberg, Karte"></span> <em>vier Generationen</em> Geduld.</h1>
<div class="neben">
<p class="mono">Weingut & Hofladen · Dalsheim, Rheinhessen</p>
<p>Neun Hektar an den Hängen über dem Dorf. <b>Ökologisch bewirtschaftet seit 2009</b>, kontrolliert von unabhängigen Stellen.</p>
<div class="tasten"><a class="knopf" href="#weine">Weine ansehen</a><a class="knopf leer" href="#lagen">Unsere Lagen ↗</a></div>
</div>`,
titel: 'Weingut Sennfeld'
}
};
window.WELT = function (id) {
const w = WELTEN[id] || REEDE;
marke.innerHTML = w.marke; marke.setAttribute('aria-label', w.label);
navListe.innerHTML = w.nav; navKnopf.textContent = w.knopf;
held.innerHTML = w.held;
};
/* ---------- Variante „Ruhiges Raster“: Handwerk, Logos in natürlicher Breite, Reihen gleich lang ---------- */
const S = 'fill="none" stroke="currentColor"';
const PARTNER = [
{ id: 'innung', name: 'Innung Dach Heide-Elbe', art: 'Mitglied seit 1994', h: 40,
svg: `<path d="M2 4h26v17c0 8-6 13-13 16C8 34 2 29 2 21z" ${S} stroke-width="2.2"/><path d="M7.5 22 15 14.5l7.5 7.5" ${S} stroke-width="2.4" stroke-linejoin="round"/><path d="M11 22v6h8v-6" ${S} stroke-width="2"/><text x="38" y="18" font-family="Archivo" font-weight="700" font-size="15" style="font-stretch:75%" letter-spacing="1.4">INNUNG DACH</text><text x="38" y="33" font-family="Archivo" font-weight="500" font-size="11" letter-spacing="2.6">HEIDE-ELBE</text>` },
{ id: 'meister', name: 'Meisterbetrieb', art: 'seit 1961', h: 40,
svg: `<circle cx="18" cy="20" r="16" ${S} stroke-width="2"/><circle cx="18" cy="20" r="12" ${S} stroke-width=".9"/><text x="18" y="26.5" text-anchor="middle" font-family="'Source Serif 4'" font-size="17">M</text><text x="44" y="23" font-family="'Source Serif 4'" font-size="21">Meisterbetrieb</text><text x="44.5" y="35" font-family="Geist Mono" font-size="7.5" letter-spacing="2">SEIT 1961</text>` },
{ id: 'wiebking', name: 'Tonwerk Wiebking', art: 'Herstellerpartner Dachziegel', h: 38,
svg: `<path d="M2 8h9v15a4.5 4.5 0 0 1-9 0zM12.5 8h9v15a4.5 4.5 0 0 1-9 0zM23 8h9v15a4.5 4.5 0 0 1-9 0z"/><path d="M1 5h32" ${S} stroke-width="2"/><text x="41" y="25" font-family="Cormorant Garamond" font-weight="600" font-size="27">Wiebking</text><text x="42" y="36" font-family="Geist Mono" font-size="6.5" letter-spacing="2.2">TONWERK · 1898</text>` },
{ id: 'kahlberg', name: 'Schieferbruch Kahlberg', art: 'Herstellerpartner Schiefer', h: 32,
svg: `<path d="M11 3 20 14 11 25 2 14z"/><path d="M22 13l7 8.5-7 8.5-7-8.5z" ${S} stroke-width="1.8"/><text x="36" y="25" font-family="Michroma" font-size="15" letter-spacing="2.5">KAHLBERG</text>` },
{ id: 'sonnwerk', name: 'Sonnwerk', art: 'Fachpartner Solar', h: 34,
svg: `<path d="M3 28a14 14 0 0 1 28 0z"/><path d="M17 2v6M5.5 7.5l4 4M28.5 7.5l-4 4M0 18h5M29 18h5" ${S} stroke-width="2.2" stroke-linecap="round"/><text x="41" y="28" font-family="Questrial" font-size="27">sonnwerk</text>` },
{ id: 'lukarne', name: 'Lukarne Dachfenster', art: 'Einbaupartner', h: 34,
svg: `<path d="M2 30 17 6l15 24" ${S} stroke-width="2.4" stroke-linejoin="round"/><path d="M11.5 18.5h11v11h-11zM17 18.5v11M11.5 24h11" ${S} stroke-width="1.8"/><text x="40" y="27" font-family="Jost" font-weight="700" font-size="17" letter-spacing="3.4">LUKARNE</text>` },
{ id: 'rehm', name: 'Blechwerk Rehm', art: 'Herstellerpartner Metalldach', h: 36,
svg: `<path d="M2 6h28v28H2z" ${S} stroke-width="2"/><path d="M9 6v28M16 6v28M23 6v28" ${S} stroke-width="1.4"/><text x="38" y="23" font-family="Unbounded" font-weight="900" font-size="18" letter-spacing=".6">REHM</text><text x="38.5" y="34" font-family="Jost" font-weight="500" font-size="10" letter-spacing="1.2">Blechwerk</text>` },
{ id: 'gruendach', name: 'Verband Gründach Nord', art: 'Fachbetrieb Dachbegrünung', h: 36,
svg: `<path d="M2 32 16 18l14 14" ${S} stroke-width="2.2" stroke-linejoin="round"/><path d="M16 18c0-8 5-13 13-13 0 8-5 13-13 13z"/><text x="38" y="27" font-family="Bricolage Grotesque" font-weight="800" font-size="22" letter-spacing="-.6">gründach nord</text>` },
{ id: 'guete', name: 'Gütegemeinschaft Steildach', art: 'Gütezeichen 2026', h: 40,
svg: `<path d="M2 4h30v30H2z"/><text x="17" y="26" text-anchor="middle" font-family="'Instrument Serif'" font-size="19" fill="#efedea">GS</text><text x="41" y="18" font-family="Instrument Sans" font-weight="600" font-size="13">Gütegemeinschaft</text><text x="41" y="33" font-family="Instrument Sans" font-weight="600" font-size="13">Steildach</text>` },
{ id: 'klima', name: 'Klimahandwerk Nord', art: 'Partnerbetrieb Energieberatung', h: 34,
svg: `<path d="M8 4h6v16a7 7 0 1 1-6 0z" ${S} stroke-width="2"/><circle cx="11" cy="26" r="3.6"/><path d="M11 11v12" ${S} stroke-width="2"/><text x="26" y="27" font-family="Bricolage Grotesque" font-weight="500" font-size="23" letter-spacing="-.4">klimahandwerk</text>` },
{ id: 'ausbildung', name: 'Ausbildungsbetrieb', art: '3 Azubis im Jahr', h: 36,
svg: `<path d="M2 14 17 6l15 8-15 8z"/><path d="M8 18v8c3 3 15 3 18 0v-8" ${S} stroke-width="2"/><text x="40" y="27" font-family="Fraunces" font-style="italic" font-weight="300" font-size="25">Wir bilden aus</text>` },
];
const ruhig = $('[data-ruhig]'), rahmen = $('[data-ruhig-rahmen]'), werteEl = $('[data-werte]');
ruhig.innerHTML = PARTNER.map(l => `<li><a href="#nachweis-${l.id}" data-id="${l.id}" aria-label="${l.name}, ${l.art}"><svg data-r-logo viewBox="0 0 220 40" style="--h:${l.h}px" fill="currentColor" aria-hidden="true">${l.svg}</svg></a></li>`).join('');
function rasterVermessen() {
$$('svg[data-r-logo]', ruhig).forEach(svg => {
try { const b = svg.getBBox(); if (b.width > 0) { const w = Math.ceil(b.x + b.width + 1); svg.setAttribute('viewBox', `0 0 ${w} 40`); svg.style.aspectRatio = `${w}/40`; } } catch (e) {}
});
}
const balanceOk = CSS.supports('flex-wrap', 'balance');
let weiche = balanceOk ? 'nativ' : 'messung';
const reihen = () => { const r = new Map(); [...ruhig.children].forEach(li => { const y = Math.round((li.offsetTop + li.offsetHeight / 2) / 8); r.set(y, (r.get(y) || 0) + 1); }); return [...r.values()]; }; // Mitte je Logo, auf 8 px gerastert (Logos sind verschieden hoch)
// Fallback ohne flex-wrap: balance: schmalste Breite suchen, bei der die Reihenzahl gleich bleibt (wie text-wrap: balance)
let gemessen = 0;
function ausgleichen() {
ruhig.style.removeProperty('--breite-max');
ruhig.classList.toggle('ohne-balance', weiche === 'messung');
if (weiche === 'nativ') { gemessen = 0; return; }
const voll = rahmen.clientWidth, n = reihen().length;
if (n < 2) { gemessen = voll; return; }
let lo = voll / n, hi = voll;
for (let i = 0; i < 12; i++) {
const mitte = (lo + hi) / 2;
ruhig.style.setProperty('--breite-max', mitte + 'px');
if (reihen().length > n) lo = mitte; else hi = mitte;
}
gemessen = Math.ceil(hi);
ruhig.style.setProperty('--breite-max', gemessen + 'px');
}
function rasterWerte() {
const r = reihen().join(' / ');
werteEl.innerHTML = (weiche === 'nativ'
? '<span>Umbruch <b>flex-wrap: balance</b> (ab Chrome 150)</span>'
: `<span>Umbruch <b>flex-wrap: wrap + Messung</b></span><span>max-inline-size <b>${gemessen} px</b></span>`)
+ `<span>Reihen <b>${r}</b></span><span>Logos <b>natürliche Breite, Höhe 32–40 px</b></span><span>gap <b>clamp(22px, 3.4vh, 36px) clamp(32px, 4.4vw, 64px)</b></span><span>Hover <b>opacity .78 → 1 · 150 ms cubic-bezier(0.4, 0, 0.2, 1)</b></span>`
+ (balanceOk ? '' : '<span>Browser <b>ohne flex-wrap: balance</b></span>');
}
const weicheKnoepfe = $$('[data-weiche]');
function setzeWeiche(w) {
weiche = (w === 'nativ' && !balanceOk) ? 'messung' : w;
weicheKnoepfe.forEach(b => b.setAttribute('aria-pressed', String(b.dataset.weiche === weiche)));
ausgleichen(); rasterWerte();
}
weicheKnoepfe.forEach(b => { if (b.dataset.weiche === 'nativ' && !balanceOk) { b.disabled = true; b.title = 'Dieser Browser kennt flex-wrap: balance noch nicht'; } b.addEventListener('click', () => setzeWeiche(b.dataset.weiche)); });
/* ---------- Variante „Auszeichnungen mit Jahr“: Gastro ---------- */
const PREISE = [
{ jahr: 2026, name: 'Küche des Jahres', von: 'Tafelführer Nord, Ausgabe 2026', farbe: '#b4472a',
siegel: `<circle cx="32" cy="32" r="29" ${S} stroke-width="2" stroke-dasharray="1.6 3.2"/><circle cx="32" cy="32" r="23" ${S} stroke-width="1.6"/><text x="32" y="36" text-anchor="middle" font-family="Fraunces" font-weight="600" font-size="16">TN</text><text x="32" y="47" text-anchor="middle" font-family="Geist Mono" font-size="5.4" letter-spacing="1.4">NORD</text><path d="M25 19.5h14" ${S} stroke-width="1.2"/>` },
{ jahr: 2025, name: 'Landgasthaus des Jahres, 2. Platz', von: 'Wettbewerb Gasthaus im Norden 2025', farbe: '#8a5a12',
siegel: `<circle cx="32" cy="26" r="17" ${S} stroke-width="2.2"/><circle cx="32" cy="26" r="12.5" ${S} stroke-width="1"/><text x="32" y="32" text-anchor="middle" font-family="Fraunces" font-weight="600" font-size="16">II</text><path d="M22 40l-6 18 7-3.5 4 6.5 5-16M42 40l6 18-7-3.5-4 6.5-5-16" ${S} stroke-width="2" stroke-linejoin="round"/>` },
{ jahr: 2025, name: 'Empfehlung, 3 von 4 Löffeln', von: 'Tafelführer Nord, Ausgabe 2025', farbe: '#b4472a',
siegel: `<g fill="currentColor"><ellipse cx="14" cy="22" rx="5" ry="7.5"/><path d="M13 29h2v22h-2z"/><ellipse cx="26" cy="22" rx="5" ry="7.5"/><path d="M25 29h2v22h-2z"/><ellipse cx="38" cy="22" rx="5" ry="7.5"/><path d="M37 29h2v22h-2z"/></g><ellipse cx="50" cy="22" rx="4.6" ry="7.1" ${S} stroke-width="1.3"/><path d="M50 29.5v21" ${S} stroke-width="1.3"/>` },
{ jahr: 2024, name: 'Weinkarte des Jahres', von: 'Elbweinpreis 2024, Kategorie Gasthaus', farbe: '#7a2140',
siegel: `<path d="M20 6h24c0 14-4 22-12 22S20 20 20 6z" ${S} stroke-width="2.2"/><path d="M21.5 16h21c-1 7-4.5 10-10.5 10s-9.5-3-10.5-10z"/><path d="M32 28v20M22 50h20" ${S} stroke-width="2.2" stroke-linecap="round"/><path d="M8 58q6-4 12 0t12 0 12 0 12 0" ${S} stroke-width="1.6"/>` },
{ jahr: 2022, name: 'Bester Service, Landesentscheid', von: 'Gastgeberwettbewerb Holstein 2022', farbe: '#2f5d7c',
siegel: `<path d="M12 46h40M16 46a16 16 0 0 1 32 0" ${S} stroke-width="2.2" stroke-linejoin="round"/><path d="M32 24v6M28 24h8" ${S} stroke-width="2.2" stroke-linecap="round"/><path d="M8 52h48" ${S} stroke-width="2.6" stroke-linecap="round"/><path d="M24 38a9 9 0 0 1 6-5" ${S} stroke-width="1.4" stroke-linecap="round"/>` },
];
const preise = $('[data-preise]');
preise.innerHTML = PREISE.map((p, i) => `<li><a class="preis" href="#presse-${i}" style="--marke:${p.farbe}" aria-label="${p.jahr}: ${p.name}, ${p.von}"><span class="preis-jahr" aria-hidden="true">${p.jahr}<i>${String(i + 1).padStart(2, '0')}</i></span><svg class="siegel" viewBox="0 0 64 64" fill="currentColor" aria-hidden="true">${p.siegel}</svg><span class="preis-text" aria-hidden="true"><b>${p.name}</b><span>${p.von}</span></span></a></li>`).join('');
function preisWerte() {
werteEl.innerHTML = '<span>Jahr <b>Fraunces 560, lnum tnum</b></span><span>Siegel <b>eigene SVG, currentColor</b></span><span>Hover <b>Siegel in Hausfarbe, rotate −8° · 700 ms</b></span><span>Nachbarn <b>opacity .42 · 200 ms</b></span><span>Kurve <b>cubic-bezier(0.16, 1, 0.3, 1)</b></span><span>Reihenfolge <b>neueste zuerst</b></span>';
}
/* ---------- Variante „Siegel mit Nachweis“: Weingut, je Zeichen Träger, Art, Nummer, Gültigkeit, Kriterien ---------- */
// Erfundene Träger und Nummern (Demo). Ein Zeichen erscheint nur, wenn alle fünf Angaben da sind und es noch gilt.
const ZEICHEN = [
{ id: 'oeko', titel: 'Öko-Kontrolle nach EU-Öko-Verordnung', traeger: 'Kontrollstelle Agrarcert Mitte', art: 'Staatlich geregelt, Kontrolle durch zugelassene Stelle', nummer: 'AC-M-2026-04417', gueltig: '2027-03-31', kriterien: '#kriterien-oeko',
logo: `<circle cx="20" cy="20" r="18.5" ${S} stroke-width="1.3"/><circle cx="20" cy="20" r="14.5" ${S} stroke-width=".7"/><text x="20" y="23.5" text-anchor="middle" font-family="DM Mono" font-weight="500" font-size="9.5" letter-spacing=".6">ÖKO</text>` },
{ id: 'boden', titel: 'Verband Lebendiger Boden', traeger: 'Lebendiger Boden e. V., Prüfung durch Agrarcert Mitte', art: 'Zertifizierungssystem mit Verbandsrichtlinien', nummer: 'VLB-07731', gueltig: '2028-02-29', kriterien: '#kriterien-boden',
logo: `<rect x="1.5" y="1.5" width="37" height="37" rx="7" ${S} stroke-width="1.3"/><path d="M8 27h24M11 22h18M14 17h12" ${S} stroke-width="1.2"/><text x="20" y="13" text-anchor="middle" font-family="DM Mono" font-weight="500" font-size="6.5" letter-spacing=".8">VLB</text>` },
{ id: 'weinbau', titel: 'Nachhaltiger Weinbau', traeger: 'Weinbau Zukunft e. V., Audit durch Prüfgesellschaft Lauterbach', art: 'Zertifizierungssystem, jährliches Audit durch Dritte', nummer: 'WZ-2026-1182', gueltig: '2027-06-30', kriterien: '#kriterien-weinbau',
logo: `<path d="M20 1.5 37 11v18L20 38.5 3 29V11z" ${S} stroke-width="1.3"/><text x="20" y="23" text-anchor="middle" font-family="DM Mono" font-weight="500" font-size="9" letter-spacing=".6">WZ</text>` },
{ id: 'lese', titel: 'Faire Arbeit in der Lese', traeger: 'Initiative Faire Lese e. V., Prüfung durch Sozialaudit Rhein', art: 'Zertifizierungssystem für Saisonarbeit', nummer: 'FL-2026-0312', gueltig: '2027-12-31', kriterien: '#kriterien-lese',
logo: `<path d="M4 3h32v21c0 8-7 12.5-16 14.5C11 36.5 4 32 4 24z" ${S} stroke-width="1.3"/><text x="20" y="22" text-anchor="middle" font-family="DM Mono" font-weight="500" font-size="9" letter-spacing=".6">FL</text>` },
// Eigenes Zeichen ohne Prüfstelle und Nummer: fällt durch und erscheint nicht (Eigensiegel, UWG Anhang Nr. 2a)
{ id: 'klima', titel: 'Klimaneutrales Weingut', traeger: '', art: 'eigene Angabe', nummer: '', gueltig: '', kriterien: '', logo: '' },
];
const nachweise = $('[data-nachweise]'), nrKnopf = $('[data-nr-weg]');
const heuteIso = new Date().toISOString().slice(0, 10);
const fmtGueltig = d => d.split('-').reverse().join('.');
let nrWeg = false;
const daten = () => ZEICHEN.map(z => (nrWeg && z.id === 'boden') ? { ...z, nummer: '' } : z);
const vollstaendig = z => !!(z.traeger && z.art && z.nummer && z.gueltig && z.kriterien) && z.gueltig >= heuteIso;
function nachweisHtml(z) {
return `<li class="nachweis" data-id="${z.id}"><div class="nachweis-kopf"><svg class="zeichen" viewBox="0 0 40 40" fill="currentColor" aria-hidden="true">${z.logo}</svg><h3>${z.titel}</h3></div>`
+ `<dl><div><dt>Träger</dt><dd>${z.traeger}</dd></div><div><dt>Art</dt><dd>${z.art}</dd></div>`
+ `<div><dt>Zertifikat</dt><dd class="nr">${z.nummer}</dd></div><div><dt>Gültig bis</dt><dd><time datetime="${z.gueltig}">${fmtGueltig(z.gueltig)}</time></dd></div>`
+ `<div><dt>Kriterien</dt><dd><a href="${z.kriterien}" aria-label="Öffentliche Kriterien: ${z.titel}">öffentlich einsehen ↗</a></dd></div></dl></li>`;
}
function nachweiseZeichnen(neuId) {
const alle = daten(), gezeigt = alle.filter(vollstaendig);
nachweise.innerHTML = gezeigt.map(nachweisHtml).join('');
if (neuId) { const li = nachweise.querySelector(`[data-id="${neuId}"]`); if (li) li.classList.add('neu'); }
nachweise.dataset.anzahl = Math.min(4, gezeigt.length);
const weg = alle.filter(z => !vollstaendig(z)).map(z => `${z.titel} (${[!z.traeger && 'Träger', !z.nummer && 'Nummer', !z.gueltig && 'Gültigkeit', !z.kriterien && 'Kriterien'].filter(Boolean).join(', ')} fehlt)`);
werteEl.innerHTML = `<span>Gezeigt <b>${gezeigt.length} von ${alle.length}</b></span><span>Ausgeblendet <b>${weg.join('; ') || 'keins'}</b></span><span>Pflicht <b>Träger, Art, Nummer, Gültigkeit, Kriterien-Link</b></span><span>Aufbau <b>dl je Zeichen, Logo nur Begleitung</b></span><span>Kurve <b>cubic-bezier(0.16, 1, 0.3, 1) 450 ms ein, cubic-bezier(0.7, 0, 0.84, 0) 200 ms aus</b></span>`;
}
$('[data-stand]').textContent = new Intl.DateTimeFormat('de-DE', { day: 'numeric', month: 'long', year: 'numeric' }).format(new Date());
$('[data-stand]').setAttribute('datetime', heuteIso);
function nummerUmschalten(weg) {
if (weg === nrWeg) return;
nrKnopf.setAttribute('aria-pressed', String(weg));
const li = nachweise.querySelector('[data-id="boden"]');
if (weg && li && !reduziert()) { li.classList.add('geht'); setTimeout(() => { nrWeg = true; nachweiseZeichnen(); }, 200); }
else { nrWeg = weg; nachweiseZeichnen(weg || reduziert() ? null : 'boden'); }
}
nrKnopf.addEventListener('click', () => nummerUmschalten(!nrWeg));
/* ---------- Hervorheben per Tippen (beide neuen Varianten) ---------- */
let an = null;
function markiere(a) { if (an) an.classList.remove('an'); an = a || null; if (a) a.classList.add('an'); }
[ruhig, preise].forEach(l => l.addEventListener('click', e => {
const a = e.target.closest('a'); if (!a) return;
e.preventDefault(); // Demo: keine Navigation
if (beruehrung.matches) markiere(a === an ? null : a);
}));
document.addEventListener('pointerdown', e => { if (beruehrung.matches && !e.target.closest('[data-ruhig] a, [data-preise] a')) markiere(null); });
/* ---------- Vorführung der neuen Varianten: gedachter Zeiger ---------- */
const zeiger = $('[data-zeiger]');
let uhr = null, t0 = 0, pausiertBei = null, welche = null;
const sanft = p => p < .5 ? 2 * p * p : 1 - Math.pow(-2 * p + 2, 2) / 2;
function zeigerAuf(el) {
if (!el) { zeiger.style.display = 'none'; return; }
const r = el.getBoundingClientRect();
zeiger.style.display = '';
zeiger.style.transform = `translate(${r.left + r.width * .55}px, ${r.top + r.height * .6}px)`;
}
const RUNDE = { raster: 10000, auszeichnungen: 9000, 'siegel-nachweis': 11000 };
function schritt(t) {
const r = (t - t0) % RUNDE[welche];
if (welche === 'raster') {
// Breite der Fläche schwingt 100 % → 58 % → 100 %: die Reihen gleichen sich jedes Mal neu aus
const p = r < 4000 ? sanft(r / 4000) : r < 5000 ? 1 : r < 9000 ? 1 - sanft((r - 5000) / 4000) : 0;
rahmen.style.maxInlineSize = (100 - 42 * p).toFixed(2) + '%';
ausgleichen(); rasterWerte();
const li = $$('a', ruhig), ziel = li[Math.floor(r / 2000) % li.length * 3 % li.length];
if (ziel !== an) markiere(ziel);
zeigerAuf(an);
} else if (welche === 'siegel-nachweis') {
// Zeiger liest die Zeichen der Reihe nach; dann fehlt eine Nummer, das Zeichen verschwindet, und kommt wieder
const li = $$('.nachweis', nachweise), i = Math.floor(r / 1300);
const ziel = r < 5200 ? li[i] : null;
if (ziel !== an) markiere(ziel);
if (r >= 5600 && r < 8800) { if (!nrWeg) nummerUmschalten(true); zeigerAuf(nrKnopf.offsetParent ? nrKnopf : null); }
else { if (nrWeg && r >= 8800) nummerUmschalten(false); zeigerAuf(an); }
} else {
const li = $$('.preis', preise), i = Math.min(li.length - 1, Math.floor(r / 1600));
const ziel = r > 8000 ? null : li[i];
if (ziel !== an) markiere(ziel);
zeigerAuf(an);
}
uhr = requestAnimationFrame(schritt);
}
function vorfuehrenNeu(id) { return () => { pauseNeu(); welche = id; html.classList.add('vorfuehrung'); t0 = performance.now() - (pausiertBei ?? 0); pausiertBei = null; uhr = requestAnimationFrame(schritt); }; }
function pauseNeu() { if (uhr) { cancelAnimationFrame(uhr); uhr = null; pausiertBei = (performance.now() - t0) % (RUNDE[welche] || 1); } }
function weiterNeu() { if (!uhr && welche && html.classList.contains('vorfuehrung')) vorfuehrenNeu(welche)(); }
function stoppNeu() { if (uhr) cancelAnimationFrame(uhr); uhr = null; pausiertBei = null; welche = null; html.classList.remove('vorfuehrung'); zeiger.style.display = 'none'; markiere(null); rahmen.style.maxInlineSize = ''; if (nrWeg) { nrWeg = false; nrKnopf.setAttribute('aria-pressed', 'false'); } }
const fertig = document.fonts ? document.fonts.ready : Promise.resolve();
let rasterBereit = false;
STEUER.raster = {
start() { fertig.then(() => { if (!rasterBereit) { rasterVermessen(); rasterBereit = true; } setzeWeiche(weiche); }); rasterWerte(); },
stopp: stoppNeu,
neustart() { stoppNeu(); setzeWeiche(weiche); },
vorfuehren: vorfuehrenNeu('raster'), pause: pauseNeu, weiter: weiterNeu
};
STEUER.auszeichnungen = {
start() { preisWerte(); },
stopp: stoppNeu,
neustart() { stoppNeu(); preisWerte(); },
vorfuehren: vorfuehrenNeu('auszeichnungen'), pause: pauseNeu, weiter: weiterNeu
};
STEUER['siegel-nachweis'] = {
start() { nachweiseZeichnen(); },
stopp: stoppNeu,
neustart() { stoppNeu(); nachweiseZeichnen(); },
vorfuehren: vorfuehrenNeu('siegel-nachweis'), pause: pauseNeu, weiter: weiterNeu
};
addEventListener('resize', () => { if (html.dataset.v === 'raster') { ausgleichen(); rasterWerte(); } });
})();
/* ---------- Varianten-Umschalter (Regel 13): ?v=laufband | raster | auszeichnungen | siegel-nachweis ---------- */
(() => {
const html = document.documentElement;
const IDS = ['laufband', 'raster', 'auszeichnungen', 'siegel-nachweis'];
let v = new URLSearchParams(location.search).get('v');
if (!IDS.includes(v)) v = IDS[0];
let vorf = false;
const knoepfe = [...document.querySelectorAll('[data-v-wahl]')];
function setze(neu, url) {
const alt = v;
v = neu; html.dataset.v = neu;
if (STEUER[alt]) STEUER[alt].stopp();
document.querySelectorAll('[data-v-sektion]').forEach(s => { s.hidden = s.dataset.vSektion !== neu; });
knoepfe.forEach(b => b.setAttribute('aria-pressed', String(b.dataset.vWahl === neu)));
window.WELT(neu);
if (url) {
const q = new URLSearchParams(location.search); q.set('v', neu);
history.replaceState(null, '', location.pathname + '?' + q.toString().replace(/=(?=&|$)/g, '') + location.hash);
}
STEUER[neu].start();
}
knoepfe.forEach(b => b.addEventListener('click', () => { setze(b.dataset.vWahl, true); if (vorf) STEUER[v].vorfuehren(); }));
setze(v, false);
})();
</script>
</body>
</html>