Graustufen zu Farbe / Duoton-Bildbehandlung
Das Team-Raster steht ruhig in Graustufen oder im Duoton der Marke; nur die Person unter dem Zeiger bekommt ihre Farbe zurück.
Bilder stehen im Ruhezustand entsättigt oder als Duoton in der Markenfarbe. Beim Hover oder Fokus bekommt nur das berührte Bild seine Vollfarbe zurück: filter: grayscale(1) geht auf 0, oder eine Duoton-Ebene blendet aus. Oft kombiniert mit leichtem Zoom; als Variante kommt die Farbe überbelichtet und setzt sich (Lichtblitz).
Ein Raster aus vielen Fotos wird ruhig und einheitlich, auch wenn Licht und Hintergrund der Aufnahmen schwanken. Farbe wird zum Fokuszeichen: Wo sie auftaucht, liegt die Aufmerksamkeit. Im Duoton tragen alle Bilder die Markenfarbe und wirken grafisch und geschlossen.
Wann einsetzen
Team- und Köpfe-Raster, Kundenlogos, Projektraster mit uneinheitlichen Fotos, Archive. Event- und Kampagnenseiten mit starker Farbidentität.
Wann nicht
Wenn die Farbe verkauft, etwa bei Produkt- oder Food-Fotos. Wenn die meisten Besucher per Touch kommen und die Farbe sonst nie sähen.
Varianten
- Graustufen zu Farbe: filter: grayscale(1) → 0 (Future Fonts, Klim)
- Duoton in Markenfarbe, das beim Hover ausblendet (SVG-Gradient-Map oder mix-blend-mode)
- Lichtblitz: brightness(300 %) → 100 % beim Einfärben (Codrops HoverGrid)
- Nur die Nachbarn werden grau (siehe nachbarn-reagieren-hover)
- Tritone oder kombiniert mit Halbton-Raster
- Statisches Duoton als Bildsprache, ganz ohne Hover
Umsetzung
@media (hover: hover) { .tile img { filter: grayscale(1) contrast(1.05); transition: filter .1s cubic-bezier(.42,0,1,1), scale .5s cubic-bezier(.4,0,.22,1); } .tile:is(:hover, :focus-within) img { filter: none; scale: 1.03; } }
Duoton: Zwei Ebenen übereinander, oben das Bild mit SVG-Filter (feColorMatrix für die Luminanz, dann feComponentTransfer type="table" mit Schatten- und Lichtfarbe als Gradient-Map). Beim Hover blendet die obere Ebene per opacity aus. mix-blend-mode ist nicht animierbar, deshalb immer über opacity. Die einfache CSS-Variante: Container mit background: var(--marke), img { filter: grayscale(1); mix-blend-mode: multiply }. Die SVG-Variante ist genauer, ihre Farben lassen sich aber nicht per Custom Property ändern.
Fallstricke: filter auf vielen großen Bildern kostet; will-change nur sparsam. Die Duoton-Ebene ist eine Kopie und bekommt aria-hidden.
Robust umsetzen
Telefon. Ohne Hover alle Bilder in Farbe zeigen. Graustufen nur unter @media (hover: hover). Statisches Duoton kann bleiben.
Ohne Bewegung. Unkritisch. Den Farbwechsel ohne Übergang zeigen, Zoom und Lichtblitz weglassen.
Barrierefrei. Keine Information nur über Farbe. Tastaturfokus löst denselben Zustand aus wie Hover, dazu ein sichtbarer Fokusrahmen. Alt-Texte am Originalbild, die Duoton-Kopie ist aria-hidden. Kontrast von Text über Duoton-Bildern prüfen.
Code
<!DOCTYPE html>
<!--
Graustufen zu Farbe / Duoton-Bildbehandlung
Fundus · Hover · https://cecodesign.de/lab/fundus/hover/graustufen-zu-farbe/
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>Graustufen zu Farbe / Duoton-Bildbehandlung · 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: 'Newsreader'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/newsreader:vf@latest/latin-opsz-normal.woff2') format('woff2-variations'); font-weight: 200 800; font-style: normal; font-display: swap; }
@font-face { font-family: 'Schibsted Grotesk'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/schibsted-grotesk:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 400 900; 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; }
:root {
/* Rezept kanzlei-beratung-souveraen: ein Markenton, warme Graus, Cyan nur für Fokus */
--grund: #f9f8f8; --flaeche: #efebeb; --linie: #d2cdc7;
--marke: #1e1e50; --text: #212529; --meta: #73645a; --akzent: #00bee6;
/* Graustufen: Future Fonts, filter .1s ease-in (gemessen) */
--grau-dauer: 100ms; --grau-kurve: cubic-bezier(0.42, 0, 1, 1);
/* Duoton und Zoom: Rezept-Kurve (Oppenhoff), 500 ms Bild-Einblendung */
--rezept: cubic-bezier(0.4, 0, 0.22, 1); --bild-dauer: 500ms;
/* Lichtblitz: Codrops HoverGrid, brightness 300 % → 100 %, 0.95 s power4.out */
--blitz-dauer: 950ms; --blitz-kurve: cubic-bezier(0.22, 1, 0.36, 1);
}
* { box-sizing: border-box; margin: 0; }
html { background: var(--grund); color: var(--text); font-family: 'Schibsted Grotesk', system-ui, sans-serif; font-size: 100%; -webkit-font-smoothing: antialiased; }
body { min-height: 100svh; padding: clamp(16px, 3.4vw, 48px); display: grid; align-content: center; }
.seite { width: 100%; max-width: 1180px; margin: 0 auto; }
a { color: inherit; }
.mono { font: 500 11px/1.3 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; color: var(--meta); }
/* Seitenkopf der Kanzlei */
.seitenkopf { display: flex; align-items: center; justify-content: space-between; gap: 24px; padding-bottom: clamp(16px, 2.4vw, 28px); border-bottom: 1px solid var(--linie); }
.marke { display: flex; align-items: baseline; gap: 12px; text-decoration: none; color: var(--marke); }
.marke b { font: 400 1.375rem/1 'Newsreader', Georgia, serif; letter-spacing: -.01em; }
.marke span { font-size: .8125rem; color: var(--meta); }
.nav { display: flex; gap: clamp(14px, 2vw, 28px); list-style: none; padding: 0; font-size: .9375rem; font-weight: 500; }
.nav a { text-decoration: none; text-underline-offset: .35em; text-decoration-thickness: 1px; }
.nav a[aria-current] { color: var(--marke); text-decoration: underline; }
.portal { font-size: .8125rem; font-weight: 500; color: var(--marke); text-decoration: none; padding: 8px 14px; border: 1px solid var(--linie); border-radius: 2px; white-space: nowrap; }
/* Sektionskopf */
.kopf { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 25rem); gap: clamp(20px, 4vw, 64px); align-items: end; padding: clamp(24px, 3.4vw, 44px) 0 clamp(20px, 2.6vw, 32px); }
.rubrik { display: flex; gap: 14px; align-items: baseline; font-size: .8125rem; font-weight: 500; color: var(--meta); margin-bottom: 14px; font-variant-numeric: tabular-nums; }
.rubrik b { color: var(--marke); font-weight: 500; text-transform: uppercase; letter-spacing: .08em; }
h2 { font: 400 clamp(1.625rem, 1.2rem + 1.6vw, 2.5rem)/1.1 'Newsreader', Georgia, serif; letter-spacing: -.01em; color: var(--marke); text-wrap: balance; }
.lead { font-size: 1.0625rem; line-height: 1.6; max-width: 65ch; text-wrap: pretty; }
/* Varianten-Umschalter (Demo-Bedienung) */
.steuerung { display: flex; gap: 4px; margin-top: 18px; flex-wrap: wrap; }
.steuerung button { font: 500 10.5px/1 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; color: var(--meta); background: none; border: 1px solid transparent; border-radius: 2px; padding: 8px 10px; cursor: pointer; transition: color 125ms var(--rezept), border-color 125ms var(--rezept); }
.steuerung button:hover { color: var(--marke); }
.steuerung button[aria-pressed="true"] { color: var(--marke); border-color: var(--linie); background: #fff; }
.steuerung button:focus-visible { outline: 2px solid var(--akzent); outline-offset: 2px; }
/* Raster der Köpfe */
.raster { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: clamp(12px, 1.8vw, 28px); list-style: none; padding: 0; }
.karte { position: relative; }
.bild { position: relative; aspect-ratio: 4 / 5; overflow: hidden; background: #8e979c; border-radius: 2px; isolation: isolate; }
.bild svg { position: absolute; inset: 0; width: 100%; height: 100%; display: block; transform-origin: 50% 62%; transition: scale var(--bild-dauer) var(--rezept); }
.bild .duo { filter: url(#duoton); opacity: 0; }
.nr { position: absolute; left: 12px; top: 12px; z-index: 2; color: rgba(30, 30, 80, .5); font: 500 10.5px/1 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; font-variant-numeric: tabular-nums; }
.text { padding-top: 14px; display: grid; gap: 4px; }
.funktion { font-size: .8125rem; font-weight: 500; color: var(--meta); }
h3 { font: 400 clamp(1.1875rem, 1rem + .5vw, 1.5rem)/1.15 'Newsreader', Georgia, serif; letter-spacing: -.01em; color: var(--marke); }
h3 a { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: .2em; text-decoration-color: transparent; transition: text-decoration-color 125ms var(--rezept); outline: none; }
h3 a::after { content: ''; position: absolute; inset: 0; z-index: 1; }
.fach { font-size: .9375rem; line-height: 1.45; hyphens: auto; overflow-wrap: break-word; }
.kontakt { display: flex; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-top: 8px; padding-top: 10px; border-top: 1px solid var(--linie); font-size: .8125rem; color: var(--meta); font-variant-numeric: tabular-nums; }
.kontakt a { position: relative; z-index: 2; color: var(--marke); font-weight: 500; text-decoration: none; }
.kontakt a:hover { text-decoration: underline; text-underline-offset: .25em; }
.karte:is(:hover, :focus-within, .an) h3 a { text-decoration-color: currentColor; }
.karte:has(h3 a:focus-visible) .bild, .kontakt a:focus-visible { outline: 2px solid var(--akzent); outline-offset: 3px; }
.fuss { display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-top: clamp(20px, 2.6vw, 32px); padding-top: 14px; border-top: 1px solid var(--linie); }
.werte { display: flex; gap: 6px 18px; flex-wrap: wrap; }
.werte span b { color: var(--marke); font-weight: 500; }
/* === Der Effekt ===
data-zeiger steht am Raster, wenn das Gerät hovern kann (oder die Vorführung läuft).
Im echten Einsatz genügt @media (hover: hover) { … }. */
[data-zeiger] .karte:is(:hover, :focus-within, .an) .bild svg { scale: 1.03; }
/* 1 Graustufen: filter grayscale(1) → 0, 100 ms ease-in */
[data-zeiger][data-variante="graustufen"] .farbe { filter: grayscale(1) contrast(1.05); transition: filter var(--grau-dauer) var(--grau-kurve), scale var(--bild-dauer) var(--rezept); }
[data-zeiger][data-variante="graustufen"] .karte:is(:hover, :focus-within, .an) .farbe { filter: grayscale(0) contrast(1); }
/* 2 Duoton: Gradient-Map per SVG-Filter auf einer zweiten Ebene, die ausblendet */
[data-zeiger][data-variante="duoton"] .duo { opacity: 1; transition: opacity var(--bild-dauer) var(--rezept), scale var(--bild-dauer) var(--rezept); }
[data-zeiger][data-variante="duoton"] .karte:is(:hover, :focus-within, .an) .duo { opacity: 0; }
/* 3 Lichtblitz: Farbe kommt überbelichtet und setzt sich */
[data-zeiger][data-variante="blitz"] .farbe { filter: grayscale(1) brightness(1); transition: filter var(--grau-dauer) var(--grau-kurve), scale var(--bild-dauer) var(--rezept); }
[data-zeiger][data-variante="blitz"] .karte:is(:hover, :focus-within, .an) .farbe { filter: grayscale(0) brightness(1); animation: blitz var(--blitz-dauer) var(--blitz-kurve); }
@keyframes blitz { from { filter: grayscale(0) brightness(3); } to { filter: grayscale(0) brightness(1); } }
/* Gedachter Zeiger für die Vorführung */
.zeiger { position: fixed; left: 0; top: 0; z-index: 9; width: 18px; height: 18px; pointer-events: none; display: none; }
.vorfuehrung .zeiger { display: block; }
/* Touch: kein Hover, also Fotos in Farbe (JS setzt data-zeiger nicht) */
@media (hover: none) {
.vorfuehrung .steuerung { display: flex; }
.steuerung { display: none; }
.werte [data-wert="touch"] { display: inline; }
}
.werte [data-wert="touch"] { display: none; }
/* Reduzierte Bewegung: Farbwechsel bleibt, aber sofort; kein Zoom, kein Blitz */
.reduziert .bild svg, .reduziert .farbe, .reduziert .duo, .reduziert h3 a { transition: none !important; animation: none !important; }
.reduziert [data-zeiger] .karte:is(:hover, :focus-within, .an) .bild svg { scale: 1; }
/* Vorschau 960 × 720: nur die Bühne */
.vorschau body { overflow: hidden; padding: 28px 40px; }
.vorschau .nav, .vorschau .lead, .vorschau .adresse { display: none; }
.vorschau .kopf { padding: 26px 0 22px; }
.vorschau .steuerung { margin-top: 0; justify-content: flex-end; }
@media (max-width: 980px) { .nav { display: none; } }
@media (max-width: 760px) {
.kopf { grid-template-columns: 1fr; align-items: start; gap: 14px; }
.raster { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 28px; }
.marke span { display: none; }
}
@media (max-width: 420px) {
.kontakt { flex-direction: column; gap: 4px; }
.fach { font-size: .875rem; }
}
/* Kontrastmodus: Knöpfe mit Rahmen */
@media (forced-colors: active) { button, [role=button], [role=tab], [role=switch] { border: 1px solid ButtonText !important; } }
</style>
</head>
<body>
<!-- Porträts: gezeichnete Studioaufnahmen, ein Licht für alle (Platzhalter für echte Teamfotos) -->
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
<defs>
<radialGradient id="g-grund" cx=".38" cy=".28" r=".9">
<stop offset="0" stop-color="#d6dbdc"/><stop offset=".5" stop-color="#a9b3b7"/><stop offset="1" stop-color="#66727a"/>
</radialGradient>
<radialGradient id="g-saum" cx=".5" cy=".5" r=".5">
<stop offset="0" stop-color="#fff" stop-opacity=".42"/><stop offset="1" stop-color="#fff" stop-opacity="0"/>
</radialGradient>
<linearGradient id="g-boden" x1="0" y1="0" x2="0" y2="1">
<stop offset=".55" stop-color="#0b0d14" stop-opacity="0"/><stop offset="1" stop-color="#0b0d14" stop-opacity=".32"/>
</linearGradient>
<radialGradient id="g-kopf" cx=".34" cy=".28" r=".8">
<stop offset="0" stop-color="#fff" stop-opacity=".3"/><stop offset=".42" stop-color="#fff" stop-opacity="0"/>
<stop offset=".78" stop-color="#2a1208" stop-opacity=".16"/><stop offset="1" stop-color="#2a1208" stop-opacity=".42"/>
</radialGradient>
<linearGradient id="g-koerper" x1="0" y1="0" x2="1" y2=".35">
<stop offset="0" stop-color="#fff" stop-opacity=".2"/><stop offset=".42" stop-color="#fff" stop-opacity="0"/><stop offset="1" stop-color="#000" stop-opacity=".38"/>
</linearGradient>
<linearGradient id="g-hals" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#3a1608" stop-opacity=".55"/><stop offset=".42" stop-color="#3a1608" stop-opacity=".1"/><stop offset="1" stop-color="#3a1608" stop-opacity=".28"/>
</linearGradient>
<radialGradient id="g-mulde" cx=".5" cy=".5" r=".5">
<stop offset="0" stop-color="#3a1608" stop-opacity=".2"/><stop offset="1" stop-color="#3a1608" stop-opacity="0"/>
</radialGradient>
<radialGradient id="g-wange" cx=".5" cy=".5" r=".5">
<stop offset="0" stop-color="#d9644e" stop-opacity=".22"/><stop offset="1" stop-color="#d9644e" stop-opacity="0"/>
</radialGradient>
<linearGradient id="g-haar" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#fff" stop-opacity=".26"/><stop offset=".4" stop-color="#fff" stop-opacity="0"/><stop offset="1" stop-color="#000" stop-opacity=".34"/>
</linearGradient>
<filter id="korn" x="0" y="0" width="100%" height="100%">
<feTurbulence type="fractalNoise" baseFrequency=".85" numOctaves="2" stitchTiles="stitch"/>
<feColorMatrix type="saturate" values="0"/>
</filter>
<!-- Duoton: Luminanz, Gamma 1.6 (mehr Tiefe), dann Gradient-Map #1E1E50 (Schatten) bis #EFEBEB (Licht) -->
<filter id="duoton" color-interpolation-filters="sRGB">
<feColorMatrix type="matrix" values=".2126 .7152 .0722 0 0 .2126 .7152 .0722 0 0 .2126 .7152 .0722 0 0 0 0 0 1 0"/>
<feComponentTransfer>
<feFuncR type="gamma" exponent="1.6"/><feFuncG type="gamma" exponent="1.6"/><feFuncB type="gamma" exponent="1.6"/>
</feComponentTransfer>
<feComponentTransfer>
<feFuncR type="table" tableValues=".118 .937"/>
<feFuncG type="table" tableValues=".118 .922"/>
<feFuncB type="table" tableValues=".314 .922"/>
</feComponentTransfer>
</filter>
<!-- gemeinsame Formen -->
<path id="f-koerper" d="M-20 520L-14 470C-6 424 36 400 108 390C140 386 158 380 170 370L230 370C242 380 260 386 292 390C364 400 406 424 414 470L420 520Z"/>
<path id="f-hals" d="M172 284L172 364Q200 386 228 364L228 284Z"/>
<path id="f-kopf" d="M200 138C246 138 269 174 269 220C269 262 256 298 232 316C220 325 210 328 200 328C190 328 180 325 168 316C144 298 131 262 131 220C131 174 154 138 200 138Z"/>
<path id="f-ohren" d="M133 219a10 19 0 1 0 .1 0ZM267 219a10 19 0 1 0 .1 0Z"/>
<path id="f-v" d="M166 376L200 448L234 376Q200 368 166 376Z"/>
<path id="f-revers" d="M166 376L200 448L190 478L138 388ZM234 376L200 448L210 478L262 388Z"/>
<g id="f-modell">
<path d="M244 170C262 196 266 240 254 278C246 300 232 314 216 322C236 300 250 268 252 232C253 206 250 186 244 170Z" fill="#3a1608" fill-opacity=".1"/>
<ellipse cx="200" cy="246" rx="44" ry="30" fill="url(#g-mulde)" opacity=".35"/>
<ellipse cx="164" cy="262" rx="20" ry="13" fill="url(#g-wange)"/>
<ellipse cx="236" cy="262" rx="20" ry="13" fill="url(#g-wange)"/>
</g>
<!-- Gesicht: Brauen in currentColor (Haarfarbe am <use>), Augen, Nase -->
<g id="f-augen">
<path d="M155 221Q172 209 192 216L191 221Q173 215 156 226Z M245 221Q228 209 208 216L209 221Q227 215 244 226Z" fill="currentColor"/>
<path d="M160 239Q172 230 185 238Q172 245 160 239Z M215 238Q228 230 240 239Q228 245 215 238Z" fill="#f3ebe4" fill-opacity=".85"/>
<circle cx="173.5" cy="238" r="4.6" fill="#2a1d17"/><circle cx="226.5" cy="238" r="4.6" fill="#2a1d17"/>
<circle cx="175" cy="236.4" r="1.2" fill="#fff" fill-opacity=".8"/><circle cx="228" cy="236.4" r="1.2" fill="#fff" fill-opacity=".8"/>
<path d="M159 239Q172 228.5 186 237.5M214 237.5Q228 228.5 241 239" fill="none" stroke="#2a140b" stroke-width="2" stroke-linecap="round" stroke-opacity=".75"/>
<path d="M163 246Q173 249 183 245M217 245Q227 249 237 246" fill="none" stroke="#3a1608" stroke-width="1" stroke-opacity=".22"/>
<path d="M204 244Q207 262 211 271Q205 277 196 273" fill="none" stroke="#3a1608" stroke-width="1.8" stroke-linecap="round" stroke-opacity=".32"/>
<ellipse cx="203" cy="274" rx="11" ry="4" fill="#3a1608" fill-opacity=".1"/>
</g>
<g id="f-mund">
<path d="M185 294Q193 289 200 291Q207 289 215 294Q200 304 185 294Z" fill="#a8574c" fill-opacity=".62"/>
<path d="M185 294Q200 297.5 215 294" fill="none" stroke="#4e1c14" stroke-width="1.5" stroke-linecap="round" stroke-opacity=".55"/>
</g>
<g id="f-studio">
<rect width="400" height="500" fill="url(#g-grund)"/>
<ellipse cx="200" cy="360" rx="210" ry="150" fill="url(#g-saum)"/>
</g>
<g id="f-finish">
<rect width="400" height="500" fill="url(#g-boden)"/>
<rect width="400" height="500" filter="url(#korn)" opacity=".16" style="mix-blend-mode:overlay"/>
</g>
<!-- 01 Dr. Imke Gerdes -->
<symbol id="p1" viewBox="0 0 400 500">
<use href="#f-studio"/>
<g transform="matrix(1.12 0 0 1.12 -24 -43)">
<g transform="rotate(-2.5 200 380) translate(-4 6)">
<path d="M122 232C114 152 158 114 202 114C248 114 290 152 280 234C278 282 286 312 292 330C262 340 244 336 236 326L164 326C156 336 138 340 108 330C114 312 124 282 122 232Z" fill="#9c7444"/>
<path d="M122 232C114 152 158 114 202 114C248 114 290 152 280 234C278 282 286 312 292 330C262 340 244 336 236 326L164 326C156 336 138 340 108 330C114 312 124 282 122 232Z" fill="#000" fill-opacity=".22"/>
<use href="#f-hals" fill="#d9ad8b"/><use href="#f-hals" fill="url(#g-hals)"/>
</g>
<use href="#f-koerper" fill="#1d5c60"/>
<use href="#f-v" fill="#efe7da"/>
<path d="M180 384Q200 418 220 384" fill="none" stroke="#c9a04a" stroke-width="1.6"/>
<use href="#f-revers" fill="#164a4d"/>
<use href="#f-koerper" fill="url(#g-koerper)"/>
<g transform="rotate(-2.5 200 380) translate(-4 6)">
<use href="#f-ohren" fill="#d4a684"/>
<use href="#f-kopf" fill="#e2b896"/><use href="#f-kopf" fill="url(#g-kopf)"/>
<use href="#f-modell"/>
<use href="#f-augen" color="#6e4a26"/><use href="#f-mund"/>
<circle cx="133" cy="258" r="3.4" fill="#d8b45c"/><circle cx="267" cy="258" r="3.4" fill="#d8b45c"/>
<path d="M131 222C127 162 162 130 206 130C248 132 272 166 268 214C256 184 232 166 196 160C176 188 152 206 131 222Z" fill="#a98050"/>
<path d="M131 222C127 162 162 130 206 130C248 132 272 166 268 214C256 184 232 166 196 160C176 188 152 206 131 222Z" fill="url(#g-haar)"/>
</g>
</g>
<use href="#f-finish"/>
</symbol>
<!-- 02 Hauke Lüken -->
<symbol id="p2" viewBox="0 0 400 500">
<use href="#f-studio"/>
<g transform="matrix(1.12 0 0 1.12 -24 -43)">
<g transform="rotate(2 200 380)"><use href="#f-hals" fill="#c49478"/><use href="#f-hals" fill="url(#g-hals)"/></g>
<use href="#f-koerper" fill="#a34d2b"/>
<use href="#f-v" fill="#f3f0ea"/>
<path d="M166 376L184 404L200 384L216 404L234 376Q200 366 166 376Z" fill="#fbfaf6"/>
<path d="M166 376L200 448L234 376" fill="none" stroke="#8a3d20" stroke-width="7" stroke-linejoin="round"/>
<use href="#f-koerper" fill="url(#g-koerper)"/>
<g transform="rotate(2 200 380)">
<use href="#f-ohren" fill="#c99c80"/>
<use href="#f-kopf" fill="#d6aa8e"/><use href="#f-kopf" fill="url(#g-kopf)"/>
<use href="#f-modell"/>
<use href="#f-augen" color="#5e554c"/><use href="#f-mund"/>
<path d="M133 216C128 170 152 134 194 129C240 124 272 156 268 212C263 192 255 179 243 171C229 163 214 162 203 166L191 157C172 162 156 172 147 186C140 196 136 206 133 216ZM132 220L141 220L140 252L134 250ZM268 220L259 220L260 252L266 250Z" fill="#7e7368"/>
<path d="M133 214L139 214L139 236C136 232 134 226 133 220ZM267 212L261 212L261 236C264 232 266 226 267 218Z" fill="#c9c4bd" opacity=".55"/>
<path d="M133 216C128 170 152 134 194 129C240 124 272 156 268 212C263 192 255 179 243 171C229 163 214 162 203 166L191 157C172 162 156 172 147 186C140 196 136 206 133 216Z" fill="url(#g-haar)"/>
</g>
</g>
<use href="#f-finish"/>
</symbol>
<!-- 03 Dr. Derya Yıldız -->
<symbol id="p3" viewBox="0 0 400 500">
<use href="#f-studio"/>
<g transform="matrix(1.12 0 0 1.12 -24 -43)">
<use href="#f-koerper" fill="#7c2236"/>
<use href="#f-koerper" fill="url(#g-koerper)"/>
<g transform="translate(5 0) rotate(-1.5 200 380)">
<path d="M118 224C108 142 158 110 200 110C242 110 292 142 282 224C284 300 292 380 300 432C274 446 248 442 238 430C238 382 236 342 232 312L168 312C164 342 162 382 162 430C152 442 126 446 100 432C108 380 116 300 118 224Z" fill="#2a1b15"/>
<path d="M118 224C108 142 158 110 200 110C242 110 292 142 282 224C284 300 292 380 300 432C274 446 248 442 238 430C238 382 236 342 232 312L168 312C164 342 162 382 162 430C152 442 126 446 100 432C108 380 116 300 118 224Z" fill="url(#g-haar)"/>
<use href="#f-hals" fill="#a97a5d"/><use href="#f-hals" fill="url(#g-hals)"/>
<path d="M170 372Q200 412 230 372L228 364Q200 386 172 364Z" fill="#a97a5d"/>
<path d="M170 372Q200 412 230 372" fill="none" stroke="#5e1828" stroke-width="5"/>
<use href="#f-kopf" fill="#b98a6c"/><use href="#f-kopf" fill="url(#g-kopf)"/>
<use href="#f-modell"/>
<use href="#f-augen" color="#1d1310"/><use href="#f-mund"/>
<path d="M200 124C156 126 132 168 130 242C142 196 168 156 200 140C232 156 258 196 270 242C268 168 244 126 200 124Z" fill="#2a1b15"/>
<path d="M200 124C156 126 132 168 130 242C142 196 168 156 200 140C232 156 258 196 270 242C268 168 244 126 200 124Z" fill="url(#g-haar)" opacity=".7"/>
<circle cx="136" cy="268" r="5" fill="#d2ab4f"/><circle cx="264" cy="268" r="5" fill="#d2ab4f"/>
</g>
</g>
<use href="#f-finish"/>
</symbol>
<!-- 04 Jan-Ole Wübbenhorst -->
<symbol id="p4" viewBox="0 0 400 500">
<use href="#f-studio"/>
<g transform="matrix(1.12 0 0 1.12 -24 -43)">
<g transform="rotate(3 200 380) translate(3 -2)"><use href="#f-hals" fill="#dcae8c"/><use href="#f-hals" fill="url(#g-hals)"/></g>
<use href="#f-koerper" fill="#525c34"/>
<use href="#f-v" fill="#c3d5e6"/>
<path d="M166 376L184 402L200 382L216 402L234 376Q200 366 166 376Z" fill="#d4e2ef"/>
<use href="#f-revers" fill="#434c29"/>
<use href="#f-koerper" fill="url(#g-koerper)"/>
<g transform="rotate(3 200 380) translate(3 -2)">
<use href="#f-ohren" fill="#ddb090"/>
<use href="#f-kopf" fill="#e9c0a0"/><use href="#f-kopf" fill="url(#g-kopf)"/>
<use href="#f-modell"/>
<use href="#f-augen" color="#7a4426"/>
<path d="M134 244C134 302 164 336 200 340C236 336 266 302 266 244C258 270 248 282 230 288C214 278 186 278 170 288C152 282 142 270 134 244Z" fill="#8f5331"/>
<path d="M134 244C134 302 164 336 200 340C236 336 266 302 266 244C258 270 248 282 230 288C214 278 186 278 170 288C152 282 142 270 134 244Z" fill="url(#g-haar)" opacity=".6"/>
<use href="#f-mund" transform="translate(0 -1)"/>
<path d="M132 216C126 170 150 132 190 126C204 116 226 118 240 128C264 140 276 170 268 216C262 196 254 184 244 176C238 170 232 167 224 166C208 172 188 168 172 166C160 172 150 180 144 190C138 198 134 206 132 216Z" fill="#a6623a"/>
<path d="M132 216C126 170 150 132 190 126C204 116 226 118 240 128C264 140 276 170 268 216C262 196 254 184 244 176C238 170 232 167 224 166C208 172 188 168 172 166C160 172 150 180 144 190C138 198 134 206 132 216Z" fill="url(#g-haar)"/>
<g fill="#fff" fill-opacity=".07" stroke="#25262b" stroke-width="4.5">
<rect x="149" y="217" width="43" height="29" rx="11"/><rect x="208" y="217" width="43" height="29" rx="11"/>
</g>
<path d="M192 228Q200 221 208 228M149 226L135 221M251 226L265 221" fill="none" stroke="#25262b" stroke-width="4.5" stroke-linecap="round"/>
</g>
</g>
<use href="#f-finish"/>
</symbol>
</defs>
</svg>
<div class="seite">
<header class="seitenkopf">
<a class="marke" href="#start"><b>Gerdes Lüken</b><span>Rechtsanwälte · Steuerberater</span></a>
<ul class="nav">
<li><a href="#leistungen">Leistungen</a></li>
<li><a href="#branchen">Branchen</a></li>
<li><a href="#koepfe" aria-current="page">Köpfe</a></li>
<li><a href="#insights">Insights</a></li>
<li><a href="#kontakt">Kontakt</a></li>
</ul>
<a class="portal" href="#portal">Mandantenportal</a>
</header>
<section class="koepfe" id="koepfe" aria-labelledby="koepfe-titel">
<div class="kopf">
<div>
<p class="rubrik"><b>Köpfe</b><span>14 Berufsträger · Oldenburg und Cloppenburg</span></p>
<h2 id="koepfe-titel">Jedes Mandat hat bei uns einen Namen.</h2>
</div>
<div>
<p class="lead">Eine Partnerin oder ein Partner führt Ihre Akte vom ersten Gespräch bis zum Abschluss. Recht und Steuern sitzen dabei an einem Tisch.</p>
<div class="steuerung" role="group" aria-label="Bildbehandlung wählen">
<button type="button" data-v="graustufen" aria-pressed="true">Graustufen</button>
<button type="button" data-v="duoton" aria-pressed="false">Duoton</button>
<button type="button" data-v="blitz" aria-pressed="false">Lichtblitz</button>
</div>
</div>
</div>
<ul class="raster" data-raster data-variante="graustufen">
<li class="karte">
<figure class="bild">
<svg class="farbe" viewBox="0 0 400 500" role="img" aria-label="Porträt von Dr. Imke Gerdes"><use href="#p1"/></svg>
<svg class="duo" viewBox="0 0 400 500" aria-hidden="true"><use href="#p1"/></svg>
<span class="nr" aria-hidden="true">01</span>
</figure>
<div class="text">
<p class="funktion">Partnerin · Rechtsanwältin</p>
<h3><a href="#gerdes">Dr. Imke Gerdes</a></h3>
<p class="fach">Fachanwältin für Arbeitsrecht</p>
<p class="kontakt"><span>DE · EN · NL</span><a href="tel:+4944192184011">0441 92 18 40-11</a></p>
</div>
</li>
<li class="karte">
<figure class="bild">
<svg class="farbe" viewBox="0 0 400 500" role="img" aria-label="Porträt von Hauke Lüken"><use href="#p2"/></svg>
<svg class="duo" viewBox="0 0 400 500" aria-hidden="true"><use href="#p2"/></svg>
<span class="nr" aria-hidden="true">02</span>
</figure>
<div class="text">
<p class="funktion">Partner · Steuerberater</p>
<h3><a href="#lueken">Hauke Lüken</a></h3>
<p class="fach">Unternehmensnachfolge und Erbschaftsteuer</p>
<p class="kontakt"><span>DE · EN</span><a href="tel:+4944192184012">0441 92 18 40-12</a></p>
</div>
</li>
<li class="karte">
<figure class="bild">
<svg class="farbe" viewBox="0 0 400 500" role="img" aria-label="Porträt von Dr. Derya Yıldız"><use href="#p3"/></svg>
<svg class="duo" viewBox="0 0 400 500" aria-hidden="true"><use href="#p3"/></svg>
<span class="nr" aria-hidden="true">03</span>
</figure>
<div class="text">
<p class="funktion">Rechtsanwältin</p>
<h3><a href="#yildiz">Dr. Derya Yıldız</a></h3>
<p class="fach">Gesellschaftsrecht und M&A</p>
<p class="kontakt"><span>DE · TR · EN</span><a href="tel:+4944192184014">0441 92 18 40-14</a></p>
</div>
</li>
<li class="karte">
<figure class="bild">
<svg class="farbe" viewBox="0 0 400 500" role="img" aria-label="Porträt von Jan-Ole Wübbenhorst"><use href="#p4"/></svg>
<svg class="duo" viewBox="0 0 400 500" aria-hidden="true"><use href="#p4"/></svg>
<span class="nr" aria-hidden="true">04</span>
</figure>
<div class="text">
<p class="funktion">Steuerberater · Cloppenburg</p>
<h3><a href="#wuebbenhorst">Jan-Ole Wübbenhorst</a></h3>
<p class="fach">Landwirtschaft und Agrarbetriebe</p>
<p class="kontakt"><span>DE · Platt</span><a href="tel:+4944719381017">04471 93 81-017</a></p>
</div>
</li>
</ul>
<div class="fuss">
<p class="werte mono" data-werte aria-live="polite"></p>
<p class="mono adresse">Haarenufer 14 · 26122 Oldenburg</p>
</div>
</section>
</div>
<svg class="zeiger" data-zeiger-bild viewBox="0 0 18 18" aria-hidden="true"><path d="M2 1.5 15.5 9.2l-6 1.3-2.9 5.6Z" fill="#1e1e50" stroke="#f9f8f8" stroke-width="1.2" stroke-linejoin="round"/></svg>
<script>
(() => {
const html = document.documentElement;
const raster = document.querySelector('[data-raster]');
const karten = [...raster.querySelectorAll('.karte')];
const knoepfe = [...document.querySelectorAll('[data-v]')];
const werteZeile = document.querySelector('[data-werte]');
const zeiger = document.querySelector('[data-zeiger-bild]');
const kannHover = matchMedia('(hover: hover)');
// Werte, die die Demo tatsächlich nutzt (Anzeige unter dem Raster)
const WERTE = {
graustufen: [['filter', 'grayscale(1) → 0'], ['Dauer', '100 ms'], ['Kurve', 'ease-in'], ['Zoom', '1 → 1.03 · 500 ms']],
duoton: [['Gradient-Map', '#1E1E50 → #EFEBEB'], ['Deckkraft', '1 → 0 · 500 ms'], ['Kurve', '(.4, 0, .22, 1)'], ['Zoom', '1 → 1.03']],
blitz: [['brightness', '3 → 1'], ['Dauer', '950 ms'], ['Kurve', 'power4.out'], ['Zoom', '1 → 1.03 · 500 ms']]
};
const REIHE = ['graustufen', 'duoton', 'blitz'];
function setzeVariante(v) {
raster.dataset.variante = v;
knoepfe.forEach(k => k.setAttribute('aria-pressed', String(k.dataset.v === v)));
werteZeile.innerHTML = WERTE[v].map(([k, w]) => `<span>${k} <b>${w}</b></span>`).join('')
+ '<span data-wert="touch">Touch <b>Fotos in Farbe</b></span>';
}
knoepfe.forEach(k => k.addEventListener('click', () => setzeVariante(k.dataset.v)));
// Graustufen nur, wo es Hover gibt (oder während der Vorführung)
function zeigerPruefen() {
if (kannHover.matches || html.classList.contains('vorfuehrung')) raster.dataset.zeiger = '';
else delete raster.dataset.zeiger;
}
kannHover.addEventListener('change', zeigerPruefen);
// Vorführung: ein gedachter Zeiger fährt die vier Köpfe ab, jede Runde eine Variante
const RUNDE = 6400;
let uhr = null, start = 0, aktiv = -1, runde = -1, versatz = 0;
const glatt = x => x * x * (3 - 2 * x);
function markieren(i) {
if (i === aktiv) return;
aktiv = i;
karten.forEach((k, j) => k.classList.toggle('an', j === i));
}
function schritt(t) {
const zeit = t - start + versatz;
const r = Math.floor(zeit / RUNDE), p = (zeit % RUNDE) / RUNDE;
if (r !== runde) { runde = r; setzeVariante(REIHE[r % REIHE.length]); }
const m = karten.map(k => k.querySelector('.bild').getBoundingClientRect());
const mitte = i => ({ x: m[i].left + m[i].width * 0.52, y: m[i].top + m[i].height * 0.46 });
const draussen = { x: m[3].right + 30, y: m[3].bottom + 120 };
let pos, ziel = -1;
if (p < 0.08) pos = draussen;
else if (p > 0.88) { const q = glatt(Math.min(1, (p - 0.88) / 0.06)); const a = mitte(3); pos = { x: a.x + (draussen.x - a.x) * q, y: a.y + (draussen.y - a.y) * q }; }
else {
const q = (p - 0.08) / 0.8 * 4, i = Math.min(3, Math.floor(q)), f = q - i;
const von = i === 0 ? draussen : mitte(i - 1), nach = mitte(i);
const g = glatt(Math.min(1, f / 0.28));
pos = { x: von.x + (nach.x - von.x) * g, y: von.y + (nach.y - von.y) * g + Math.sin(f * Math.PI) * 14 };
}
m.forEach((b, i) => { if (pos.x >= b.left && pos.x <= b.right && pos.y >= b.top && pos.y <= b.bottom) ziel = i; });
markieren(ziel);
zeiger.style.transform = `translate(${pos.x - 2}px, ${pos.y - 1}px)`;
uhr = requestAnimationFrame(schritt);
}
function vorfuehren() {
pause();
html.classList.add('vorfuehrung'); zeigerPruefen();
start = performance.now();
uhr = requestAnimationFrame(schritt);
}
function pause() {
if (uhr) { cancelAnimationFrame(uhr); versatz += performance.now() - start; }
uhr = null;
}
function weiter() {
if (uhr || !html.classList.contains('vorfuehrung')) return;
start = performance.now(); uhr = requestAnimationFrame(schritt);
}
function neustart() {
if (uhr) cancelAnimationFrame(uhr);
uhr = null; versatz = 0; runde = -1;
html.classList.remove('vorfuehrung'); zeigerPruefen();
markieren(-1); setzeVariante(raster.dataset.variante || 'graustufen');
}
raster.addEventListener('click', e => { if (e.target.closest('h3 a')) e.preventDefault(); });
setzeVariante('graustufen'); zeigerPruefen();
})();
</script>
</body>
</html>