/* gbernard theme — 2026 rework of the Christliche Bücherstube look.
   All visual tokens are CSS custom properties (light AND dark set).
   No inline styles anywhere; JS hook classes from site.js are preserved. */

/* ---------- Local fonts (never a foreign domain — compliance) ---------- */
@font-face {
  font-family: "P052"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("/static/fonts/p052-roman.woff2") format("woff2");
}
@font-face {
  font-family: "P052"; font-style: normal; font-weight: 700; font-display: swap;
  src: url("/static/fonts/p052-bold.woff2") format("woff2");
}
@font-face {
  font-family: "P052"; font-style: italic; font-weight: 400; font-display: swap;
  src: url("/static/fonts/p052-italic.woff2") format("woff2");
}
@font-face {
  font-family: "Carlito"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("/static/fonts/carlito-regular.woff2") format("woff2");
}
@font-face {
  font-family: "Carlito"; font-style: normal; font-weight: 700; font-display: swap;
  src: url("/static/fonts/carlito-bold.woff2") format("woff2");
}
@font-face {
  font-family: "Carlito"; font-style: italic; font-weight: 400; font-display: swap;
  src: url("/static/fonts/carlito-italic.woff2") format("woff2");
}

/* ---------- Tokens ---------- */
:root, :root[data-theme="light"] {
  /* brand core: burgundy + gold + paper cream (non-negotiable palette) */
  --color-bg: #f8f5ef;
  --color-surface: #fffdf9;
  --color-surface-alt: #f3ede2;
  --color-text: #2b2420;
  --color-text-muted: #77695f;
  --color-primary: #6a1122;
  --color-primary-strong: #550d1b;
  --color-primary-deep: #350607;      /* "das edle Burgunderrot" (reference) */
  --color-primary-contrast: #f6e3d3;
  --color-blush: #f1ccc6;             /* readable text on burgundy */
  --color-accent: #b9862f;            /* gold — links, highlights */
  /* Fließtext-Ton für den Kurztext (.product-teaser): zweimal nachgedunkelt
     auf Betreiber-Feedback — erst von --color-accent (#b9862f, zu blass) auf
     #8a6114, dann bis fast an Schwarz heran. Behält einen warmen Goldstich,
     liest sich aber wie Text, nicht wie eine Auszeichnung (~12:1 gegen
     --color-bg). */
  --color-accent-ink: #3b2b0d;
  --color-gold: #c9a04f;              /* gold surfaces / buttons */
  --color-gold-strong: #d4af37;
  --color-gold-hover: #a8874a;
  --color-gold-ink: #2f2109;          /* dark text on gold (badges) */
  --color-gold-contrast: #fdf6ea;     /* light cream text on gold buttons (Vorlage) */
  --color-accent-contrast: #ffffff;
  --color-border: #e5dccb;
  --color-border-soft: #eee7d9;
  --color-ok: #2e7d43;
  --color-warn: #b3541e;

  --font-body: "Carlito", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-display: "P052", "Palatino Linotype", Palatino, Georgia, serif;

  --radius: 8px;
  --radius-lg: 14px;
  --radius-pill: 999px;
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 1rem;
  --space-4: 1.5rem;
  --space-5: 2.5rem;
  --space-6: 4rem;
  --shadow-card: 0 1px 2px rgb(53 6 7 / 5%), 0 2px 8px rgb(53 6 7 / 6%);
  --shadow-card-hover: 0 4px 10px rgb(53 6 7 / 8%), 0 12px 28px rgb(53 6 7 / 12%);
  --shadow-panel: 0 8px 30px rgb(53 6 7 / 14%);

  /* full-width layout: gentle side padding that grows toward 4K */
  --page-pad: clamp(1rem, 2.5vw, 6rem);
  /* INHALTS-Breite (ohne Seitenpolster) — 90rem ist die Breite, die der
     Footer schon immer hatte und die auf 4K gut aussieht. Jeder Wrapper
     (Kopf, Nav, Inhalt, Fuß) rechnet sich daraus seine Außenbreite als
     calc(--max-width + 2*--page-pad) und behält sein eigenes --page-pad;
     so stehen alle Textkanten der Seite exakt übereinander, und jenseits
     der Grenze wachsen die Ränder statt der Spalten.
     WICHTIG: Die Grundschrift wird bewusst NICHT über 20px hinaus skaliert
     — sie hängt an jedem rem, auch am Mindestmaß der Produktkarten, und
     hatte die Karten und damit die auf 480px optimierten Rasterbilder
     aufgeblasen. */
  --max-width: 90rem;

  /* paper texture behaviour differs per theme */
  --paper-blend: multiply;
  --paper-opacity: 0.25;
}

:root[data-theme="dark"] {
  /* dark burgundy premium mode */
  --color-bg: #1b1214;
  --color-surface: #271b1e;
  --color-surface-alt: #2f2125;
  --color-text: #efe4d4;
  --color-text-muted: #b5a190;
  --color-primary: #7d1f33;
  --color-primary-strong: #5e1626;
  --color-primary-deep: #2a0a0e;
  --color-primary-contrast: #f6e3d3;
  --color-blush: #e9c8c1;
  --color-accent: #d9ae62;
  /* helles Gegenstück: im Dunkelmodus heißt "gut lesbar" hell statt dunkel,
     der Ton geht dort also Richtung Papierweiß statt Richtung Schwarz —
     spiegelbildlich zur Abdunklung im Light-Theme. */
  --color-accent-ink: #ece2cf;
  --color-gold: #c9a04f;
  --color-gold-strong: #e3bd6e;
  --color-gold-hover: #e8c987;
  --color-gold-ink: #2f2109;
  --color-gold-contrast: #fdf6ea;
  --color-accent-contrast: #ffffff;
  --color-border: #46333a;
  --color-border-soft: #382a2f;
  --color-ok: #58b06e;
  --color-warn: #d8824e;
  --shadow-card: 0 1px 3px rgb(0 0 0 / 40%);
  --shadow-card-hover: 0 6px 18px rgb(0 0 0 / 55%);
  --shadow-panel: 0 8px 30px rgb(0 0 0 / 60%);
  --paper-blend: overlay;
  --paper-opacity: 0.08;
}

/* ---------- Base ---------- */
* { box-sizing: border-box; }
html {
  /* fluid type: ~15px auf Telefonen → 20px ab ~1470px Viewport */
  font-size: clamp(15px, 0.45vw + 13.4px, 20px);
  /* Platz für die Scrollleiste immer reservieren. Kurze Seiten (Warenkorb,
     Blog, Versandkosten, Erweiterte Suche) scrollen nicht und bekommen
     deshalb keine Leiste — ihr Viewport ist ~15px breiter, und weil der
     Inhalt seit --max-width zentriert ist, sprang die ganze Seite beim
     Wechsel von einer langen Seite um die halbe Leistenbreite zur Seite.
     Vor dem Deckel lief der Inhalt über die volle Breite, da fiel derselbe
     Effekt nicht auf. */
  scrollbar-gutter: stable;
  /* glides to a #product-<sku> target (e.g. the "Alle Bücher ansehen"
     link) instead of snapping straight there -- covers same-page anchor
     clicks and, in Chromium/Firefox, the initial scroll on landing from
     a cross-page link with a fragment already in the URL. */
  scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}
body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
  line-height: 1.55;
  position: relative;
}
/* procedural paper texture (SVG feTurbulence — no image assets) */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='paper'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.04' numOctaves='5' stitchTiles='stitch' result='noise'/%3E%3CfeDiffuseLighting in='noise' lighting-color='white' surfaceScale='2'%3E%3CfeDistantLight azimuth='45' elevation='60'/%3E%3C/feDiffuseLighting%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23paper)'/%3E%3C/svg%3E");
  mix-blend-mode: var(--paper-blend);
  opacity: var(--paper-opacity);
  pointer-events: none;
  z-index: -1;
}
h1, h2, h3, .section-title { font-family: var(--font-display); letter-spacing: 0.01em; }
a { color: var(--color-accent); text-decoration: none; }
a:hover { text-decoration: underline; }
img { max-width: 100%; height: auto; }
/* Das hidden-Attribut stammt aus dem UA-Stylesheet und verliert gegen JEDE
   eigene display-Regel — .btn { display: inline-block } liess z.B. den per
   site.js versteckten ✓-Button im Warenkorb sichtbar. Eine globale Regel
   statt Einzelfall-Pflaster (das Flipbook hatte sein eigenes). */
[hidden] { display: none !important; }
:focus-visible { outline: 2px solid var(--color-gold-strong); outline-offset: 2px; border-radius: 2px; }

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--color-surface); color: var(--color-text);
  padding: var(--space-2) var(--space-3); border-radius: var(--radius);
}
.skip-link:focus { left: var(--space-2); }

