Zum Inhalt
← Mikroanimationen

Skeleton mit Schimmer

Graue Platzhalter in exakter Größe, über die eine Lichtkante wandert; die Inhalte blenden ohne Layoutsprung herein.

Listen, Karten und Dashboards mit bekannter Struktur: Gerüst aus Zeilen und Bildflächen in exakter Größe, umschaltbar zwischen Schimmer, synchroner Lichtwelle und Pulsieren.

Statt eines Spinners zeigt der Platz die künftige Struktur als graue Formen: Bildflächen, Textzeilen, Avatare, jeweils in exakter Größe. Eine helle Lichtkante wandert darüber (Schimmer), oder die Formen pulsieren in der Deckkraft. Sobald die Daten da sind, blendet der echte Inhalt in 200 bis 300 ms über. Das Layout bleibt stehen.

Die Seite zeigt sofort, was kommt. Das Auge kann sich schon orientieren, die Wartezeit wirkt kürzer. Weil Platzhalter und Inhalt gleich groß sind, gibt es keinen Layoutsprung (CLS). Der Inhalt wächst in seine Form hinein.

Wann einsetzen

Listen, Karten, Dashboards, Bildergalerien, nachgeladene Abschnitte und KI-Antworten. Überall, wo die Struktur vorher feststeht und das Laden spürbar dauert.

Wann nicht

Bei sehr kurzen Ladezeiten unter etwa 300 ms: dann gar nichts zeigen, sonst flackert es. Bei unbekannter Struktur: dann lieber Spinner oder Fortschrittsanzeige.

Varianten

  • Schimmer: Lichtkante per background-position-Animation, einzeln über jede Form.
  • Synchron: dieselbe Animation mit background-attachment: fixed. Eine gemeinsame Lichtwelle läuft über alle Formen.
  • Pulsieren: nur die Deckkraft schwingt, ruhiger und ohne Richtung.
  • Bild-Platzhalter: Mittelfarbe oder 20 bis 30 px breite Mini-Vorschau (unscharf) in festem Seitenverhältnis, das Bild blendet nach decode() in etwa 150 ms ein; nie beim LCP-Bild. ThumbHash statt BlurHash bei transparenten Bildern

Umsetzung

Gerüst und Inhalt liegen in derselben Grid-Zelle (grid-area: 1 / 1). So bestimmt der Inhalt die Größe, und beim Wechsel springt nichts. Schimmer: .sk { background: #dad2c3 linear-gradient(100deg, transparent 62%, var(--licht) 75%, transparent 88%) 0 0 / 200% 100% repeat-x; animation: schimmer 1.5s cubic-bezier(.42,0,.58,1) infinite } @keyframes schimmer { to { background-position: -200% 0 } }. Synchron: zusätzlich background-attachment: fixed. Pulsieren: @keyframes puls { 50% { opacity: .5 } } bei Grundwert .8, 2 s. Bildflächen mit aspect-ratio, Zeilen 0,74em hoch mit der Schriftgröße des echten Textes. Ankunft: Gerüst und Inhalt tauschen die Deckkraft in 250 ms. Liste mit aria-busy="true", Gerüst aria-hidden.

Robust umsetzen

Telefon. Identisch. Auf schmalen Schirmen passt sich das Gerüst dem mobilen Raster an (z. B. Liste mit Daumenbild). Hinweis: iOS Safari ignoriert background-attachment: fixed, dort läuft die synchrone Variante wie der einfache Schimmer.

Ohne Bewegung. Kein wandernder Verlauf. Entweder statisches Grau (so macht es 14islands) oder ein sehr langsames Pulsieren. Chrome empfiehlt für Ladezustände: verlangsamen statt stoppen, etwa 6 s statt 1,5 s.

Barrierefrei. aria-busy="true" am Container, solange geladen wird, danach false. Das Gerüst ist aria-hidden. Den Abschluss bei Bedarf über eine aria-live-Region melden, z. B. „Drei Beiträge geladen“. Der Kontrast der Platzhalter muss nicht hoch sein, sie tragen keine Information.

Code

<!DOCTYPE html>
<!--
  Skeleton mit Schimmer
  Fundus · Mikroanimationen · https://cecodesign.de/lab/fundus/mikroanimationen/skeleton-shimmer/
  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>Skeleton mit Schimmer · 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: 'Libre Caslon Display'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/libre-caslon-display@latest/latin-400-normal.woff2') format('woff2'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'Hanken Grotesk'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/hanken-grotesk:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-display: swap; }
@font-face { font-family: 'Geist Mono'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/geist-mono:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-display: swap; }

@font-face { font-family: 'Instrument Serif'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/instrument-serif@latest/latin-400-normal.woff2') format('woff2'); font-weight: 400; font-style: normal; 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 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: 'Fragment Mono'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/fragment-mono@latest/latin-400-normal.woff2') format('woff2'); font-weight: 400; font-display: swap; }

/* Weidner Immobilien, Hamburg-Eppendorf · Rezept „immobilien-diskret-hochwertig“:
   Antiqua nur für Headlines ab 2rem, Fakten in der Grotesk mit Tabellenziffern, helle Flächen,
   tiefer Markenton, ein einziger Akzent für den Haupt-CTA. */
:root {
  --grund: #f6f6f4; --flaeche: #fcfaf5; --text: #313131; --marke: #02385c; --akzent: #ed7f1a;
  --leise: rgba(49, 49, 49, .72); --linie: rgba(2, 56, 92, .14);
  --sk: #e4dfd7;                                              /* Grundton der Platzhalter (Sand) */
  --licht: color-mix(in oklab, var(--sk), white 62%);         /* Lichtkante des Schimmers */
  --kurve: cubic-bezier(0.42, 0, 0.58, 1);                    /* ease-in-out, gemessen (Quai, 14islands, Bolt) */
  --schimmer: 1500ms;                                         /* ein Durchlauf (Quai, Bolt) */
  --puls: 2000ms;                                             /* Pulsieren (v0) */
  --ueber: 250ms;                                             /* Überblenden zum echten Inhalt */
  /* Satzmaße, die Gerüst und Inhalt gemeinsam nutzen */
  --t-gr: clamp(18px, 1.55vw, 21px); --t-zh: 1.24;            /* Objekttitel */
  --p-gr: 15px; --p-zh: 1.5;                                  /* Kurzbeschreibung */
}
* { box-sizing: border-box; margin: 0; }
html { background: var(--grund); color: var(--text); font-family: 'Hanken Grotesk', system-ui, sans-serif; -webkit-font-smoothing: antialiased; font-variant-numeric: tabular-nums; }
body { min-height: 100svh; padding: clamp(20px, 2.8vw, 40px) clamp(20px, 3.9vw, 56px); display: grid; align-content: center; gap: clamp(22px, 2.5vw, 32px); }
.label { font: 600 11px/1.3 'Hanken Grotesk', system-ui, sans-serif; letter-spacing: .09em; text-transform: uppercase; color: var(--leise); }
.mono { font: 500 11px/1.35 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; color: var(--leise); }

/* Kopf: Marke und Navigation stehen sofort, nur die Objektliste lädt nach */
.marke { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding-bottom: 16px; border-bottom: 1px solid var(--linie); }
.wortmarke { display: flex; align-items: baseline; gap: 12px; color: var(--marke); text-decoration: none; }
.wortmarke b { font-weight: 600; font-size: 15px; letter-spacing: .32em; }
.wortmarke .zusatz { display: flex; font-size: 11px; letter-spacing: .09em; text-transform: uppercase; color: var(--leise); font-weight: 500; white-space: nowrap; }
.wortmarke .zusatz span + span::before { content: '·'; margin: 0 .55em; }
.nav { display: flex; align-items: center; gap: clamp(14px, 2.2vw, 30px); list-style: none; padding: 0; }
.nav a { color: var(--text); text-decoration: none; font-size: 14px; }
.nav a[aria-current] { color: var(--marke); box-shadow: inset 0 -1px var(--marke); }
.cta { font: 600 12px/1 'Hanken Grotesk', sans-serif; letter-spacing: .06em; color: var(--text); background: var(--akzent); border-radius: 2px; padding: 11px 14px; text-decoration: none; white-space: nowrap; }

