Zum Inhalt
← Sektionen & Funktionen

Kaufbox mit Farb- und Größenwahl

Kaufbox mit echten Radio-Gruppen für Farbe und Größe, ausverkauften Größen mit „Benachrichtige mich“, Bestand und Lieferdatum, Menge und einem Knopf mit drei Zuständen; das selbst gezeichnete Produktbild wechselt mit der Farbe, auf dem Telefon bleibt eine Kaufleiste unten.

Produktdetailseite im D2C- oder Mode-Shop: Galerie links, Kaufbox rechts sticky, darunter Material, Pflege, Versand.

Der Kaufbereich einer Produktseite: Name, Preis mit Hinweis auf Mehrwertsteuer und Versand, Farbwahl als Farbfelder, Größenwahl als Raster, Bestand und Lieferzeit, Menge und „In den Warenkorb“. Farbe und Größe sind echte Radio-Gruppen. Ausverkaufte Größen bleiben sichtbar und wählbar, der Knopf wird dann zu „Benachrichtigen“. Das Produktbild wechselt mit der Farbe. Der Knopf zeigt seinen Zustand (wird hinzugefügt, im Warenkorb). Auf dem Telefon bleibt eine Leiste mit Auswahl und Knopf unten stehen, solange der eigentliche Knopf nicht zu sehen ist. Unter den Details steht ein Akkordeon „Hersteller und Sicherheit“ mit den Angaben nach Art. 19 der Produktsicherheitsverordnung (GPSR).

Alle Kaufentscheidungen liegen an einem Ort und reagieren sofort: Farbe ändert das Bild, Größe ändert Bestand und Lieferzeit. Wer sieht, wann das Paket kommt und was es kostet, kauft mit weniger Zweifel. Die feste Leiste spart auf dem Telefon das Zurückscrollen.

Wann einsetzen

Produktseiten in D2C- und Mode-Shops mit Varianten (Farbe, Größe, Ausführung), Manufakturen mit Shop, Schnellkauf aus einer Produktliste.

Wann nicht

Für Produkte ohne Varianten reicht ein Preis und ein Knopf. Nicht als Konfigurator mit vielen abhängigen Optionen (eigenes Muster). Keine künstliche Verknappung („nur noch 2“), wenn der Bestand das nicht hergibt. Keine Streichpreise ohne den niedrigsten Preis der letzten 30 Tage (§ 11 PAngV).

Varianten

  • Produktseite: Galerie links, Kaufbox rechts sticky (D2C, Mode)
  • Schnellkauf: kompakte Kaufbox als Panel aus der Produktliste
  • Größen als Liste mit Bestand je Zeile (Mode mit vielen Größen)
  • Ausführung statt Farbe (Material, Kapazität) mit Preis je Option

Umsetzung

Farbe und Größe als <fieldset> mit <input type="radio">; Gestaltung über label:has(input:checked). Größe nicht vorauswählen. Ausverkaufte Größen bleiben wählbar (nicht disabled), tragen einen Durchstrich und den versteckten Zusatz „, ausverkauft“; dann wird der Knopf zu „Benachrichtige mich“ und öffnet ein E-Mail-Feld. Die Statuszeile (role="status") nennt Bestand und Lieferdatum aus Intl.DateTimeFormat. Das Bild ist eine SVG-Zeichnung als <symbol>; seine Farben kommen aus CSS-Variablen am äußeren <svg> und gehen mit fill-Übergang 400 ms in den <use>-Schattenbaum. Ansichten (Seite, Ferse, Schnürung) sind nur andere viewBox-Ausschnitte desselben Symbols. Knopf mit fester Breite und drei übereinanderliegenden Texten, aria-disabled und aria-busy während des Ladens, Meldung in einer role="status"-Zeile. Ohne Größe: Fehler an der Gruppe, Fokus auf die erste lieferbare Größe. Telefon: feste Leiste über IntersectionObserver auf dem echten Knopf. Preisangabe (inkl. MwSt., Versand), Lieferzeit und Restbestand müssen aus dem Warenwirtschaftssystem kommen und vom Kunden rechtlich geprüft werden. Hersteller und Sicherheit (GPSR Art. 19, gilt seit 13.12.2024 für Verbraucherprodukte; vom Kunden prüfen lassen): Schon im Angebot stehen Name oder Marke des Herstellers mit Post- und E-Mail-Adresse, bei Herstellern außerhalb der EU auch der Einführer bzw. die verantwortliche Person in der EU, dazu eine Produktkennung (Modell, Typ, Art.-Nr.), eine Abbildung und die Warn- und Sicherheitshinweise. Als <details> „Hersteller und Sicherheit“ unter den übrigen Akkordeons; im Schnellkauf bleibt es als einziges sichtbar. Die Angaben kommen wie Preis und Bestand aus dem Warenwirtschaftssystem, je Variante (hier Art.-Nr. je Farbe). Streichpreise: Wer eine Preisermäßigung bekannt gibt, nennt den niedrigsten Gesamtpreis der letzten 30 Tage vor der Ermäßigung (§ 11 PAngV). Einen Streichpreis deshalb nur zusammen mit diesem Wert aus dem Shop-System zeigen, nie von Hand pflegen.

Robust umsetzen

Telefon. Galerie oben, Kaufbox darunter. Eine feste Leiste unten mit Name, Auswahl und Knopf erscheint, solange der Knopf der Kaufbox nicht sichtbar ist. Ohne gewählte Größe springt die Leiste zur Größenwahl. Größenfelder mindestens 44 px.

Ohne Bewegung. Bild wechselt die Farbe ohne Übergang, Leiste erscheint ohne Gleiten, kein Ladekreisel-Dreh (Text bleibt).

Barrierefrei. Radio-Gruppen mit legend, die gewählte Farbe steht im Text der legend. Ausverkauft als Text, nicht nur als Durchstrich. Statuszeile für Bestand und Lieferzeit, aria-live-Meldung nach dem Hinzufügen. Knopf behält Fokus und Breite beim Zustandswechsel (aria-disabled statt disabled während des Ladens). Menge als echtes Zahlenfeld mit Knöpfen. Kontrast der Farbfelder gegen den Grund mit Rand. WCAG 1.4.1 Benutzung von Farbe, 3.3.1 Fehlererkennung, 4.1.3 Statusmeldungen.

Code

<!DOCTYPE html>
<!--
  Kaufbox mit Farb- und Größenwahl
  Fundus · Sektionen & Funktionen · https://cecodesign.de/lab/fundus/sektionen/produkt-kaufbox-varianten/
  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>Albtrauf Low · Achalm Schuhwerk, Reutlingen</title>