/* ---------- Icons: masked SVG silhouettes, colored via currentColor ---------- */
.icon {
  display: inline-block; width: 1.3em; height: 1.3em; flex: none;
  background: currentColor; vertical-align: -0.25em;
  -webkit-mask: var(--icon) center / contain no-repeat;
  mask: var(--icon) center / contain no-repeat;
}
.icon-search { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M15.5 14h-.79l-.28-.27a6.5 6.5 0 1 0-.7.7l.27.28v.79l5 4.99L20.49 19zm-6 0A4.5 4.5 0 1 1 14 9.5 4.5 4.5 0 0 1 9.5 14z'/%3E%3C/svg%3E"); }
.icon-account { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 12a5 5 0 1 0-5-5 5 5 0 0 0 5 5zm0 2c-3.34 0-10 1.67-10 5v3h20v-3c0-3.33-6.66-5-10-5z'/%3E%3C/svg%3E"); }
.icon-cart { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7 18a2 2 0 1 0 2 2 2 2 0 0 0-2-2zm10 0a2 2 0 1 0 2 2 2 2 0 0 0-2-2zM7.16 14.26l.03-.12.9-1.64h7.45a2 2 0 0 0 1.75-1.03l3.86-7L19.4 3.5l-3.86 7H8.53L4.27 1.5H1v2h2l3.6 7.59-1.35 2.45A2 2 0 0 0 7 16.5h12v-2H7.42a.25.25 0 0 1-.26-.24z'/%3E%3C/svg%3E"); }
.icon-truck { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M20 8h-3V4H3c-1.1 0-2 .9-2 2v11h2c0 1.66 1.34 3 3 3s3-1.34 3-3h6c0 1.66 1.34 3 3 3s3-1.34 3-3h2v-5l-3-4zM6 18.5c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm13.5-9l1.96 2.5H17V9.5h2.5zm-1.5 9c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z'/%3E%3C/svg%3E"); }
.icon-book { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M18 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2zM6 4h5v8l-2.5-1.5L6 12z'/%3E%3C/svg%3E"); }
.icon-heart { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54z'/%3E%3C/svg%3E"); }
.icon-gift { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M20 6h-2.18A3 3 0 0 0 15 2a3 3 0 0 0-3 2 3 3 0 0 0-3-2 3 3 0 0 0-2.82 4H4a2 2 0 0 0-2 2v2h9V6.5h2V10h9V8a2 2 0 0 0-2-2zM9 4a1 1 0 0 1 2 0v2H9zm4 0a1 1 0 0 1 2 0v2h-2zm-2 18v-10H2v8a2 2 0 0 0 2 2zm2 0h7a2 2 0 0 0 2-2v-8h-9z'/%3E%3C/svg%3E"); }
.icon-clock { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm4.2 14.2L11 13V7h1.5v5.2l4.5 2.7z'/%3E%3C/svg%3E"); }
.icon-tag { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M21.41 11.58l-9-9A2 2 0 0 0 11 2H4a2 2 0 0 0-2 2v7a2 2 0 0 0 .59 1.42l9 9a2 2 0 0 0 2.82 0l7-7a2 2 0 0 0 0-2.84zM6.5 8A1.5 1.5 0 1 1 8 6.5 1.5 1.5 0 0 1 6.5 8z'/%3E%3C/svg%3E"); }
.icon-shield { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5zm-1.06 15.54L7.4 13l1.41-1.41 2.12 2.12 4.24-4.24L16.6 10.9z'/%3E%3C/svg%3E"); }
.icon-smile { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm-3.5 6A1.5 1.5 0 1 1 7 9.5 1.5 1.5 0 0 1 8.5 8zm7 0A1.5 1.5 0 1 1 14 9.5 1.5 1.5 0 0 1 15.5 8zM12 18a6 6 0 0 1-5.19-3h10.38A6 6 0 0 1 12 18z'/%3E%3C/svg%3E"); }
.icon-coins { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M15 18.5c-2.51 0-4.68-1.42-5.76-3.5H15v-2H8.58a6.5 6.5 0 0 1 0-2H15V9H9.24A6.49 6.49 0 0 1 15 5.5c1.61 0 3.09.59 4.23 1.57L21 5.3A8.96 8.96 0 0 0 15 3c-3.92 0-7.24 2.51-8.48 6H3v2h3.06a8.5 8.5 0 0 0 0 2H3v2h3.52c1.24 3.49 4.56 6 8.48 6 2.31 0 4.41-.87 6-2.3l-1.78-1.77A6.47 6.47 0 0 1 15 18.5z'/%3E%3C/svg%3E"); }
.icon-lock { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M18 8h-1V6a5 5 0 0 0-10 0v2H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2zm-6 9a2 2 0 1 1 2-2 2 2 0 0 1-2 2zm3.1-9H8.9V6a3.1 3.1 0 0 1 6.2 0z'/%3E%3C/svg%3E"); }
.icon-check { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='2.5 2.5 19 19' fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='8.6'/%3E%3Cpath d='M8.2 12.3l2.6 2.6 5-5.2'/%3E%3C/svg%3E"); }
/* outline variants (stroke-based): feiner, eleganter Strich wie in der
   Referenz (Revision.png) — für trust-bar, benefit-bar und Sidebar-Karten.
   Einheitlich stroke 1.3/24: skaliert bei 3.4em auf ~3.3px statt klobiger 4.3px */
.icon-clock-o { --icon-ratio: 1; --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='1.95 1.95 20.1 20.1' fill='none' stroke='black' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9.2'/%3E%3Cpath d='M12 6.8V12l3.7 2.2'/%3E%3C/svg%3E"); }
/* Lieferwagen nach Referenz-Vorbild: breiter Kasten, Speed-Lines, Kabine mit
   Fenster, Räder in die Bodenlinie eingelassen (Pfad-Lücken statt Überlappung) */
.icon-truck-o { --icon-ratio: 1.62; --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0.05 6.15 21.4 13.2' fill='none' stroke='black' stroke-width='0.87' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6.6 16.2H5.8A1.2 1.2 0 0 1 4.6 15V8.2A1.2 1.2 0 0 1 5.8 7h7.6a1.2 1.2 0 0 1 1.2 1.2v8'/%3E%3Cpath d='M11 16.2h3.6'/%3E%3Cpath d='M14.6 9.6h2.9a1.1 1.1 0 0 1 .9.5l1.9 2.9c.13.2.2.44.2.68v1.32a1.2 1.2 0 0 1-1.2 1.2h-.35'/%3E%3Cpath d='M16.8 9.6v2.5h2.6'/%3E%3Ccircle cx='8.8' cy='16.2' r='2.1'/%3E%3Ccircle cx='17.4' cy='16.2' r='2.1'/%3E%3Cpath d='M1.4 9.6h2.4M.9 12h2.9M1.4 14.4h2.4'/%3E%3C/svg%3E"); }
.icon-book-o { --icon-ratio: 1.23; --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='1.15 3.15 21.7 17.7' fill='none' stroke='black' stroke-width='1.17' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 4h6a4 4 0 0 1 4 4v12a3 3 0 0 0-3-3H2z'/%3E%3Cpath d='M22 4h-6a4 4 0 0 0-4 4v12a3 3 0 0 1 3-3h7z'/%3E%3C/svg%3E"); }
.icon-heart-o { --icon-ratio: 1.09; --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='1.35 2.25 21.3 19.6' fill='none' stroke='black' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20.8 4.6a5.5 5.5 0 0 0-7.8 0L12 5.7l-1-1.1a5.5 5.5 0 0 0-7.8 7.8l1 1L12 21l7.8-7.6 1-1a5.5 5.5 0 0 0 0-7.8z'/%3E%3C/svg%3E"); }
.icon-shield-o { --icon-ratio: 0.9; --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='3.15 2.15 17.7 19.7' fill='none' stroke='black' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3l8 3.5V11c0 4.8-3.4 8.7-8 10-4.6-1.3-8-5.2-8-10V6.5z'/%3E%3Cpath d='M8.5 12l2.5 2.5 4.5-4.5'/%3E%3C/svg%3E"); }
.icon-smile-o { --icon-ratio: 1; --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='2.15 2.15 19.7 19.7' fill='none' stroke='black' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M8.5 14.5c.9 1.2 2.1 1.8 3.5 1.8s2.6-.6 3.5-1.8'/%3E%3Ccircle cx='9.1' cy='9.7' r='1' fill='black' stroke='none'/%3E%3Ccircle cx='14.9' cy='9.7' r='1' fill='black' stroke='none'/%3E%3C/svg%3E"); }
.icon-lock-o { --icon-ratio: 0.84; --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='4.15 2.15 15.7 18.7' fill='none' stroke='black' stroke-width='1.23' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='5' y='11' width='14' height='9' rx='2'/%3E%3Cpath d='M8 11V7a4 4 0 0 1 8 0v4'/%3E%3Cpath d='M12 15v1'/%3E%3C/svg%3E"); }
.icon-mail-o { --icon-ratio: 1.25; --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='1.75 3.75 20.5 16.5' fill='none' stroke='black' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='2.5' y='4.5' width='19' height='15' rx='2'/%3E%3Cpath d='M3.2 6l8.8 7 8.8-7'/%3E%3C/svg%3E"); }
.icon-facebook { --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M24 12.073C24 5.405 18.627 0 12 0S0 5.405 0 12.073C0 18.1 4.388 23.094 10.125 24v-8.437H7.078v-3.49h3.047v-2.66c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.267h3.328l-.532 3.49h-2.796V24C19.612 23.094 24 18.1 24 12.073z'/%3E%3C/svg%3E"); }

/* ---------- Leather texture helper (main nav, footer, hero) ---------- */
.site-footer, .hero { position: relative; overflow: hidden; }
/* .main-nav is position:sticky (already a containing block for the ::before);
   no overflow:hidden there — it would risk clipping focus rings, and the
   inset-0 pseudo can't overflow anyway. */
.main-nav::before, .site-footer::before, .hero::before {
  content: "";
  position: absolute; inset: 0;
  /* the "High-Quality Cellular Leather" (reference/custom.css) — procedural, no assets */
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='leather'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.03 0.05' numOctaves='3' result='cells'/%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.6' numOctaves='1' result='grain'/%3E%3CfeComposite in='cells' in2='grain' operator='arithmetic' k1='0.5' k2='0.5' result='combined'/%3E%3CfeDiffuseLighting in='combined' lighting-color='white' surfaceScale='2.5'%3E%3CfeDistantLight azimuth='45' elevation='55'/%3E%3C/feDiffuseLighting%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23leather)'/%3E%3C/svg%3E");
  mix-blend-mode: soft-light;
  opacity: 0.45;
  pointer-events: none;
  z-index: 1;
}
.main-nav > *, .site-footer > *, .hero > * { position: relative; z-index: 2; }
/* schmale Leiste: etwas kräftigere Textur, damit das Leder wie im Footer lesbar ist */
.main-nav::before { opacity: 0.6; }

/* ---------- Header ---------- */
.site-header { background: var(--color-surface); border-bottom: 1px solid var(--color-border-soft); }
.header-main {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3) var(--page-pad);
  flex-wrap: wrap;
  max-width: calc(var(--max-width) + 2 * var(--page-pad)); margin-inline: auto;
}
.site-logo { flex: none; display: inline-flex; max-width: 100%; }
/* wide text-logo — prominent, per Vorlage. Über die HÖHE dimensioniert,
   damit es die Header-Höhe ausfüllt, unabhängig vom Seitenverhältnis der
   SVG-Datei; max-width + object-fit verhindern Overflow/Verzerrung auf
   schmalen Screens. */
.site-logo-img { height: clamp(42px, 4vw, 64px); width: auto; max-width: 100%; object-fit: contain; }
.site-nav { display: flex; gap: var(--space-3); align-items: center; margin-left: auto; }
.site-nav-item {
  display: inline-flex; flex-direction: column; align-items: center; gap: 2px;
  color: var(--color-text); font-size: 0.8rem;
}
/* Konto/Warenkorb-Icons etwa logohoch: nutzt die Headerhöhe voll aus */
.site-nav-item .icon { width: 2.4em; height: 2.4em; color: var(--color-primary); }
.site-nav-item:hover { text-decoration: none; color: var(--color-accent); }
.site-nav-item:hover .icon { color: var(--color-accent); }
/* Signed-in account menu: reveals on hover AND :focus-within. The latter is
   load-bearing, not polish -- while the menu is hidden its button cannot
   receive focus at all, so focusing the account link itself is what paints
   the menu and hands keyboard users the next Tab stop.

   The menu must survive the pointer briefly leaving the hover area, which a
   padding bridge alone cannot guarantee. Measured: the trigger is 39px wide
   (x 1616-1655) while the menu hangs 74px further left (x 1542-1655). The
   two touch vertically -- menu top is exactly the link's bottom edge -- so
   there is no gap to bridge; the miss is horizontal. Anyone moving diagonally
   toward the button's text, which is where the eye goes, leaves the trigger's
   narrow column a few pixels before reaching the menu's top edge, and the
   menu vanished mid-reach. You cannot pad away a cut corner.

   So closing is delayed instead: hover may lapse for 320ms without the menu
   going anywhere, far longer than crossing 74px takes, and re-entering
   cancels it. Hiding uses visibility (not display) because visibility is
   transitionable and, like display:none, still removes the button from the
   tab order -- so the :focus-within mechanism above is unaffected. */
.nav-account { position: relative; display: inline-flex; }
/* The corridor the pointer actually travels: a catcher spanning the lower
   half of the trigger row and reaching left across the menu's overhang, so
   the diagonal never leaves .nav-account in the first place. It is inert
   (pointer-events:none) until the menu is open, so it can never swallow a
   click meant for the search field beside it, and it only becomes live
   while something in here is already hovered or focused -- which keeps it
   from opening the menu on its own from empty space. */
.nav-account::after {
  content: ""; position: absolute; top: 50%; bottom: 0; right: 0; width: 12rem;
  pointer-events: none;
}
.nav-account:hover::after,
.nav-account:focus-within::after { pointer-events: auto; }
.nav-account-menu {
  position: absolute; top: 100%; right: 0; z-index: 50;
  padding-top: var(--space-2);
  visibility: hidden; opacity: 0;
  transition: opacity 0.12s ease 0.32s, visibility 0s linear 0.44s;
}
.nav-account:hover .nav-account-menu,
.nav-account:focus-within .nav-account-menu {
  visibility: visible; opacity: 1;
  transition: opacity 0.12s ease 0s, visibility 0s linear 0s;
}
.nav-account-logout {
  display: block; white-space: nowrap; cursor: pointer; font: inherit; font-size: 0.85rem;
  background: var(--color-surface); color: var(--color-text);
  border: 1px solid var(--color-border); border-radius: var(--radius);
  padding: var(--space-2) var(--space-3); box-shadow: var(--shadow-card);
  transition: border-color 0.2s ease, color 0.2s ease;
}
.nav-account-logout:hover { border-color: var(--color-gold); color: var(--color-accent); }
.theme-toggle {
  background: none; border: 1px solid var(--color-border); border-radius: var(--radius-pill);
  color: var(--color-text); cursor: pointer; padding: var(--space-1) var(--space-2);
  transition: border-color 0.2s ease;
}
.theme-toggle:hover { border-color: var(--color-gold); }

/* search: always visible (familiar from the old shop); soft rectangle to
   match the button geometry used everywhere in this design */
.header-search {
  position: relative; display: flex; align-items: center;
  flex: 1 1 18rem; max-width: 42rem; margin-inline: auto;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: calc(var(--radius) + 3px);
  padding: 3px;
  /* focus ring on the whole pill: confirms where typing goes */
  transition: border-color 0.25s ease, box-shadow 0.25s ease;
}
.header-search:focus-within { border-color: var(--color-gold); box-shadow: 0 0 0 1px var(--color-gold); }
.header-search-input {
  flex: 1; min-width: 0; border: none; outline: none; background: transparent;
  padding: 0.45em 0.4em 0.45em 1.1em; color: var(--color-text); font: inherit;
}
.header-search-btn {
  display: inline-flex; align-items: center; gap: 0.4em;
  padding: 0.45em 1.2em; border: none; border-radius: var(--radius);
  background: var(--color-gold); color: var(--color-gold-contrast);
  font: inherit; font-weight: 700; cursor: pointer;
  transition: background-color 0.25s ease;
}
.header-search-btn:hover { background: var(--color-gold-strong); }
.autocomplete-panel {
  position: absolute; top: calc(100% + 6px); left: 0; right: 0; z-index: 40;
  background: var(--color-surface); border: 1px solid var(--color-border);
  border-radius: var(--radius); box-shadow: var(--shadow-panel);
  max-height: 24rem; overflow-y: auto;
}
.autocomplete-item {
  display: flex; gap: var(--space-2); align-items: center;
  padding: var(--space-2); color: var(--color-text);
}
.autocomplete-item:hover, .autocomplete-item.is-active { background: var(--color-surface-alt); text-decoration: none; }
.autocomplete-item img { width: 2.6rem; height: 3.4rem; object-fit: contain; border-radius: 3px; background: var(--color-bg); }
.autocomplete-item-title { flex: 1; }
.autocomplete-item-price { color: var(--color-accent); font-weight: 700; white-space: nowrap; }
.autocomplete-item-category { flex-wrap: wrap; }
.autocomplete-item-badge {
  font-size: 0.65rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--color-gold-ink); background: var(--color-gold-strong);
  border-radius: var(--radius-pill); padding: 0.15em 0.7em; white-space: nowrap;
}
.autocomplete-item-note {
  flex-basis: 100%; color: var(--color-text-muted); font-size: 0.8rem;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden;
}
.autocomplete-fuzzy-hint { padding: var(--space-1) var(--space-2); color: var(--color-text-muted); font-size: 0.85rem; border-bottom: 1px solid var(--color-border-soft); }
.autocomplete-price-note { padding: var(--space-1) var(--space-2); color: var(--color-text-muted); font-size: 0.75rem; border-top: 1px solid var(--color-border-soft); }
/* Settings.SearchNoteLines (autocomplete dropdown only): server picks the
   class (1-10), no inline styles. */
.lines-1 { -webkit-line-clamp: 1; }
.lines-2 { -webkit-line-clamp: 2; }
.lines-3 { -webkit-line-clamp: 3; }
.lines-4 { -webkit-line-clamp: 4; }
.lines-5 { -webkit-line-clamp: 5; }
.lines-6 { -webkit-line-clamp: 6; }
.lines-7 { -webkit-line-clamp: 7; }
.lines-8 { -webkit-line-clamp: 8; }
.lines-9 { -webkit-line-clamp: 9; }
.lines-10 { -webkit-line-clamp: 10; }

/* main nav: the familiar burgundy bar, framed by gold hairlines.
   Same dark base as the footer — the light soft-light leather texture
   washes the brighter --color-primary out to pink on a thin strip. */
.main-nav {
  background: linear-gradient(180deg, var(--color-primary-strong) 0%, var(--color-primary-deep) 100%);
  border-top: 1px solid var(--color-gold);
  border-bottom: 1px solid var(--color-gold);
  position: sticky; top: 0; z-index: 30; /* stays reachable on long product pages */
}
.main-nav-inner {
  display: flex; gap: clamp(var(--space-3), 2vw, var(--space-5));
  padding: 0 var(--page-pad);
  max-width: calc(var(--max-width) + 2 * var(--page-pad)); margin-inline: auto;
  overflow-x: auto; /* phones: swipe the nav instead of collapsing it (no JS needed) */
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.main-nav-inner::-webkit-scrollbar { display: none; }
.main-nav-inner a {
  color: var(--color-primary-contrast);
  font-weight: 700; font-size: 0.95rem; letter-spacing: 0.02em;
  padding: 0.7em 0.15em; white-space: nowrap;
  border-bottom: 2px solid transparent;
  /* gold underline slides in on hover: signals the active choice without layout shift */
  transition: color 0.2s ease, border-color 0.2s ease;
}
.main-nav-inner a:hover { color: var(--color-gold-strong); border-bottom-color: var(--color-gold-strong); text-decoration: none; }
/* aktueller Ort: dauerhafte Gold-Unterstreichung + helle Schrift — gleiche
   Sprache wie der Hover, aber persistent */
.main-nav-inner a.is-active { color: var(--color-gold-strong); border-bottom-color: var(--color-gold-strong); }

/* ---------- Layout ---------- */
.site-main {
  max-width: calc(var(--max-width) + 2 * var(--page-pad));
  margin: 0 auto;
  /* kompakter Auslauf: benefit-bar bringt eigenen Abstand mit, der Footer
     folgt direkt — keine Design-Lücke vor dem Footer */
  padding: var(--space-4) var(--page-pad) var(--space-3);
}

/* ---------- Hero slider ---------- */
.hero {
  border-radius: var(--radius-lg);
  background: linear-gradient(115deg, var(--color-primary-deep) 30%, var(--color-primary) 100%);
  color: var(--color-primary-contrast);
  margin-bottom: var(--space-4);
  box-shadow: var(--shadow-panel);
}
/* slide strip: moved via transform (compositor-only, no layout work) */
/* Ruhiger Wechsel: 1s statt 0.6s, und ease-in-out statt ease. Über eine ganze
   Slide-Breite fällt `ease` durch seinen schnellen Beginn als Ruck auf; die
   symmetrische Kurve läuft weich an und weich aus. Das Gegenstück ist der
   9s-Takt in site.js (start()) — beide gehören zusammen geändert, die
   Transition läuft innerhalb des Intervalls. */
.hero-track { display: flex; transition: transform 1s ease-in-out; }
.hero-slide {
  flex: 0 0 100%; min-width: 100%;
  position: relative; display: flex; align-items: center;
  min-height: clamp(300px, 34vw, 560px);
}
.hero-img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; z-index: 0;
}
/* Der Ausschnitt hängt am Bildinhalt, also trägt jedes mitgelieferte Motiv
   seine eigene Klasse. Ein per Admin gesetztes Bild bekommt keine davon und
   bleibt mittig — über dessen Bildaufbau wissen wir hier nichts.

   Der Laden ist 2.49:1, der Slide auf dem Desktop 3.21:1: beschnitten wird
   hier also die HÖHE, nicht die Breite — und auf 1800px Breite stehen dafür
   nur 163px zur Verfügung (1800/2.49 = 723 gegen 560 Slot-Höhe). Um die
   konkurrieren zwei Dinge: die Ladenbeschriftung oben (die GB-Zeichen sitzen
   auf den obersten ~85px) und Eingangsstufen samt Kopfsteinpflaster unten
   (die untersten ~155px). Beides geht nicht. Betreiber-Entscheidung: die
   Straße vor den Schaufenstern soll zu sehen sein, also 60 % statt oben.
   Die großen GB-Rundzeichen auf der Blende fallen damit weg, der Laden bleibt
   aber über die Schriftzüge auf beiden Vordächern benannt — die liegen tiefer
   und stehen bei 60 % noch vollständig im Bild. Bewusst nicht 45 %: dort
   blieb von den Rundzeichen eine angeschnittene Hälfte stehen, und ein halb
   abgeschnittenes Logo sieht nach Fehler aus, nicht nach Absicht. 60 % zeigt
   zugleich mehr Pflaster als 45 %, ist also in beidem die bessere Wahl.
   Die 40 % auf der X-Achse greifen erst, wenn der Slide schmaler wird als
   2.49:1 (Telefon) und stattdessen die Breite beschnitten wird: dann bleiben
   beide GB-Zeichen rechts der dunklen Textfläche stehen. Eine Media Query
   braucht das nicht — pro Achse ist der Wert wirkungslos, solange auf ihr
   nichts überhängt; auf dem Telefon ändert das Y also nichts.

   Der Innenraum ist 1.81:1 und wird auf dem Desktop noch stärker in der Höhe
   beschnitten (994 gegen 560, also 434px). 20 % hält die Regalbeschriftung
   („Gebet", „Glaube", „Nachfolge") im Bild, die den Laden als christliche
   Buchhandlung ausweist, und vermeidet gleichzeitig die tote weiße
   Deckenfläche, die bei 0 % oben rechts stehen bliebe. X bleibt mittig: auf
   dem Telefon liegen Regalschilder und Kartenständer damit rechts der
   dunklen Textfläche, gedeckt wird nur allgemeines Regal. */
.hero-img-shop { object-position: 40% 60%; }
.hero-img-interior { object-position: center 20%; }
/* text stays readable over any admin-chosen image */
.hero-slide::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(100deg, rgb(42 8 10 / 88%) 25%, rgb(42 8 10 / 30%) 70%, transparent 100%);
}
.hero-content {
  position: relative; z-index: 2;
  padding: clamp(var(--space-4), 4vw, var(--space-6)) clamp(var(--space-4), 5vw, 7rem);
  max-width: 46em;
  /* one-time entrance: draws the first glance to the brand message */
  animation: hero-in 0.7s ease-out both;
}
/* dots: current position + direct jump; gold on burgundy stays on brand */
.hero-dots {
  position: absolute; left: 0; right: 0; bottom: clamp(0.7rem, 1.5vw, 1.4rem);
  display: flex; justify-content: center; gap: 0.6em; z-index: 3;
}
.hero-dot {
  width: 0.75em; height: 0.75em; padding: 0;
  border: 1px solid var(--color-gold-strong); border-radius: 50%;
  background: transparent; cursor: pointer;
  transition: background-color 0.25s ease, transform 0.25s ease;
}
.hero-dot.is-active { background: var(--color-gold-strong); transform: scale(1.15); }
.hero-headline {
  font-family: var(--font-display);
  font-size: clamp(1.9rem, 3.4vw, 3.6rem);
  line-height: 1.12;
  margin: 0 0 0.35em;
  color: #fdf6ea;
}
.hero-headline em { color: var(--color-gold-strong); font-style: italic; }
.hero-subline { font-size: clamp(1rem, 1.3vw, 1.3rem); margin: 0 0 1.4em; color: var(--color-blush); }
.hero-cta { font-size: clamp(1rem, 1.1vw, 1.15rem); }
@keyframes hero-in {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: none; }
}

/* ---------- Trust / benefit bars ---------- */
.trust-bar, .benefit-bar {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  margin: var(--space-4) 0;
  box-shadow: var(--shadow-card);
}
/* benefit bar: three columns (Buchpreisbindung — kein "Faire Preise"-Punkt) */
.benefit-bar { grid-template-columns: repeat(3, 1fr); }
/* jedes Item in seiner Spalte zentriert: gleichmäßige Verteilung über die
   volle Bar-Breite statt Links-Klumpung mit Restfläche rechts */
.trust-item, .benefit-item { display: flex; align-items: center; justify-content: center; gap: var(--space-3); min-width: 0; }
/* Icon füllt exakt die Höhe des zweizeiligen Textblocks; Breite folgt dem
   Seitenverhältnis der Glyphe (--icon-ratio, enge viewBox) — so beanspruchen
   alle Icons denselben vertikalen Raum */
.trust-item .icon, .benefit-item .icon {
  height: 2.6em; width: calc(2.6em * var(--icon-ratio, 1));
  color: var(--color-gold);
}
.trust-item-text { display: flex; flex-direction: column; line-height: 1.35; }
.trust-item-text strong { font-size: 1.05rem; }
.trust-item-text small { color: var(--color-text-muted); font-size: 0.88rem; }

/* ---------- Home layout: products + info cards ---------- */
.home-layout { display: grid; grid-template-columns: 1fr; gap: var(--space-4); align-items: start; }
/* breitere Sidebar: die Info-Karten (Öffnungszeiten, Versand, PayPal) sollen
   atmen — die Produktspalte trägt trotzdem noch 5 Karten pro Reihe (FHD) */
/* Prozent statt vw: Bezug ist die Inhaltsbreite, die seit --max-width bei
   90rem gedeckelt ist. Mit 21vw wuchs die Sidebar auf 4K bis an ihre
   24rem-Grenze (480px) und nahm dem Produktraster 77px weg, obwohl der
   Inhalt links wie rechts exakt 1800px breit ist — dadurch passten auf dem
   GRÖSSEREN Schirm weniger Karten nebeneinander als auf 1080p. */
@media (min-width: 64rem) { .home-layout { grid-template-columns: 1fr clamp(17rem, 22%, 24rem); } }
.home-sidebar { display: grid; gap: var(--space-3); }
/* Info-Karten laufen beim Scrollen mit, wie der Kaufkasten auf der
   Produktseite: Öffnungszeiten, Zahlung und Versand bleiben erreichbar,
   während die Produktspalte weiterläuft. Erst ab der zweispaltigen Stufe —
   darunter steht die Sidebar ohnehin im Fluss unter den Produkten.

   Kein max-height, kein overflow-y -- bewusst NICHT mehr (zwei frühere
   Fassungen hatten das versucht und beide dieselbe Beschwerde ausgelöst:
   "Öffnungszeiten und Versand haben je einen eigenen Scrollbalken". Das
   war nie wörtlich wahr (jede .side-card ist computed overflow-y:visible,
   nie eine zweite Bildlaufleiste), aber EIN Balken über die volle
   Spaltenhöhe, ohne Rahmen um die Spalte, liest sich optisch trotzdem
   genau so -- und wichtiger: der Betreiber will dieses Verhalten schlicht
   nicht. Erwartet ist die klassische Sticky-Sidebar: oben angeheftet,
   solange die Produktspalte daneben weiterläuft, dann normal mit der
   Seite weiterscrollen, sobald die eigene Kartenhöhe erreicht ist -- nie
   ein zweiter, innerer Scrollbereich.

   Das braucht hier keine Sonderbehandlung "falls die Sidebar mal höher
   als der Viewport ist": .home-layout hat align-items:start (die Spalte
   wird NICHT auf Zeilenhöhe gestreckt), aber die GRID-ZEILE selbst ist so
   hoch wie die höhere der beiden Spalten -- und das ist praktisch immer
   die Produktspalte (Empfohlen + Neu eingetroffen + „Alle Bücher
   ansehen" türmen sich weit über die ~1100-1200px der vier Karten).
   position:sticky bindet sich an genau diese Zeilenhöhe als eigenen
   Containing Block, kann sich darin also frei bewegen -- die Sidebar
   bleibt oben kleben, bis ihr eigenes Ende diese Zeile erreicht, und
   nichts wird je abgeschnitten oder unerreichbar, ganz ohne max-height.
   Nur wenn die Produktspalte einmal kürzer als die Sidebar wäre, bliebe
   ihr entsprechend weniger Klebezeit -- das ist normales Sticky-
   Verhalten, kein Fehlerfall, der eine Sonderregel braucht. */
@media (min-width: 64rem) {
  .home-sidebar { position: sticky; top: var(--space-3); }
}
.side-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg);
  padding: var(--space-3) var(--space-4);
  box-shadow: var(--shadow-card);
  /* Die Karte ist Grid-Item der Sidebar und hätte sonst min-width:auto, also
     die eigene min-content-Breite als Untergrenze. Bei vergrößerter
     Schriftgröße (System-Textskalierung, gemessen bei 120 %) wurde die Karte
     dadurch 334px breit in einer 326px-Spalte: die Sidebar bekam eine
     waagerechte Leiste, weil overflow-y:auto die andere Achse laut Spec
     ebenfalls auf auto setzt. min-width:0 nimmt die Untergrenze weg.
     overflow-wrap ist der zweite Teil: ein einzelnes langes Kompositum
     ("Versandkostenpauschale") ist breiter als die Textspalte und läuft
     sonst heraus, egal wie die Spalten definiert sind -- gemessen 44px
     Überstand trotz min-width:0 und minmax(0,1fr), mit break-word 0. */
  min-width: 0;
  overflow-wrap: break-word;
}
.side-card-title {
  display: flex; align-items: center; gap: 0.5em;
  font-size: 1.05rem; margin: 0 0 var(--space-2);
}
.side-card-title .icon { color: var(--color-gold); }
/* Referenz-Layout: großes, feines Line-Art-Symbol neben dem Inhalt
   (Revision.png) — kein Kreis, das Icon selbst trägt die Präsenz.
   Feste Icon-Spalte (breitestes Icon = LKW, 2.8em × 1.62 = 4.54em): alle
   Karten teilen sich dieselbe Aufteilung, Text beginnt überall auf gleicher
   Höhe. Symbol und Spalte waren 3.4em/5.6em und sind bewusst je einen Tick
   kleiner: bei vergrößerter Schrift fraß die Spalte so viel Karte, dass
   neben Uhr und LKW kaum Text übrig blieb (gemessen 148px Textspalte, mit
   diesem Maß 159px bei sonst gleicher Karte). minmax(0,1fr) statt 1fr, weil
   ein fr-Track sonst min-content als Untergrenze behält und die Textspalte
   nicht schrumpfen kann. */
.side-card-icon { display: grid; grid-template-columns: 4.6em minmax(0, 1fr); gap: var(--space-3); align-items: start; }
.side-card-icon-badge { display: flex; justify-content: center; margin-top: 0.15em; }
.side-card-icon-badge .icon { height: 2.8em; width: calc(2.8em * var(--icon-ratio, 1)); color: var(--color-gold); }
/* Zwischen 64rem und 75rem steht die Sidebar auf ihrem Minimum (17rem): die
   feste Symbolspalte (4.6em, früher 5.6em) frisst dort rund ein Drittel der
   Karte, und der Text quetschte sich in ~140px — "innerhalb von Deutschland" brach dann
   mitten im Begriff um. Symbolspalte und Abstand hier schmaler; ab 75rem gilt
   wieder das großzügige Maß. Bewusst NICHT die Sidebar verbreitert: das hätte
   dem Produktraster in genau diesem Bereich eine Spalte gekostet.
   Die Symbolhöhe muss zur Spaltenbreite passen, denn die Breite folgt dem
   Seitenverhältnis und der LKW ist mit --icon-ratio 1.62 das breiteste
   Symbol: 2em × 1.62 = 3.24em passt in die 3.4em-Spalte, bei 3.4em Höhe ragte
   er heraus und schob sich über den Text.
   Muss NACH der Basisregel stehen — gleiche Spezifität, die spätere gewinnt. */
@media (min-width: 64rem) and (max-width: 75rem) {
  .side-card-icon { grid-template-columns: 3.4em minmax(0, 1fr); gap: var(--space-2); }
  .side-card-icon-badge .icon { height: 2em; width: calc(2em * var(--icon-ratio, 1)); }
}
.side-card-icon-body .side-card-row:last-child { margin-bottom: 0; }
.side-card-row { margin: 0 0 var(--space-2); color: var(--color-text); }
.side-card-note { margin: 0; color: var(--color-accent); font-style: italic; font-family: var(--font-display); }
.side-card-links { display: flex; flex-direction: column; }
.side-card-links a {
  color: var(--color-text); padding: 0.45em 0;
  border-bottom: 1px solid var(--color-border-soft);
  display: flex; justify-content: space-between; align-items: center;
  transition: color 0.15s ease, padding-left 0.15s ease;
}
.side-card-links a::after { content: "›"; color: var(--color-gold); font-weight: 700; }
.side-card-links a:last-child { border-bottom: none; }
.side-card-links a:hover { color: var(--color-accent); text-decoration: none; padding-left: 0.3em; }
.side-card-more { font-weight: 700; }
/* Freigrenze rein typografisch statt als Fläche (erster Versuch — ein
   goldgefülltes Rechteck — wurde vom Betreiber als generischer Fremdkörper
   abgelehnt; die Karte trägt mit LKW-Icon links und der burgunderroten
   PayPal-Karte direkt darüber ohnehin schon zwei kräftige Flächen, eine
   dritte hätte sie überladen). Vorbild ist die Preisdarstellung der
   Produktseite (.product-price-amount): der Betrag trägt die Aussage groß
   in der Display-Schrift und derselben Akzentfarbe, Label und Region bleiben
   klein und gedämpft daneben/darunter — kein Kasten, keine zweite Fläche. */
.ship-free {
  display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 0.5em;
  margin: 0 0 var(--space-2);
  /* Bezugsgröße für die cqw-Einheit des Labels darunter */
  container-type: inline-size;
}
.ship-free-amount {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(1.3rem, 1.8vw, 1.6rem); line-height: 1.1;
  color: var(--color-accent);
}
/* "versandkostenfrei" ist zusammen mit dem Betrag die eigentliche Botschaft
   und trägt deshalb eine eigene Zeile über die volle Breite, nicht mehr eine
   Randnotiz neben dem Preis. Die Größe hängt an cqw (1% der .ship-free-Breite),
   damit das Wort die Zeile in jeder Sidebar-Breite ausfüllt statt bei einer
   festen Punktgröße Luft zu lassen; der Faktor ergibt sich aus der Länge des
   Wortes (17 Zeichen × ~0.62em Vorschub + Sperrung). clamp() begrenzt es nach
   unten und oben, damit es weder unlesbar klein noch plakativ groß wird.
   Dunkle Textfarbe statt gedämpft: die Zeile soll gelesen werden, konkurriert
   aber durch die kleinere Größe nicht mit dem goldenen Betrag darüber. */
.ship-free-label {
  flex-basis: 100%;
  font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: clamp(0.85rem, 9.9cqw, 1.8rem);
  line-height: 1.2; color: var(--color-text);
}
/* Region auf eigener Zeile (flex-basis 100% erzwingt den Umbruch). Bewusst
   ohne Zierstrich davor: ein kurzer Goldstrich als Aufzähler wirkte an dieser
   einzelnen Zeile deplatziert (Betreiber-Feedback) — die Zeile ordnet sich
   allein über Größe und gedämpfte Farbe unter. */
.ship-free-region {
  flex-basis: 100%;
  font-size: 0.8rem; color: var(--color-text-muted);
}
.side-card-pay { background: var(--color-primary); border-color: var(--color-primary-strong); color: var(--color-primary-contrast); }
.side-card-pay .side-card-title { color: var(--color-gold-strong); }
.side-card-pay .side-card-row { color: var(--color-blush); }
.side-card-pay-logo { display: block; width: 7.5rem; height: auto; margin: 0 0 var(--space-2); }

/* ---------- Sections & product grid ---------- */
.section-title {
  font-size: clamp(1.35rem, 1.8vw, 1.9rem);
  margin: var(--space-4) 0 var(--space-3);
}
.section-title::after {
  /* short gold rule: quiet hierarchy marker under section headings */
  content: ""; display: block; width: 2.2em; height: 3px;
  background: var(--color-gold); border-radius: 2px; margin-top: 0.25em;
}
.product-grid {
  display: grid; gap: var(--space-3);
  /* Mindestbreite → max. 5 Karten neben der Sidebar auf FHD, ~9 auf 4K.
     auto-fill (nicht auto-fit): Spurenzahl folgt der Mindestbreite und die
     Karten bleiben überall gleich groß — auch auf dünn besetzten
     Kategorieseiten, wo auto-fit sie grotesk aufblasen würde. Volle
     Startseiten-Reihen sind Sache der Daten (new/featured_count). */
  /* Mindestbreite als Prozent der Produktspalte, nicht als vw: der Inhalt ist
     bei 90rem gedeckelt, der Viewport nicht — mit 13vw kletterte das Minimum
     auf 4K an seine Obergrenze (300px), während die Spalte gleich breit
     blieb, und aus fünf Karten pro Reihe wurden vier. Mit 18% bleiben es
     oberhalb des Deckels konstant fünf.
     Der 12rem-Boden ist das Gegenstück dazu: sobald das Fenster schmaler
     wird, gewinnt er gegen die 18% und die Reihe wird wie gewünscht kürzer
     (5 → 4 → 3 → 2), statt immer fünf immer kleinere Karten zu zeigen. */
  grid-template-columns: repeat(auto-fill, minmax(clamp(12rem, 18%, 15rem), 1fr));
}
.section-more { margin: var(--space-4) 0 0; }

/* ---------- "Alle Bücher ansehen" ---------- */
/* A plain, full-width burgundy panel closes the row — the same flat
   solid-burgundy/cream-text surface as .side-card-pay (the PayPal panel
   in the sidebar), not a new decorative device. The chevron matches
   .side-card-links a::after, so "there's more this way" reads the same
   everywhere on the site. Kept deliberately plain: an ornamented reading
   ("book spine", cartouche, gold fileten) was tried and rejected as
   artificial-looking. Full width still comes from the panel itself, not
   a stretched .btn. */
.section-cta {
  display: flex; align-items: center; justify-content: center; gap: 0.5em;
  width: 100%; padding: var(--space-3) var(--space-4);
  background: var(--color-primary); color: var(--color-primary-contrast);
  border-radius: var(--radius-lg);
  font-family: var(--font-display); font-size: clamp(1rem, 1.1vw, 1.15rem);
  letter-spacing: 0.01em;
  box-shadow: var(--shadow-card);
  transition: background-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
}
.section-cta::after {
  content: "›"; color: var(--color-gold); font-weight: 700;
  transition: translate 0.2s ease;
}
.section-cta:hover {
  text-decoration: none;
  background: var(--color-primary-strong);
  box-shadow: var(--shadow-card-hover);
  transform: translateY(-2px);
}
.section-cta:hover::after { translate: 0.25rem 0; }

/* Kuratierte Startseiten-Reihen (Empfohlen / Neu eingetroffen, beide in
   .product-section) füllen die Reihe aus: der Restplatz einer nicht voll
   besetzten Reihe wird zu Abstand ZWISCHEN den Karten statt zu einem Loch am
   rechten Rand, und die Reihen werden nach Stückzahl ausbalanciert — 6 Titel
   bei 5 möglichen Spalten ergeben 3+3, nicht 5+1.
   Die Karten behalten dabei exakt die Breite, die sie in einer vollen Reihe
   hätten: --pg-track ist das ausgerechnete 1fr, kein 1fr. auto-fit + 1fr
   würde sie stattdessen aufblasen — genau das, was die Regel oben verhindert.
   Bewusst NICHT für Kategorie- und Suchtreffer: dort ist die Trefferzahl
   offen, und eine dünn besetzte Trefferseite mit zwei Karten an den
   Außenkanten sähe kaputt aus. Die bleiben beim reinen auto-fill.
   @supports statt blindem Vertrauen: ohne round() oder Container-Queries
   fiele grid-template-columns auf `none` zurück, also auf eine einspaltige
   Liste. Dann bleibt lieber das auto-fill von oben stehen. :has() braucht
   keine Abfrage — fehlt es, greifen die Regeln hier gar nicht. */
@supports (container-type: inline-size) and (grid-template-columns: repeat(round(up, 3, 2), 1px)) {
  /* Container- und keine Media-Query: die Produktspalte springt je nach
     Sidebar-Zustand, bei 1024px Viewport ist sie schmaler als bei 900px. */
  .product-section { container-type: inline-size; }

  .product-section > .product-grid {
    /* Spaltenzahl, die die geteilte Regel oben bei dieser Containerbreite
       erzeugt. Nachgerechnet aus ihr: n Spalten passen, solange
       n * clamp(12rem, 18%, 15rem) + (n-1) * 1rem <= Breite. Unterhalb von
       66.67rem gewinnt der 12rem-Boden (Schwellen 25/38/51/64rem), zwischen
       66.67rem und 83.33rem sind es durch die 18% konstant 5, darüber
       deckelt 15rem (Schwellen 95/111/127/143rem). Die Werte ab 6 sind
       heute unerreichbar (--max-width deckelt die Spalte bei ~69rem), stehen
       aber da, damit ein breiteres Layout die Rechnung nicht still verstimmt. */
    --pg-fit: 1;
    --pg-track: calc((100% - (var(--pg-fit) - 1) * var(--space-3)) / var(--pg-fit));
    --pg-rows: round(up, var(--pg-n) / var(--pg-fit), 1);
    --pg-cols: min(var(--pg-fit), round(up, var(--pg-n) / var(--pg-rows), 1));
    --pg-spread: calc((100% - var(--pg-cols) * var(--pg-track)) / max(1, var(--pg-cols) - 1));
  }
  @container (min-width: 25rem)  { .product-section > .product-grid { --pg-fit: 2; } }
  @container (min-width: 38rem)  { .product-section > .product-grid { --pg-fit: 3; } }
  @container (min-width: 51rem)  { .product-section > .product-grid { --pg-fit: 4; } }
  @container (min-width: 64rem)  { .product-section > .product-grid { --pg-fit: 5; } }
  @container (min-width: 95rem)  { .product-section > .product-grid { --pg-fit: 6; } }
  @container (min-width: 111rem) { .product-section > .product-grid { --pg-fit: 7; } }
  @container (min-width: 127rem) { .product-section > .product-grid { --pg-fit: 8; } }
  @container (min-width: 143rem) { .product-section > .product-grid { --pg-fit: 9; } }

  /* Stückzahl ist in CSS nur per Mengenabfrage lesbar, also eine Regel je
     Zahl. 20 reicht mit Luft für die beiden Admin-Werte (featured_count,
     new_count — aktuell 4 und 15); darüber greift die Regel nicht mehr und
     es bleibt beim auto-fill, was bei so vielen Karten ohnehin kaum noch
     eine sichtbar unausgewogene letzte Reihe ergibt. */
  .product-section > .product-grid:has(> :nth-child(1):last-child)  { --pg-n: 1; }
  .product-section > .product-grid:has(> :nth-child(2):last-child)  { --pg-n: 2; }
  .product-section > .product-grid:has(> :nth-child(3):last-child)  { --pg-n: 3; }
  .product-section > .product-grid:has(> :nth-child(4):last-child)  { --pg-n: 4; }
  .product-section > .product-grid:has(> :nth-child(5):last-child)  { --pg-n: 5; }
  .product-section > .product-grid:has(> :nth-child(6):last-child)  { --pg-n: 6; }
  .product-section > .product-grid:has(> :nth-child(7):last-child)  { --pg-n: 7; }
  .product-section > .product-grid:has(> :nth-child(8):last-child)  { --pg-n: 8; }
  .product-section > .product-grid:has(> :nth-child(9):last-child)  { --pg-n: 9; }
  .product-section > .product-grid:has(> :nth-child(10):last-child) { --pg-n: 10; }
  .product-section > .product-grid:has(> :nth-child(11):last-child) { --pg-n: 11; }
  .product-section > .product-grid:has(> :nth-child(12):last-child) { --pg-n: 12; }
  .product-section > .product-grid:has(> :nth-child(13):last-child) { --pg-n: 13; }
  .product-section > .product-grid:has(> :nth-child(14):last-child) { --pg-n: 14; }
  .product-section > .product-grid:has(> :nth-child(15):last-child) { --pg-n: 15; }
  .product-section > .product-grid:has(> :nth-child(16):last-child) { --pg-n: 16; }
  .product-section > .product-grid:has(> :nth-child(17):last-child) { --pg-n: 17; }
  .product-section > .product-grid:has(> :nth-child(18):last-child) { --pg-n: 18; }
  .product-section > .product-grid:has(> :nth-child(19):last-child) { --pg-n: 19; }
  .product-section > .product-grid:has(> :nth-child(20):last-child) { --pg-n: 20; }

  .product-section > .product-grid:has(> :nth-child(-n+20):last-child) {
    grid-template-columns: repeat(var(--pg-cols), var(--pg-track));
    /* Deckel auf den Abstand, auf halbe Kartenbreite. Bei „4 Karten, 5
       Spalten" liegt der gesuchte Abstand darunter (~0,45 Karten), die Reihe
       reicht also wie gewollt von Kante zu Kante. Bei „2 Karten, 3 Spalten"
       — auf 1280–1440px-Notebooks die Empfohlen-Reihe — läge er über einer
       ganzen Kartenbreite und risse ein Loch mitten in die Reihe; dort rückt
       sie stattdessen zusammen und wird zentriert. Solange der Deckel nicht
       greift, ist center identisch zu space-between, weil kein Restplatz
       übrig bleibt; bei voller Reihe ist --pg-spread genau --space-3. */
    column-gap: min(var(--pg-spread), calc(var(--pg-track) / 2));
    justify-content: center;
  }
}

.product-card {
  position: relative;
  display: flex; flex-direction: column;
  background: var(--color-surface); border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg); overflow: hidden;
  color: var(--color-text); box-shadow: var(--shadow-card);
  /* lift on hover: signals the whole card is one clickable object */
  transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
  /* room above a #product-<sku> jump target so the sticky .main-nav
     (~60px) doesn't cover the card it just scrolled to */
  scroll-margin-top: 5rem;
}
.product-card:hover {
  text-decoration: none;
  transform: translateY(-4px);
  border-color: var(--color-gold);
  box-shadow: var(--shadow-card-hover);
}
.product-card-badge {
  position: absolute; top: var(--space-2); left: var(--space-2); z-index: 2;
  background: var(--color-gold-strong); color: var(--color-gold-ink);
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  padding: 0.25em 0.8em; border-radius: var(--radius-pill);
  box-shadow: 0 1px 4px rgb(0 0 0 / 25%);
}
.product-card-img { display: block; background: var(--color-surface-alt); padding: var(--space-3); }
.product-card-img img {
  width: 100%; aspect-ratio: 3 / 4; object-fit: contain;
  filter: drop-shadow(0 6px 10px rgb(43 21 14 / 22%)); /* books read as objects, not flat scans */
}
.product-card-body { display: flex; flex-direction: column; gap: var(--space-1); padding: var(--space-2) var(--space-3) var(--space-3); flex: 1; }
.product-card-title { font-weight: 700; line-height: 1.3; }
.product-card-price { color: var(--color-accent); font-weight: 700; font-size: 1.12rem; margin-top: auto; }
.product-card-cta {
  margin-top: var(--space-2);
  text-align: center; font-weight: 700; font-size: 0.9rem;
  color: var(--color-primary); border: 1px solid var(--color-border);
  border-radius: var(--radius); padding: 0.4em 0;
  transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
.product-card:hover .product-card-cta {
  background: var(--color-primary); color: var(--color-primary-contrast); border-color: var(--color-primary);
}
:root[data-theme="dark"] .product-card-cta { color: var(--color-primary-contrast); }
.category-empty, .search-empty { color: var(--color-text-muted); }

/* ---------- Empfohlen / Gottfried Bernard's recommendations ---------- */
/* A soft-tinted wrapper distinguishes this hand-picked row from the
   plain "Neu eingetroffen" grid below it, without a heavy border box
   that would fight the paper texture. */
.product-section-featured {
  background: var(--color-surface-alt);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
}
.product-section-featured .section-title { margin-top: 0; }
/* Die Unterschrift trägt den Tintenton (Stimme des Inhabers); der
   Empfehlungstext dazwischen bleibt neutral. Burgunder liest im
   Dunkelmodus nicht mehr — dort steht --color-blush dafür (Override am
   Ende des Abschnitts). */

/* Der Empfehlungstext ist ein Handzettel, wie er im Laden am Regal steht:
   ein rechtwinklig geschnittener Papierstreifen (kein Radius — Papier wird
   geschnitten, nicht abgerundet), leicht schief aufgelegt und mit zwei
   goldenen Albumecken aufgesteckt. Die Ecken ersetzen den früheren
   Akzentbalken am linken Rand: sie halten das Blatt, statt es nur zu
   markieren, und geben der Fläche eine Diagonale.
   Breite: exakt die Reihe der Bücher darüber (volle Innenbreite der
   Tafel) — ein schmaleres Blatt saß nach der Reihen-Ausgleichsregel oben
   (.product-section > .product-grid) sichtbar linksbündig unter einer
   jetzt randfüllenden Reihe. */
.featured-note {
  position: relative;
  /* hält den Papier-Blend unten im Blatt, auch ohne das rotate (das für
     sich schon einen Stacking-Context aufspannt) */
  isolation: isolate;
  margin: var(--space-5) 0 var(--space-2);
  padding: var(--space-4) clamp(var(--space-3), 4vw, var(--space-5));
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  box-shadow: var(--shadow-card);
  transform: rotate(-0.4deg);
}
/* dieselbe Körnung wie der Seitenhintergrund (body::before, identische
   Daten-URL — der Browser dekodiert das Bild also nur einmal), hier aber
   AUF dem Blatt: die Tafel ringsum bleibt glatt, das Blatt bekommt
   Struktur und liest sich damit als aufgelegtes Papier statt als Kasten.
   Blend und Deckkraft kommen aus den Theme-Tokens, gelten also in beiden
   Modi. z-index: -1 legt die Körnung über den eigenen Hintergrund, aber
   unter den Text. */
.featured-note::before {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='paper'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.04' numOctaves='5' stitchTiles='stitch' result='noise'/%3E%3CfeDiffuseLighting in='noise' lighting-color='white' surfaceScale='2'%3E%3CfeDistantLight azimuth='45' elevation='60'/%3E%3C/feDiffuseLighting%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23paper)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
  mix-blend-mode: var(--paper-blend);
  opacity: var(--paper-opacity);
  pointer-events: none;
}
/* Albumecken: gleichschenklige Dreiecke wie die Papierecken im Fotoalbum.
   inset: -1px, damit sie die Blattkante samt Rahmenlinie überdecken —
   die Ecke liegt auf dem Papier, nicht daneben. */
.featured-note-mount { position: absolute; inset: -1px; pointer-events: none; }
.featured-note-mount::before, .featured-note-mount::after {
  content: ""; position: absolute; width: 1.5rem; height: 1.5rem;
  background: var(--color-gold-strong);
  /* 1px Schlagschatten auf das Papier: erst damit liegt die Ecke sichtbar
     ÜBER dem Blatt statt in der Fläche zu kleben */
  filter: drop-shadow(1px 1px 1px rgb(53 6 7 / 22%));
}
.featured-note-mount::before { top: 0; left: 0; clip-path: polygon(0 0, 100% 0, 0 100%); }
.featured-note-mount::after { right: 0; bottom: 0; clip-path: polygon(100% 0, 100% 100%, 0 100%); }
.featured-note p {
  margin: 0 0 var(--space-3);
  font-family: var(--font-display);
  font-size: 1.05rem;
  line-height: 1.7;
}
/* zwei Klassen tief, damit die Unterschrift ohne !important gegen
   .featured-note p gewinnt */
.featured-note .featured-note-sig {
  margin-bottom: 0;
  font-style: italic;
  color: var(--color-primary);
  text-align: right;
}
:root[data-theme="dark"] .featured-note .featured-note-sig { color: var(--color-blush); }

/* ---------- Buttons ----------
   Vorlage: eher rechteckig mit abgerundeten Ecken (kein Pill), heller
   Creme-Text auf Gold. Gilt für alle Buttons dieses Designs. */
.btn {
  display: inline-block; padding: 0.65em 1.6em;
  border: none; border-radius: var(--radius); cursor: pointer;
  font: inherit; font-weight: 700; font-size: 1rem;
  color: var(--color-text);
  background: var(--color-surface-alt);
  transition: background-color 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;
}
/* Bare .btn (no .btn-primary/.btn-gold/.btn-danger variant, e.g. the
   checkout review's "Ändern" button) had a resting background but no
   visible hover feedback at all -- .btn already declares the transition,
   this just gives it something to transition to, matching the darken +
   lift the other variants already use. */
.btn:hover { text-decoration: none; background: var(--color-border); box-shadow: var(--shadow-card-hover); }
.btn-primary { background: var(--color-primary); color: var(--color-primary-contrast); }
.btn-primary:hover { background: var(--color-primary-strong); box-shadow: var(--shadow-card-hover); }
.btn-gold { background: var(--color-gold); color: var(--color-gold-contrast); }
.btn-gold:hover { background: var(--color-gold-strong); box-shadow: var(--shadow-card-hover); }
.btn-small { padding: 0.15em 0.7em; font-size: 0.85rem; }
.btn-danger { background: var(--color-warn); color: #fff; }
.btn-danger:hover { filter: brightness(1.1); background: var(--color-warn); box-shadow: var(--shadow-card-hover); }

/* ---------- Breadcrumb / headings ---------- */
.breadcrumb { color: var(--color-text-muted); margin-bottom: var(--space-2); font-size: 0.9rem; }
.category-title { font-size: clamp(1.5rem, 2.2vw, 2.3rem); margin-top: 0; }
/* Sortier-Leiste: bewusst leise (Text-Links, Aktiv-Markierung über Farbe+Gewicht),
   damit sie das Produktraster nicht mit dem Nav-Band konkurrieren lässt */
.category-sort { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-3); align-items: baseline; margin-bottom: var(--space-3); font-size: 0.9rem; }
.category-sort-label { color: var(--color-text-muted); }
.category-sort a { color: var(--color-text-muted); }
.category-sort a:hover { color: var(--color-accent); }
.category-sort a.is-active { color: var(--color-accent); font-weight: 700; }
.category-value-list { line-height: 2.1; columns: 3 16rem; column-gap: var(--space-5); list-style: none; padding: 0; }
.category-value-list a { color: var(--color-text); }
.category-value-list a:hover { color: var(--color-accent); }
.category-count { color: var(--color-text-muted); font-size: 0.85rem; }
.category-search-card { border-color: var(--color-gold); }
.category-search-badge {
  align-self: flex-start;
  background: var(--color-gold-strong); color: var(--color-gold-ink);
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  padding: 0.25em 0.8em; border-radius: var(--radius-pill);
}
.category-search-note {
  /* Full results page has room the autocomplete dropdown doesn't: show much
     more of the note, capped only so a very long blurb can't stretch the
     grid row past reason -- not tied to Settings.SearchNoteLines, which
     governs the tight autocomplete snippet only (see .lines-N further up). */
  color: var(--color-text-muted); font-size: 0.85rem;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 10; overflow: hidden;
}
.category-search-note p { margin: 0; }

/* ---------- Product detail ---------- */
/* Drei Abschnitte: .product-detail (Bild/Text/Kaufkasten), .product-about
   (Beschreibung/Details als eigene Karte) und der unveränderte Flipbook-
   Block. grid-template-areas statt reiner Spaltenreihenfolge, damit der
   Kaufkasten bei 48–75rem sauber unter Bild+Text springen kann, ohne die
   Quellreihenfolge (wichtig für mobil: Bild → Text → Kaufkasten) zu ändern. */
.product-detail {
  display: grid; gap: var(--space-4);
  grid-template-columns: 1fr;
  grid-template-areas: "media" "info" "buy";
}
@media (min-width: 48rem) {
  /* zwei Spalten (Bild | Text), Kaufkasten volle Breite darunter */
  .product-detail {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
    grid-template-areas: "media info" "buy buy";
    gap: var(--space-5);
  }
}
@media (min-width: 75rem) {
  /* drei Spalten nebeneinander: Bild ~ein Drittel, Text etwas mehr,
     Kaufkasten schmaler — Richtwert aus der Vorgabe */
  .product-detail {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr) minmax(0, 0.85fr);
    grid-template-areas: "media info buy";
  }
}
.product-detail-media {
  grid-area: media;
  background: var(--color-surface); border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg); padding: var(--space-4);
  box-shadow: var(--shadow-card); align-self: start;
}
.product-detail-media img { display: block; margin: 0 auto; max-height: 34rem; object-fit: contain; filter: drop-shadow(0 10px 18px rgb(43 21 14 / 25%)); }
.product-detail-info { grid-area: info; min-width: 0; }

/* Eyebrow (Kategorie-Pfad über dem Titel): bewusst NEUTRAL statt gold —
   Gold liest sich auf der Creme-Fläche hier schlecht (Betreiber-Feedback).
   Kleine Versalien, gedämpfte Farbe; erst im Hover eine dezente Aufwertung,
   keine dauerhafte Akzentfarbe. */
.product-eyebrow {
  margin: 0 0 var(--space-2);
  text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.78rem;
  color: var(--color-text-muted);
}
.product-eyebrow a { color: inherit; transition: color 0.15s ease; }
.product-eyebrow a:hover { color: var(--color-text); text-decoration: underline; }
.product-eyebrow-sep { margin: 0 0.4em; opacity: 0.6; }

.product-title { margin: 0 0 var(--space-1); font-size: clamp(1.5rem, 2.4vw, 2.4rem); line-height: 1.15; }
/* Untertitel: zweite, leisere Stimme unter dem Titel — kursive Display-
   Schrift, gedämpfte Farbe. Lauter als .product-teaser, leiser als .product-title. */
.product-subtitle {
  font-family: var(--font-display); font-style: italic;
  font-size: clamp(1.05rem, 1.3vw, 1.25rem);
  color: var(--color-text-muted);
  margin: 0 0 var(--space-3);
}
/* Autor-Zeile: kräftig und in Textfarbe — war zu dünn/blass zum Lesen */
.product-field-heading { color: var(--color-text); font-weight: 700; font-size: 1.2rem; font-family: var(--font-display); margin: 0 0 var(--space-1); }
/* Kurzbeschreibung: jetzt die prominente Mittelspalte, kein kleiner Teaser
   mehr. Betreiber mag die goldene Anmutung, fand sie aber schwer lesbar —
   --color-accent-ink (siehe Tokens) statt --color-accent behält den
   goldenen Charakter bei deutlich besserer Lesbarkeit. Großzügige
   Zeilenhöhe + Absatzabstände für mehrere <p>. */
.product-teaser {
  max-width: 62ch; font-family: var(--font-display); font-size: 1.05rem;
  line-height: 1.6; color: var(--color-accent-ink);
}
.product-teaser p { margin: 0 0 var(--space-2); }
.product-teaser p:last-child { margin-bottom: 0; }

/* ---------- Kaufkasten (buy-box) ---------- */
/* Karte in derselben Sprache wie .side-card. align-self:start hält sie
   oben bündig zum Bild, statt mit der oft längeren Textspalte mitzuwachsen. */
.buy-box {
  grid-area: buy;
  background: var(--color-surface); border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-card);
  padding: var(--space-4); align-self: start;
}
.buy-box > p { margin: 0 0 var(--space-2); }
.buy-box > p:last-of-type { margin-bottom: 0; }
@media (min-width: 75rem) {
  /* erst ab der Drei-Spalten-Stufe klebend — bei 48–75rem liegt die Box
     volle Breite unter Bild+Text, Sticky würde dort nur seltsam wirken.
     Kein Platz für die .main-nav reservieren: die ist zwar sticky, wird
     aber von ihrem <header> begrenzt und scrollt mit ihm weg (im Browser
     geprüft). Der Klebe-Weg endet ohnehin mit der Grid-Zeile von
     Abschnitt 1 — er lohnt sich vor allem bei langem Kurztext. */
  .buy-box { position: sticky; top: var(--space-3); }
}
.product-sku { color: var(--color-text-muted); font-size: 0.85rem; margin: 0 0 var(--space-2); }
.product-price-amount { font-size: 2rem; font-weight: 700; color: var(--color-accent); font-family: var(--font-display); }
.product-price-tax { color: var(--color-text-muted); margin-left: var(--space-1); }
/* Im Kaufkasten auf eigene Zeile: inline hinter dem Betrag riss der Umbruch
   den Pflichthinweis mitten im Satz auseinander ("… zzgl. / Versandkosten").
   Nur hier — auf Karten und in den Belegen bleibt der Hinweis inline. */
.buy-box .product-price-tax { display: block; margin-left: 0; font-size: 0.9rem; }
.product-shipping-estimate { display: block; color: var(--color-text-muted); font-size: 0.9rem; }
/* Symbolzeilen im Kaufkasten: feste Symbolspalte, damit Status und
   Lieferzeit auf derselben Textkante beginnen — auch wenn ein Status
   (z.B. "nicht auf Lager") bewusst kein Symbol trägt. Dafür steht dort ein
   leerer Platzhalter statt gar nichts, sonst rutschte der Text in die
   Symbolspalte. */
.product-availability, .product-shipping-estimate {
  display: grid; grid-template-columns: 1.4em 1fr; align-items: center; gap: 0.5em;
}
.product-availability .icon, .product-shipping-estimate .icon {
  height: 1.2em; width: calc(1.2em * var(--icon-ratio, 1));
  vertical-align: 0; justify-self: center;
}
.status-icon-slot { display: block; }
.product-availability-in_stock { color: var(--color-ok); font-weight: 700; }
/* "low": kaufbar, aber knapp – Warnfarbe als sanfter Dringlichkeits-Hinweis */
.product-availability-low { color: var(--color-warn); font-weight: 700; }
.product-availability-out_of_stock, .product-availability-reprint { color: var(--color-warn); font-weight: 700; }
/* archiviert: bewusst gedämpft statt Warnfarbe – kein Handlungsbedarf, nur Historie */
.product-availability-archived { color: var(--color-text-muted); font-weight: 700; }

/* Mengen-Stepper: Rahmen sitzt auf dem Container, nicht auf den Kindern —
   Buttons + Feld wirken als eine Einheit statt drei einzelner Kästen. */
.qty-stepper {
  /* align-self, weil das Formular eine Flex-Spalte ist: ohne das streckt
     sich der Stepper auf Kastenbreite und lässt rechts eine leere Fläche */
  display: inline-flex; align-items: stretch; align-self: start;
  border: 1px solid var(--color-border); border-radius: var(--radius);
  overflow: hidden; /* ein gemeinsamer Rahmen, kein Doppelrand an den Nähten */
}
.qty-btn {
  display: flex; align-items: center; justify-content: center;
  width: 2.6rem; height: 2.6rem; flex: none;
  border: none; background: var(--color-surface-alt); color: var(--color-text);
  font-size: 1.1rem; line-height: 1; cursor: pointer;
  /* Hover-/Fokus-Feedback auf einem reinen +/- Klickziel */
  transition: background-color 0.15s ease, color 0.15s ease;
}
.qty-btn:hover { background: var(--color-gold); color: var(--color-gold-contrast); }
.qty-btn:focus-visible { position: relative; z-index: 1; outline: 2px solid var(--color-gold-strong); outline-offset: -2px; }
.qty-input {
  width: 3.5rem; height: 2.6rem; text-align: center;
  border: none; border-left: 1px solid var(--color-border); border-right: 1px solid var(--color-border);
  background: var(--color-bg); color: var(--color-text); font: inherit;
  /* native Spinner aus — die ±-Buttons übernehmen die Funktion; das Feld
     selbst bleibt voll bedienbar (Tippen, Fokus, Validierung). */
  appearance: textfield; -moz-appearance: textfield;
}
.qty-input::-webkit-outer-spin-button, .qty-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* Add-to-cart: Stepper über dem Button, die primäre Handlung des Kaufkastens. */
.add-to-cart-form { display: flex; flex-direction: column; gap: var(--space-3); margin: var(--space-3) 0; }
.add-to-cart-btn { width: 100%; font-size: 1.1rem; padding: 0.9em 1.6em; }

/* Vertrauens-Liste im Kaufkasten: kleinere Icons als in der Trust-Bar
   (dort 2.6em, hier 1.9em — der Kasten ist deutlich schmaler). Feste
   Icon-Spalte wie bei .side-card-icon: Breite folgt --icon-ratio, sonst
   verzerren die Glyphen. */
.buy-box-trust {
  list-style: none; margin: var(--space-4) 0 0; padding: var(--space-3) 0 0;
  border-top: 1px solid var(--color-border-soft); /* setzt die Liste vom Button ab */
  display: flex; flex-direction: column; gap: var(--space-1);
}
.buy-box-trust-item {
  display: grid; grid-template-columns: 3.2rem 1fr; column-gap: var(--space-2);
  align-items: center; padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-border-soft);
}
.buy-box-trust-item:last-child { border-bottom: none; padding-bottom: 0; }
/* Freigrenze als zweite Zeile am Versand-Punkt: kräftiger als der graue
   Untertitel darüber, weil sie ein Kaufargument ist und kein Beiwerk. */
.trust-item-highlight { color: var(--color-accent-ink); font-weight: 700; }
.buy-box-trust-item .icon {
  height: 1.9em; width: calc(1.9em * var(--icon-ratio, 1));
  color: var(--color-gold); justify-self: center;
}
.buy-box-trust-item .trust-item-text strong { font-size: 0.95rem; }
.buy-box-trust-item .trust-item-text small { font-size: 0.8rem; }

/* ---------- Product about (Beschreibung + Details als eigene Karte) ---------- */
.product-about {
  background: var(--color-surface); border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-card);
  padding: var(--space-4); margin-top: var(--space-5);
  display: grid; gap: var(--space-4); grid-template-columns: 1fr;
}
@media (min-width: 64rem) {
  /* Beschreibung nimmt zwei Drittel, Details ein Drittel; feine
     Trennlinie statt zusätzlichem Kartenrand.
     Das Verhältnis läuft bewusst über fr: feste Spaltenbreiten kippten es
     zwischen 1024 und 1400px, weil beide Spuren gleichmäßig schrumpfen und
     die Detailspalte dann breiter wurde als der Text. Die Breite der Karte
     regelt NICHT diese Sektion, sondern zentral .site-main — sonst stehen
     die drei Abschnitte der Seite auf verschiedenen Kanten. */
  .product-about {
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    gap: var(--space-5);
  }
  .product-about-side {
    padding-left: var(--space-5);
    border-left: 1px solid var(--color-border-soft);
  }
}
.product-about-main, .product-about-side { min-width: 0; }
/* Bewusst leise: die Überschriften sind hier reine Wegweiser ("Beschreibung",
   "Details") und sollen dem Inhalt nicht die Aufmerksamkeit nehmen — kleine
   Versalien statt Display-Headline, ohne das Gold-Ornament der übrigen
   Sektionstitel. */
.product-about .section-title, .flipbook .section-title {
  font-size: 0.85rem; font-family: var(--font-body);
  text-transform: uppercase; letter-spacing: 0.09em; font-weight: 700;
  color: var(--color-text-muted);
  margin: 0 0 var(--space-3); padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--color-border-soft);
}
.product-about .section-title::after, .flipbook .section-title::after { content: none; }
/* .flipbook ist eine zentrierte Flex-Spalte: die Überschrift soll trotzdem
   wie in Abschnitt 2 auf der linken Kante stehen und die volle Breite der
   Trennlinie tragen. */
.flipbook .section-title { align-self: stretch; text-align: left; }
/* Keine eigene Breitengrenze: der Langtext soll seine zwei Drittel der
   Sektion ausfüllen (so ist der Entwurf gedacht) — die Lesebreite begrenzt
   die Karte selbst über ihre max-width, siehe .product-about. */
.product-description { line-height: 1.65; }
.product-description p { margin: 0 0 var(--space-3); }
.product-description p:last-child { margin-bottom: 0; }
/* Autoren-Biografie unter dem Langtext: feine obere Linie setzt sie ab,
   Überschrift kleiner als .section-title */
.field-value-content {
  margin-top: var(--space-4); padding-top: var(--space-4);
  border-top: 1px solid var(--color-border-soft);
}
.field-value-content h3 { font-size: 1.05rem; margin: 0 0 var(--space-2); }

.product-specs { border-collapse: collapse; margin: var(--space-3) 0; width: 100%; max-width: 40rem; }
.product-specs th, .product-specs td { text-align: left; padding: var(--space-2) var(--space-3) var(--space-2) 0; border-bottom: 1px solid var(--color-border-soft); vertical-align: top; }
.product-specs th { color: var(--color-text-muted); font-weight: 400; white-space: nowrap; }
/* schmale Seitenspalte: volle Breite nutzen, feste Label/Wert-Aufteilung
   statt inhaltsgetriebenem Auto-Layout — verhindert, dass ein langer
   Label-Text (nowrap oben) die Spalte sprengt; Werte dürfen umbrechen. */
.product-about-side .product-specs { max-width: none; table-layout: fixed; }
.product-about-side .product-specs th { white-space: normal; width: 42%; }
.product-about-side .product-specs td { width: 58%; }
.badge {
  display: inline-block; background: var(--color-surface-alt); border: 1px solid var(--color-border);
  border-radius: var(--radius-pill); padding: 0.1em 0.9em; margin: 0 var(--space-1) var(--space-1) 0;
  color: var(--color-text); font-size: 0.9rem;
}
.badge:hover { border-color: var(--color-gold); color: var(--color-accent); text-decoration: none; }
.product-gpsr-title { font-size: 0.95rem; margin: var(--space-3) 0 var(--space-1); }
.product-gpsr-body { white-space: pre-line; color: var(--color-text-muted); font-size: 0.9rem; }

/* External video trailer (GDPR two-click embed). Deliberately small/quiet:
   fixed 16:9 box, muted scrim, one small circular play glyph -- no hero
   treatment, no bright colours. The <img>/empty span is always a locally
   served asset (see internal/video, internal/web/video.go); nothing here
   ever requests img.youtube.com/i.ytimg.com. */
.product-trailer { margin: var(--space-3) 0; }
.product-trailer-title { font-size: 0.95rem; margin: 0 0 var(--space-1); }
.product-trailer-frame {
  position: relative; width: 100%; max-width: 22rem; aspect-ratio: 16 / 9;
  border-radius: var(--radius); overflow: hidden;
  background: var(--color-border); box-shadow: var(--shadow-card);
}
.product-trailer-thumb { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.product-trailer-thumb-empty { background: linear-gradient(135deg, var(--color-border), var(--color-surface)); }
.product-trailer-play {
  position: absolute; inset: 0; width: 100%; height: 100%;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-1);
  border: none; margin: 0; cursor: pointer; font: inherit; font-size: 0.8rem; font-weight: 600;
  background: rgb(0 0 0 / 0%); color: #fff; text-shadow: 0 1px 3px rgb(0 0 0 / 65%);
  transition: background-color 0.15s ease;
}
.product-trailer-play:hover, .product-trailer-play:focus-visible { background: rgb(0 0 0 / 18%); }
.product-trailer-play-icon {
  width: 2.75rem; height: 2.75rem; border-radius: 50%;
  background: rgb(0 0 0 / 45%); border: 1.5px solid rgb(255 255 255 / 85%);
  display: flex; align-items: center; justify-content: center;
}
.product-trailer-play-icon::before {
  content: ""; margin-left: 0.15em;
  border-style: solid; border-width: 0.5em 0 0.5em 0.85em;
  border-color: transparent transparent transparent #fff;
}
.product-trailer-note { margin: var(--space-1) 0 0; max-width: 32rem; color: var(--color-text-muted); font-size: 0.8rem; }
.product-trailer-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.product-thumbs { display: flex; gap: var(--space-2); margin-top: var(--space-3); flex-wrap: wrap; }
.product-thumb { padding: 0; border: 2px solid var(--color-border); border-radius: var(--radius); background: var(--color-surface-alt); cursor: pointer; width: 4.5rem; transition: border-color 0.15s ease; }
.product-thumb img { display: block; width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: calc(var(--radius) - 2px); }
.product-thumb picture { display: block; }
.product-thumb.is-current, .product-thumb:hover { border-color: var(--color-gold); }
.product-file a { font-weight: 700; }

/* --- Leseprobe-Flipbook (pdf.js + StPageFlip, flipbook.js) ------------- */
/* das hidden-Attribut gegen das display:flex unten durchsetzen: erledigt
   jetzt die globale [hidden]-Regel oben bei den Basis-Elementen */
.flipbook {
  display: flex; flex-direction: column; align-items: center;
  margin-top: var(--space-6); padding: var(--space-4);
  background: var(--color-surface); border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-card);
}
/* Vollbild: gleiche Papier-Fläche statt Browser-Schwarz, Inhalt zentriert */
.flipbook.is-fullscreen { justify-content: center; background: var(--color-bg); border-radius: 0; }
.flipbook-status { font-style: italic; color: var(--color-text-muted); }
.flipbook-status-error { font-style: normal; color: var(--color-warn); }
.flipbook-stage {
  position: relative;
  /* Die sich wölbende Seite ragt beim Umblätter über die ruhende
     Buchfläche hinaus — plain hidden schnitt sie ab, plain visible
     erzeugte Browser-Scrollbalken (Überstand zählt zum Scrollbereich).
     clip + clip-margin: zeichnet den Überstand bis 10rem, trägt aber
     nie zum Scroll-Overflow bei. (Safari ohne clip-margin-Support
     clippt an der Boxkante — alte Optik, keine Scrollbalken.) */
  overflow: clip;
  overflow-clip-margin: 10rem;
  display: flex; justify-content: center; align-items: center;
  background: var(--color-surface);
  box-shadow: 0 20px 50px rgb(43 21 14 / 20%);
}
/* Kein will-change/contain hier: je Seite ein Canvas — Layer-Promotion
   aller Seiten lässt den Compositor auf der RX 480 minutenlang hängen
   (Screenshot-/Frame-Stalls); StPageFlip transformiert ohnehin nur die
   gerade umblätternde Seite. */
.flipbook-page { position: absolute; top: 0; left: 0; background: #fff; backface-visibility: hidden; }
/* Seitenkontur: wird beim Rendern in die Canvas-Pixel gezeichnet
   (flipbook.js) — CSS-Ränder würden von StPageFlips Fold-Clipping
   weggeschnitten und wären beim Umblättern unsichtbar. */
.flipbook-page canvas { display: block; width: 100%; height: 100%; }
/* Falz-Schatten: signalisiert die Buchmitte, macht den Seitenübergang plastisch */
.flipbook-page::after { content: ""; position: absolute; top: 0; width: 50px; height: 100%; pointer-events: none; z-index: 10; }
.flipbook-page:nth-child(odd)::after { right: 0; background: linear-gradient(to left, rgb(0 0 0 / 10%), rgb(0 0 0 / 0%)); }
.flipbook-page:nth-child(even)::after { left: 0; background: linear-gradient(to right, rgb(0 0 0 / 10%), rgb(0 0 0 / 0%)); }
@media (max-width: 50rem) { .flipbook-page::after { display: none; } }
.flipbook-controls {
  display: flex; gap: var(--space-3); align-items: center;
  margin-top: var(--space-4); padding: var(--space-2) var(--space-4);
  background: var(--color-surface-alt); border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
}
.flipbook-info { font-variant-numeric: tabular-nums; color: var(--color-text-muted); }
.flipbook-download { margin-top: var(--space-3); }
:root[data-theme="dark"] .flipbook-stage { box-shadow: 0 20px 50px rgb(0 0 0 / 45%); }

/* ---------- Toast (kurze Statusmeldung, z. B. "Warenkorb aktualisiert") ----------
   Opakes Burgund statt transluzenter Fläche (GPU-Workaround, s. cart-modal) */
.toast {
  position: fixed; left: 50%; bottom: 1.6rem; z-index: 95;
  transform: translate(-50%, 0.6rem);
  background: var(--color-primary-deep); color: var(--color-blush);
  border: 1px solid var(--color-gold); border-radius: var(--radius);
  padding: 0.6em 1.4em; font-weight: 700;
  box-shadow: var(--shadow-panel);
  opacity: 0; pointer-events: none;
  /* kurzes Aufsteigen + Einblenden: bestätigt die Aktion, ohne zu unterbrechen */
  transition: opacity 0.25s ease, transform 0.25s ease;
}
.toast.is-visible { opacity: 1; transform: translate(-50%, 0); }

/* ---------- Warenkorb-Bestätigungs-Modal (CSS-only via :target) ---------- */
.cart-modal {
  display: none; position: fixed; inset: 0; z-index: 90;
  align-items: center; justify-content: center; padding: var(--space-3);
}
.cart-modal:target { display: flex; }
/* unsichtbare Klickfläche: Klick neben die Box schließt (Ziel #main) */
.cart-modal-backdrop { position: absolute; inset: 0; cursor: default; }
.cart-modal-box {
  position: relative; text-align: center;
  background: var(--color-surface); border: 1px solid var(--color-border);
  border-top: 3px solid var(--color-gold);
  border-radius: var(--radius-lg);
  /* Seiten-Dimmer als Riesen-Spread-Shadow statt transluzentem Overlay-
     Hintergrund: einfarbige transluzente Vollflächen-Quads werden vom
     GPU-Compositing (beobachtet auf AMD/Vulkan) verschluckt, Shadows nicht. */
  box-shadow: var(--shadow-panel), 0 0 0 200vmax rgb(20 4 5 / 70%);
  padding: var(--space-5) var(--space-5) var(--space-4);
  max-width: 26rem;
  /* kurzes Aufsteigen: bestätigt die Aktion, einmalig beim Öffnen */
  animation: hero-in 0.3s ease-out both;
}
.cart-modal-check { width: 3em; height: 3em; color: var(--color-ok); margin-bottom: var(--space-2); }
.cart-modal-title { margin: 0 0 var(--space-1); font-size: 1.4rem; }
.cart-modal-text { margin: 0 0 var(--space-4); color: var(--color-text-muted); }
.cart-modal-actions { display: flex; gap: var(--space-2); justify-content: center; flex-wrap: wrap; }
.cart-modal-actions .btn { border: 1px solid var(--color-border); }
.cart-modal-actions .btn-primary { border-color: var(--color-primary); }

/* ---------- Lightbox ---------- */
.lightbox-overlay {
  position: fixed; inset: 0; z-index: 100;
  background: rgb(20 4 5 / 85%);
  display: flex; align-items: center; justify-content: center;
  padding: var(--space-3); cursor: zoom-out;
}
/* gleicher GPU-Workaround wie beim cart-modal: Dimmer als Spread-Shadow */
.lightbox-overlay img { max-width: 95vw; max-height: 95vh; object-fit: contain; box-shadow: 0 0 0 200vmax rgb(20 4 5 / 85%); }
.lightbox-overlay picture { display: contents; }

/* ---------- Tables (cart, orders, review) ---------- */
.table-scroll { overflow-x: auto; }
.cart-table, .order-table, .admin-order-table, .order-items-table, .widerruf-review-table { border-collapse: collapse; width: 100%; }
.cart-table th, .cart-table td, .order-table th, .order-table td,
.admin-order-table th, .admin-order-table td, .order-items-table td,
.widerruf-review-table th, .widerruf-review-table td { text-align: left; padding: var(--space-2); border-bottom: 1px solid var(--color-border-soft); }
.cart-table thead th, .order-table thead th { font-family: var(--font-display); font-weight: 700; color: var(--color-text-muted); border-bottom: 2px solid var(--color-gold); }
.widerruf-review-table th { color: var(--color-text-muted); font-weight: 400; }
.cart-img img { width: 3.2rem; height: 4.2rem; object-fit: contain; border-radius: 4px; background: var(--color-surface-alt); }
.cart-qty-form { display: flex; gap: var(--space-1); }
.cart-qty-input {
  width: 4.2rem; padding: var(--space-1); font: inherit;
  border: 1px solid var(--color-border); border-radius: var(--radius);
  background: var(--color-bg); color: var(--color-text);
}
.cart-total { font-size: 1.25rem; }
.cart-total strong { color: var(--color-accent); font-family: var(--font-display); font-size: 1.35rem; }
/* Checkout-page live total: a small receipt-style card (Warenwert/Versand
   muted, Summe emphasized) instead of one dot-separated sentence -- reuses
   .order-ship-row/.order-total-row from the review/confirmation tables so
   it matches their visual language, just leaner (2 cols, no item rows). */
.checkout-total-table { border-collapse: collapse; width: 100%; }
.checkout-total-table td { padding: var(--space-1) 0; }
.checkout-total-table td:last-child { text-align: right; white-space: nowrap; }
/* Slightly larger than the review/confirmation Summe (1.15rem): this is
   the first total a customer sees, before committing to the order. */
.checkout-grand-total td { font-size: 1.4rem; }
.order-total-row td { font-size: 1.15rem; }
.order-total-row strong { color: var(--color-accent); }
/* MwSt-Vermerk klein direkt hinter dem Summenbetrag, nicht als eigene Zeile */
.order-total-row .product-price-tax { font-size: 0.82rem; font-weight: 400; white-space: nowrap; }

/* Bestell-Tabellen (Review, Bestellbestätigung, Konto-Detail) liegen als
   Karte auf dem Papierhintergrund: die Fläche macht die Zusammengehörigkeit
   sichtbar, die Trennlinien gliedern die Zeilen — statt lose platziertem Text. */
.order-card {
  background: var(--color-surface); border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-card);
  padding: var(--space-2) var(--space-4); margin: 0 0 var(--space-3);
}
.order-card tr:last-child th, .order-card tr:last-child td { border-bottom: none; }
/* Schmale, oben ausgerichtete Label-Spalte: Wert steht sichtbar bei seinem
   Label statt in der Seitenmitte zu schweben. */
.widerruf-review-table th { vertical-align: top; white-space: nowrap; width: 12rem; padding-right: var(--space-4); }
/* Posten-Tabelle als Beleg lesbar: Menge gedämpft, Beträge rechtsbündig
   untereinander, Goldlinie als Summenstrich über der Gesamtsumme. */
.order-items-table td:first-child { color: var(--color-text-muted); white-space: nowrap; width: 3rem; }
.order-items-table td.cart-img + td { color: var(--color-text-muted); white-space: nowrap; width: 3rem; }
.order-items-table td:last-child { text-align: right; white-space: nowrap; }
.order-ship-row td { color: var(--color-text-muted); border-bottom: none; }
.order-ship-subrow td { color: var(--color-text-muted); font-size: 0.85em; border-bottom: none; }
.order-subtotal-row td { border-top: 1px solid var(--color-border-soft); border-bottom: none; padding-top: var(--space-1); }
.order-total-row td { border-top: 2px solid var(--color-gold); }
.order-detail-actions { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; }
.pay-completed { color: var(--color-ok); font-weight: 700; }
.pay-info { background: var(--color-surface); border: 1px solid var(--color-border-soft); border-radius: var(--radius-lg); padding: var(--space-3) var(--space-4); margin: var(--space-3) 0; box-shadow: var(--shadow-card); }
.bank-details { background: var(--color-surface-alt); border: 1px solid var(--color-border); border-radius: var(--radius); padding: var(--space-3); overflow-x: auto; }
.pay-ref { font-weight: 700; color: var(--color-primary); margin: var(--space-2) 0 0; }
.pay-ref-code { font-size: 1.5em; letter-spacing: 0.06em; }
.order-number-line strong { font-family: var(--font-display); font-size: 1.2em; }

/* ---------- Forms (auth, account, checkout, widerruf, deep search) ---------- */
.auth-columns { display: grid; gap: var(--space-4); grid-template-columns: 1fr; max-width: 64rem; }
@media (min-width: 48rem) { .auth-columns { grid-template-columns: 1fr 1fr; } }
.auth-box, .account-section, .landing-intro {
  background: var(--color-surface); border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg); padding: var(--space-4);
  margin-bottom: var(--space-3); box-shadow: var(--shadow-card);
}
.auth-box h1, .auth-box h2, .account-section h2 { margin-top: 0; }
.auth-form { display: flex; flex-direction: column; gap: var(--space-3); max-width: 36rem; }
.auth-form label { display: flex; flex-direction: column; gap: var(--space-1); font-weight: 700; font-size: 0.92rem; }
.auth-form input, .auth-form textarea, .auth-form select,
.checkout-fieldset input:not([type="checkbox"]):not([type="radio"]), .checkout-fieldset select, .checkout-fieldset textarea,
.deep-search-field input, .deep-search-field select {
  padding: 0.55em 0.9em; border: 1px solid var(--color-border);
  border-radius: var(--radius); background: var(--color-bg); color: var(--color-text);
  font: inherit; font-weight: 400;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.auth-form input:focus, .auth-form textarea:focus, .auth-form select:focus,
.checkout-fieldset input:focus, .checkout-fieldset select:focus, .checkout-fieldset textarea:focus,
.deep-search-field input:focus, .deep-search-field select:focus {
  outline: none; border-color: var(--color-gold); box-shadow: 0 0 0 1px var(--color-gold);
}
/* Note textarea (checkout.note): resize vertically only, matches the other
   checkout-fieldset inputs above, just taller and elastic. */
.checkout-fieldset textarea { resize: vertical; min-height: 4.5em; }
.form-error { color: var(--color-warn); font-weight: 700; }
.form-note { color: var(--color-ok); font-weight: 700; }
.form-hint { color: var(--color-text-muted); font-size: 0.9rem; }
/* Newsletter switched on but not yet confirmed: a customer who assumes the
   switch alone subscribed them would never click the mailbox link -- same
   "impossible to miss" bordered-card treatment as .checkout-pickup-only-notice
   rather than a plain .form-hint, since the cost of missing it (silently
   never subscribed) is the same shape of problem. */
.account-newsletter-pending {
  background: color-mix(in srgb, var(--color-warn) 12%, var(--color-surface));
  border: 1px solid var(--color-warn); border-radius: var(--radius);
  color: var(--color-warn); font-weight: 700;
  padding: var(--space-2) var(--space-3); margin: var(--space-2) 0 0;
}
/* Off-screen, not display:none/visibility:hidden -- some password managers
   skip fields hidden that way. Kept in normal layout flow (and so in the
   accessibility tree), just positioned outside the viewport. Used for the
   hidden username field on account_password.html. */
.visually-hidden-field { position: absolute; left: -9999px; }
/* Change-password field + its show/hide toggle (site.js: type-to-reveal
   with a debounced re-mask, or pin visible/masked via the button). */
.pw-field { display: flex; gap: var(--space-2); align-items: stretch; }
.pw-field input { flex: 1; }
.pw-toggle {
  flex: none; padding: 0 var(--space-2); border: 1px solid var(--color-border);
  border-radius: var(--radius); background: var(--color-surface); color: var(--color-text);
  cursor: pointer; font-size: 1rem; line-height: 1;
}
.pw-toggle:hover { background: var(--color-border); }
.pw-toggle:focus-visible { outline: 2px solid var(--color-gold); outline-offset: 2px; }
/* Weak-password advisory interstitial (login_weak.html) */
.login-weak-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; margin: var(--space-3) 0; }
.login-weak-details { margin-top: var(--space-3); color: var(--color-text-muted); }
.login-weak-details summary { color: var(--color-text); cursor: pointer; font-weight: 700; }
.login-weak-details ul { margin: var(--space-2) 0 0; padding-left: var(--space-4); display: flex; flex-direction: column; gap: var(--space-1); }

/* ---------- Account page: sidebar tabs ---------- */
/* CSS-only tab switch (radio inputs + labels, same :checked idiom as
   .checkout-billing above) so the account page's sections show one at a
   time behind a table-of-contents sidebar instead of stacking end to end.
   No JS: the hidden radio group is the source of truth for which
   .account-panel is visible, addressed purely through sibling combinators
   -- inputs, nav and panels are siblings in that order, so each rule below
   only has to reach sideways, never up. Radios sit off-screen rather than
   display:none (same technique as .visually-hidden-field) so they stay in
   the tab order for keyboard users. */
.account-email { margin-top: calc(-1 * var(--space-2)); color: var(--color-text-muted); }
/* Account header: who you are on the left, the way out on the right -- the
   ordinary page-header shape, so the control reads as part of the heading
   rather than as something dropped into the whitespace above the tabs.
   It deliberately does NOT go into .account-nav: that strip is a swipeable
   tab bar on phones, and the one control a touch visitor cannot do without
   must not sit behind a swipe.
   The button repeats the header menu's card treatment (surface, hairline,
   gold on hover) because it is the same action in the same theme, and
   because a bare .btn reads as a text label against this cream page. */
.account-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  flex-wrap: wrap; gap: var(--space-2) var(--space-4);
  margin-bottom: var(--space-4);
}
.account-head-id { min-width: 0; }
.account-head .section-title { margin-bottom: var(--space-2); }
.account-head .account-email { margin: 0; overflow-wrap: break-word; }
.account-signout {
  font: inherit; font-size: 0.85rem; font-weight: 700; cursor: pointer;
  white-space: nowrap;
  background: var(--color-surface); color: var(--color-text);
  border: 1px solid var(--color-border); border-radius: var(--radius);
  padding: var(--space-2) var(--space-3); box-shadow: var(--shadow-card);
  transition: border-color 0.2s ease, color 0.2s ease;
}
.account-signout:hover { border-color: var(--color-gold); color: var(--color-accent); }
.account-tab-input { position: absolute; left: -9999px; }
.account-shell {
  display: grid;
  grid-template-areas: "nav" "content";
  grid-template-columns: 1fr;
  gap: var(--space-3);
  align-items: start;
}
.account-nav {
  grid-area: nav;
  display: flex; gap: var(--space-2);
  /* Phones: swipe the tab bar instead of collapsing it -- same escape
     hatch as .main-nav-inner, no JS needed either place. */
  overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none;
  padding-bottom: 2px; /* keeps the focus outline below from clipping */
}
.account-nav::-webkit-scrollbar { display: none; }
.account-nav-link {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius);
  border: 1px solid var(--color-border-soft);
  background: var(--color-surface);
  color: var(--color-text); font-weight: 700; font-size: 0.92rem;
  white-space: nowrap; cursor: pointer;
  box-shadow: var(--shadow-card);
  transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
.account-nav-link .icon { width: 1.4em; height: 1.4em; color: var(--color-primary); flex: none; }
.account-nav-link:hover { background: var(--color-surface-alt); }
#account-tab-profile:checked ~ .account-nav .account-nav-link[for="account-tab-profile"],
#account-tab-orders:checked ~ .account-nav .account-nav-link[for="account-tab-orders"],
#account-tab-newsletter:checked ~ .account-nav .account-nav-link[for="account-tab-newsletter"],
#account-tab-manage:checked ~ .account-nav .account-nav-link[for="account-tab-manage"] {
  background: var(--color-primary); border-color: var(--color-primary); color: var(--color-primary-contrast);
}
#account-tab-profile:checked ~ .account-nav .account-nav-link[for="account-tab-profile"] .icon,
#account-tab-orders:checked ~ .account-nav .account-nav-link[for="account-tab-orders"] .icon,
#account-tab-newsletter:checked ~ .account-nav .account-nav-link[for="account-tab-newsletter"] .icon,
#account-tab-manage:checked ~ .account-nav .account-nav-link[for="account-tab-manage"] .icon {
  color: inherit;
}
#account-tab-profile:focus-visible ~ .account-nav .account-nav-link[for="account-tab-profile"],
#account-tab-orders:focus-visible ~ .account-nav .account-nav-link[for="account-tab-orders"],
#account-tab-newsletter:focus-visible ~ .account-nav .account-nav-link[for="account-tab-newsletter"],
#account-tab-manage:focus-visible ~ .account-nav .account-nav-link[for="account-tab-manage"] {
  outline: 2px solid var(--color-gold); outline-offset: 2px;
}
.account-panel { grid-area: content; display: none; min-width: 0; }
#account-tab-profile:checked ~ #account-panel-profile,
#account-tab-orders:checked ~ #account-panel-orders,
#account-tab-newsletter:checked ~ #account-panel-newsletter,
#account-tab-manage:checked ~ #account-panel-manage {
  display: block;
}
@media (min-width: 48rem) {
  .account-shell { grid-template-areas: "nav content"; grid-template-columns: minmax(11rem, 14rem) 1fr; gap: var(--space-4); }
  .account-nav { flex-direction: column; overflow: visible; position: sticky; top: var(--space-3); }
  .account-nav-link { white-space: normal; }
}

.checkout-form { max-width: 46rem; }
.checkout-fieldset {
  background: var(--color-surface);
  border: 1px solid var(--color-border-soft); border-radius: var(--radius-lg);
  padding: var(--space-4); margin: 0 0 var(--space-3);
  display: flex; flex-direction: column; gap: var(--space-3);
  box-shadow: var(--shadow-card);
}
.checkout-fieldset legend {
  font-family: var(--font-display); font-weight: 700; font-size: 1.15rem;
  padding: 0 var(--space-2); color: var(--color-primary);
  background: var(--color-surface); border-radius: var(--radius); /* masks the fieldset border line */
}
:root[data-theme="dark"] .checkout-fieldset legend { color: var(--color-accent); }
/* Optional marketing opt-in among fieldsets the customer must fill in: one
   tone flatter (surface-alt, no card shadow, muted legend) so it reads as
   a deliberate but secondary step, never competing with address/shipping/
   payment -- while keeping every other .checkout-fieldset affordance. */
.checkout-newsletter { background: var(--color-surface-alt); box-shadow: none; }
.checkout-newsletter legend, :root[data-theme="dark"] .checkout-newsletter legend { color: var(--color-text-muted); }
.checkout-fieldset label { display: flex; flex-direction: column; gap: var(--space-1); font-weight: 700; font-size: 0.92rem; }
.checkout-check { flex-direction: row !important; align-items: center; gap: var(--space-2) !important; font-weight: 400 !important; }
.checkout-check input { accent-color: var(--color-primary); width: 1.1em; height: 1.1em; }
/* Free-text "other country" field: sits directly under the region select as
   an escape hatch, not a fieldset of its own -- pulled up to read as part
   of the same choice. CSS-only reveal (same :has() technique as
   .checkout-billing's billing-fields), gated on the region <select>'s
   "OTHER" sentinel option being selected -- this is what makes the field
   invisible (and so, in practice, unreachable by mobile autofill) whenever
   a real region is chosen, which is the actual fix for the 2026-08-18
   autofill incident; site.js's job is only to mirror the same gate for the
   pickup-only notice/toggle. Scoped with the .checkout-fieldset ancestor
   class, not the bare class alone, so the display:none reliably beats the
   .checkout-fieldset label rule above (same specificity trap the billing
   toggle above does not hit, since it targets a div, not a label). */
.checkout-fieldset .checkout-country-other { display: none; margin-top: calc(-1 * var(--space-2)); }
[data-delivery-fieldset]:has(select[name="region"] option[value="OTHER"]:checked) .checkout-country-other { display: flex; }
/* Pickup-only advisory: must be impossible to miss (a customer from a
   non-shippable country must never reach the review step believing
   shipping applies) -- a bordered warning card, not just bold text. */
.checkout-pickup-only-notice {
  background: color-mix(in srgb, var(--color-warn) 12%, var(--color-surface));
  border: 1px solid var(--color-warn); border-radius: var(--radius);
  color: var(--color-warn); font-weight: 700;
  padding: var(--space-2) var(--space-3); margin: 0;
}
.checkout-delivery-methods { display: flex; flex-direction: column; gap: var(--space-2); }
.checkout-ship-options { display: flex; flex-direction: column; gap: var(--space-3); }
.checkout-ship-options[hidden] { display: none; }
/* Toggle switch: CSS-only, checkbox-under-the-hood so it stays a real form
   control. Used where a boolean setting reads better as a switch than a
   checkbox (e.g. ship_tax_dynamic) -- see admin_settings.html. */
.switch { position: relative; display: inline-flex; flex: none; width: 2.75em; height: 1.5em; }
.switch input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
.switch-track { position: absolute; inset: 0; background: var(--color-border-soft); border: 1px solid var(--color-text-muted); border-radius: 999px; transition: background 0.15s ease, border-color 0.15s ease; pointer-events: none; }
.switch-track::before { content: ""; position: absolute; top: 0.15em; left: 0.15em; width: 1.2em; height: 1.2em; background: var(--color-surface); border-radius: 50%; box-shadow: 0 1px 2px rgb(0 0 0 / 25%); transition: transform 0.15s ease; }
.switch input:checked + .switch-track { background: var(--color-primary); border-color: var(--color-primary); }
.switch input:checked + .switch-track::before { transform: translateX(1.25em); }
.switch input:focus-visible + .switch-track { outline: 2px solid var(--color-gold); outline-offset: 2px; }
/* Admin settings: local image-conversion capability panel (2026-07-27).
   Read-only diagnostics, not a form -- deliberately understated except in
   the "nothing works, images silently degrade" case, which must stand out. */
.admin-image-diag { background: var(--color-bg); }
.admin-image-diag-warn { border-color: var(--color-warn); box-shadow: 0 0 0 1px var(--color-warn); }
.admin-image-diag-verdict { font-weight: 600; }
.admin-image-diag-warn .admin-image-diag-verdict { color: var(--color-warn); }
.admin-image-diag-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.admin-image-diag-list code { font-size: 0.85rem; color: var(--color-text-muted); }
.admin-image-diag-keydots { display: inline-flex; gap: var(--space-1); margin-left: var(--space-1); }
.admin-image-diag-keydot { display: inline-block; padding: 0 var(--space-1); border-radius: 999px; border: 1px solid var(--color-ok); color: var(--color-ok); font-size: 0.75rem; }
.admin-image-diag-keydot-exhausted { border-color: var(--color-warn); color: var(--color-warn); }
/* On the review page the consent sits outside a fieldset: give it the same
   flex-row look and its own line directly above the order button. */
.checkout-review-form .checkout-legal { display: flex; margin: var(--space-3) 0; }
.checkout-login-offer { color: var(--color-text-muted); }
.deep-search-form { background: var(--color-surface); border: 1px solid var(--color-border-soft); border-radius: var(--radius-lg); padding: var(--space-4); box-shadow: var(--shadow-card); }
.deep-search-grid { display: grid; gap: var(--space-3); grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); margin-bottom: var(--space-3); }
.deep-search-field { display: flex; flex-direction: column; gap: var(--space-1); font-weight: 700; font-size: 0.92rem; }
.search-deep-link { margin-top: calc(-1 * var(--space-2)); color: var(--color-text-muted); }
.hp-field { position: absolute; left: -9999px; }

/* ---------- 404 ---------- */
.notfound { text-align: center; padding: var(--space-6) var(--space-3); max-width: 34rem; margin: 0 auto; }
.notfound-code {
  font-family: var(--font-display); font-size: clamp(5rem, 10vw, 9rem);
  line-height: 1; margin: 0; color: var(--color-gold); opacity: 0.55;
}
.notfound-title { margin: var(--space-2) 0; font-size: clamp(1.5rem, 2.4vw, 2.2rem); }
.notfound-text { color: var(--color-text-muted); margin: 0 0 var(--space-4); }

/* ---------- Legal / blog / text pages ---------- */
/* Both legal text and blog posts run full-width now (operator's explicit
   call, reversing an earlier "keep blog posts at a narrower reading
   measure" decision): the only bound is .site-main (--max-width). Image
   overflow is handled independently below (.blog-body img), not by
   constraining the article width, so this is safe on its own. */
.legal-page { max-width: none; }
.blog-post { max-width: none; }
/* body_de/body_en is raw admin-authored HTML (WordPress-era content in
   several posts, e.g. the "Vaterherz Gottes" conference post) that can
   embed <img> tags at their native pixel size (seen up to 1600px wide) --
   without this, one such post's images render at raw pixel size instead
   of scaling down to the article width. Standard responsive-image
   constraint, scoped to post body content only. */
.blog-body img { max-width: 100%; height: auto; display: block; margin: var(--space-3) auto; border-radius: var(--radius); }
.blog-body iframe, .blog-body video, .blog-body embed { max-width: 100%; }
/* max-width: 100% alone isn't enough here, unlike .blog-body above: a
   thumbnail-less .blog-row-full sits in a single-column row with no
   narrower sibling column to bound it, so "100%" resolves against the
   *entire* list width (~1000px) instead of a reading column -- an image
   embedded at its raw size (e.g. the "Vaterherz Gottes" post's four
   ~1600px photos) still rendered page-wide on the list page even though
   the identical markup is correctly contained on the post's own page
   (which does have a real column: .blog-post { max-width: 46rem }).
   Give it the same kind of cap directly, centered, rather than relying on
   an ambient container width that doesn't exist here. */
.blog-row-full img { max-width: min(100%, 28rem); height: auto; display: block; margin: var(--space-2) auto; }
/* Legacy-Archiv-Fragmente kommen roh aus einem WooCommerce-Export und sind
   nicht auf dieses Theme abgestimmt -- eigenes overflow-x statt die ganze
   Seite waagerecht scrollen zu lassen, wenn dort eine breite Tabelle
   auftaucht (siehe LEGACY_ARCHIVE.md §3: es kommt nur rohes HTML, keine
   Klassen). Regeln unterhalb geben unstyled table/th/td eine lesbare
   Grundoptik, ohne die Fragmentmarkup selbst anzufassen. */
.account-legacy-archive { overflow-x: auto; }
.account-legacy-archive table { border-collapse: collapse; width: 100%; min-width: 28rem; }
.account-legacy-archive th, .account-legacy-archive td { text-align: left; padding: var(--space-2); border-bottom: 1px solid var(--color-border-soft); }
.account-legacy-archive th { font-family: var(--font-display); font-weight: 700; color: var(--color-text-muted); border-bottom: 2px solid var(--color-gold); }
.account-legacy-archive p { margin: 0 0 var(--space-2); }
.legal-page h1, .blog-post h1 { font-size: clamp(1.6rem, 2.4vw, 2.4rem); }
.legal-page h2 { font-size: 1.25rem; margin-top: var(--space-4); }
.blog-date { color: var(--color-text-muted); font-size: 0.9rem; white-space: nowrap; }
/* ---------- Blog list (Aktuelles) ---------- */
/* One post per row, stacked vertically (newest first -- the data is already
   ORDER BY created_at DESC, see internal/web/blog.go; this is a pure visual
   restyle on top of that), each row spanning the full content width of
   .site-main -- replaces an earlier multi-column card grid (.blog-grid/
   .blog-card) the Betreiber looked at and asked to change: not a catalog-
   style grid, one story at a time, same as a homepage/product-page reader
   would expect. Visual language is deliberately borrowed, not invented:
   the row card uses the same --color-surface/--color-border-soft/
   --radius-lg/--shadow-card recipe as .product-detail-media and
   .product-about, and the thumb-beside-text split at 40rem mirrors
   .product-detail's media|info split (image first in source order, so it
   still leads on mobile once the columns collapse). */
.blog-list {
  display: flex; flex-direction: column; gap: var(--space-4);
  list-style: none; padding: 0; margin: 0;
}
.blog-row {
  /* minmax(0, 1fr), not plain 1fr: a grid track's default min size is
     "auto", which lets its content's intrinsic width (e.g. a full-size
     <img> in a "full body" row, see .blog-row-full below) force the track
     wider instead of shrinking to the row -- max-width:100% on the image
     then just means 100% of that already-oversized track and does nothing.
     Reported live: the "Vaterherz Gottes Konferenz" post, which has 4
     ~1600px-wide inline images and renders in full on the list page
     (IsFull), blew out past the row/page width on this page even though
     the same images are correctly contained on the post's own page --
     the difference is exactly this grid track not being told it may
     shrink below its content's natural size. */
  display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-3);
  background: var(--color-surface); border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg); overflow: hidden;
  box-shadow: var(--shadow-card);
  transition: box-shadow 0.2s ease, border-color 0.2s ease;
}
.blog-row:hover { border-color: var(--color-gold); box-shadow: var(--shadow-card-hover); }
/* Only posts with a thumbnail (and not already rendering their full body,
   see the template) get the two-column split -- a thumbnail-less row stays
   single-column and its text fills the whole row instead of being left
   narrow next to an empty image slot. */