.kopf { display: grid; grid-template-columns: 1fr auto; align-items: end; gap: 10px 24px; }
.kopf h1 { font: 400 clamp(38px, 5vw, 68px)/1.04 'Libre Caslon Display', Georgia, serif; letter-spacing: -.005em; color: var(--marke); }
.kopf .unter { grid-column: 1; font-size: 14px; color: var(--leise); }
.status { display: flex; align-items: center; gap: 10px; white-space: nowrap; grid-row: 1 / 3; grid-column: 2; align-self: end; }
.status i { width: 7px; height: 7px; border-radius: 50%; background: currentColor; opacity: .35; transition: opacity var(--ueber) var(--kurve), background-color var(--ueber) var(--kurve); }
.fertig .status i { opacity: 1; background: #3e7b5a; }

/* Objektliste */
.strom { list-style: none; padding: 0; display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(16px, 2.4vw, 32px); }
.karte { display: grid; position: relative; }
.karte > * { grid-area: 1 / 1; }                      /* Platzhalter und Inhalt teilen sich eine Zelle: kein Layoutsprung */

.inhalt, .geruest { display: grid; grid-template-rows: auto auto auto auto 1fr; align-content: start; }
.bildrahmen { aspect-ratio: 4 / 3; border-radius: 2px; overflow: hidden; margin-bottom: 18px; }
.bildrahmen svg { display: block; width: 100%; height: 100%; }
.kicker { display: flex; justify-content: space-between; gap: 8px; margin-bottom: 9px; }
.inhalt h2 { font-size: var(--t-gr); line-height: var(--t-zh); font-weight: 500; letter-spacing: -.012em; color: var(--marke); margin-bottom: 8px; min-height: calc(var(--t-zh) * 2em);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; text-wrap: balance; }
.inhalt .text { font-size: var(--p-gr); line-height: var(--p-zh); color: var(--leise); margin-bottom: 16px; min-height: calc(var(--p-zh) * 2em); }
.vorschau .inhalt .text { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
.fakten { display: grid; grid-template-columns: .8fr 1fr 1.5fr; gap: 12px; padding-top: 12px; border-top: 1px solid var(--linie); }
.fakten dt { font: 600 10.5px/1.3 'Hanken Grotesk', sans-serif; letter-spacing: .09em; text-transform: uppercase; color: var(--leise); margin-bottom: 3px; }
.fakten dd { font-size: 16px; line-height: 1.3; font-weight: 500; color: var(--text); white-space: nowrap; }
.fakten div:last-child { text-align: right; }

/* Das Gerüst: gleiches Raster, gleiche Satzmaße. Jede Platzhalterzeile ist .74em hoch
   und sitzt mittig in einer Zeile des echten Textes (Rand = (Zeilenhöhe − .74em) / 2). */
.zeile { display: block; height: .74em; border-radius: .4em; }
.geruest .k { display: flex; justify-content: space-between; margin-bottom: 9px; font-size: 11px; }
.geruest .k .zeile { margin: calc((1.3em - .74em) / 2) 0; width: 34%; }
.geruest .k .zeile + .zeile { width: 14%; }
.geruest .t { display: flex; flex-direction: column; font-size: var(--t-gr); margin-bottom: 8px; }
.geruest .t .zeile { margin: calc((var(--t-zh) * 1em - .74em) / 2) 0; width: 96%; }
.geruest .t .zeile + .zeile { width: 58%; }
.geruest .p { display: flex; flex-direction: column; font-size: var(--p-gr); margin-bottom: 16px; }
.geruest .p .zeile { height: .62em; margin: calc((var(--p-zh) * 1em - .62em) / 2) 0; width: 100%; }
.geruest .p .zeile + .zeile { width: 72%; }
.geruest .f { display: grid; grid-template-columns: .8fr 1fr 1.5fr; gap: 12px; padding-top: 12px; border-top: 1px solid var(--linie); }
.geruest .f > div { display: flex; flex-direction: column; }
.geruest .f > div:last-child { align-items: flex-end; }
.geruest .f .l { font-size: 10.5px; margin: calc((1.3em - .74em) / 2) 0; margin-bottom: calc((1.3em - .74em) / 2 + 3px); width: 70%; }
.geruest .f .w { font-size: 16px; margin: calc((1.3em - .74em) / 2) 0; width: 80%; }
.geruest .f > div:last-child .w { width: 92%; }

.sk { background-color: var(--sk); }

/* Variante 1 – Schimmer: Lichtkante wandert über jede Form einzeln (Quai: 200 %, 0 → −200 %, 1,5 s ease-in-out) */
.v-schimmer .sk, .v-synchron .sk {
  background-image: linear-gradient(100deg, transparent 62%, var(--licht) 75%, transparent 88%);
  background-size: 200% 100%; background-repeat: repeat-x;
  animation: schimmer var(--schimmer) var(--kurve) infinite;
}
/* Variante 2 – Synchron: Verlauf am Viewport verankert, eine gemeinsame Lichtwelle über alle Formen */
.v-synchron .sk { background-attachment: fixed; }
@keyframes schimmer { from { background-position: 0 0; } to { background-position: -200% 0; } }
/* Variante 3 – Pulsieren: nur Deckkraft, ruhiger (Bolt .8 ↔ .5, v0 2 s) */
.v-puls .sk { animation: puls var(--puls) var(--kurve) infinite; }
@keyframes puls { 0%, 100% { opacity: .8; } 50% { opacity: .5; } }

/* Ankunft: Gerüst geht, Inhalt kommt, 250 ms Überblenden, kein Versatz */
.inhalt { opacity: 0; visibility: hidden; transition: opacity var(--ueber) var(--kurve), visibility 0s var(--ueber); }
.geruest { transition: opacity var(--ueber) var(--kurve); }
.karte.da .inhalt { opacity: 1; visibility: visible; transition: opacity var(--ueber) var(--kurve); }
.karte.da .geruest { opacity: 0; }
.karte.da .geruest .sk { animation: none; }
.sofort .inhalt, .sofort .geruest { transition: none !important; }

/* Bedienung, dezent */
.fuss { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px 24px; padding-top: 14px; border-top: 1px solid var(--linie); }
.wahl { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
.wahl button, .neu { font: 500 11px/1 'Geist Mono', monospace; letter-spacing: .14em; text-transform: uppercase; color: var(--leise); background: none; border: 1px solid transparent; border-radius: 2px; padding: 8px 12px; cursor: pointer; transition: color 150ms, border-color 150ms; }
.wahl button:hover, .neu:hover { color: var(--marke); }
.wahl button[aria-pressed="true"] { color: var(--marke); border-color: var(--linie); }
.neu { border-color: var(--linie); color: var(--marke); }
.wahl button:focus-visible, .neu:focus-visible, .cta:focus-visible, .nav a:focus-visible { outline: 2px solid var(--marke); outline-offset: 2px; }
.werte { flex-basis: 100%; }
@media (min-width: 1280px) { .werte { flex-basis: auto; order: 1; } .neu { order: 2; } }

/* Vorschau: nur die Bühne */
.vorschau body { overflow: hidden; height: 100svh; padding: 34px 44px; align-content: center; gap: 24px; }
.vorschau .wahl, .vorschau .neu, .vorschau .nav { display: none; }
.vorschau .fuss { padding-top: 12px; }

/* Schmalere Desktops: Navigation weicht vor der CTA */
@media (max-width: 900px) { .nav { display: none; } }

/* Telefon: Liste mit Daumenbild, so stehen alle drei Objekte auf dem Schirm */
@media (max-width: 640px) {
  body { gap: 22px; }
  .wortmarke { flex-direction: column; gap: 4px; }
  .wortmarke b { font-size: 14px; }
  .wortmarke .zusatz { flex-direction: column; gap: 2px; font-size: 10px; }
  .wortmarke .zusatz span + span::before { content: none; }
  .cta { padding: 11px 12px; font-size: 11.5px; }
  .kopf { grid-template-columns: 1fr; }
  .status { grid-row: auto; grid-column: 1; }
  .strom { grid-template-columns: 1fr; gap: 0; }
  .karte { padding: 16px 0; border-top: 1px solid var(--linie); }
  .karte:first-child { border-top: 0; padding-top: 0; }
  .inhalt, .geruest { grid-template-columns: 96px 1fr; grid-template-rows: auto auto auto 1fr; column-gap: 16px; }
  .bildrahmen { grid-row: 1 / 5; grid-column: 1; aspect-ratio: 1; margin: 0; align-self: start; }
  .inhalt > :not(.bildrahmen), .geruest > :not(.bildrahmen) { grid-column: 2; }
  .inhalt .text, .geruest .p { display: none; }
  :root { --t-gr: 17px; }
  .kicker, .geruest .k { margin-bottom: 6px; }
  .inhalt h2, .geruest .t { margin-bottom: 8px; }
  .fakten, .geruest .f { border-top: 0; padding-top: 0; gap: 10px; grid-template-columns: .7fr .9fr 1.6fr; }
  .fakten dt, .geruest .f .l { font-size: 10px; }
  .fakten dd, .geruest .f .w { font-size: 14px; }
}
@media (hover: none) { .wahl button, .neu { padding: 11px 13px; } }
/* 320 px: CTA darf umbrechen, Raster nie breiter als der Viewport */
@media (max-width: 380px) { body, .strom, .karte { grid-template-columns: minmax(0, 1fr); } .inhalt, .geruest { grid-template-columns: 80px minmax(0, 1fr); column-gap: 14px; } .fakten, .geruest .f { gap: 8px; } .fakten dd, .geruest .f .w { font-size: 13px; } .cta { white-space: normal; text-align: center; line-height: 1.2; } .werte { overflow-wrap: anywhere; } }

/* ===== Variante „bild-platzhalter“: Bild-Platzhalter aus Mittelfarbe oder Mini-Vorschau, Rezept fotograf-film-bild-zuerst ===== */
.bild-welt { display: none; --f-grund: #f4f4f2; --f-text: #141414; --f-meta: #6d6d6d; --f-linie: #d0d0ce; --f-grau: #e6e6e6;
  --ein: 150ms; --ein-kurve: cubic-bezier(0.16, 1, 0.3, 1);   /* Rezept: ease-ein */
  font-family: 'Instrument Sans', system-ui, sans-serif; color: var(--f-text); }
html:has(body.ist-bild), body.ist-bild { background: #f4f4f2; }
.ist-bild .marke, .ist-bild .kopf, .ist-bild .strom { display: none; }
.ist-bild .bild-welt { display: grid; gap: clamp(14px, 2vw, 24px); }
.ist-bild .fuss { border-top-color: #d0d0ce; }
.ist-bild .wahl button[aria-pressed="true"], .ist-bild .neu { color: #141414; }
.b-kopf { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; }
.b-kopf b { font: 400 1.5rem/1 'Instrument Serif', Georgia, serif; letter-spacing: -.01em; }
.b-kopf nav { display: flex; gap: clamp(14px, 2vw, 28px); font-size: .875rem; }
.b-kopf nav a { color: var(--f-text); text-decoration: none; }
.b-kopf nav a[aria-current] { box-shadow: inset 0 -1px currentColor; }
.b-mono { font: 400 .75rem/1.4 'Fragment Mono', ui-monospace, monospace; letter-spacing: .02em; color: var(--f-meta); font-variant-numeric: tabular-nums; }
.b-titel { display: flex; justify-content: space-between; align-items: baseline; gap: 8px 20px; flex-wrap: wrap; }
.b-titel h1 { font: 400 clamp(1.5rem, 3vw, 2.75rem)/1.1 'Instrument Serif', Georgia, serif; letter-spacing: -.01em; }
.b-titel h1 em { font-style: italic; }
/* LCP-Bild: sofort, fetchpriority=high, kein Platzhalter, kein Einblenden */
.b-held { margin: 0; }
.b-held img { display: block; width: 100%; height: clamp(180px, 30vh, 420px); object-fit: cover; object-position: 50% 88%; background: var(--f-grau); }   /* Höhe fest aus CSS: kein Sprung */
.b-held figcaption, .b-bild figcaption { display: flex; justify-content: space-between; gap: 12px; margin-top: 8px; }
.b-held figcaption span:last-child { color: var(--f-text); }
/* Das Muster: fester Rahmen (aspect-ratio), darunter Mittelfarbe oder Mini-Vorschau, das Bild blendet nach decode() ein */
.b-reihe { list-style: none; padding: 0; display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: clamp(10px, 1.4vw, 18px); }
.b-bild { margin: 0; }
.b-rahmen { position: relative; aspect-ratio: 4 / 5; overflow: hidden; background-color: var(--mittel, var(--f-grau)); }
.b-rahmen::before { content: ''; position: absolute; inset: -8%; background: var(--lqip) center / cover no-repeat; filter: blur(14px); opacity: 0; }
[data-platzhalter="vorschau"] .b-rahmen::before { opacity: 1; }
[data-platzhalter="grau"] .b-rahmen { background-color: var(--f-grau); }
.b-rahmen img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0; transition: opacity var(--ein) var(--ein-kurve); }
.b-rahmen img.da { opacity: 1; }
.b-zustand { position: absolute; left: 8px; bottom: 8px; padding: 3px 6px; background: rgba(244, 244, 242, .9); color: #141414; transition: opacity var(--ein) var(--ein-kurve); }
.b-rahmen img.da + .b-zustand { opacity: 0; }
.b-bild figcaption span:first-child { color: var(--f-text); }
.vorschau .b-kopf nav, .vorschau .b-held figcaption { display: none; }
.vorschau body.ist-bild { gap: 14px; }
.vorschau .b-held img { height: 190px; }
@media (max-width: 900px) { .b-reihe { grid-template-columns: repeat(3, minmax(0, 1fr)); } .b-held img { height: auto; aspect-ratio: 16 / 9; } }
@media (max-width: 640px) {
  .b-reihe { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .b-held img { height: auto; aspect-ratio: 4 / 3; }
  .b-kopf nav a:not([aria-current]) { display: none; }
  .b-held figcaption span:first-child { display: none; }
}
.reduziert .b-rahmen img, .reduziert .b-zustand { transition: none; }
.platz-wahl { display: none; }
.ist-bild .platz-wahl { display: flex; }
.wahl .trenner { width: 1px; align-self: stretch; margin: 6px 6px; background: var(--linie); }

/* Reduzierte Bewegung: kein wandernder Verlauf; Chrome-Prinzip „verlangsamen statt stoppen“ – Puls 6 s */
.reduziert .sk, .reduziert .v-schimmer .sk, .reduziert .v-synchron .sk { background-image: none; animation: puls 6000ms var(--kurve) infinite; }
.reduziert .karte.da .geruest .sk { animation: none; }
/* Kontrastmodus: Knöpfe mit Rahmen */
@media (forced-colors: active) { button, [role=button], [role=tab], [role=switch] { border: 1px solid ButtonText !important; } }
</style>
</head>
<body class="v-schimmer">
<header class="marke">
  <a class="wortmarke" href="#" aria-label="Weidner Immobilien, Hamburg-Eppendorf"><b>WEIDNER</b><span class="zusatz"><span>Immobilien</span><span>Hamburg-Eppendorf</span></span></a>
  <ul class="nav">
    <li><a href="#" aria-current="page">Kaufen</a></li>
    <li><a href="#">Mieten</a></li>
    <li><a href="#">Verkaufen</a></li>
    <li><a href="#">Marktbericht</a></li>
  </ul>
  <a class="cta" href="#">Immobilie bewerten</a>
</header>

<div class="kopf">
  <h1>Aktuelle Angebote</h1>
  <p class="unter">Kauf · Eppendorf, Harvestehude, Hoheluft · Käuferprovision 3,57 % inkl. MwSt.</p>
  <p class="mono status" aria-live="polite"><i aria-hidden="true"></i><span data-status>Lädt drei Objekte</span></p>
</div>

<ul class="strom" data-strom aria-busy="true" aria-label="Neue Objekte">
  <li class="karte">
    <div class="geruest" aria-hidden="true">
      <div class="bildrahmen sk"></div>
      <div class="k"><span class="zeile sk"></span><span class="zeile sk"></span></div>
      <div class="t"><span class="zeile sk"></span><span class="zeile sk"></span></div>
      <div class="p"><span class="zeile sk"></span><span class="zeile sk"></span></div>
      <div class="f"><div><span class="zeile sk l"></span><span class="zeile sk w"></span></div><div><span class="zeile sk l"></span><span class="zeile sk w"></span></div><div><span class="zeile sk l"></span><span class="zeile sk w"></span></div></div>
    </div>
    <article class="inhalt">
      <div class="bildrahmen">
        <svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Zeichnung: Gründerzeitfassade mit Erker, die Fenster der angebotenen Wohnung sind erleuchtet">
          <defs>
            <linearGradient id="a-himmel" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#c3cfd7"/><stop offset="1" stop-color="#dfe5e8"/></linearGradient>
            <linearGradient id="a-glas" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#55626e"/><stop offset=".55" stop-color="#3a4652"/><stop offset="1" stop-color="#2d3742"/></linearGradient>
            <linearGradient id="a-warm" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f4dcaa"/><stop offset="1" stop-color="#e2b26e"/></linearGradient>
            <linearGradient id="a-licht" x1="0" y1="0" x2="1" y2="0"><stop offset="0" stop-color="#fff" stop-opacity=".22"/><stop offset=".5" stop-color="#fff" stop-opacity="0"/><stop offset="1" stop-color="#02385c" stop-opacity=".08"/></linearGradient>
            <g id="a-fenster">
              <rect x="-26" y="-6" width="52" height="6" fill="#ddd3c3"/><rect x="-22" y="-10" width="44" height="4" fill="#e6ddcf"/>
              <rect x="-20" y="0" width="40" height="86" fill="#f3efe7"/>
              <rect x="-17" y="3" width="34" height="80" fill="url(#a-glas)"/>
              <path d="M0 3v80M-17 27h34" stroke="#f3efe7" stroke-width="2.4"/>
              <rect x="-24" y="86" width="48" height="5" fill="#d8cdbb"/>
            </g>
            <g id="a-hell">
              <rect x="-26" y="-6" width="52" height="6" fill="#ddd3c3"/><rect x="-22" y="-10" width="44" height="4" fill="#e6ddcf"/>
              <rect x="-20" y="0" width="40" height="86" fill="#f3efe7"/>
              <rect x="-17" y="3" width="34" height="80" fill="url(#a-warm)"/>
              <path d="M0 3v80M-17 27h34" stroke="#f3efe7" stroke-width="2.4"/>
              <rect x="-24" y="86" width="48" height="5" fill="#d8cdbb"/>
            </g>
            <g id="a-gitter" stroke="#02385c" stroke-opacity=".55" stroke-width="1.2"><path d="M-24 58h48M-24 86h48"/><path d="M-20 58v28M-12 58v28M-4 58v28M4 58v28M12 58v28M20 58v28"/></g>
          </defs>
          <rect width="400" height="300" fill="url(#a-himmel)"/>
          <path d="M0 40 L36 14 H364 L400 40 Z" fill="#5b6670"/>
          <rect x="172" y="0" width="56" height="34" fill="#5b6670"/><path d="M168 4 L200 -12 L232 4" fill="#4d5862"/>
          <rect x="184" y="10" width="32" height="22" fill="#f3efe7"/><rect x="187" y="13" width="26" height="19" fill="url(#a-glas)"/>
          <rect y="40" width="400" height="260" fill="#ece6db"/>
          <rect y="40" width="400" height="14" fill="#ddd3c4"/><rect y="54" width="400" height="3" fill="#e6ddcf"/>
          <path d="M0 54.5h400" stroke="#02385c" stroke-opacity=".18"/>
          <rect x="146" y="57" width="108" height="243" fill="#e4dccf"/>
          <rect x="146" y="57" width="10" height="243" fill="#d9cfbf"/><rect x="244" y="57" width="10" height="243" fill="#d3c8b7"/>
          <rect y="176" width="400" height="7" fill="#e0d6c8"/><path d="M0 176.5h400M0 182.5h400" stroke="#02385c" stroke-opacity=".12"/>
          <use href="#a-fenster" x="44" y="78"/><use href="#a-fenster" x="112" y="78"/>
          <use href="#a-fenster" x="200" y="78"/>
          <use href="#a-fenster" x="288" y="78"/><use href="#a-fenster" x="356" y="78"/>
          <use href="#a-fenster" x="44" y="204"/><use href="#a-gitter" x="44" y="204"/>
          <use href="#a-fenster" x="112" y="204"/>
          <use href="#a-hell" x="177" y="204"/><use href="#a-hell" x="223" y="204"/>
          <use href="#a-fenster" x="288" y="204"/>
          <use href="#a-fenster" x="356" y="204"/><use href="#a-gitter" x="356" y="204"/>
          <path d="M150 194 L200 186 L250 194 Z" fill="#ddd3c3"/>
          <rect width="400" height="300" fill="url(#a-licht)"/>
          <g fill="#7f8a72"><circle cx="-6" cy="250" r="44"/><circle cx="34" cy="286" r="34"/><circle cx="-10" cy="300" r="40"/></g>
          <g fill="#97a189" opacity=".9"><circle cx="18" cy="244" r="20"/><circle cx="46" cy="270" r="16"/></g>
        </svg>
      </div>
      <p class="kicker label"><span>Eppendorf · Kauf</span><span>Neu</span></p>
      <h2>Altbauwohnung mit Erker und Stuck, Erikastraße</h2>
      <p class="text">Drittes Obergeschoss, Stuck und Dielen im Original, Südbalkon zum ruhigen Innenhof.</p>
      <dl class="fakten"><div><dt>Zimmer</dt><dd>4</dd></div><div><dt>Wohnfläche</dt><dd>128 m²</dd></div><div><dt>Kaufpreis</dt><dd>1.290.000 €</dd></div></dl>
    </article>
  </li>
  <li class="karte">
    <div class="geruest" aria-hidden="true">
      <div class="bildrahmen sk"></div>
      <div class="k"><span class="zeile sk"></span><span class="zeile sk"></span></div>
      <div class="t"><span class="zeile sk"></span><span class="zeile sk"></span></div>
      <div class="p"><span class="zeile sk"></span><span class="zeile sk"></span></div>
      <div class="f"><div><span class="zeile sk l"></span><span class="zeile sk w"></span></div><div><span class="zeile sk l"></span><span class="zeile sk w"></span></div><div><span class="zeile sk l"></span><span class="zeile sk w"></span></div></div>
    </div>
    <article class="inhalt">
      <div class="bildrahmen">
        <svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Grundriss der oberen Ebene des Penthouse mit Dachterrasse">
          <defs>
            <pattern id="b-deck" width="7" height="7" patternUnits="userSpaceOnUse"><path d="M0 0v7" stroke="#f2f1ec" stroke-opacity=".22"/></pattern>
          </defs>
          <rect width="400" height="300" fill="#02385c"/>
          <g stroke="#f2f1ec" stroke-opacity=".07"><path d="M0 50h400M0 100h400M0 150h400M0 200h400M0 250h400M50 0v300M100 0v300M150 0v300M200 0v300M250 0v300M300 0v300M350 0v300"/></g>
          <rect x="282" y="52" width="86" height="196" fill="url(#b-deck)"/>
          <path d="M282 52h86v196h-86" fill="none" stroke="#f2f1ec" stroke-opacity=".5" stroke-dasharray="3 3"/>
          <g fill="none" stroke="#f2f1ec" stroke-linecap="square">
            <path d="M40 52H282V96 M282 132V248H40V52" stroke-width="7"/>
            <path d="M176 52V112 M176 140V160 M40 160H120 M146 160H176 M176 160V248 M118 160V248 M176 112H226 M250 112H282 M226 160H282 M226 160V196 M226 222V248" stroke-width="3.5"/>
          </g>
          <g stroke="#02385c" stroke-width="8"><path d="M64 52H128M206 52H254M60 248H100M236 248H270M282 100V128"/></g>
          <g stroke="#f2f1ec" stroke-width="1.1"><path d="M64 49H128M64 55H128M206 49H254M206 55H254M60 245H100M60 251H100M236 245H270M236 251H270M279 100V128M285 100V128"/></g>
          <g fill="none" stroke="#f2f1ec" stroke-opacity=".7" stroke-width=".9">
            <path d="M120 160A26 26 0 0 1 146 134V160"/><path d="M176 140A28 28 0 0 1 204 112"/><path d="M226 196A26 26 0 0 0 252 222H226"/><path d="M226 112A24 24 0 0 0 250 136V112"/>
            <rect x="60" y="80" width="56" height="22" rx="3"/><circle cx="142" cy="122" r="13"/>
            <rect x="196" y="62" width="70" height="12"/><rect x="190" y="182" width="0" height="0"/>
            <rect x="52" y="186" width="54" height="44" rx="2"/><path d="M52 198h54"/>
            <rect x="128" y="200" width="38" height="18" rx="9"/><circle cx="160" cy="174" r="5"/>
            <path d="M232 120v32M240 120v32M248 120v32M256 120v32M264 120v32M272 120v32"/>
            <rect x="300" y="72" width="44" height="20" rx="2"/><circle cx="322" cy="184" r="16"/>
          </g>
          <g fill="#f2f1ec" font-family="Hanken Grotesk, sans-serif" font-size="8.5" font-weight="600" letter-spacing="1.2">
            <text x="60" y="130" opacity=".85">WOHNEN</text><text x="60" y="141" opacity=".55" font-weight="500" letter-spacing=".4">46,8 m²</text>
            <text x="190" y="92" opacity=".85">KOCHEN</text><text x="190" y="103" opacity=".55" font-weight="500" letter-spacing=".4">18,2 m²</text>
            <text x="54" y="178" opacity=".85">SCHLAFEN</text>
            <text x="128" y="236" opacity=".85">BAD</text>
            <text x="188" y="236" opacity=".85">WC</text><text x="236" y="236" opacity=".85">GAST</text>
            <text x="296" y="122" opacity=".85">TERRASSE</text><text x="296" y="133" opacity=".55" font-weight="500" letter-spacing=".4">46,0 m²</text>
          </g>
          <g fill="#f2f1ec" font-family="Hanken Grotesk, sans-serif" font-size="7.5" font-weight="500" letter-spacing="1" opacity=".55">
            <text x="40" y="276">EBENE 2 · M 1:100</text>
            <path d="M300 272h40" stroke="#f2f1ec" stroke-width="1.5"/><path d="M300 268v8M320 270v4M340 268v8" stroke="#f2f1ec"/>
            <text x="346" y="276">5 m</text>
          </g>
          <g transform="translate(370 30)" fill="none" stroke="#f2f1ec" stroke-opacity=".6"><circle r="9"/><path d="M0 -7L3 4L0 2L-3 4Z" fill="#f2f1ec" fill-opacity=".6" stroke="none"/></g>
        </svg>
      </div>
      <p class="kicker label"><span>Harvestehude · Kauf</span><span>Exklusiv</span></p>
      <h2>Penthouse mit Dachterrasse über dem Isekanal</h2>
      <p class="text">Zwei Ebenen, 46 m² Terrasse mit Blick über das Wasser, Aufzug bis in die Wohnung.</p>
      <dl class="fakten"><div><dt>Zimmer</dt><dd>3</dd></div><div><dt>Wohnfläche</dt><dd>142 m²</dd></div><div><dt>Kaufpreis</dt><dd>2.150.000 €</dd></div></dl>
    </article>
  </li>
  <li class="karte">
    <div class="geruest" aria-hidden="true">
      <div class="bildrahmen sk"></div>
      <div class="k"><span class="zeile sk"></span><span class="zeile sk"></span></div>
      <div class="t"><span class="zeile sk"></span><span class="zeile sk"></span></div>
      <div class="p"><span class="zeile sk"></span><span class="zeile sk"></span></div>
      <div class="f"><div><span class="zeile sk l"></span><span class="zeile sk w"></span></div><div><span class="zeile sk l"></span><span class="zeile sk w"></span></div><div><span class="zeile sk l"></span><span class="zeile sk w"></span></div></div>
    </div>
    <article class="inhalt">
      <div class="bildrahmen">
        <svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Zeichnung: weiße Stadtvilla mit Walmdach hinter Hecke und alten Bäumen in der Abendsonne">
          <defs>
            <linearGradient id="c-himmel" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#d3dad2"/><stop offset=".75" stop-color="#ece7dd"/></linearGradient>
            <radialGradient id="c-sonne" cx=".78" cy=".62" r=".5"><stop offset="0" stop-color="#f6dfb8" stop-opacity=".9"/><stop offset="1" stop-color="#f6dfb8" stop-opacity="0"/></radialGradient>
            <linearGradient id="c-glas" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#4f5c67"/><stop offset="1" stop-color="#36414b"/></linearGradient>
            <linearGradient id="c-warm" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f4dcaa"/><stop offset="1" stop-color="#e2b26e"/></linearGradient>
            <g id="c-f"><rect x="-9" y="0" width="18" height="30" fill="#fbfaf7"/><rect x="-7" y="2" width="14" height="26" fill="url(#c-glas)"/><path d="M0 2v26M-7 11h14" stroke="#fbfaf7" stroke-width="1.4"/><rect x="-11" y="30" width="22" height="2.5" fill="#dedad2"/></g>
            <g id="c-h"><rect x="-9" y="0" width="18" height="30" fill="#fbfaf7"/><rect x="-7" y="2" width="14" height="26" fill="url(#c-warm)"/><path d="M0 2v26M-7 11h14" stroke="#fbfaf7" stroke-width="1.4"/><rect x="-11" y="30" width="22" height="2.5" fill="#dedad2"/></g>
          </defs>
          <rect width="400" height="300" fill="url(#c-himmel)"/>
          <rect width="400" height="300" fill="url(#c-sonne)"/>
          <ellipse cx="208" cy="246" rx="150" ry="10" fill="#7d8770" opacity=".35"/>
          <rect x="250" y="70" width="12" height="26" fill="#8f877c"/>
          <path d="M96 124 L140 82 H276 L320 124 Z" fill="#56616b"/>
          <path d="M140 82 H276 L282 88 H134 Z" fill="#4a545d"/>
          <rect x="108" y="124" width="200" height="118" fill="#f4f2ee"/>
          <rect x="262" y="124" width="46" height="118" fill="#e6e2db"/>
          <rect x="104" y="122" width="208" height="5" fill="#e3dfd8"/>
          <rect x="176" y="96" width="64" height="146" fill="#f8f6f2"/>
          <path d="M170 98 L208 66 L246 98 Z" fill="#f8f6f2"/><path d="M168 99 L208 64 L248 99" fill="none" stroke="#56616b" stroke-width="4"/>
          <circle cx="208" cy="86" r="7" fill="#fbfaf7"/><circle cx="208" cy="86" r="5" fill="url(#c-glas)"/>
          <rect x="104" y="180" width="208" height="3" fill="#e3dfd8"/>
          <use href="#c-f" x="128" y="138"/><use href="#c-f" x="156" y="138"/><use href="#c-h" x="196" y="132"/><use href="#c-h" x="220" y="132"/><use href="#c-f" x="260" y="138"/><use href="#c-f" x="288" y="138"/>
          <use href="#c-h" x="128" y="194"/><use href="#c-f" x="156" y="194"/><use href="#c-f" x="260" y="194"/><use href="#c-h" x="288" y="194"/>
          <path d="M198 242 V208 A10 10 0 0 1 218 208 V242 Z" fill="#2f3a44"/><path d="M190 200 H226 L222 194 H194 Z" fill="#56616b"/>
          <path d="M190 300 L200 242 H216 L226 300 Z" fill="#e8e2d6"/>
          <rect y="242" width="400" height="58" fill="#a7ae95"/>
          <path d="M0 262 H190 L188 274 H0 Z M228 262 H400 V274 H230 Z" fill="#9aa289"/>
          <rect x="60" y="226" width="128" height="24" rx="10" fill="#6f7b63"/><rect x="228" y="226" width="134" height="24" rx="10" fill="#6f7b63"/>
          <rect x="70" y="150" width="7" height="90" fill="#5e5548"/>
          <g fill="#6c785f"><circle cx="58" cy="120" r="44"/><circle cx="96" cy="150" r="34"/><circle cx="30" cy="160" r="34"/><circle cx="74" cy="88" r="30"/></g>
          <g fill="#869178"><circle cx="46" cy="104" r="24"/><circle cx="88" cy="128" r="18"/></g>
          <rect x="350" y="170" width="6" height="70" fill="#5e5548"/>
          <ellipse cx="353" cy="140" rx="26" ry="64" fill="#7a8669"/><ellipse cx="346" cy="128" rx="12" ry="42" fill="#909b80"/>
        </svg>
      </div>
      <p class="kicker label"><span>Eppendorf · Kauf</span><span>Neu</span></p>
      <h2>Weiße Stadtvilla von 1911 mit altem Garten am Park</h2>
      <p class="text">Behutsam saniert, 620 m² Grundstück mit Linde und Buche, Kamin im Salon.</p>
      <dl class="fakten"><div><dt>Zimmer</dt><dd>6</dd></div><div><dt>Wohnfläche</dt><dd>214 m²</dd></div><div><dt>Kaufpreis</dt><dd>3.400.000 €</dd></div></dl>
    </article>
  </li>
</ul>

<!-- Variante „bild-platzhalter“: Fotografie-Portfolio, Rezept fotograf-film-bild-zuerst -->
<div class="bild-welt" data-bild-welt data-platzhalter="vorschau">
  <header class="b-kopf"><b>Ines Morawe</b><nav aria-label="Hauptnavigation (Bild-Variante)"><a href="#" aria-current="page">Arbeiten</a><a href="#">Info</a><a href="#">Kontakt</a></nav></header>
  <div class="b-titel"><h1>Stillleben <em>und</em> Räume, 2026</h1><p class="b-mono" aria-live="polite" data-b-status>Lädt 6 Bilder</p></div>
  <figure class="b-held">
    <img src="bilder/huegel-nebel.jpg" width="1200" height="800" fetchpriority="high" alt="Hügelkette im Morgennebel, Schicht hinter Schicht in hellen Blautönen">
    <figcaption class="b-mono"><span>01 · Leipziger Land, Morgennebel</span><span>LCP-Bild: sofort, fetchpriority=high, ohne Platzhalter und ohne Einblenden</span></figcaption>
  </figure>
  <ul class="b-reihe" data-b-reihe aria-busy="true" aria-label="Serie Stillleben">
    <li><figure class="b-bild"><div class="b-rahmen" style="--mittel:#ac9c8b;--lqip:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDABMNDhEODBMRDxEVFBMXHTAfHRoaHToqLCMwRT1JR0Q9Q0FMVm1dTFFoUkFDX4JgaHF1e3x7SlyGkIV3j214e3b/2wBDARQVFR0ZHTgfHzh2T0NPdnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnb/wAARCAAeABgDASIAAhEBAxEB/8QAGQAAAwADAAAAAAAAAAAAAAAAAAIEAQMF/8QAJBAAAQQABgIDAQAAAAAAAAAAAQACAxEEBRITITFBcUJSYYH/xAAWAQEBAQAAAAAAAAAAAAAAAAABAAP/xAAWEQEBAQAAAAAAAAAAAAAAAAAAEVH/2gAMAwEAAhEDEQA/AOnQPtY1AXfpKZWh1cucPA5WjFYlkDbnkbGD00cuKyaKgQR2KQpsKd8att7G/HV5/iFJA7MJsQdjL4tLfvVcfipweWNiduzu3Zj24m1ZG2OFgZGwNYOaAQ2UAmgU3BDiq46Ql1HsAIQX/9k=)"><img alt="Freitragende Betonstufen an einer hellen Sichtbetonwand im tiefen Abendlicht" width="800" height="1000" decoding="async" data-src="bilder/beton-treppe-hoch.jpg"><span class="b-zustand b-mono" aria-hidden="true">lädt</span></div><figcaption class="b-mono"><span>Kragtreppe, Sichtbeton</span><span>02</span></figcaption></figure></li>
    <li><figure class="b-bild"><div class="b-rahmen" style="--mittel:#a7a179;--lqip:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDABMNDhEODBMRDxEVFBMXHTAfHRoaHToqLCMwRT1JR0Q9Q0FMVm1dTFFoUkFDX4JgaHF1e3x7SlyGkIV3j214e3b/2wBDARQVFR0ZHTgfHzh2T0NPdnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnb/wAARCAAeABgDASIAAhEBAxEB/8QAGQAAAwADAAAAAAAAAAAAAAAAAAQFAgMG/8QAIxAAAgIBBAICAwAAAAAAAAAAAQIAAxEEEiExE0EFkRVR8f/EABcBAAMBAAAAAAAAAAAAAAAAAAABAgT/xAAWEQEBAQAAAAAAAAAAAAAAAAAAEQH/2gAMAwEAAhEDEQA/AGPxG6sMluCR0RFrNDdSWzhgB6luoqaVGfUAgdmBPIH8meqjnfHZ2UP1CW0QrYyjqEKUb62UUIW6x+syer3L81a+xxp2pxk8AsDxj7MYq1J8O0qOsRW5W1fjBCrsXb3EuMtPfqUvLWqNj8EbhxCGm09VLtvRbCR7HUIDMf/Z)"><img alt="Salbeigrüne Wand mit Sonnenfleck eines Sprossenfensters, davor eine Eichenbank mit Vase" width="800" height="1000" decoding="async" data-src="bilder/fenster-licht-hoch.jpg"><span class="b-zustand b-mono" aria-hidden="true">lädt</span></div><figcaption class="b-mono"><span>Salbeiwand, Abendsonne</span><span>03</span></figcaption></figure></li>
    <li><figure class="b-bild"><div class="b-rahmen" style="--mittel:#be764f;--lqip:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDABMNDhEODBMRDxEVFBMXHTAfHRoaHToqLCMwRT1JR0Q9Q0FMVm1dTFFoUkFDX4JgaHF1e3x7SlyGkIV3j214e3b/2wBDARQVFR0ZHTgfHzh2T0NPdnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnb/wAARCAAeABgDASIAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAAMCBAUB/8QAJhAAAgEEAgECBwAAAAAAAAAAAQIDAAQRIRITBRQiMUFRYZGxwf/EABcBAQEBAQAAAAAAAAAAAAAAAAIDBAX/xAAZEQADAQEBAAAAAAAAAAAAAAAAARECEiH/2gAMAwEAAhEDEQA/AJBK7xqzGqCyupW2UTX7/lYtt5Qy+RNs3WyMMq8edazWLltU6HaThoFaKlkH50URmhbLDJZ8JApDH3An41QufCWr3cXo40h5cmkKE7GtfalBqbHcPCjdSIZGGMuTgfiqLXkJPDvSK1xYt46275ewnkBhZAQM0UxpZZIwtw/YRs/TNFFwpmpen//Z)"><img alt="Handgedrehte Keramik auf Gipssockeln vor einer Terrakotta-Hohlkehle" width="800" height="1000" decoding="async" data-src="bilder/keramik-stillleben-hoch.jpg"><span class="b-zustand b-mono" aria-hidden="true">lädt</span></div><figcaption class="b-mono"><span>Keramik auf Gips</span><span>04</span></figcaption></figure></li>
    <li><figure class="b-bild"><div class="b-rahmen" style="--mittel:#b7967b;--lqip:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDABMNDhEODBMRDxEVFBMXHTAfHRoaHToqLCMwRT1JR0Q9Q0FMVm1dTFFoUkFDX4JgaHF1e3x7SlyGkIV3j214e3b/2wBDARQVFR0ZHTgfHzh2T0NPdnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnb/wAARCAAeABgDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAMEAQX/xAApEAABAwIDBgcAAAAAAAAAAAABAAIDBBEFEkETFSEicbEzNERRYZHR/8QAFgEBAQEAAAAAAAAAAAAAAAAAAwEA/8QAFhEBAQEAAAAAAAAAAAAAAAAAABEB/9oADAMBAAIRAxEAPwDr2Qlz1Bpw20D5b+zSeyXvLh5CQdWu/ERpunkISoKk1LnnYOiDbWBB4/aFkRzS1IsKeNjxqXPI7JO3xAenjPSUhUtdotJ5iNENKjc/EJX5soiAFvFzXQqb8p+EK1H/2Q==)"><img alt="Gefaltete Stoffe in warmen Tönen, gestapelt" width="800" height="1000" decoding="async" data-src="bilder/stoff-stapel-hoch.jpg"><span class="b-zustand b-mono" aria-hidden="true">lädt</span></div><figcaption class="b-mono"><span>Leinen, gefaltet</span><span>05</span></figcaption></figure></li>
    <li><figure class="b-bild"><div class="b-rahmen" style="--mittel:#7b7362;--lqip:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDABMNDhEODBMRDxEVFBMXHTAfHRoaHToqLCMwRT1JR0Q9Q0FMVm1dTFFoUkFDX4JgaHF1e3x7SlyGkIV3j214e3b/2wBDARQVFR0ZHTgfHzh2T0NPdnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnb/wAARCAAeABgDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAQFBgL/xAAnEAACAgEDAgUFAAAAAAAAAAABAgMEAAURIRIiBjFBUVIUQmGx0f/EABYBAQEBAAAAAAAAAAAAAAAAAAECA//EABgRAQEBAQEAAAAAAAAAAAAAAAARAQJB/9oADAMBAAIRAxEAPwBdgQvOJTDc+WMaQz2qctm5uleMnvBJLHLlDT6V2oswgPS247id+Pzvmd8VGSZB8cMv6zoH08DWahYonLoeSB7g4YhQ8PWq9qmY4ivUhIZQR5E53NUu0wz6dJH08bpIm/A9sxlSrJUnLJMy/FkOzA++U21rWok6VuRuPRmjHV+sN5NX72qxQ6DYlsdrlDGFP3sRtxhmNsLZuzCa/YaZvTfyH8wysyB//9k=)"><img alt="Zitronen auf einem Tisch im Seitenlicht" width="800" height="1000" decoding="async" data-src="bilder/zitronen-tisch-hoch.jpg"><span class="b-zustand b-mono" aria-hidden="true">lädt</span></div><figcaption class="b-mono"><span>Zitronen, Marmor</span><span>06</span></figcaption></figure></li>
    <li><figure class="b-bild"><div class="b-rahmen" style="--mittel:#455fb8;--lqip:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDABMNDhEODBMRDxEVFBMXHTAfHRoaHToqLCMwRT1JR0Q9Q0FMVm1dTFFoUkFDX4JgaHF1e3x7SlyGkIV3j214e3b/2wBDARQVFR0ZHTgfHzh2T0NPdnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnb/wAARCAAeABgDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAMEBgX/xAAnEAABBAECBQQDAAAAAAAAAAACAAEDEQQSMQUhIlFhExRBgXGSsf/EABcBAAMBAAAAAAAAAAAAAAAAAAIEBQP/xAAeEQACAQQDAQAAAAAAAAAAAAAAAQIDESFBEhMUUf/aAAwDAQACEQMRAD8Ay4Q3uqAh8J8UQE3Xq3+HpPeMQKhuvKtd8XUdPaFZRnwU9MkKHlshWONshHcyyaU8DCcGYsYKbbT0/wAR7XGlB4QxYR5PRlu32snFPLGOkJphHs0j0nR5c8UnqBNIx1V6rtvtT/HJPkpZGu9NWtg7svBLgBoiEZBbrIy5F+OyFyT4tlk1PI36MhGo11tAt0/h/9k=)"><img alt="Glasflakon vor kobaltblauem Grund" width="800" height="1000" decoding="async" data-src="bilder/flakon-hoch.jpg"><span class="b-zustand b-mono" aria-hidden="true">lädt</span></div><figcaption class="b-mono"><span>Flakon, Kobalt</span><span>07</span></figcaption></figure></li>
  </ul>
</div>

<footer class="fuss">
  <div class="wahl" role="group" aria-label="Variante">
    <button type="button" data-v="schimmer" aria-pressed="true">Schimmer</button>
    <button type="button" data-v="synchron" aria-pressed="false">Synchron</button>
    <button type="button" data-v="puls" aria-pressed="false">Pulsieren</button>
    <span class="trenner" aria-hidden="true"></span>
    <button type="button" data-welt="bild-platzhalter" aria-pressed="false">Bild-Platzhalter</button>
  </div>
  <div class="wahl platz-wahl" role="group" aria-label="Platzhalter">
    <button type="button" data-platz="vorschau" aria-pressed="true">Mini-Vorschau</button>
    <button type="button" data-platz="mittel" aria-pressed="false">Mittelfarbe</button>
    <button type="button" data-platz="grau" aria-pressed="false">Grau</button>
  </div>
  <button type="button" class="neu" data-neu>Neu laden ↻</button>
  <p class="mono werte" data-werte></p>
</footer>

<script>
(() => {
  const html = document.documentElement, body = document.body;
  const reduziert = () => html.classList.contains('reduziert');
  const strom = document.querySelector('[data-strom]');
  const karten = [...strom.querySelectorAll('.karte')];
  const status = document.querySelector('[data-status]');
  const werte = document.querySelector('[data-werte]');
  const knoepfe = [...document.querySelectorAll('.wahl [data-v]')];

  const ANKUNFT = [1800, 2300, 2800];          // simulierte Antwortzeiten der drei Objekte (ms)
  const VARIANTEN = ['schimmer', 'synchron', 'puls'];
  const WERTE = {
    schimmer: 'Schimmer · 1500 ms · ease-in-out · 200 % · 0 → −200 % · Überblenden 250 ms',
    synchron: 'Synchron · background-attachment: fixed · 1500 ms · ease-in-out · Überblenden 250 ms',
    puls:     'Pulsieren · 2000 ms · ease-in-out · Deckkraft .8 ↔ .5 · Überblenden 250 ms',
    reduziert:'Reduziert · kein Verlauf · Puls 6000 ms · Überblenden 250 ms'
  };
  let variante = 'schimmer', uhren = [], schleife = null, start = 0;

  function setzeVariante(v) {
    if (welt !== 'basis') weltSetzen('basis');
    variante = v;
    VARIANTEN.forEach(n => body.classList.toggle('v-' + n, n === v));
    knoepfe.forEach(k => k.setAttribute('aria-pressed', String(k.dataset.v === v)));
    werte.textContent = reduziert() ? WERTE.reduziert : WERTE[v];
  }

  /* ---------- Variante „bild-platzhalter“ ---------- */
  const bWelt = document.querySelector('[data-bild-welt]'), bReihe = document.querySelector('[data-b-reihe]');
  const bBilder = [...bReihe.querySelectorAll('img')], bStatus = document.querySelector('[data-b-status]');
  const B_ANKUNFT = [1500, 700, 2300, 1100, 2700, 1900];   // simulierte Ladezeiten je Bild (ms)
  const PLATZ = { vorschau: 'Mini-Vorschau 24 × 30 px, JPEG als Daten-URI (≈ 430 Byte), unscharf (blur 14px)', mittel: 'Mittelfarbe je Bild als background-color (7 Byte)', grau: 'Grau #e6e6e6 für alle (Vergleich)' };
  let welt = 'basis', platz = 'vorschau';
  const qv = new URLSearchParams(location.search).get('v');
  function bWerte() {
    werte.textContent = `Bild-Platzhalter · ${PLATZ[platz]} · aspect-ratio 4 / 5 · Einblenden nach decode() ${reduziert() ? 'hart, ohne Übergang' : '150 ms opacity cubic-bezier(0.16, 1, 0.3, 1)'} · LCP-Bild ohne Platzhalter`;
  }
  function platzSetzen(pz) {
    platz = pz; bWelt.dataset.platzhalter = pz;
    document.querySelectorAll('[data-platz]').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.platz === pz)));
    bWerte();
  }
  function bLaden() {
    uhren.forEach(clearTimeout); uhren = [];
    bReihe.setAttribute('aria-busy', 'true'); bStatus.textContent = 'Lädt 6 Bilder';
    bBilder.forEach(img => { img.classList.remove('da'); img.removeAttribute('src'); });
    let fertig = 0; const t0 = performance.now();
    bBilder.forEach((img, i) => uhren.push(setTimeout(() => {
      img.src = img.dataset.src;
      // Erst dekodieren, dann einblenden: so erscheint das Bild in einem Zug statt zeilenweise
      const zeigen = () => {
        if (!img.getAttribute('src')) return;
        img.classList.add('da');
        if (++fertig === bBilder.length) { bReihe.setAttribute('aria-busy', 'false'); bStatus.textContent = `6 Bilder geladen · ${((performance.now() - t0) / 1000).toFixed(1).replace('.', ',')} s`; }
      };
      img.decode().then(zeigen, zeigen);
    }, B_ANKUNFT[i])));
  }
  function weltSetzen(w, schreiben = true) {
    welt = w;
    body.classList.toggle('ist-bild', w === 'bild-platzhalter');
    document.querySelector('[data-welt]').setAttribute('aria-pressed', String(w === 'bild-platzhalter'));
    if (w === 'bild-platzhalter') { knoepfe.forEach(k => k.setAttribute('aria-pressed', 'false')); bWerte(); }
    if (schreiben) { const q = new URLSearchParams(location.search); q.set('v', w); history.replaceState(null, '', location.pathname + '?' + q.toString().replace(/=(?=&|$)/g, '') + location.hash); }
  }
  document.querySelector('[data-welt]').addEventListener('click', () => { pause(); weltSetzen('bild-platzhalter'); bLaden(); });
  document.querySelectorAll('[data-platz]').forEach(b => b.addEventListener('click', () => { pause(); platzSetzen(b.dataset.platz); bLaden(); }));

  function laden() {
    if (welt === 'bild-platzhalter') return bLaden();
    uhren.forEach(clearTimeout); uhren = [];
    // sofort zurück auf das Gerüst, Animation neu starten
    body.classList.add('sofort');
    karten.forEach(k => k.classList.remove('da'));
    body.classList.remove('fertig');
    strom.setAttribute('aria-busy', 'true');
    status.textContent = 'Lädt drei Objekte';
    void strom.offsetWidth;
    body.classList.remove('sofort');
    start = performance.now();
    karten.forEach((k, i) => uhren.push(setTimeout(() => {
      k.classList.add('da');
      if (i === karten.length - 1) {
        strom.setAttribute('aria-busy', 'false');
        body.classList.add('fertig');
        status.textContent = 'Drei neue Objekte · ' + ((performance.now() - start) / 1000).toFixed(1).replace('.', ',') + ' s';
      }
    }, ANKUNFT[i])));
  }

  knoepfe.forEach(k => k.addEventListener('click', () => { pause(); const war = welt; setzeVariante(k.dataset.v); if (war !== 'basis') { const q = new URLSearchParams(location.search); q.set('v', 'basis'); history.replaceState(null, '', location.pathname + '?' + q.toString().replace(/=(?=&|$)/g, '') + location.hash); } laden(); }));
  document.querySelector('[data-neu]').addEventListener('click', () => { pause(); laden(); });
  document.querySelectorAll('a[href="#"]').forEach(a => a.addEventListener('click', e => e.preventDefault()));

  function neustart() { pause(); html.classList.remove('vorfuehrung'); if (welt === 'basis') setzeVariante(variante); else bWerte(); laden(); }
  // Vorführung: laden, Inhalt kurz stehen lassen, nächste Variante
  function vorfuehren() {
    pause();
    html.classList.add('vorfuehrung');
    if (welt === 'bild-platzhalter') {
      // Laden, 2,4 s stehen lassen, Platzhalter wechseln (Mini-Vorschau ↔ Mittelfarbe), neu laden
      const bRunde = () => { bLaden(); schleife = setTimeout(() => { platzSetzen(platz === 'vorschau' ? 'mittel' : 'vorschau'); bRunde(); }, B_ANKUNFT[4] + 2400); };
      bRunde(); return;
    }
    setzeVariante('synchron');                 // die gemeinsame Lichtwelle liest sich in der kleinen Vorschau am besten
    const runde = () => {
      laden();
      schleife = setTimeout(() => {
        setzeVariante(VARIANTEN[(VARIANTEN.indexOf(variante) + 1) % VARIANTEN.length]);
        runde();
      }, ANKUNFT[2] + 2400);
    };
    runde();
  }
  function pause() { clearTimeout(schleife); schleife = null; }
  function weiter() { if (!schleife && html.classList.contains('vorfuehrung')) vorfuehren(); }

  setzeVariante('schimmer');
  if (qv === 'bild-platzhalter') weltSetzen('bild-platzhalter', false);
  laden();
  addEventListener('DOMContentLoaded', () => { if (welt === 'basis') setzeVariante(variante); else bWerte(); });   // ?reduziert setzt das Rahmen-Skript erst danach

})();
</script>
</body>
</html>