<script>document.documentElement.classList.add('js');if(matchMedia('(prefers-reduced-motion: reduce)').matches)document.documentElement.classList.add('reduziert')</script>
<style>
@font-face { font-family: 'Instrument Sans'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/instrument-sans:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 400 700; font-display: swap; }
@font-face { font-family: 'Young Serif'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/young-serif@latest/latin-400-normal.woff2') format('woff2'); font-weight: 400; 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 d2c-produkt-zuerst: warme Neutraltöne als Bühne, schwarzer Knopf, Grün für verfügbar */
  --grund: #f5f4f1; --kachel: #ece9e2; --tinte: #212121; --leise: #6b6b6b; --linie: #dcd8cf; --linie-stark: #a9a49a;
  --gruen: #10714e; --akzent: #c4472d; --warn: #9a5b00; --fehler: #b3261e;
  --auf: cubic-bezier(0, 0, 0.2, 1);      /* Öffnen, Auswahl */
  --zu: cubic-bezier(0.4, 0, 1, 1);       /* Schließen */
  --ui: 150ms; --bild: 400ms;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { background: var(--grund); color: var(--tinte); font-family: 'Instrument Sans', system-ui, sans-serif; -webkit-font-smoothing: antialiased; }
body { min-height: 100svh; }
button, input { font: inherit; color: inherit; }
.sr { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.ziffern { font-variant-numeric: tabular-nums; }
.etikett { font-size: 11.5px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; }
:focus-visible { outline: 2px solid var(--tinte); outline-offset: 3px; }

/* Kopf */
.kopf { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 14px clamp(16px, 3vw, 40px); background: var(--grund); border-bottom: 1px solid var(--linie); }
.marke { display: flex; align-items: center; gap: 10px; font: 400 21px/1 'Young Serif', serif; letter-spacing: -.01em; }
.marke svg { width: 30px; height: 20px; }
.kopf nav { display: flex; gap: 24px; font-size: 14px; font-weight: 500; }
.kopf nav a { color: inherit; text-decoration: none; }
.korb { color: inherit; text-decoration: none; position: relative; display: inline-flex; align-items: center; gap: 8px; font-size: 14px; font-weight: 500; }
.korb svg { width: 22px; height: 22px; }
.korb .zahl { position: absolute; left: 13px; top: -6px; min-width: 18px; height: 18px; border-radius: 9px; background: var(--tinte); color: var(--grund); font-size: 11px; font-weight: 700; display: grid; place-items: center; padding: 0 4px; scale: 0; transition: scale 200ms var(--auf); }
.korb .zahl.an { scale: 1; }
.korb .zahl.puls { animation: puls 400ms var(--auf); }
@keyframes puls { 40% { scale: 1.35; } }
.ankuendigung { background: var(--tinte); color: var(--grund); font-size: 12.5px; text-align: center; padding: 8px 16px; }

.seite { padding: 18px clamp(16px, 3vw, 40px) 70px; }
.pfad { font-size: 12.5px; color: var(--leise); margin-bottom: 16px; }
.pfad a { color: inherit; }
.produkt { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(320px, 1fr); gap: clamp(20px, 3vw, 48px); align-items: start; }

/* Galerie */
.galerie { display: grid; gap: 10px; }
.buehne { position: relative; aspect-ratio: 5 / 4; background: radial-gradient(90% 70% at 50% 40%, #f3f1ec 0%, var(--kachel) 62%, #e4e0d7 100%); border-radius: 6px; overflow: hidden; }
.buehne > svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.buehne .abzeichen { position: absolute; left: 14px; top: 14px; background: var(--akzent); color: #fff; padding: 6px 9px; border-radius: 3px; }
.buehne .bildtext { position: absolute; left: 12px; bottom: 12px; font-size: 12.5px; color: var(--leise); background: rgba(245, 244, 241, .85); padding: 3px 8px; border-radius: 3px; }
.daumen { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.daumen button { aspect-ratio: 4 / 3; border: 0; border-radius: 4px; background: var(--kachel); cursor: pointer; padding: 0; overflow: hidden; box-shadow: inset 0 0 0 1px transparent; transition: box-shadow var(--ui) var(--auf); }
.daumen button svg { width: 100%; height: 100%; display: block; }
.daumen button[aria-pressed="true"] { box-shadow: inset 0 0 0 1.5px var(--tinte); }
@media (hover: hover) { .daumen button:hover { box-shadow: inset 0 0 0 1px var(--linie-stark); } .daumen button[aria-pressed="true"]:hover { box-shadow: inset 0 0 0 1.5px var(--tinte); } }

/* Schuh: Farben als Variablen, Bildwechsel 400 ms */
.schuh { --bt: var(--bild) var(--auf); }   /* Farben und Übergang erben in den <use>-Schattenbaum */
.reduziert .schuh { --bt: .01ms; }

/* Kaufbox */
.kaufbox { position: sticky; top: 18px; display: grid; gap: 20px; }
.kaufbox-kopf { display: grid; gap: 8px; }
.kaufbox .claim { font: 400 15px/1.3 'Young Serif', serif; color: var(--leise); }
.kaufbox h1 { font-weight: 600; font-size: clamp(26px, 2.3vw, 32px); line-height: 1.08; letter-spacing: -.015em; }
.preiszeile { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.preis { font-size: 22px; font-weight: 600; }
.preiszeile small { font-size: 13px; color: var(--leise); }
.preiszeile small a { color: inherit; }

.gruppe { border: 0; min-width: 0; display: grid; gap: 10px; }
.gruppe legend { display: flex; justify-content: space-between; align-items: baseline; width: 100%; font-size: 14px; font-weight: 600; margin-bottom: 10px; float: left; }
.gruppe legend + * { clear: both; }
.gruppe legend span { font-weight: 400; color: var(--leise); }
/* Farbfelder */
.farben { display: flex; gap: 10px; flex-wrap: wrap; }
.farbe { position: relative; width: 44px; height: 44px; border-radius: 50%; cursor: pointer; display: grid; place-items: center; }
.farbe input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; border-radius: 50%; }
.farbe i { width: 34px; height: 34px; border-radius: 50%; background: linear-gradient(135deg, var(--a) 0 50%, var(--b) 50% 100%); box-shadow: inset 0 0 0 1px rgba(33, 33, 33, .25); transition: scale var(--ui) var(--auf); }
.farbe::after { content: ''; position: absolute; inset: 0; border-radius: 50%; box-shadow: inset 0 0 0 1.5px var(--tinte); opacity: 0; scale: .9; transition: opacity var(--ui) var(--auf), scale var(--ui) var(--auf); }
.farbe:has(input:checked)::after { opacity: 1; scale: 1; }
.farbe:has(input:focus-visible) { outline: 2px solid var(--tinte); outline-offset: 3px; }
@media (hover: hover) { .farbe:hover i { scale: 1.06; } }
/* Größen */
.groessen { display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px; }
.gr { position: relative; display: grid; place-items: center; height: 46px; border: 1px solid var(--linie); border-radius: 4px; background: #fff; font-size: 14.5px; font-weight: 500; cursor: pointer; transition: border-color var(--ui) var(--auf), background-color var(--ui) var(--auf), color var(--ui) var(--auf); }
.gr input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.gr.weg { color: #9b978f; background: repeating-linear-gradient(135deg, transparent 0 6px, rgba(33, 33, 33, .045) 6px 7px), #faf9f7; }
.gr.weg::before { content: ''; position: absolute; left: 10%; right: 10%; top: 50%; height: 1px; background: #b0aba2; rotate: -24deg; }
.gr.knapp::after { content: ''; position: absolute; right: 6px; top: 6px; width: 6px; height: 6px; border-radius: 50%; background: var(--warn); }
.gr:has(input:checked) { border-color: var(--tinte); background: var(--tinte); color: var(--grund); }
.gr.weg:has(input:checked) { background: #fff; color: var(--tinte); box-shadow: inset 0 0 0 1px var(--tinte); }
.gr:has(input:focus-visible) { outline: 2px solid var(--tinte); outline-offset: 2px; }
@media (hover: hover) { .gr:hover { border-color: var(--tinte); } }
.hilfe-knopf { border: 0; background: none; font-size: 13px; font-weight: 500; text-decoration: underline; text-underline-offset: 3px; cursor: pointer; padding: 2px; anchor-name: --hilfe; }
.hilfe { position-anchor: --hilfe; position-area: block-end span-inline-start; margin: 8px 0 0; inset: auto; width: min(300px, 90vw); border: 1px solid var(--tinte); border-radius: 6px; background: #fff; padding: 14px 16px; font-size: 13.5px; line-height: 1.5; box-shadow: 0 16px 40px -20px rgba(0, 0, 0, .35); }
.hilfe b { display: block; margin-bottom: 4px; }
.fehlertext { display: none; color: var(--fehler); font-size: 13.5px; font-weight: 600; }
.gruppe.fehlt .fehlertext { display: block; }
.gruppe.fehlt .gr:not(.weg) { border-color: #e3a8a3; }

/* Bestand und Lieferung */
.lage { display: grid; gap: 4px; font-size: 14px; line-height: 1.45; padding: 12px 14px; background: #fff; border-radius: 6px; border: 1px solid var(--linie); }
.lage .zeile { display: flex; gap: 8px; align-items: baseline; }
.lage .punkt { width: 8px; height: 8px; border-radius: 50%; flex: none; translate: 0 -1px; background: var(--gruen); }
.lage[data-art="knapp"] .punkt { background: var(--warn); }
.lage[data-art="weg"] .punkt { background: var(--fehler); }
.lage[data-art="offen"] .punkt { background: var(--linie-stark); }
.lage small { color: var(--leise); font-size: 13px; padding-left: 16px; }

/* Menge und Knopf */
.kauf { display: grid; grid-template-columns: auto 1fr; gap: 10px; }
.menge { display: flex; align-items: center; border: 1px solid var(--linie-stark); border-radius: 999px; height: 54px; background: #fff; }
.menge button { width: 44px; height: 100%; border: 0; background: none; font-size: 20px; cursor: pointer; border-radius: 999px; }
.menge button[aria-disabled="true"] { color: #c3bfb7; cursor: default; }
.menge input { width: 34px; text-align: center; border: 0; background: none; font-size: 16px; font-weight: 600; -moz-appearance: textfield; appearance: textfield; }
.menge input::-webkit-inner-spin-button, .menge input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.cta { position: relative; height: 54px; border: 0; border-radius: 999px; background: var(--tinte); color: #fff; font-size: 16px; font-weight: 600; cursor: pointer; overflow: hidden; transition: background-color 200ms var(--auf), transform var(--ui) var(--auf); }
.cta > span { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; gap: 10px; transition: opacity var(--ui) var(--auf), translate var(--ui) var(--auf); }
.cta .z-laden, .cta .z-fertig { opacity: 0; translate: 0 8px; }
.cta[data-zustand="laden"] .z-bereit, .cta[data-zustand="fertig"] .z-bereit { opacity: 0; translate: 0 -8px; }
.cta[data-zustand="laden"] .z-laden, .cta[data-zustand="fertig"] .z-fertig { opacity: 1; translate: 0 0; }
.cta[data-zustand="fertig"] .z-laden { opacity: 0; translate: 0 -8px; }
.cta[data-zustand="fertig"] { background: var(--gruen); }
.cta.benachrichtigen { background: #fff; color: var(--tinte); box-shadow: inset 0 0 0 1.5px var(--tinte); }
.cta:active, .cta.gedrueckt { transform: scale(.97); }
.cta .kreis { width: 16px; height: 16px; border-radius: 50%; border: 2px solid rgba(255, 255, 255, .35); border-top-color: #fff; animation: dreh 700ms linear infinite; }
@keyframes dreh { to { rotate: 360deg; } }
.cta .betrag { font-weight: 500; opacity: .75; }
@media (hover: hover) { .cta:not(.benachrichtigen):not([data-zustand="fertig"]):hover { background: #000; } }
.melden { display: none; grid-template-columns: 1fr auto; gap: 8px; }
.melden.an { display: grid; }
.melden input { height: 46px; border: 1px solid var(--linie-stark); border-radius: 6px; padding: 0 12px; font-size: 16px; background: #fff; }
.melden button { height: 46px; border: 0; border-radius: 6px; padding: 0 16px; background: var(--tinte); color: #fff; font-weight: 600; cursor: pointer; }
.meldung { font-size: 13.5px; color: var(--gruen); font-weight: 600; min-height: 1px; }
.meldung:empty { display: none; }

.vertrauen { list-style: none; display: grid; gap: 8px; font-size: 13.5px; }
.vertrauen li { display: flex; gap: 10px; align-items: center; }
.vertrauen svg { width: 18px; height: 18px; flex: none; }

.details { border-top: 1px solid var(--linie); }
.details details { border-bottom: 1px solid var(--linie); }
.details summary { list-style: none; display: flex; justify-content: space-between; padding: 14px 0; font-size: 14.5px; font-weight: 600; cursor: pointer; }
.details summary::-webkit-details-marker { display: none; }
.details summary::after { content: '+'; font-weight: 400; font-size: 18px; line-height: 1; transition: rotate 200ms var(--auf); }
.details details[open] summary::after { rotate: 45deg; }
.details p { font-size: 14px; line-height: 1.55; color: var(--leise); padding-bottom: 14px; max-width: 52ch; }
/* Hersteller und Sicherheit (GPSR Art. 19): Angaben schon im Angebot, nicht erst im Paket */
.gpsr dl { display: grid; grid-template-columns: 8.5em minmax(0, 1fr); gap: 10px 16px; padding-bottom: 16px; font-size: 14px; line-height: 1.5; max-width: 60ch; }
.gpsr dt { color: var(--leise); }
.gpsr dd { margin: 0; overflow-wrap: anywhere; }
.gpsr dd a { color: inherit; text-underline-offset: 3px; }
.gpsr ul { margin: 0; padding-left: 1.1em; }
.gpsr ul li + li { margin-top: 3px; }

/* Leiste unten (Telefon) */
.leiste { position: fixed; left: 0; right: 0; bottom: 0; z-index: 20; display: none; align-items: center; gap: 12px; padding: 10px 14px calc(10px + env(safe-area-inset-bottom)); background: rgba(245, 244, 241, .96); backdrop-filter: blur(10px); border-top: 1px solid var(--linie); translate: 0 110%; transition: translate 200ms var(--zu); }
.leiste.an { translate: 0 0; transition: translate 250ms var(--auf); }
.leiste .mini { width: 52px; height: 42px; border-radius: 4px; background: var(--kachel); flex: none; overflow: hidden; }
.leiste .mini svg { width: 100%; height: 100%; }
.leiste p { flex: 1; min-width: 0; display: grid; gap: 1px; font-size: 13px; line-height: 1.3; }
.leiste p b { font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.leiste p span { color: var(--leise); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.leiste .cta { width: 156px; height: 48px; font-size: 14.5px; flex: none; }

/* Variante Schnellkauf */
[data-modus="kompakt"] body, body[data-modus="kompakt"] { background: #dedad2; }
body[data-modus="kompakt"] .kopf, body[data-modus="kompakt"] .ankuendigung, body[data-modus="kompakt"] .pfad, body[data-modus="kompakt"] .details, body[data-modus="kompakt"] .daumen, body[data-modus="kompakt"] .vertrauen li:nth-child(n+2) { display: none; }
body[data-modus="kompakt"] .seite { display: grid; place-items: center; min-height: calc(100svh - 70px); padding-top: 28px; }
body[data-modus="kompakt"] .produkt { position: relative; width: min(900px, 100%); grid-template-columns: minmax(0, 1fr) minmax(300px, 1fr); gap: 0; background: var(--grund); border-radius: 10px; overflow: hidden; box-shadow: 0 40px 80px -40px rgba(0, 0, 0, .45); }
body[data-modus="kompakt"] .galerie { align-self: stretch; }
body[data-modus="kompakt"] .leiste { display: none; }
body[data-modus="kompakt"] .seite { padding-bottom: 40px; }
body[data-modus="kompakt"] .buehne { border-radius: 0; height: 100%; aspect-ratio: auto; min-height: 380px; }
body[data-modus="kompakt"] .kaufbox { position: static; padding: 26px 26px 24px; gap: 16px; }
body[data-modus="kompakt"] .kaufbox h1 { font-size: 24px; }
body[data-modus="kompakt"] .lage small { display: none; }
body[data-modus="kompakt"] .details { display: block; }   /* Schnellkauf: Hersteller und Sicherheit bleiben erreichbar */
body[data-modus="kompakt"] .details details:not(.gpsr) { display: none; }
body[data-modus="kompakt"] .details summary { padding: 11px 0; }
.schnell-kopf { display: none; }
body[data-modus="kompakt"] .schnell-kopf { padding-right: 44px; display: flex; justify-content: space-between; align-items: center; font-size: 12.5px; color: var(--leise); margin-bottom: -6px; }
.schnell-kopf a { color: var(--tinte); font-weight: 600; }
.zu-knopf { position: absolute; right: 12px; top: 12px; z-index: 2; width: 34px; height: 34px; border-radius: 50%; border: 0; background: rgba(255, 255, 255, .8); display: none; place-items: center; cursor: pointer; font-size: 18px; }
body[data-modus="kompakt"] .zu-knopf { display: grid; }

/* Gedachter Finger in der Vorführung */
.tipp { position: fixed; z-index: 60; width: 34px; height: 34px; margin: -17px 0 0 -17px; border-radius: 50%; background: rgba(33, 33, 33, .16); box-shadow: 0 0 0 1px rgba(33, 33, 33, .4); pointer-events: none; animation: tipp 450ms var(--auf) both; }
@keyframes tipp { from { scale: .4; opacity: 1; } to { scale: 1.2; opacity: 0; } }

/* Pult */
.pult { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 10px 24px; padding: 16px clamp(16px, 3vw, 40px) 26px; border-top: 1px solid var(--linie); font: 500 10.5px/1.5 'Geist Mono', ui-monospace, monospace; letter-spacing: .1em; text-transform: uppercase; color: var(--leise); }
.pult .modi { display: flex; border: 1px solid var(--linie); border-radius: 6px; overflow: hidden; }
.pult button { font: inherit; letter-spacing: inherit; text-transform: inherit; color: var(--leise); background: none; border: 0; padding: 8px 10px; cursor: pointer; }
.pult button + button { border-left: 1px solid var(--linie); }
.pult button[aria-pressed="true"] { background: var(--tinte); color: var(--grund); }
.pult b { color: var(--tinte); font-weight: 600; }

/* Reduziert */
:root.reduziert *, :root.reduziert *::before, :root.reduziert *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; }
.reduziert .cta .kreis { animation: none; border-top-color: rgba(255, 255, 255, .35); }
.reduziert .tipp { display: none; }

/* Touch */
@media (hover: none) {
  .gr { height: 48px; }
  .details summary { padding: 16px 0; }
}

/* Telefon */
@media (max-width: 760px) {
  .kopf nav { display: none; }
  .produkt { grid-template-columns: minmax(0, 1fr); }
  .kaufbox { position: static; }
  .buehne { aspect-ratio: 4 / 3; }
  .groessen { grid-template-columns: repeat(4, 1fr); }
  .leiste { display: flex; }
  .seite { padding-bottom: 110px; }
  body[data-modus="kompakt"] .produkt { grid-template-columns: minmax(0, 1fr); }
  body[data-modus="kompakt"] .buehne { min-height: 0; aspect-ratio: 4 / 3; }
  body[data-modus="kompakt"] .kaufbox { padding: 18px 16px 20px; }
  .pult .lang { display: none; }
}

/* Vorschau 960 × 720 */
.vorschau body { overflow: hidden; }
.vorschau .pult, .vorschau .ankuendigung, .vorschau .pfad, .vorschau .details { display: none; }
.vorschau .seite { padding-top: 18px; }
.vorschau .kaufbox { gap: 15px; }
.vorschau .vertrauen li:nth-child(n+3) { display: none; }
@media (max-width: 560px) { .gpsr dl { grid-template-columns: minmax(0, 1fr); gap: 2px; } .gpsr dd + dt { margin-top: 10px; } }
@media (max-width: 360px) {
  .leiste .mini { display: none; }
  .leiste .cta { width: 128px; font-size: 13.5px; }
  .leiste { gap: 10px; padding-inline: 12px; }
  .leiste p span { white-space: normal; }
}
@media (forced-colors: active) { button, [role=button], [role=tab], [role=switch] { border: 1px solid ButtonText !important; } }
</style>
</head>
<body data-modus="seite">
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <defs>
    <linearGradient id="glanz" x1="0" x2="0" y1="0" y2="1"><stop offset="0" stop-color="#fff" stop-opacity=".22"/><stop offset=".5" stop-color="#fff" stop-opacity="0"/><stop offset="1" stop-color="#000" stop-opacity=".18"/></linearGradient>
    <linearGradient id="quer" x1="0" x2="1" y1="0" y2="0"><stop offset="0" stop-color="#000" stop-opacity=".16"/><stop offset=".35" stop-color="#000" stop-opacity="0"/><stop offset=".8" stop-color="#fff" stop-opacity=".06"/><stop offset="1" stop-color="#000" stop-opacity=".1"/></linearGradient>
    <radialGradient id="boden"><stop offset="0" stop-color="#3a3126" stop-opacity=".42"/><stop offset="1" stop-color="#3a3126" stop-opacity="0"/></radialGradient>
    <pattern id="profil" width="9" height="12" patternUnits="userSpaceOnUse"><path d="M0 0h4v12H0z" fill="#000" fill-opacity=".08"/></pattern>
    <filter id="weich2" x="-10%" y="-50%" width="120%" height="200%"><feGaussianBlur stdDeviation="6"/></filter>
    <path id="s-ober" d="M84 262 L79 206 Q77 172 98 164 Q130 158 160 174 Q188 188 214 170 L238 141 Q250 131 262 141 L300 176 Q340 197 404 207 L470 214 Q530 222 550 248 Q556 258 548 263 Z"/>
    <symbol id="schuh" viewBox="0 0 600 380">
      <ellipse cx="318" cy="312" rx="262" ry="14" fill="url(#boden)" filter="url(#weich2)"/>
      <!-- Sohle aus Naturkautschuk -->
      <path style="fill:var(--sohle);transition:fill var(--bt)" d="M70 262 Q64 298 94 304 L500 306 Q548 304 556 280 Q560 265 548 260 L78 257 Z"/>
      <path style="fill:var(--sohle);transition:fill var(--bt)" d="M78 296 Q92 304 112 304 L498 306 Q540 305 553 291 L555 297 Q545 312 498 313 L110 312 Q86 311 78 302 Z"/>
      <path d="M78 296 Q92 304 112 304 L498 306 Q540 305 553 291 L555 297 Q545 312 498 313 L110 312 Q86 311 78 302 Z" fill="url(#profil)"/>
      <path d="M70 262 Q64 298 94 304 L500 306 Q548 304 556 280 Q560 265 548 260 L78 257 Z" fill="url(#glanz)" opacity=".6"/>
      <path d="M74 274 L554 274" style="stroke:var(--naht);transition:stroke var(--bt)" stroke-width="1.2" stroke-dasharray="4 5" fill="none" opacity=".5"/>
      <!-- Oberteil -->
      <use href="#s-ober" style="fill:var(--ober);transition:fill var(--bt)"/>
      <path style="fill:var(--ueber);transition:fill var(--bt)" d="M440 208 Q510 216 544 240 Q552 252 548 263 L446 262 Q428 236 440 208 Z"/>
      <path style="fill:var(--ueber);transition:fill var(--bt)" d="M84 262 L80 204 Q79 176 96 166 L118 162 Q108 200 128 230 Q150 256 212 262 Z"/>
      <path style="fill:var(--ferse);transition:fill var(--bt)" d="M79 210 Q77 175 96 165 L105 172 Q91 182 92 214 L94 262 L85 262 Z"/>
      <path style="fill:var(--futter);transition:fill var(--bt)" d="M98 166 Q130 160 160 175 Q188 188 214 171 Q194 194 160 191 Q124 186 98 173 Z"/>
      <path style="fill:var(--ueber);transition:fill var(--bt)" d="M222 166 L247 150 L306 182 Q350 201 410 207 L405 221 Q344 215 300 197 L236 176 Z"/>
      <g fill="#1d1d1d" fill-opacity=".5"><circle cx="252" cy="161" r="3"/><circle cx="275" cy="173" r="3"/><circle cx="298" cy="185" r="3"/><circle cx="323" cy="195" r="3"/><circle cx="349" cy="202" r="3"/><circle cx="376" cy="207" r="3"/></g>
      <g style="stroke:var(--senkel);transition:stroke var(--bt)" stroke-width="4" stroke-linecap="round" fill="none"><path d="M252 161 L270 153 M275 173 L291 165 M298 185 L312 178 M323 195 L335 189 M349 202 L358 198"/><path d="M262 155 Q252 145 241 149 Q248 158 262 155 Q273 146 284 150 Q276 158 262 155 M262 156 Q257 166 249 171 M263 156 Q267 167 263 177" stroke-width="3"/></g>
      <g style="stroke:var(--naht);transition:stroke var(--bt)" stroke-width="1.2" stroke-dasharray="4 5" fill="none" opacity=".6">
        <path d="M445 213 Q506 222 538 244"/><path d="M121 167 Q113 200 132 226 Q154 251 206 256"/><path d="M234 166 L302 192 Q346 208 402 214"/>
      </g>
      <!-- Markenzeichen: Achalm mit Turm, gestickt -->
      <g style="stroke:var(--naht);transition:stroke var(--bt)" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round" opacity=".85"><path d="M290 244 L314 224 Q321 219 328 224 L354 244"/><path d="M318 222 V211 H324 V220"/></g>
      <use href="#s-ober" fill="url(#glanz)"/>
      <use href="#s-ober" fill="url(#quer)"/>
      <use href="#s-ober" fill="none" stroke="#000" stroke-opacity=".18" stroke-width="1.2"/>
    </symbol>
  </defs>
</svg>

<p class="ankuendigung">Kostenloser Versand und Rückversand · Neu besohlen lassen in unserer Werkstatt in Reutlingen</p>
<header class="kopf">
  <span class="marke"><svg viewBox="0 0 30 20" aria-hidden="true"><path d="M2 18 L12 8 Q15 5 18 8 L28 18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M14 7 V2 H17 V6" fill="none" stroke="currentColor" stroke-width="1.6"/></svg>Achalm</span>
  <nav aria-label="Hauptnavigation"><a href="#">Schuhe</a><a href="#">Pflege</a><a href="#">Werkstatt</a><a href="#">Journal</a></nav>
  <a class="korb" href="#" aria-label="Warenkorb" data-korb><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 8h16l-1.4 11.2a2 2 0 0 1-2 1.8H7.4a2 2 0 0 1-2-1.8L4 8Z M8.5 8V6.5a3.5 3.5 0 0 1 7 0V8" fill="none" stroke="currentColor" stroke-width="1.6"/></svg><span class="zahl ziffern" data-korb-zahl aria-hidden="true">0</span><span>Warenkorb</span></a>
</header>

<main class="seite">
  <p class="pfad"><a href="#">Schuhe</a> / <a href="#">Halbschuhe</a> / Albtrauf Low</p>
  <div class="produkt">
    <button type="button" class="zu-knopf" aria-label="Schnellkauf schließen">×</button>
    <section class="galerie" aria-label="Produktbilder">
      <div class="buehne" data-buehne>
        <svg class="schuh" viewBox="45 95 530 250" role="img" data-hauptbild aria-label="Albtrauf Low in Tanne, Seitenansicht"><use href="#schuh" width="600" height="380"/></svg>
        <span class="abzeichen etikett">Neu</span>
        <span class="bildtext" data-bildtext>Tanne · Seitenansicht</span>
      </div>
      <div class="daumen" role="group" aria-label="Ansicht wählen">
        <button type="button" aria-pressed="true" data-ansicht="45 95 530 250" data-name="Seitenansicht"><svg class="schuh" viewBox="45 95 530 250" aria-hidden="true"><use href="#schuh" width="600" height="380"/></svg><span class="sr">Seitenansicht</span></button>
        <button type="button" aria-pressed="false" data-ansicht="40 115 330 230" data-name="Ferse und Markenzeichen"><svg class="schuh" viewBox="40 115 330 230" aria-hidden="true"><use href="#schuh" width="600" height="380"/></svg><span class="sr">Ferse</span></button>
        <button type="button" aria-pressed="false" data-ansicht="200 100 330 230" data-name="Schnürung"><svg class="schuh" viewBox="200 100 330 230" aria-hidden="true"><use href="#schuh" width="600" height="380"/></svg><span class="sr">Schnürung</span></button>
      </div>
    </section>

    <section class="kaufbox" aria-labelledby="produktname" data-kaufbox>
      <p class="schnell-kopf"><span class="etikett">Schnellkauf</span><a href="#">Alle Details →</a></p>
      <div class="kaufbox-kopf">
        <p class="claim">Läuft sich ein, nicht durch.</p>
        <h1 id="produktname">Albtrauf Low</h1>
        <div class="preiszeile"><span class="preis ziffern" data-preis>149 €</span><small>inkl. MwSt. · <a href="#">Versand</a> kostenlos</small></div>
      </div>

      <fieldset class="gruppe">
        <legend>Farbe: <span data-farbname>Tanne</span></legend>
        <div class="farben" data-farben></div>
      </fieldset>

      <fieldset class="gruppe" data-groessen-gruppe aria-describedby="g-fehler">
        <legend>Größe (EU) <button type="button" class="hilfe-knopf" popovertarget="hilfe">Größenhilfe</button></legend>
        <div class="groessen" data-groessen></div>
        <p class="fehlertext" id="g-fehler">Bitte wähle eine Größe.</p>
      </fieldset>
      <div class="hilfe" id="hilfe" popover><b>Fällt normal aus.</b>Zwischen zwei Größen? Nimm die größere, das Leder gibt in der Breite etwas nach. Innenlänge Größe 42: 27,0 cm.</div>

      <div class="lage" role="status" data-lage data-art="offen">
        <p class="zeile"><span class="punkt" aria-hidden="true"></span><span data-lage-text>Wähle deine Größe, dann siehst du Bestand und Lieferzeit.</span></p>
        <small data-lage-klein></small>
      </div>

      <div class="kauf" data-kauf>
        <div class="menge" role="group" aria-label="Menge">
          <button type="button" data-minus aria-label="Eins weniger" aria-disabled="true">−</button>
          <input type="number" inputmode="numeric" min="1" max="5" value="1" aria-label="Menge" data-menge class="ziffern">
          <button type="button" data-plus aria-label="Eins mehr">+</button>
        </div>
        <button type="button" class="cta" data-cta data-zustand="bereit">
          <span class="z-bereit" data-cta-text>In den Warenkorb <span class="betrag ziffern" data-betrag>149 €</span></span>
          <span class="z-laden" aria-hidden="true"><i class="kreis"></i>Wird hinzugefügt</span>
          <span class="z-fertig" aria-hidden="true"><svg width="18" height="18" viewBox="0 0 18 18"><path d="M3.5 9.4 7.2 13 14.5 5.4" fill="none" stroke="#fff" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>Im Warenkorb</span>
        </button>
      </div>
      <form class="melden" data-melden><label class="sr" for="m-mail">E-Mail für die Benachrichtigung</label><input id="m-mail" type="email" required autocomplete="email" placeholder="deine@email.de"><button type="submit">Eintragen</button></form>
      <p class="meldung" role="status" data-meldung></p>

      <ul class="vertrauen">
        <li><svg viewBox="0 0 18 18" aria-hidden="true"><path d="M1.5 5.5h9v7h-9zM10.5 8h3.5l2.5 2.5v2h-6z" fill="none" stroke="currentColor" stroke-width="1.3"/><circle cx="4.5" cy="13.5" r="1.6" fill="#f5f4f1" stroke="currentColor" stroke-width="1.3"/><circle cx="13" cy="13.5" r="1.6" fill="#f5f4f1" stroke="currentColor" stroke-width="1.3"/></svg>Kostenloser Versand und Rückversand</li>
        <li><svg viewBox="0 0 18 18" aria-hidden="true"><path d="M3 9a6 6 0 1 0 2-4.5M3 3v3h3" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>30 Tage Zeit zum Zurückschicken</li>
        <li><svg viewBox="0 0 18 18" aria-hidden="true"><path d="M2 14h14M4 14l2-6h6l2 6M7 8V4h4v4" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/></svg>Neu besohlbar in Reutlingen, ab 59 €</li>
      </ul>

      <div class="details">
        <details><summary>Material</summary><p>Rindsleder, pflanzlich gegerbt in Süddeutschland. Futter aus unbehandeltem Ziegenleder, Sohle aus Naturkautschuk, genäht statt geklebt.</p></details>
        <details><summary>Pflege und Besohlung</summary><p>Mit einem feuchten Tuch abwischen, ab und zu Lederbalsam. Ist die Sohle abgelaufen, schick uns das Paar: neue Sohle in etwa zehn Werktagen.</p></details>
        <details><summary>Versand und Rückgabe</summary><p>Versand mit DHL, klimaneutral. Rücksendeschein liegt bei. Rückgabe innerhalb von 30 Tagen, ungetragen.</p></details>
        <details class="gpsr"><summary>Hersteller und Sicherheit</summary>
          <dl>
            <dt>Hersteller</dt><dd>Achalm Schuhwerk GmbH<br>Werkstattweg 4, 72764 Reutlingen, Deutschland<br><a href="mailto:produktsicherheit@achalm.example">produktsicherheit@achalm.example</a></dd>
            <dt>Produkt</dt><dd>Albtrauf Low, Halbschuh aus Leder<br>Art.-Nr. <span class="ziffern" data-artnr>AL-1407-TAN</span> · Charge auf dem Etikett im Schaft</dd>
            <dt>Abbildung</dt><dd>Siehe Produktbilder; sie zeigen die gewählte Farbe.</dd>
            <dt>Warnhinweise</dt><dd><ul><li>Sohle aus Naturkautschuk, enthält Latex.</li><li>Auf nassen Fliesen und Metall rutschiger als eine Profilsohle.</li><li>Kein Sicherheits- oder Arbeitsschuh.</li></ul></dd>
          </dl>
        </details>
      </div>
    </section>
  </div>
</main>

<div class="leiste" data-leiste aria-label="Kaufen">
  <span class="mini" role="img" aria-label="Albtrauf Low"><svg class="schuh" viewBox="40 110 540 230" aria-hidden="true"><use href="#schuh" width="600" height="380"/></svg></span>
  <p><b class="ziffern" data-leiste-preis>149 €</b><span data-leiste-wahl>Tanne · noch keine Größe</span></p>
  <button type="button" class="cta" data-cta-leiste data-zustand="bereit">
    <span class="z-bereit" data-leiste-text>Größe wählen</span>
    <span class="z-laden" aria-hidden="true"><i class="kreis"></i>Moment</span>
    <span class="z-fertig" aria-hidden="true"><svg width="16" height="16" viewBox="0 0 18 18"><path d="M3.5 9.4 7.2 13 14.5 5.4" fill="none" stroke="#fff" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>Im Korb</span>
  </button>
</div>

<div class="pult">
  <div class="modi" role="group" aria-label="Variante">
    <button type="button" data-v="seite" aria-pressed="true">Produktseite</button>
    <button type="button" data-v="kompakt" aria-pressed="false">Schnellkauf</button>
  </div>
  <p data-werte></p>
</div>

<script>
(() => {
  const html = document.documentElement;
  const reduziert = () => html.classList.contains('reduziert');
  const vorfuehrung = () => html.classList.contains('vorfuehrung');
  const euro = new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR', maximumFractionDigits: 0 });
  const tag = new Intl.DateTimeFormat('de-DE', { weekday: 'short', day: 'numeric', month: 'numeric' });
  const PREIS = 149, GROESSEN = [36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46];

  /* Farbwelten des Schuhs (eigene Zeichnung, SVG) und Bestand je Größe */
  const FARBEN = {
    tanne: { name: 'Tanne', ober: '#2f4a3c', ueber: '#263d31', sohle: '#ece5d6', ferse: '#c9a66b', futter: '#18241d', senkel: '#ece5d6', naht: '#e9dfc9',
      bestand: [4, 6, 7, 9, 11, 2, 0, 5, 6, 3, 0], wieder: 'KW 49' },
    kalk: { name: 'Kalk', ober: '#e9e5dc', ueber: '#d9d3c6', sohle: '#f4f2ed', ferse: '#c4472d', futter: '#a39b8c', senkel: '#f7f5f0', naht: '#8f887a',
      bestand: [5, 8, 0, 3, 12, 9, 6, 2, 7, 4, 1], wieder: 'KW 48' },
    lehm: { name: 'Lehm', ober: '#ad7748', ueber: '#94612f', sohle: '#d8c296', ferse: '#3b2a1c', futter: '#6e4a2c', senkel: '#f0e5d3', naht: '#f0dcc0',
      bestand: [0, 3, 5, 6, 8, 7, 0, 2, 4, 0, 0], wieder: 'KW 50' },
    nacht: { name: 'Nacht', ober: '#25282f', ueber: '#1b1d22', sohle: '#2e2e2e', ferse: '#8a8f99', futter: '#0f1013', senkel: '#2b2e35', naht: '#7d828c',
      bestand: [6, 7, 9, 10, 14, 12, 9, 8, 6, 5, 3], wieder: 'KW 49' }
  };

  const $ = s => document.querySelector(s);
  const farbenEl = $('[data-farben]'), groessenEl = $('[data-groessen]'), gruppe = $('[data-groessen-gruppe]');
  const lage = $('[data-lage]'), lageText = $('[data-lage-text]'), lageKlein = $('[data-lage-klein]');
  const cta = $('[data-cta]'), ctaText = $('[data-cta-text]'), betrag = $('[data-betrag]');
  const ctaLeiste = $('[data-cta-leiste]'), leisteText = $('[data-leiste-text]'), leisteWahl = $('[data-leiste-wahl]'), leistePreis = $('[data-leiste-preis]');
  const menge = $('[data-menge]'), minus = $('[data-minus]'), plus = $('[data-plus]');
  const melden = $('[data-melden]'), meldung = $('[data-meldung]');
  const korbZahl = $('[data-korb-zahl]'), leiste = $('[data-leiste]');
  const hauptbild = $('[data-hauptbild]'), bildtext = $('[data-bildtext]');
  const werte = $('[data-werte]'), schalter = [...document.querySelectorAll('[data-v]')];
  const VARIANTEN = ['seite', 'kompakt'];

  /* ---------- Echte Radio-Gruppen aufbauen ---------- */
  farbenEl.innerHTML = Object.entries(FARBEN).map(([k, f]) =>
    `<label class="farbe" style="--a:${f.ober};--b:${f.sohle}"><input type="radio" name="farbe" value="${k}"${k === 'tanne' ? ' checked' : ''}><i aria-hidden="true"></i><span class="sr">${f.name}</span></label>`).join('');
  groessenEl.innerHTML = GROESSEN.map(g => `<label class="gr ziffern"><input type="radio" name="groesse" value="${g}">${g}<span class="sr" data-zusatz></span></label>`).join('');
  const groessenInputs = [...groessenEl.querySelectorAll('input')];

  let korb = 0, ctaZeit = [];
  const farbe = () => document.querySelector('[name="farbe"]:checked').value;
  const groesse = () => { const i = document.querySelector('[name="groesse"]:checked'); return i ? +i.value : null; };
  const bestand = () => { const g = groesse(); return g ? FARBEN[farbe()].bestand[GROESSEN.indexOf(g)] : null; };

  /* ---------- Lieferzeit: Werktage ab heute, Annahmeschluss 14 Uhr ---------- */
  function werktage(von, n) { const d = new Date(von); while (n > 0) { d.setDate(d.getDate() + 1); if (d.getDay() % 6) n--; } return d; }
  function lieferung() {
    const jetzt = new Date(), heuteRaus = jetzt.getDay() % 6 && jetzt.getHours() < 14;
    const start = heuteRaus ? jetzt : werktage(jetzt, 1);
    return { von: werktage(start, 1), bis: werktage(start, 3), heuteRaus, frist: heuteRaus ? `Bestellst du bis 14 Uhr, geht das Paket heute raus.` : `Das Paket geht am nächsten Werktag raus.` };
  }

  /* ---------- Farbe: Bild wechselt per CSS-Variablen (fill 400 ms) ---------- */
  function setzeFarbe() {
    const f = FARBEN[farbe()];
    document.querySelectorAll('.schuh').forEach(s => ['ober', 'ueber', 'sohle', 'ferse', 'futter', 'senkel', 'naht'].forEach(k => s.style.setProperty('--' + k, f[k])));
    $('[data-farbname]').textContent = f.name;
    $('[data-artnr]').textContent = 'AL-1407-' + farbe().slice(0, 3).toUpperCase();   // Produktkennung je Farbe
    const ansicht = document.querySelector('.daumen [aria-pressed="true"]').dataset.name;
    bildtext.textContent = `${f.name} · ${ansicht}`;
    hauptbild.setAttribute('aria-label', `Albtrauf Low in ${f.name}, ${ansicht}`);
    // Bestand je Größe zeigen: ausverkauft bleibt wählbar, mit Text
    groessenInputs.forEach((inp, i) => {
      const n = f.bestand[i], lab = inp.closest('.gr');
      lab.classList.toggle('weg', n === 0);
      lab.classList.toggle('knapp', n > 0 && n <= 2);
      lab.querySelector('[data-zusatz]').textContent = n === 0 ? ', ausverkauft' : n <= 2 ? `, nur noch ${n}` : '';
    });
    aktualisieren();
  }

  /* ---------- Zustand von Bestand, Lieferzeit, Knopf und Leiste ---------- */
  function aktualisieren() {
    const f = FARBEN[farbe()], g = groesse(), n = bestand();
    const m = Math.max(1, Math.min(+menge.value || 1, n > 0 ? Math.min(5, n) : 5));
    menge.value = m;
    minus.setAttribute('aria-disabled', String(m <= 1));
    plus.setAttribute('aria-disabled', String(m >= (n > 0 ? Math.min(5, n) : 5)));
    menge.max = n > 0 ? Math.min(5, n) : 5;
    const summe = euro.format(PREIS * m);
    leistePreis.textContent = summe;
    const l = lieferung();
    if (!g) {
      lage.dataset.art = 'offen'; lageText.textContent = 'Wähle deine Größe, dann siehst du Bestand und Lieferzeit.'; lageKlein.textContent = '';
    } else if (n === 0) {
      lage.dataset.art = 'weg'; lageText.textContent = `Größe ${g} in ${f.name} ist ausverkauft.`; lageKlein.textContent = `Neue Lieferung voraussichtlich ${f.wieder}. Wir sagen dir gern Bescheid.`;
    } else {
      lage.dataset.art = n <= 2 ? 'knapp' : 'da';
      lageText.textContent = `${n <= 2 ? `Nur noch ${n} auf Lager` : 'Auf Lager'} · Lieferung ${tag.format(l.von)} bis ${tag.format(l.bis)}`;
      lageKlein.textContent = l.frist;
    }
    const weg = g && n === 0;
    cta.classList.toggle('benachrichtigen', !!weg);
    ctaText.innerHTML = weg ? 'Benachrichtige mich' : `In den Warenkorb <span class="betrag ziffern" data-betrag>${summe}</span>`;
    if (!weg) melden.classList.remove('an');
    ctaLeiste.classList.toggle('benachrichtigen', !!weg);
    leisteText.textContent = !g ? 'Größe wählen' : weg ? 'Benachrichtigen' : 'In den Warenkorb';
    leisteWahl.textContent = `${f.name} · ${g ? 'Größe ' + g + (weg ? ', ausverkauft' : '') : 'noch keine Größe'}`;
    if (g) gruppe.classList.remove('fehlt');
  }

  document.addEventListener('change', e => {
    if (e.target.name === 'farbe') setzeFarbe();
    if (e.target.name === 'groesse') aktualisieren();
  });
  minus.addEventListener('click', () => { if (minus.getAttribute('aria-disabled') === 'true') return; menge.value = +menge.value - 1; aktualisieren(); });
  plus.addEventListener('click', () => { if (plus.getAttribute('aria-disabled') === 'true') return; menge.value = +menge.value + 1; aktualisieren(); });
  menge.addEventListener('change', aktualisieren);

  /* ---------- Ansichten der Galerie ---------- */
  document.querySelectorAll('.daumen button').forEach(b => b.addEventListener('click', () => {
    document.querySelectorAll('.daumen button').forEach(x => x.setAttribute('aria-pressed', String(x === b)));
    hauptbild.setAttribute('viewBox', b.dataset.ansicht);
    setzeFarbe();
  }));

  /* ---------- In den Warenkorb: bereit → laden → im Warenkorb ---------- */
  function kaufen(quelle) {
    if (cta.dataset.zustand !== 'bereit') return;
    if (!groesse()) {
      gruppe.classList.add('fehlt');
      const r = gruppe.getBoundingClientRect();
      if (r.top < 0 || r.bottom > innerHeight - 80) gruppe.scrollIntoView({ block: 'center', behavior: reduziert() ? 'auto' : 'smooth' });
      if (!vorfuehrung()) groessenInputs.find(i => !i.closest('.weg'))?.focus({ preventScroll: true });
      meldung.textContent = '';
      return;
    }
    if (bestand() === 0) {
      melden.classList.add('an');
      if (quelle === ctaLeiste) melden.scrollIntoView({ block: 'center', behavior: reduziert() ? 'auto' : 'smooth' });
      if (!vorfuehrung()) melden.querySelector('input').focus({ preventScroll: quelle === ctaLeiste });
      return;
    }
    const m = +menge.value, f = FARBEN[farbe()], g = groesse();
    [cta, ctaLeiste].forEach(b => { b.dataset.zustand = 'laden'; b.setAttribute('aria-disabled', 'true'); });
    cta.setAttribute('aria-busy', 'true');
    ctaZeit.push(setTimeout(() => {
      korb += m;
      korbZahl.textContent = korb; korbZahl.classList.add('an');
      korbZahl.classList.remove('puls'); void korbZahl.offsetWidth; korbZahl.classList.add('puls');
      $('[data-korb]').setAttribute('aria-label', `Warenkorb, ${korb} ${korb === 1 ? 'Artikel' : 'Artikel'}`);
      [cta, ctaLeiste].forEach(b => { b.dataset.zustand = 'fertig'; });
      cta.removeAttribute('aria-busy');
      meldung.textContent = `${m} × Albtrauf Low, ${f.name}, Größe ${g} liegt im Warenkorb.`;
      ctaZeit.push(setTimeout(() => { meldung.textContent = ''; }, 5000));
      ctaZeit.push(setTimeout(() => {
        [cta, ctaLeiste].forEach(b => { b.dataset.zustand = 'bereit'; b.removeAttribute('aria-disabled'); });
      }, 1800));
    }, reduziert() ? 300 : 650));
  }
  cta.addEventListener('click', () => kaufen(cta));
  ctaLeiste.addEventListener('click', () => kaufen(ctaLeiste));
  melden.addEventListener('submit', e => {
    e.preventDefault();
    meldung.textContent = `Danke. Wir schreiben dir, sobald Größe ${groesse()} in ${FARBEN[farbe()].name} wieder da ist.`;
    melden.classList.remove('an');
  });

  /* ---------- Telefon: Leiste, solange der echte Knopf nicht sichtbar ist ---------- */
  if ('IntersectionObserver' in window) {
    new IntersectionObserver(([e]) => leiste.classList.toggle('an', !e.isIntersecting), { rootMargin: '0px 0px -10px 0px' }).observe($('[data-kauf]'));
  } else leiste.classList.add('an');

  /* ---------- Varianten (?v=seite|kompakt) ---------- */
  function variante(v, inUrl) {
    if (!VARIANTEN.includes(v)) v = VARIANTEN[0];
    document.body.dataset.modus = v;
    schalter.forEach(b => b.setAttribute('aria-pressed', String(b.dataset.v === v)));
    werte.innerHTML = `Auswahl <b>150 ms</b> · cubic-bezier(0, 0, 0.2, 1) · Bild <b>400 ms</b> <span class="lang">· Knopf laden <b>650 ms</b> → im Warenkorb <b>1800 ms</b> · Leiste <b>250 / 200 ms</b> · ${v === 'kompakt' ? 'Schnellkauf-Panel' : 'Kaufbox sticky neben der Galerie'}</span>`;
    if (inUrl) { const q = new URLSearchParams(location.search); q.set('v', v); history.replaceState(null, '', location.pathname + '?' + q + location.hash); }
  }
  schalter.forEach(b => b.addEventListener('click', () => { const lief = vorfuehrung(); if (lief) neustart(); variante(b.dataset.v, true); if (lief) vorfuehren(); }));

  /* ---------- Vorführung (ca. 17 s) ---------- */
  let zeitgeber = [], laeuft = false;
  const nach = (ms, fn) => zeitgeber.push(setTimeout(fn, ms));
  function tippAuf(el) {
    if (!el || reduziert()) return;
    const r = el.getBoundingClientRect(); if (!r.width) return;
    const t = document.createElement('span'); t.className = 'tipp';
    t.style.left = (r.left + r.width / 2) + 'px'; t.style.top = (r.top + r.height / 2) + 'px';
    document.body.appendChild(t); setTimeout(() => t.remove(), 500);
  }
  function waehle(name, wert) {
    const i = document.querySelector(`[name="${name}"][value="${wert}"]`);
    tippAuf(i.closest('label')); i.checked = true; i.dispatchEvent(new Event('change', { bubbles: true }));
  }
  function druecke(el, fn) { tippAuf(el); el.classList.add('gedrueckt'); setTimeout(() => { el.classList.remove('gedrueckt'); fn ? fn() : el.click(); }, 150); }
  function grundzustand() {
    ctaZeit.forEach(clearTimeout); ctaZeit = [];
    [cta, ctaLeiste].forEach(b => { b.dataset.zustand = 'bereit'; b.removeAttribute('aria-disabled'); b.removeAttribute('aria-busy'); });
    document.querySelector('[name="farbe"][value="tanne"]').checked = true;
    groessenInputs.forEach(i => { i.checked = false; });
    menge.value = 1; korb = 0; korbZahl.textContent = '0'; korbZahl.classList.remove('an', 'puls');
    melden.classList.remove('an'); meldung.textContent = ''; gruppe.classList.remove('fehlt');
    const h = $('#hilfe'); if (h.matches(':popover-open')) h.hidePopover();
    document.querySelectorAll('.daumen button')[0].click();
  }
  function eineRunde() {
    grundzustand();
    let t = 900;
    nach(t, () => druecke(cta)); t += 1300;                                      // ohne Größe: Hinweis an der Gruppe
    nach(t, () => waehle('farbe', 'lehm')); t += 1300;                           // Bild wechselt die Farbe
    nach(t, () => waehle('groesse', '42')); t += 1600;                           // ausverkauft → Benachrichtigen
    nach(t, () => waehle('groesse', '43')); t += 1400;                           // nur noch 2
    nach(t, () => druecke(plus)); t += 900;
    nach(t, () => druecke(cta)); t += 3000;                                      // laden → im Warenkorb
    nach(t, () => waehle('farbe', 'kalk')); t += 1300;
    nach(t, () => druecke(document.querySelectorAll('.daumen button')[2])); t += 1500;
    nach(t, () => waehle('farbe', 'nacht')); t += 1500;
    nach(t, () => druecke(document.querySelectorAll('.daumen button')[0])); t += 1400;
    nach(t, eineRunde);
  }
  function pause() { zeitgeber.forEach(clearTimeout); zeitgeber = []; laeuft = false; }
  function neustart() { pause(); html.classList.remove('vorfuehrung'); grundzustand(); }
  function vorfuehren() { pause(); html.classList.add('vorfuehrung'); laeuft = true; eineRunde(); }
  function weiter() { if (!laeuft) vorfuehren(); }

  variante(new URLSearchParams(location.search).get('v'), false);
  setzeFarbe();

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