@media (min-width: 40rem) {
  .blog-row-with-thumb { grid-template-columns: minmax(14rem, 26rem) minmax(0, 1fr); }
}
.blog-row-thumb {
  display: block; background: var(--color-surface-alt); overflow: hidden;
  aspect-ratio: 4 / 3; min-height: 12rem; padding: var(--space-3);
}
@media (min-width: 40rem) {
  /* align-self: start, not the grid default of stretch -- a stretched item
     with both a width (from the grid column) and an aspect-ratio still
     resolves its height from the stretch, not the ratio, once the row is
     taller than the ratio would give (e.g. a full-body row, or simply a
     portrait-shaped source thumbnail): the image loses its crop and grows
     to whatever height the text column happens to be. Pinning the ratio
     via align-self keeps every thumbnail a real, consistently sized crop
     regardless of how tall its row's text turns out to be. */
  .blog-row-with-thumb .blog-row-thumb { align-self: start; }
}
.blog-row-thumb img {
  width: 100%; height: 100%; object-fit: contain; display: block;
  /* Thumbnails span both landscape photos and portrait book/product covers
     (most of the actual content -- see main.css comment history) -- cover
     center-crops a tall portrait cover down to a sliver (reported live:
     a portrait photo thumbnail showed only a torso). contain always shows
     the whole image, letterboxed on --color-surface-alt when its ratio
     doesn't match the box, same non-destructive approach as
     .product-card-img img above. */
}
/* min-width: 0 -- same reasoning as the grid track above, one level down:
   a flex item's default min-width is "auto" (its content's intrinsic
   size), so without this a wide inline image still refuses to shrink
   even once the grid track itself can. */
.blog-row-body { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-4); min-width: 0; }
.blog-row-title { font-family: var(--font-display); font-size: clamp(1.3rem, 1.9vw, 1.7rem); line-height: 1.25; }
.blog-row-title a { color: var(--color-text); text-decoration: none; }
.blog-row-title a:hover { color: var(--color-primary); text-decoration: underline; }
/* No ch cap on the excerpt/body text -- same choice as .product-description
   (see above: "Kein eigene Breitengrenze"), so the text actually fills its
   column at full row width instead of sitting in a narrow fixed-width
   block the way the old card layout did. */
.blog-row-excerpt { color: var(--color-text); font-size: 1.05rem; line-height: 1.6; flex: 1; }
/* Short posts render the real body instead of a flattened excerpt (see
   internal/web/blog.go, newListPost) -- constrain any inline media so a
   post's own <picture>/.blog-product-card markup never outgrows the row. */
.blog-row-full { font-size: 1.05rem; line-height: 1.6; flex: 1; min-width: 0; }
.blog-row-full :is(p, ul, ol) { margin: 0 0 var(--space-2); }
.blog-row-full :is(p, ul, ol):last-child { margin-bottom: 0; }
.blog-row-full .blog-product-card { max-width: 100%; }
.blog-row-more {
  align-self: flex-start; margin-top: auto;
  font-weight: 700; color: var(--color-primary);
  transition: color 0.2s ease;
}
.blog-row-more:hover { color: var(--color-gold-hover); text-decoration: underline; }
:root[data-theme="dark"] .blog-row-more { color: var(--color-primary-contrast); }
/* ---------- Home "Aktuelles" widget (single latest post) ---------- */
/* Sits full-width below the trust-bar (see home.html), outside
   .home-layout's product|sidebar split, at the same content width as the
   trust-bar itself. Reuses .blog-row wholesale -- same card, same
   thumb/excerpt/full-body branching as /blog -- with a gold border instead
   of the default neutral one, since this is a single featured slot rather
   than one row among many. No section heading and no "alle Beiträge"
   button here (Dan, 2026-08-18: "just the block post itself is
   sufficient") -- the card sits directly under the trust-bar with no extra
   wrapping chrome, so .home-blog itself carries no more margin than any
   other section on the page to avoid a leftover gap where the heading/
   button used to be. All sizing below is scoped under .home-blog so
   /blog's own list rows (.blog-row-title, .blog-row-body, etc.) are
   untouched. */
.home-blog { margin: var(--space-4) 0; }
.home-blog .blog-row { border-color: var(--color-gold); }
/* Smaller than the shared .blog-row-title (clamp 1.3-1.7rem) -- the
   Betreiber asked for a title that doesn't dominate the compact homepage
   slot the way it does as a single row heading on /blog. */
.home-blog .blog-row-title { font-size: clamp(1.05rem, 1.3vw, 1.3rem); line-height: 1.2; }
/* Titel, Datum und Text sollen auf der Startseite unmittelbar
   aufeinanderfolgen (Betreiber, 2026-08-18: zwischen Titel und Datum und
   zwischen Datum und Text stand "eine wirklich breite Lücke"). Der
   Zwischenraum kam nicht vom Flex-gap allein, sondern von den
   Vorgabe-Aussenabständen des Browsers: <h3> bringt margin: 1em 0 mit,
   <p class="blog-row-excerpt"> ebenso. Beide werden hier genullt, der gap
   entfällt ganz, und der einzige verbleibende Abstand steht bewusst unter
   dem Datum, damit Titel und Datum als ein Block zusammenstehen.
   Alles unter .home-blog eingegrenzt -- die Zeilen auf /blog behalten
   ihre luftigere Aufteilung. */
.home-blog .blog-row-body { gap: 0; padding: var(--space-3) var(--space-4); }
.home-blog .blog-row-title { margin: 0; }
.home-blog .blog-date { display: block; margin: 0 0 var(--space-2); }
.home-blog .blog-row-excerpt, .home-blog .blog-row-full { margin: 0; }
/* The homepage excerpt is long enough (up to ~1000 chars, see
   homeBlogExcerptLimit in blog.go) to span several real paragraphs --
   pre-line renders the "\n\n" paragraph breaks the backend now preserves
   as actual line breaks. /blog's own short excerpt keeps the default
   `white-space: normal`, which collapses those same breaks to a space,
   so it looks exactly as it did before this change. */
.home-blog .blog-row-excerpt { white-space: pre-line; }
/* margin-top: auto der geteilten Regel greift hier nicht (die Spalte ist
   nicht höher als ihr Inhalt) -- der Abstand zum "Weiterlesen" muss also
   ausdrücklich gesetzt werden, sonst klebt er am Text. */
.home-blog .blog-row-more { margin-top: var(--space-3); }
/* Vorschaubild als echte zweite Grid-Spalte neben .blog-row-body, nicht
   als fließendes Bild in der Textecke (das wirkte "fehl am Platz", sobald
   der Text unter seiner Höhe weiterlief -- Betreiber, 2026-08-18). Mobile-
   first gestapelt (eine Spalte); ab 40rem (dieselbe Schwelle wie /blog's
   .blog-row-with-thumb) rechte Bildspalte neben dem Text. object-fit:
   cover statt .blog-row-thumb's contain -- das Bild soll die volle
   Zeilenhöhe ausfüllen und wird dafür bewusst beschnitten, nicht
   letterboxed; anders als auf /blog ist dies immer genau ein bekanntes,
   bewusst gewähltes Foto für den hervorgehobenen Beitrag, kein beliebiges
   Postbild unbekannten Zuschnitts. rem-basierte Spaltenbreite, keine %,
   bleibt daher auf jeder Bildschirmbreite (auch 4K) gleich groß relativ
   zum Text -- die eigentliche Deckelung der Zeilenbreite kommt ohnehin von
   .site-main's --max-width. */
.home-blog-row-with-thumb { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 40rem) {
  .home-blog-row-with-thumb { grid-template-columns: minmax(0, 1fr) minmax(12rem, 22rem); }
}
.home-blog-row-thumb { display: block; overflow: hidden; min-height: 12rem; }
.home-blog-row-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Inline product reference inside a blog post body (body_de is raw
   admin-authored HTML -- see internal/web/blog.go -- so this markup is
   written directly into the backfill SQL rather than via a Go template).
   Small horizontal card, distinct from the grid .product-card. */
.blog-product-card {
  display: flex; align-items: center; gap: var(--space-3);
  background: var(--color-surface); border: 1px solid var(--color-border-soft);
  border-radius: var(--radius); box-shadow: var(--shadow-card);
  padding: var(--space-2) var(--space-3); margin: var(--space-3) 0;
  max-width: 28rem; color: var(--color-text); text-decoration: none;
  transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
.blog-product-card:hover {
  text-decoration: none; transform: translateY(-2px);
  border-color: var(--color-gold); box-shadow: var(--shadow-card-hover);
}
.blog-product-card-img { flex: 0 0 auto; width: 3.5rem; height: 5rem; background: var(--color-surface-alt); border-radius: var(--radius); overflow: hidden; display: flex; align-items: center; justify-content: center; }
.blog-product-card-img img { width: 100%; height: 100%; object-fit: cover; }
.blog-product-card-title { font-weight: 700; line-height: 1.3; }
.versand-table { border-collapse: collapse; margin: var(--space-2) 0; min-width: 20rem; }
.versand-table th, .versand-table td { padding: var(--space-2); border-bottom: 1px solid var(--color-border-soft); text-align: left; }
.versand-table thead th { border-bottom: 2px solid var(--color-gold); font-family: var(--font-display); }

/* ---------- Price notes (PAngV) ---------- */
.price-note { color: var(--color-text-muted); font-size: 0.85rem; margin-left: var(--space-1); }
.price-note a { color: inherit; text-decoration: underline; }

/* ---------- Cookie banner ---------- */
.cookie-banner {
  position: fixed; bottom: 0; left: 0; right: 0;
  background: var(--color-surface); border-top: 2px solid var(--color-gold);
  padding: var(--space-2) var(--page-pad);
  display: flex; gap: var(--space-3); align-items: center; justify-content: center;
  z-index: 50; flex-wrap: wrap; box-shadow: 0 -6px 24px rgb(0 0 0 / 12%);
}

/* ---------- Footer ---------- */
.site-footer {
  background: linear-gradient(180deg, var(--color-primary-strong) 0%, var(--color-primary-deep) 55%);
  color: var(--color-blush);
  /* Hintergrund randlos, Seitenpolster an den inneren Wrappern — nur so
     steht der Fuß-Inhalt auf derselben Kante wie Kopf, Nav und Inhalt. */
  padding: var(--space-5) 0 var(--space-3);
}
.footer-grid {
  display: grid; gap: var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  max-width: calc(var(--max-width) + 2 * var(--page-pad)); margin-inline: auto;
  padding-inline: var(--page-pad);
}
.footer-col { display: flex; flex-direction: column; gap: 0.45em; align-items: flex-start; }
.footer-col a { color: var(--color-blush); }
.footer-col a:hover { color: var(--color-gold-strong); text-decoration: none; }
.footer-heading {
  font-family: var(--font-display); font-size: 1rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--color-gold-strong); margin: 0 0 var(--space-2);
}
.footer-logo { filter: brightness(0) invert(0.92) sepia(0.25); opacity: 0.9; }
.footer-address, .footer-contact, .footer-vat { margin: 0; line-height: 1.7; }
.footer-contact a { font-weight: 700; }
.footer-social { display: inline-flex; align-items: center; gap: 0.5em; font-weight: 700; }
.footer-social .icon { width: 1.6em; height: 1.6em; color: var(--color-gold-strong); }
.footer-bottom {
  text-align: center; margin-top: var(--space-5); padding-top: var(--space-3);
  max-width: calc(var(--max-width) + 2 * var(--page-pad)); margin-inline: auto;
  padding-inline: var(--page-pad);
  border-top: 1px solid rgb(241 204 198 / 25%);
  font-size: 0.88rem; color: rgb(241 204 198 / 75%);
}
.footer-bottom p { margin: 0; }
/* PAngV-Fußnote: löst das "*" an den Listenpreisen auf */
.footer-bottom .footer-price-note { margin-bottom: var(--space-1); }
.footer-price-note a { color: inherit; text-decoration: underline; }

/* ---------- Newsletter band (above the footer) ----------
   Own paper-cream band, not a fifth .footer-col: the consent copy
   (newsletter.hint) is a full paragraph and needs width to read well, and a
   fifth ~14rem footer-grid column would either force it onto three lines or
   push the whole footer taller on every single page. Sits between the page
   background and the burgundy footer as a third, quieter tone -- gold
   hairlines top and bottom read as a deliberate frame, not a stray divider. */
.newsletter-band {
  background: var(--color-surface-alt);
  border-top: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-gold);
}
.newsletter-band-inner {
  display: flex; align-items: flex-start; justify-content: center; gap: var(--space-4);
  max-width: calc(var(--max-width) + 2 * var(--page-pad)); margin-inline: auto;
  padding: var(--space-4) var(--page-pad);
}
.newsletter-band-icon {
  flex: none; height: 2.6em; width: calc(2.6em * var(--icon-ratio, 1));
  color: var(--color-gold); margin-top: 0.2em;
}
/* min-width:0 + break-word: same guard as .side-card -- a long German
   compound in newsletter.hint must wrap inside the column, never push it
   wider than the flex row (see engineNote.md / CLAUDE.md convention 6).
   max-width caps how far the body grows so justify-content:center above
   has room to actually center the icon+body pair -- without it, flex-grow
   filled the whole row on wide screens and pinned the block to the left. */
.newsletter-band-body { flex: 1 1 20rem; max-width: 40rem; min-width: 0; overflow-wrap: break-word; }
.newsletter-band-title {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(1.2rem, 1.6vw, 1.5rem);
  margin: 0 0 var(--space-2); color: var(--color-primary);
}
:root[data-theme="dark"] .newsletter-band-title { color: var(--color-accent); }
.newsletter-form { max-width: 34rem; }
.newsletter-row { display: flex; gap: var(--space-2); flex-wrap: wrap; }
.newsletter-row input {
  flex: 1 1 14rem; min-width: 0;
  padding: 0.55em 0.9em; border: 1px solid var(--color-border);
  border-radius: var(--radius); background: var(--color-bg); color: var(--color-text);
  font: inherit;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.newsletter-row input:focus {
  outline: none; border-color: var(--color-gold); box-shadow: 0 0 0 1px var(--color-gold);
}
.newsletter-row .btn { flex: none; }
.newsletter-hint { margin: var(--space-2) 0 0; color: var(--color-text-muted); font-size: 0.82rem; }

/* ---------- Print ----------
   Used by internal/legalpdf: headless Chromium prints a legal page
   (e.g. /legal/agb) to PDF for the order-confirmation mail attachment,
   via its own --print-to-pdf, which applies @media print exactly like a
   browser's print dialog. Goal: logo + content + minimal footer (address,
   copyright) only -- no search bar, no burgundy nav, no footer link
   columns. Chrome strips background colors on print by default (ink
   saving), so anything keeping its brand background here needs an
   explicit print-color-adjust override. */
@media print {
  html, body { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
  /* .newsletter-band kam mit fb67062 erst NACH diesem Block dazu und fehlte
     deshalb in der Liste — es stand mitsamt Eingabefeld, "Anmelden"-Knopf und
     vollem DSGVO-Einwilligungstext im AGB-Anhang der Bestellbestätigung (im
     erzeugten PDF nachgewiesen) und schob den Fuß auf eine eigene Restseite.
     Ein Anmeldeformular ist in einem Anhang ohnehin totes Papier: nicht
     ausfüllbar, und der Einwilligungstext liest sich dort wie Vertragstext. */
  .header-search, .site-nav, .main-nav, .cookie-banner, .newsletter-band,
  .footer-col:not(.footer-brand), .footer-price-note { display: none; }
  .site-header { border-bottom: none; }
  .footer-grid { grid-template-columns: 1fr; }
  /* The procedural paper/leather-texture overlays (body::before, and
     .site-footer::before/.hero::before -- all SVG feTurbulence
     +feDiffuseLighting filters) are cheap on screen but Chromium can't keep
     them vector once print-color-adjust:exact forces them opaque -- it
     rasterizes each filter at print resolution on every page. Measured on
     the real /legal/agb page: 1.96MB with just body::before hidden (footer
     leather texture still active) vs 47KB with both kinds hidden. Print
     doesn't need the texture's visual richness, only the solid brand
     background colors (already set elsewhere), so just hide all of them.
     .main-nav::before needs no entry here: .main-nav itself is display:none
     above, which already suppresses its ::before. */
  body::before, .site-footer::before, .hero::before, .featured-note::before { display: none; }
}

/* Page numbering for anything printed to PDF (legalpdf's AGB attachment is
   the only current consumer). Chromium's headless --print-to-pdf honors CSS
   Paged Media @page margin boxes for this, so no header/footer CLI flag or
   JS pagination library is needed -- verified against the real binary. */
@page {
  @bottom-right {
    content: "Seite " counter(page) " von " counter(pages);
    font-family: Georgia, 'Times New Roman', serif;
    font-size: 9pt;
    color: #77695f;
  }
}

/* ---------- Admin (standard styling is fine — keep it plain) ---------- */
.admin-nav { display: flex; gap: var(--space-3); padding: var(--space-2) 0; border-bottom: 2px solid var(--color-gold); margin-bottom: var(--space-3); flex-wrap: wrap; }
/* Admin sign-out lives in the admin nav as a form, so its button is dressed
   as one of the surrounding links rather than as a control -- no theme
   overrides .admin-nav a, so they all take the global link colour. Pushed to
   the far end because it is the one item that leaves instead of navigating. */
.admin-nav-logout { margin-left: auto; display: inline-flex; }
.admin-nav-logout button {
  font: inherit; color: var(--color-accent); background: none; border: 0;
  padding: 0; cursor: pointer;
}
.admin-nav-logout button:hover { text-decoration: underline; }
.admin-inline-form { display: inline-flex; gap: var(--space-1); margin: 0 var(--space-1) var(--space-1) 0; }
.admin-tracking-input { width: 9rem; }
.admin-inline-form .form-hint { flex-basis: 100%; margin: 0; }
.widerruf-msg-input { width: 100%; min-width: 16rem; }
.admin-image-grid { display: flex; gap: var(--space-2); flex-wrap: wrap; }
.admin-image-item { margin: 0; width: 8rem; }
.admin-image-item img { width: 100%; aspect-ratio: 3/4; object-fit: cover; border-radius: var(--radius); }
.admin-dim-group { display: flex; gap: var(--space-1); align-items: center; flex-wrap: wrap; }
.admin-dim-group input { width: 5rem; }

/* ---------- Responsive tuning ---------- */
@media (max-width: 64rem) {
  .trust-bar, .benefit-bar { grid-template-columns: repeat(2, 1fr); }
  /* Sobald die Bar umbricht, stehen Items übereinander — und dann fällt auf,
     dass justify-content:center jede Gruppe EINZELN mittet: unterschiedlich
     breite Symbole (LKW 1.62 vs. Buch 1.23) und unterschiedlich lange Texte
     verschoben die linke Kante um bis zu 28px gegeneinander. In der einzeiligen
     Vier-Spalten-Ansicht fällt das nicht auf, hier schon.
     Deshalb ab hier: feste Symbolspalte (4.3em = das breiteste Symbol, der LKW
     mit 2.6em × 1.62) und linksbündig — damit beginnen Symbol und Text in
     jedem Item auf derselben Kante. */
  .trust-item, .benefit-item {
    display: grid; grid-template-columns: 4.3em 1fr;
    justify-content: start; padding-inline-start: var(--space-3);
  }
  .trust-item .icon, .benefit-item .icon { justify-self: center; }
}
@media (max-width: 40rem) {
  .header-main { gap: var(--space-2); }
  .header-search { order: 3; flex-basis: 100%; max-width: none; }
  .header-search-btn-label { display: none; } /* icon-only search button on phones */
  .header-search-btn { padding: 0.45em 0.9em; }
  .site-nav-label { font-size: 0.7rem; }
  .trust-bar, .benefit-bar { grid-template-columns: 1fr 1fr; padding: var(--space-2); gap: var(--space-2); }
  .trust-item-text small { display: none; } /* keep the bar scannable on small screens */
  .hero-slide { min-height: 260px; }
  /* Das Bücher-Motiv ist ein 3.2:1-Panorama, der Slide hier aber nur noch
     ~1.3:1 — object-fit: cover schneidet also den Löwenanteil der Breite weg.
     Mittig zentriert bliebe fast nur der unscharfe Hintergrund stehen; 85 %
     holt die Bücher, das Steinkreuz und die Kerze ins Bild und lässt die
     weiche, sonnige Fläche genau links liegen, wo der dunkle Textverlauf
     sitzt. Bewusst nur auf .hero-img-books: .hero-img-shop braucht hier
     nichts, sein 40%/top gilt über alle Breiten (siehe dort). */
  .hero-img-books { object-position: 85% center; }
  .benefit-bar { grid-template-columns: 1fr; }
  .cart-table { font-size: 0.9rem; }
}

/* motion is a garnish: honor users who opt out */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* Deviating billing address: CSS-only reveal, no JS needed */
.checkout-billing .billing-fields { display: none; flex-direction: column; gap: var(--space-3); }
.checkout-billing:has(.billing-toggle:checked) .billing-fields { display: flex; }

/* ---------- Checkout address picker ----------
   Progressive enhancement: the trigger/clear buttons only do anything once
   site.js runs (it fills the form from a <dialog> button's data-* set), so
   they stay hidden until it marks <html class="js"> -- without JS the
   shipping/billing fields are just a normal, fully editable form. */
.address-picker-actions { display: none; gap: var(--space-2); margin-bottom: var(--space-1); }
.js .address-picker-actions { display: flex; }

.address-picker-dialog {
  width: min(48rem, 92vw);
  max-height: min(80vh, 42rem);
  padding: 0;
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  color: var(--color-text);
  /* Seiten-Dimmer als Spread-Shadow statt ::backdrop-Fläche -- gleicher
     GPU-Workaround wie beim cart-modal und der Lightbox (s. dort). */
  box-shadow: var(--shadow-panel), 0 0 0 200vmax rgb(20 4 5 / 70%);
}
.address-picker-dialog::backdrop { background: transparent; }
.address-picker-header {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
  padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--color-border-soft);
}
.address-picker-title { margin: 0; font-family: var(--font-display); font-size: 1.15rem; color: var(--color-primary); }
:root[data-theme="dark"] .address-picker-title { color: var(--color-accent); }
.address-picker-close {
  background: none; border: 1px solid var(--color-border); border-radius: var(--radius);
  color: var(--color-text); cursor: pointer; padding: var(--space-1) var(--space-2);
  line-height: 1; font-size: 1.1rem; transition: background-color 0.2s ease, border-color 0.2s ease;
}
.address-picker-close:hover { background: var(--color-surface-alt); border-color: var(--color-gold); }
/* auto-fit reflows the grid on its own: one card per row on a phone, up to
   3-4 on a wide dialog, no media-query thresholds to maintain. The list
   scrolls inside the grid, not the page (max-height mirrors the dialog's). */
.address-picker-grid {
  display: grid; gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  padding: var(--space-4);
  max-height: calc(min(80vh, 42rem) - 4.5rem);
  overflow-y: auto;
}
.address-card-use {
  display: flex; flex-direction: column; gap: var(--space-1);
  width: 100%; text-align: left; cursor: pointer; font: inherit;
  background: var(--color-surface-alt); color: var(--color-text);
  border: 1px solid var(--color-border-soft); border-radius: var(--radius);
  padding: var(--space-3);
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.address-card-use:hover, .address-card-use:focus-visible { border-color: var(--color-gold); box-shadow: var(--shadow-card-hover); }
.address-card-company, .address-card-name { font-weight: 700; }
.address-card-name { font-family: var(--font-display); }
.address-card-street, .address-card-city, .address-card-country, .address-card-phone {
  color: var(--color-text-muted); font-size: 0.88rem;
}
.address-card-action { margin-top: var(--space-1); color: var(--color-accent); font-size: 0.8rem; font-weight: 700; }

/* "Über uns" legal-page hero portrait (content/ueber-uns.html). */
.ueber-uns-hero {
  float: right; margin: 0 0 var(--space-3) var(--space-4); max-width: 16rem;
}
.ueber-uns-hero img { display: block; width: 100%; height: auto; border-radius: var(--radius); box-shadow: var(--shadow-card); }
.ueber-uns-hero figcaption {
  margin-top: var(--space-1); text-align: center; font-size: 0.82rem; color: var(--color-text-muted);
}
@media (max-width: 40rem) {
  .ueber-uns-hero { float: none; margin: 0 auto var(--space-4); }
}

/* Blog-post before/after screenshots (e.g. willkommen post) -- capped
   narrower than the now-full-width .blog-post article itself, so a
   full-page screenshot stays legible as a "here's a screenshot" image
   instead of stretching edge to edge at whatever width the article
   happens to have. */
.blog-body-figure { margin: var(--space-4) auto; max-width: 40rem; }
.blog-body-figure img { margin: 0; }
.blog-body-figure figcaption {
  margin-top: var(--space-1); text-align: center; font-size: 0.85rem; color: var(--color-text-muted);
}

/* A post's own hero image (e.g. willkommen's butterfly photo) -- floated
   beside the text rather than run full-width, since .blog-post now has no
   width cap of its own (see the note above on .blog-post { max-width: none
   }) and a large photo stretched edge to edge felt overwhelming. Same
   float-with-text-wrap pattern as .ueber-uns-hero, just a bit wider since
   this source is landscape rather than portrait. Scoped to this class only
   -- other posts' inline images (e.g. "Vaterherz", the before/after
   screenshots) keep the plain full-width .blog-body img treatment. */
.blog-post-hero {
  float: right; margin: 0 0 var(--space-4) var(--space-4); max-width: 28rem;
}
.blog-post-hero img { display: block; width: 100%; height: auto; margin: 0; border-radius: var(--radius); box-shadow: var(--shadow-card); }
@media (max-width: 40rem) {
  .blog-post-hero { float: none; margin: 0 auto var(--space-4); }
}

/* ---------- Vorher/Nachher-Vergleichsschieber (.compare-slider) --------- */
/* Allgemeine Komponente, nicht auf einen Beitrag zugeschnitten: zwei
   deckungsgleich übereinanderliegende Bilder, dazwischen eine verschiebbare
   senkrechte Trennlinie. Aufgebaut wird sie von site.js über
   [data-compare-slider]; im Beitragstext steht nur die Auszeichnung.

   Zwei Festlegungen des Betreibers (2026-08-18) stecken hier drin:

   1. Beide Ansichten werden GANZ gezeigt, nicht auf einen Ausschnitt
      gezoomt ("has to include the full page ... and is not just a zoom in
      on a specific or adjustable extract area"). Eine frühere Fassung
      hatte ein gedeckeltes Sichtfenster mit eigenem Ausschnittsregler;
      das ist ersatzlos entfallen.
   2. Beide Aufnahmen entstehen bei DERSELBEN Fensterbreite (hier 1920 px)
      und werden hier auch in derselben Breite dargestellt. Alles andere
      verglichen zwei verschieden weit aufgezogene Fenster miteinander.

   Aus 2. folgt der Umgang mit der ungleichen Länge: die neue Startseite
   ist bei gleicher Breite deutlich länger als die alte. Die Bühne ist so
   hoch wie die LÄNGERE der beiden Aufnahmen (site.js rechnet dafür
   --compare-aspect aus den Bildmassen aus), die kürzere endet vorher und
   darunter bleibt das Passepartout stehen. Das ist der Befund selbst und
   kein Darstellungsfehler: die alte Seite war schlicht kürzer. Beide
   Aufnahmen sitzen deshalb oben bündig, nicht mittig.

   Weil beide Seiten in voller Länge hineinpassen müssen, wird die Bühne
   hoch und schmal. Deshalb steht sie als eigene, mittige Spalte im
   Beitrag und nicht über dessen volle Breite. */
.compare-slider { margin: var(--space-4) 0; }
.compare-stage {
  position: relative;
  width: 100%; max-width: 34rem; margin: 0 auto;
  /* Seitlicher Rand ("some padding to the left and right") und der etwas
     höhere Kopfrand, in dem die beiden Beschriftungen sitzen. */
  --compare-inset: var(--space-4);
  --compare-top: 2.4rem;
  --compare-pos: 50%;
  background: var(--color-surface-alt);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  cursor: ew-resize;
  /* pan-y, nicht none: senkrechtes Wischen soll weiterhin die Seite
     blättern. Waagerechtes Ziehen läuft ohnehin über den Griff, der seine
     eigene touch-action mitbringt. */
  touch-action: pan-y;
  user-select: none; -webkit-user-select: none;
}
/* Beide Bildebenen liegen absolut übereinander und können deshalb die Höhe
   der Bühne nicht bestimmen. Dieser leere Platzhalter steht als einziges
   Element im normalen Fluss und gibt ihr die Höhe der längeren Aufnahme,
   plus Kopf- und Fussrand. Ein aspect-ratio direkt auf der Bühne ginge
   nicht: die beiden Ränder sind feste Längen, kein fester Anteil der
   Breite, und liessen sich in einem Seitenverhältnis nicht ausdrücken. */
.compare-sizer {
  margin: var(--compare-top) var(--compare-inset) var(--compare-inset);
  aspect-ratio: var(--compare-aspect, 1 / 2.6);
}
/* Die Ecken werden von den Ebenen selbst gerundet, nicht per overflow von
   der Bühne: overflow ausser visible macht die Bühne zum Scroll-Container
   und nähme dem Griff unten seine position: sticky. */
.compare-pane {
  position: absolute; inset: 0; overflow: hidden;
  border-radius: inherit;
  /* Deckend, nicht durchsichtig: die obere Ebene zeigt ihr Bild mit Rand,
     und durch diesen Rand würde sonst die untere Ebene scheinen. */
  background: var(--color-surface-alt);
}
/* Die obere Ebene wird beschnitten, nicht skaliert: beide Bilder behalten
   ihre Darstellungsgrösse, sonst würde beim Schieben die eine Seite
   gestaucht statt aufgedeckt. */
.compare-pane-before { clip-path: inset(0 calc(100% - var(--compare-pos)) 0 0); }
/* Eine feine Kante mit Schatten um jede Aufnahme: die Seite liegt sichtbar
   als Blatt auf dem Passepartout. Ohne das sähe gerade die kürzere
   Aufnahme so aus, als wäre das Bild unten abgeschnitten. Gerahmt wird das
   <picture>, nicht das <img> -- so sitzt die Kante an der Bildkante. */
.compare-pane picture {
  position: absolute;
  top: var(--compare-top);
  left: var(--compare-inset); right: var(--compare-inset);
  display: block; width: auto; height: auto;
  box-shadow: 0 0 0 1px var(--color-border), var(--shadow-card);
}
/* Die allgemeinen .blog-body-Bildregeln (Aussenabstand, mittige
   Ausrichtung, Eckenrundung, max-width) würden hier die Deckungsgleichheit
   der beiden Ebenen zerstören. */
.compare-pane img {
  width: 100%; height: auto; max-width: none; display: block;
  margin: 0; border-radius: 0;
}
.compare-label {
  position: absolute; top: 0.5rem; z-index: 1;
  background: var(--color-primary-deep); color: var(--color-primary-contrast);
  font-family: var(--font-display); font-size: 0.78rem; letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 0.15rem 0.65rem; border-radius: var(--radius-pill);
  pointer-events: none;
}
.compare-label-before { left: var(--compare-inset); }
.compare-label-after { right: var(--compare-inset); }
.compare-handle {
  position: absolute; top: 0; bottom: 0; left: var(--compare-pos);
  width: 2.75rem; margin-left: -1.375rem;
  cursor: ew-resize; touch-action: none;
}
/* Die sichtbare Linie ist ein Pseudo-Element, damit die Greiffläche
   (2.75rem breit, auch für einen Finger noch treffbar) unabhängig von der
   Strichstärke bleibt. */
.compare-handle::before {
  content: ""; position: absolute; top: 0; bottom: 0; left: 50%;
  width: 2px; margin-left: -1px;
  background: var(--color-gold-strong);
  box-shadow: 0 0 0 1px rgb(53 6 7 / 25%);
}
/* sticky: die Bühne ist höher als der Bildschirm, weil beide Seiten in
   voller Länge hineinpassen müssen. Ein in der Bühnenmitte festgenagelter
   Griff wäre beim Betrachten des oberen Seitendrittels ausserhalb des
   Sichtfelds. So wandert er mit und bleibt greifbar, solange der Vergleich
   zu sehen ist. */
.compare-handle-grip {
  position: sticky; top: 45vh; display: block; margin: 0 auto;
  width: 2.25rem; height: 2.25rem; border-radius: 50%;
  background: var(--color-gold-strong); border: 2px solid var(--color-gold-contrast);
  box-shadow: var(--shadow-card-hover);
}
/* Zwei Pfeilspitzen als reines Zeichen, dass hier gezogen werden darf. */
.compare-handle-grip::before, .compare-handle-grip::after {
  content: ""; position: absolute; top: 50%; margin-top: -5px;
  border-top: 5px solid transparent; border-bottom: 5px solid transparent;
}
.compare-handle-grip::before { left: 0.4rem; border-right: 6px solid var(--color-gold-ink); }
.compare-handle-grip::after { right: 0.4rem; border-left: 6px solid var(--color-gold-ink); }
.compare-handle:focus-visible { outline: none; }
.compare-handle:focus-visible .compare-handle-grip,
.compare-handle-active .compare-handle-grip {
  outline: 3px solid var(--color-primary); outline-offset: 2px;
}
/* Ohne JavaScript bleibt der Ersatzinhalt des Behälters stehen: zwei
   Verweise auf die beiden Aufnahmen. Bewusst kein <img> im Beitragstext,
   siehe die Begründung im Kopf der Komponente in site.js. */
.compare-nojs {
  margin: 0; padding: var(--space-3);
  background: var(--color-surface-alt); border: 1px solid var(--color-border);
  border-radius: var(--radius); color: var(--color-text-muted); font-size: 0.95rem;
}
@media (max-width: 30rem) {
  .compare-stage { --compare-inset: var(--space-2); --compare-top: 2rem; }
  .compare-label { font-size: 0.7rem; padding: 0.1rem 0.45rem; }
}
