/* ============================================================
   NONNA'S CREW — MAIN CSS · Systemebene „Stempel"
   ------------------------------------------------------------
   Regeln aus dem Brand-System:
   · eine Druckfarbe pro Fläche (nie Green + Grey gemischt)
   · Rahmen ist Pflicht, nichts unter 2px
   · nichts sitzt exakt gerade — aber höchstens 2-3 gekippte
     Elemente pro Bildschirm, sonst wird Handwerk zur Manier
   · unter 24px: keine Kippung, kein Filter — Lesbarkeit gewinnt
   ============================================================ */

*, *::before, *::after { box-sizing: border-box; }
/* Statsbar und Header kleben gemeinsam oben — zusammen 102px auf dem
   Telefon, 114px auf dem Desktop. scroll-padding-top hält die
   Sprungziele der Navigation darunter statt dahinter.

   Der Wert deckt die Kopfzeile bewusst NICHT voll ab: die angesprungenen
   Sektionen bringen selbst 96px (Desktop) bzw. 64px (Telefon) oberes
   Padding mit, und dieser leere Streifen darf hinter der Kopfzeile
   verschwinden. Vorher standen hier 96px, was sich mit dem
   Sektions-Padding zu rund 110px Leerraum über der Überschrift
   addierte. Mit 32px bleiben 46px (Desktop) bzw. 26px (Telefon) —
   nachgemessen über alle sechs Nav-Ziele. Wer hier weiter reduziert,
   drückt zuerst die Überschriften auf dem Telefon an die Header-Kante. */
html {
  -webkit-text-size-adjust: 100%; scroll-behavior: smooth;
  scroll-padding-top: calc(var(--statsbar-h) + 32px);
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--size-body);
  line-height: var(--lead-body);
  -webkit-font-smoothing: antialiased;
  /* clip statt hidden: hidden wäre programmatisch scrollbar und
     ließe die ganze Seite seitlich verrutschen */
  overflow-x: clip;
}
html { overflow-x: clip; }

img, svg { max-width: 100%; }
[hidden] { display: none !important; }
h1, h2, h3, p, figure, blockquote, fieldset { margin: 0; }
button { font: inherit; }

:focus-visible { outline: var(--min-line) solid var(--ink); outline-offset: 3px; }
.on-ink :focus-visible { outline-color: var(--paper); }

.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; left: var(--s-8); top: -100px; z-index: 600;
  background: var(--ink); color: var(--paper); padding: 10px 16px;
  text-decoration: none; font-weight: 700;
}
.skip-link:focus { top: var(--s-8); }

/* ------------------------------------------------------------
   BAUSTEINE
   ------------------------------------------------------------ */

.wrap { max-width: var(--max); margin: 0 auto; padding-left: var(--gutter); padding-right: var(--gutter); }

.kicker {
  font-family: var(--font-accent); font-weight: 700;
  font-size: var(--size-label); letter-spacing: var(--track-claim);
  text-transform: uppercase; color: var(--ink);
}
.on-ink .kicker { color: var(--paper); opacity: .8; }

.headline {
  font-family: var(--font-display); font-weight: 400;
  font-size: var(--size-h2); line-height: var(--lead-display);
  color: var(--ink); overflow-wrap: anywhere;
  margin-top: var(--s-12);
}
.on-ink .headline { color: var(--paper); }

.section { padding: var(--section-pad) 0; }
.section-note { margin-top: var(--s-16); max-width: 52ch; color: var(--text-quiet); }
/* Auf der Druckfarbe gilt die Papierfarbe — Grey auf Green wären
   zwei Druckfarben in einem Element. */
.on-ink .section-note { color: inherit; opacity: .85; }

/* Fehlstellen-Maske: aufgedruckte Farbe deckt nie zu 100 % */
.ink-surface { position: relative; isolation: isolate; background: var(--ink); color: var(--paper); }
.ink-surface::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 1;
  background-image: var(--grain); opacity: var(--ink-cover); mix-blend-mode: screen;
}
.ink-surface > * { position: relative; z-index: 2; }

/* Naht — verbindet zwei Farbflächen. Farben pro Einsatz über
   --seam-a (oben) und --seam-b (unten), Standard Green → Cream. */
.seam { display: block; --seam-a: var(--green); --seam-b: var(--cream); }
.seam i { display: block; height: var(--seam-row); }
.seam i:first-child {
  background: var(--seam-a);
  background-image: repeating-linear-gradient(90deg, var(--seam-b) 0 var(--seam-tooth), transparent var(--seam-tooth) calc(var(--seam-tooth) * 2));
}
.seam i:last-child {
  background: var(--seam-b);
  background-image: repeating-linear-gradient(90deg, var(--seam-a) 0 var(--seam-tooth), transparent var(--seam-tooth) calc(var(--seam-tooth) * 2));
}

/* Dunkles Kontrast-Segment (Amici-Idee) — eine Farbe: Grey */
.grey-surface { position: relative; isolation: isolate; background: var(--grey); color: var(--cream); }
.grey-surface::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 1;
  background-image: var(--grain); opacity: calc(var(--ink-cover) * .7); mix-blend-mode: screen;
}
.grey-surface > * { position: relative; z-index: 2; }
.grey-surface .kicker { color: var(--giallo); opacity: 1; }
.grey-surface .headline { color: var(--cream); }

/* Perforation & Halbton */
.perf {
  height: 10px;
  background-image: radial-gradient(var(--ink) 1.8px, transparent 2.4px);
  background-size: 12px 10px; background-position: center;
}
.halftone {
  background-image: radial-gradient(var(--ink) var(--pat-mid-dot), transparent calc(var(--pat-mid-dot) + .6px));
  background-size: var(--pat-mid) var(--pat-mid);
}

/* ------------------------------------------------------------
   BUTTONS — gedrückt heißt: sackt ab, verliert Deckung
   ------------------------------------------------------------ */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: var(--tap); padding: 13px 22px;
  font-size: var(--size-small); font-weight: 700;
  letter-spacing: var(--track-label); text-transform: uppercase;
  background: var(--ink); color: var(--paper);
  border: var(--frame-w) solid var(--ink); border-radius: 0;
  cursor: pointer; text-decoration: none; text-align: center;
  transition: transform var(--dur-fast) var(--ease-press),
              background-color var(--dur-base) var(--ease),
              color var(--dur-base) var(--ease),
              opacity var(--dur-base) var(--ease);
}
.btn:hover { transform: translateY(-2px) rotate(var(--tilt-alt)); }
.btn:active { transform: translateY(1px) scale(.985); opacity: .82; }
.btn[disabled] {
  background: transparent; color: rgb(var(--grey-rgb) / .4);
  border-color: rgb(var(--grey-rgb) / .3); border-style: dashed;
  transform: none; cursor: not-allowed;
}
.btn--secondary { background: var(--paper); color: var(--ink); }
/* Rosso: der eine laute Knopf pro Bildschirm — Primär-CTA */
.btn--rosso { background: var(--rosso); border-color: var(--rosso); color: var(--cream); }
.btn--on-ink { background: var(--paper); color: var(--ink); border-color: var(--paper); }
/* Ghost auf Cream: nur der Pflichtrahmen, keine Fläche. Im Hero
   steht der Sekundär-CTA seit dem Hintergrundfoto (24.08.2026)
   wieder auf dunklem Grund und trägt dort zusätzlich --on-ink;
   die Variante gewinnt per Spezifität. */
.btn--ghost { background: transparent; color: var(--ink); }
.btn--on-ink.btn--ghost { background: transparent; color: var(--paper); }
.btn--block { width: 100%; }

/* ------------------------------------------------------------
   STATS-LEISTE — schmale Grey-Zeile ganz oben (Amici-Idee)
   ------------------------------------------------------------ */
/* ------------------------------------------------------------
   FOMO-LEISTE — die oberste Zeile der Seite.

   Umgekehrt zur Stats-Leiste darunter: Papier statt Druckfarbe.
   Zwei dunkle Balken übereinander wären ein Werbeklotz; so
   liest sich die Zeile als Notiz auf dem Beleg, nicht als
   Banner. Kein Blinken, keine Farbe außer der Druckfarbe — die
   Dringlichkeit steht im Satz, nicht im Effekt.
   ------------------------------------------------------------ */
.fomo {
  display: flex; align-items: center; justify-content: center;
  flex-wrap: wrap; gap: var(--s-8) var(--s-12);
  padding: 8px var(--gutter);
  background: var(--paper); color: var(--ink);
  border-bottom: var(--min-line) dashed rgb(var(--green-rgb) / .45);
  font-size: var(--size-micro); font-weight: 700; line-height: 1.35;
  letter-spacing: var(--track-label); text-transform: uppercase;
  text-align: center; text-decoration: none;
}
.fomo:hover { background: rgb(var(--green-rgb) / .1); }
/* Der Zusatz ist der Knopf-Ersatz: unterstrichen, damit auch
   ohne Rahmen klar ist, dass die Zeile irgendwo hinführt. */
.fomo-cta {
  text-decoration: underline; text-underline-offset: 3px;
  white-space: nowrap;
}
.fomo-cta:empty { display: none; }

/* Stats-Leiste — laufendes Band, kein statischer Dreisatz mehr.

   Sie hängt NICHT am Scroll: sie läuft zeitbasiert und gleichmäßig
   durch, auch wenn niemand scrollt.
   Deshalb eine CSS-Animation statt eines zweiten rAF-Loops — die
   braucht kein JS, läuft im Compositor und funktioniert in einem
   sticky-Element genauso wie in einem normal positionierten.

   Der Umlauf schließt nahtlos, weil der Inhalt exakt doppelt vorliegt
   (main.js → renderStatic) und die Animation um genau eine Kopie
   schiebt: -50 % der Spurbreite. width: max-content ist Bedingung —
   ohne sie richtet sich die Breite nach dem Flex-Container und die
   -50 % träfen die Naht nicht.

   Optik: Grey auf Cream.

   Sie klebt zusammen mit dem Header oben. --statsbar-h hält ihre Höhe
   an einer Stelle: hier als Höhe, in .header als top-Versatz. */
.statsbar {
  position: sticky; top: 0; z-index: var(--layer-header);
  overflow: hidden; height: var(--statsbar-h);
  display: flex; align-items: center;
  background: var(--grey); color: var(--cream);
}
.statsbar-track {
  display: flex; flex: none; align-items: center;
  width: max-content; will-change: transform;
  /* Die Dauer hängt an der Länge der Spur, nicht an einem Gefühl:
     sie ist so bemessen, dass ein Eintrag ungefähr gleich schnell
     vorbeizieht, egal wie viele in config.js → stats stehen. Bei
     der Verdopplung auf zehn Einträge (24.08.2026) ist sie von 20s
     auf 45s mitgewachsen. Wer Einträge ergänzt oder streicht, zieht
     diesen Wert im selben Verhältnis nach. */
  animation: statsbar-loop 45s linear infinite;
}
@keyframes statsbar-loop {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
/* Keine Dauerbewegung, wenn sie abbestellt ist. Das Band steht
   dann beim ersten Eintrag. */
@media (prefers-reduced-motion: reduce) {
  .statsbar-track { animation: none; }
}
.statsbar-track span {
  font-size: var(--size-micro); font-weight: 700;
  letter-spacing: var(--track-label); text-transform: uppercase;
  padding: 0 var(--s-12); white-space: nowrap;
}

/* ------------------------------------------------------------
   LOGO-BILD — das richtige Nonna-Logo (assets/img/nonna-logo.png).
   Fehlt die Datei, setzt JS die SVG-Arbeitsform ein.
   ------------------------------------------------------------ */
/* Die Bildmarke liegt freigestellt als Alpha-Maske vor
   (assets/img/nonna-mark.png, aus dem Original erzeugt).
   Als Maske gesetzt nimmt sie jede Markenfarbe an — eine Datei
   fuer Cream-auf-Green und Grey-auf-Cream. */
/* Die vektorisierte Bildmarke. Eine Datei, ein Symbol, überall
   dasselbe Gesicht — Header, Preloader, Einwürfe, Unterschrift.
   Im Hero steht sie seit dem Neubau nicht mehr: dort trägt allein
   die Typografie. Farbe kommt über currentColor: Grey auf Cream,
   Cream auf der Druckfarbe. Nie zwei Farben in einer Fläche. */
.nonna-mark { display: block; width: 100%; height: auto; fill: currentColor; }

.preloader-img { color: var(--cream); }

/* ------------------------------------------------------------
   HEADER — Kassenbon-Zeile
   ------------------------------------------------------------ */
.header {
  position: sticky; top: var(--statsbar-h); z-index: var(--layer-header);
  background: var(--paper);
  border-bottom: var(--min-line) solid var(--ink);
}
/* padding-block gibt der Wortmarke Luft nach oben und unten, damit sie
   nicht an der border-bottom-Kante klebt. Der Wert stand bei 18px, solange
   Smokums grobe Versalien in der Zeile standen; Climate Crisis ist
   kompakter und braucht weniger Rand. position: relative wird für die
   Wortmarke nicht mehr gebraucht, bleibt aber als Bezugsrahmen für
   spätere absolute Kinder stehen. */
.header-inner {
  display: flex; align-items: stretch; gap: var(--s-16);
  min-height: 56px;
  padding-block: 12px;
  position: relative;
}
/* Die Wortmarke läuft wieder in der Display-Schrift der Seite
   (--font-logo = Climate Crisis) und trägt als einzige das Grau/Schwarz
   (--ink-alt = --grey), nicht die Druckfarbe. Sie steht weiterhin allein
   als reines Text-Element ohne Icon-Slot und ohne gap — der Nonna-Kopf
   war für den Smokum-Versuch aus dem Lockup genommen worden und ist
   bislang nicht zurückgeholt.

   Der Versuch, sie per position: absolute unten aus der Leiste
   herausragen zu lassen, ist zurückgebaut: der Header klebt oben und
   wandert damit über die ganze Seite — über der Stimmen-Sektion
   (derselbe Grauton wie die Schrift) und der Galerie verschwand der
   überstehende Teil vollständig, der Wortzug sah abgeschnitten aus.
   Sie steht deshalb wieder als normales Flex-Kind in der Zeile, groß,
   aber mit Luft oben und unten. „NONNA'S CREW" bleibt einzeilig. */
.brand {
  display: inline-flex; align-items: center; flex: none;
  white-space: nowrap;
  font-family: var(--font-logo); font-weight: 400;
  font-size: clamp(24px, 6.4vw, 34px); line-height: 1;
  color: var(--ink-alt); text-decoration: none;
}
.header-nav { display: none; margin-left: auto; }
.header-nav-ctas { display: none; }
/* Direkte Kinder: im mobilen Panel steht am Ende noch .header-nav-ctas
   mit zwei echten Knöpfen — die dürfen weder Farbe noch Trennlinien der
   Nav-Links erben.
   nowrap: „Über uns" und „Menü & Preis" sind zweiwortig und brachen
   sonst innerhalb ihrer Zelle auf zwei Zeilen um. Die Zeile bleibt
   dadurch einzeilig; den Platz dafür holt sich die 900–1280-Regel
   weiter unten über kleinere Innenabstände. */
.header-nav > a {
  display: inline-flex; align-items: center; padding: 0 14px;
  white-space: nowrap;
  min-height: 56px; text-decoration: none; color: var(--ink);
  font-size: var(--size-label); font-weight: 700;
  letter-spacing: var(--track-label); text-transform: uppercase;
  border-left: var(--min-line) dashed rgb(var(--green-rgb) / .4);
  transition: background-color var(--dur-base) var(--ease), color var(--dur-base) var(--ease);
}
.header-nav > a:hover { background: rgb(var(--green-rgb) / .12); }
.header-nav > a.is-active { background: var(--ink); color: var(--paper); }
/* Tapfläche bleibt bei 44px — Systemregel, auch für den kleinen
   Header-Knopf. */
.header-cta { align-self: center; min-height: var(--tap); padding: 8px 14px; font-size: var(--size-micro); }
/* 1024 statt vormals 900: mit sechs Einträgen brauchte die Zeile bei
   900px rund 966px — der Anfrage-Knopf wurde abgeschnitten. Die Grenze
   durfte nach oben wandern, seit unterhalb davon das Burger-Panel
   steht; vorher war unter 900px gar keine Navigation erreichbar. */
@media (min-width: 1024px) {
  .header-nav { display: flex; }
  .header-cta { margin-left: var(--s-16); }
  /* Ab hier steht die schmale Nav-Zeile — die zwei großen Knöpfe
     gehören nur ins mobile Panel. */
  .header-nav-ctas { display: none; }
}

/* Eigener linker Rand für die Header-Zeile. .wrap trägt links wie rechts
   --gutter; der Wert springt ab 1080px auf 48px und addiert sich auf
   breiten Monitoren zum Auto-Margin der 1180px-Zentrierung — die
   Wortmarke startete dadurch spürbar weit vom Fensterrand entfernt.
   --gutter selbst bleibt unangetastet: daran hängen die Innenabstände
   aller übrigen Sektionen. padding-right bleibt ebenfalls stehen, damit
   der Anfrage-Knopf rechts nicht an die Kante rutscht. */
@media (min-width: 1080px) {
  .header .wrap { padding-left: 24px; }
}

/* ------------------------------------------------------------
   MOBILE-NAVIGATION — Panel + Drei-Striche-Knopf
   Unterhalb der Grenze stand .header-nav bisher ersatzlos auf
   display:none: die Navigation war auf dem Telefon schlicht nicht
   erreichbar. Es bleibt dasselbe Element (der Scroll-Spy und das
   Befüllen aus content.js hängen daran), es bekommt hier nur ein
   anderes Erscheinungsbild.
   ------------------------------------------------------------ */
/* Standardmäßig aus; eingeschaltet wird der Knopf allein im
   max-width:1023px-Block weiter unten. Andersherum (Basis an, oben
   wieder aus) verliert die min-width-Regel gegen die spätere
   Basisregel — gleiche Spezifität, die letzte gewinnt. */
.nav-burger {
  display: none; flex-direction: column; justify-content: center;
  gap: 5px; flex: none; align-self: center;
  width: var(--tap); height: var(--tap); padding: 0 10px;
  background: none; border: 0; cursor: pointer;
}
.nav-burger span {
  display: block; height: var(--min-line); width: 100%;
  background: var(--ink);
  /* steps() statt weicher Kurve — dieselbe mechanische Bewegung wie
     im Rest der Seite. */
  transition: transform var(--dur-base) var(--ease),
              opacity var(--dur-base) var(--ease);
}
.nav-burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* Unterhalb der Nav-Grenze trägt die Zeile nur noch Wortmarke und
   Burger. Der Anfrage-Knopf entfällt hier ganz: dieselbe Handlung steht
   im aufgeklappten Menü als erster, voll breiter Knopf, und im Hero
   gleich darunter noch einmal. Der frei gewordene Platz geht an die
   Wortmarke. */
@media (max-width: 1023px) {
  .nav-burger { display: inline-flex; }
  .header-cta { display: none; }
  /* Echte Mitte der Zeile, nicht die Mitte des Restplatzes neben dem
     Burger — dafür aus dem Fluss genommen. Die Zeilenhöhe trägt jetzt
     der Burger mit seinen 44px Tapfläche, der Header bleibt dadurch
     genauso hoch wie vorher.

     Die Obergrenze ist nicht frei gewählt: mittig gesetzt muss die
     Wortmarke schmaler bleiben als Fensterbreite minus zweimal
     (Rand + Burger), sonst läuft sie unter den Knopf.

     Neu bemessen am 31.08.2026 für „NONNA'S CREW". Der Vorgänger
     „CIAO NONNA" war 9,13 × Schriftgrad breit, der neue Wortzug ist
     11,18 × Schriftgrad — mit dem alten clamp(19px, 6.4vw, 28px) lief
     er bei 320px um 19px unter den Burger. Der Spielraum wächst linear
     mit dem Fenster (bei 320px sind es 192px), also gilt
     11,18 × Grad ≤ Fensterbreite − 136px, aufgelöst nach dem Grad:
     8.9vw − 12px. Das hält von 320px bis zum Deckel bei 450px
     durchgehend 4px Luft zum Knopf. Der untere clamp-Wert greift erst
     unterhalb von ~304px. Wird der Wortzug je wieder geändert, muss
     der Faktor 8.9 mit ihm neu bemessen werden. */
  .brand {
    position: absolute; left: 50%; top: 50%;
    transform: translate(-50%, -50%);
    font-size: clamp(15px, calc(8.9vw - 12px), 28px);
  }
}

@media (max-width: 1023px) {
  /* --header-h misst initNavBurger() am echten Header und schreibt es
     auf :root — die Header-Höhe hängt an der Wortmarke, deren Grad per
     clamp() an der Fensterbreite hängt. Der Wert im Fallback ist nur
     die Notbremse, falls das JS nicht läuft. */
  .header-nav {
    display: flex; flex-direction: column;
    position: fixed;
    inset: calc(var(--statsbar-h) + var(--header-h, 70px)) 0 auto 0;
    z-index: var(--layer-header);
    max-height: calc(100dvh - var(--statsbar-h) - var(--header-h, 70px));
    overflow-y: auto;
    margin-left: 0;
    background: var(--paper);
    border-bottom: var(--min-line) solid var(--ink);
    transform: translateY(-8px); opacity: 0; pointer-events: none;
    transition: transform var(--dur-base) steps(4), opacity var(--dur-base) steps(4);
  }
  .header-nav.is-open { transform: translateY(0); opacity: 1; pointer-events: auto; }
  /* In der Spalte trennt eine Linie oben statt links, und die Labels
     dürfen umbrechen — Platz ist hier keiner knapp. */
  .header-nav > a {
    padding: 14px var(--gutter); white-space: normal;
    border-left: 0;
    border-top: var(--min-line) dashed rgb(var(--green-rgb) / .4);
  }
  .header-nav > a:first-child { border-top: 0; }
  .header-nav-ctas {
    display: flex; flex-direction: column; gap: var(--s-8);
    padding: var(--s-16) var(--gutter) var(--s-24);
    border-top: var(--min-line) solid var(--ink);
  }
  .header-nav-ctas .btn { width: 100%; }
}
/* Zwischen 900 und 1280 teilt sich die Wortmarke die Zeile mit der
   vollen Navigation. Bei voller Größe bliebe für die Menüpunkte zu
   wenig Platz — deshalb hier ein kleinerer Logo-Schritt und engere
   Innenabstände in der Navigation. Die 34px sind die Größe für den
   breiten Desktop. */
@media (min-width: 1024px) and (max-width: 1279px) {
  .brand { font-size: 24px; }
  .header-nav > a { padding: 0 10px; }
}
@media (min-width: 1280px) {
  .header-nav > a { white-space: nowrap; }
}
/* Die Wortmarke steht absolut in der Mitte, im Fluss bleibt allein der
   Burger — der gehört an den rechten Rand. */
@media (max-width: 1023px) {
  .header-inner { justify-content: flex-end; }
}

/* ------------------------------------------------------------
   PRELOADER — die Nonna füllt den Viewport, dann hebt sich
   die Fläche wie ein Stempel ab. Nur 1. Besuch pro Session.
   Standard: versteckt — JS zeigt ihn nur, wenn nötig (kein
   Aufblitzen bei Reload).
   ------------------------------------------------------------ */
.preloader {
  position: fixed; inset: 0; z-index: var(--layer-preload);
  display: none; place-items: center;
  background: var(--green); color: var(--cream);
  isolation: isolate;
}
.preloader::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background-image: var(--grain); opacity: var(--ink-cover); mix-blend-mode: screen;
}
.preloader.is-on { display: grid; }
/* Die Marke selbst wird nicht gekippt — nur aufgesetzt. */
.preloader-img {
  width: min(62vmin, 480px);
  transform: scale(var(--reveal-scale));
  opacity: 0;
}
.preloader.is-on .preloader-img {
  animation: pre-stamp .5s var(--ease-press) .12s forwards;
}
@keyframes pre-stamp { to { transform: none; opacity: 1; } }
.preloader.is-leaving { animation: pre-lift .55s var(--ease-soft) forwards; }
@keyframes pre-lift { to { transform: translateY(-100%); } }

/* ------------------------------------------------------------
   HERO — Druckfarbe und Foto nebeneinander (24.08.2026).

   Bis zum 23.08. galt hier „kein Bild". Der erste Anlauf legte
   das Foto ganzflächig HINTER die Typografie — und scheiterte an
   einer schlichten Geometrie: die Headline belegt über drei
   Zeilen rund 70 % der Breite, und genau dort steht im Bild das
   Paar. Jedes Overlay, das die Schrift lesbar machte, deckte den
   Moment zu, um den es geht.

   Also stehen sie jetzt nebeneinander statt übereinander: links
   die Druckfarbe mit dem Text, rechts das Foto über die volle
   Höhe. Beide Personen sind vollständig zu sehen, die Schrift
   steht auf einer ruhigen Fläche — kein Schleier nötig, der
   irgendetwas zudeckt. Schmale Viewports stapeln: Foto oben,
   Text darunter.

   Die Kante zwischen Farbe und Foto ist hart, nicht weich —
   Bedruckstoff trifft Bild, wie überall im System. Zusammen hält
   beides die Körnung, die über die ganze Fläche läuft.
   ------------------------------------------------------------ */
.hero {
  position: relative; isolation: isolate;
  /* Zwei Zellen. Schmal untereinander (Foto oben), breit
     nebeneinander (Foto rechts) — siehe Media Query unten.
     Das Padding sitzt in der Textzelle, nicht im Hero: sonst
     bekaeme das Foto einen Rand statt einer Kante. */
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  /* Gestapelt (bis 700px) trug der Hero vorher min(86svh, 760px) — die
     Druckfarbe unter dem Foto endete damit knapp hinter dem letzten
     Knopf und wirkte abgeschnitten. Mehr Raum nach unten gibt dem
     Claim Boden. Ab 700px setzt die Media Query min-height wieder auf
     0, dort tragen die zwei Spalten die Höhe. */
  min-height: min(96svh, 860px);
  padding: 0;
  background: var(--ink); color: var(--paper);
  border-bottom: var(--min-line) solid var(--ink);
  overflow: hidden;
}

/* Die Foto-Zelle. Die Bilddatei ist 4:5 hoch — dasselbe Verhaeltnis
   wie die Zelle im gestapelten Zustand, deshalb ist dort nichts
   beschnitten: beide Personen komplett, vom Scheitel bis zum Saum.
   Daneben (ab 900px) traegt die Zelle die volle Hero-Hoehe und das
   Bild wird nur seitlich beschnitten — auch das laesst beide stehen,
   weil im Hochformat links und rechts Luft ist.

   Keine Unschaerfe mehr und keine Abdunklung: beides war noetig,
   solange Schrift ueber dem Bild stand. Jetzt steht sie daneben. */
.hero-photo {
  position: relative; grid-area: 1 / 1; z-index: 0;
  /* Die Zelle traegt das Seitenverhaeltnis der Bilddatei — 1:1: so
     passt das Bild exakt hinein und wird nie beschnitten, Braut,
     Caterer und Ofen bleiben vollstaendig. Gestapelt zusaetzlich
     gedeckelt, sonst stuende der Claim unter der Falz; was dann an
     Breite uebrig bleibt, ist Druckfarbe.

     width: 100% ist noetig, sonst rechnet der Browser bei
     gedeckelter Hoehe die Breite aus dem Seitenverhaeltnis zurueck
     und das Band steht linksbuendig statt ueber die volle Zeile. */
  aspect-ratio: 1 / 1; max-height: 46svh; width: 100%;
  overflow: hidden; pointer-events: none;
}
.hero-photo i {
  position: absolute; inset: 0;
  display: block;
  background-image: url("../assets/img/hero-bg-event.jpg");
  background-size: contain; background-repeat: no-repeat;
  background-position: 50% 50%;
}
/* Ein duenner Farbschleier ueber dem Foto — die einzige Aufgabe,
   die hier noch uebrig ist: das Bild in die Markenpalette ziehen,
   damit es neben dem Ein-Farben-Stempelsystem nicht wie ein
   Fremdkoerper steht. Zum Kontrast traegt er nichts mehr bei und
   darf deshalb duenn bleiben. */
.hero-photo::after {
  content: ""; position: absolute; inset: 0;
  background: rgb(var(--green-rgb) / .18);
}

/* Beide Ebenen sind absichtlich groesser als der Hero: sie
   duerfen sich verschieben, ohne dass eine Kante sichtbar wird.
   multiply, damit das Korn in den Bedruckstoff einsinkt statt
   darauf zu liegen. */
.hero-grain { position: absolute; inset: 0; z-index: 1; pointer-events: none; overflow: hidden; }
.hero-grain i {
  position: absolute; left: 0; right: 0; top: -30%; bottom: -30%;
  display: block; background-image: var(--grain);
  mix-blend-mode: multiply; will-change: transform;
}
/* Die feine Ebene liegt vorn und laeuft schneller, die grobe
   liegt hinten und bleibt fast stehen — so herum liest das Auge
   Tiefe statt Flimmern. */
.hero-grain i:nth-child(1) { background-size: 420px 420px; opacity: .16; }
.hero-grain i:nth-child(2) { background-size: 180px 180px; opacity: .30; }

/* --s-56 gibt es in der Skala nicht (4·8·12·16·24·32·48·64·96) — die
   ganze padding-block-Deklaration war dadurch ungültig und fiel auf 0
   zurück: der Textblock stand auf Mobile ohne jeden Innenabstand
   zwischen Fotokante und Knopf. Auf die nächste Stufe der Skala
   gerastet, Richtung beibehalten (unten mehr Luft als oben). */
.hero-inner {
  position: relative; z-index: 2; grid-area: 2 / 1;
  align-self: center; width: 100%;
  padding-block: var(--s-48) var(--s-64);
}

/* Auf der Druckfarbe gilt die Papierfarbe — Green auf Green waere
   unlesbar, Grey auf Green waeren zwei Druckfarben. */
.hero-eyebrow {
  display: inline-block;
  font-family: var(--font-accent); font-weight: 700;
  font-size: var(--size-label); letter-spacing: var(--track-claim);
  /* Seit die Zeile IN der H1 steht (29.08.2026), erbt sie deren
     line-height von .96 — das quetschte den Kasten zusammen.
     Vorher kam der Wert von <body>. Hier explizit gesetzt, damit
     er nicht mehr davon abhängt, wo das Element hängt. */
  line-height: 1.35;
  text-transform: uppercase; color: var(--paper);
  border: var(--min-line) solid var(--paper);
  padding: 6px 12px; transform: rotate(var(--tilt-alt));
}

.hero-head {
  /* Der Abstand saß bis zum 29.08.2026 hier oben, weil die
     Eyebrow-Zeile ein eigener Absatz DAVOR war. Jetzt steht sie
     als erstes Kind in der H1 — der Abstand gehört deshalb
     zwischen sie und die Headline, siehe .hero-head-lead. */
  margin-top: 0;
  font-family: var(--font-display); font-weight: 400;
  /* Die Untergrenze ist bewusst niedrig: die laengste Zeile muss
     auch auf 320px Breite in EINE Zeile passen, sonst bricht
     Climate Crisis mitten im Wort. Der vw-Anteil traegt die
     Groesse, nicht der Mindestwert. Seit die erste Zeile
     „Nonnas Rezept." heisst und nicht mehr „Ihr Rezept.", ist sie
     die laengste — nicht mehr das Wechselwort „Handschrift." */
  font-size: clamp(1.55rem, 7.9vw, 6.4rem);
  line-height: .96; color: var(--paper);
}
/* Zwei Toene wie vorher auf Creme, nur umgekehrt: dort stand die
   erste Zeile im Grau und der Rest in der Druckfarbe. Auf der
   Druckfarbe uebernimmt das Papier die zweite Rolle, das Grau
   bleibt das Grau — der Wechsel sitzt an derselben Stelle. */
.hero-head-lead {
  display: block; color: var(--ink-alt);
  /* Trägt den Abstand, den vorher .hero-head oben hatte. */
  margin-top: var(--s-24);
}
.hero-head-tail { display: block; }

/* Der Wortstapel: alle Wechselwoerter liegen in derselben
   Rasterzelle. Die Zeile ist dadurch immer so breit wie das
   laengste Wort und springt beim Wechsel nicht. */
/* Eigene Zeile, nicht zufällig umgebrochen: „Unsere Handschrift."
   passt in Climate Crisis auf keiner Breite neben „Unsere", also
   steht das Wechselwort grundsätzlich allein. Drei Zeilen, die
   dritte wechselt — die Bewegung sitzt damit immer an derselben
   Stelle statt mal hier, mal dort. */
.hero-swap { display: grid; justify-items: start; }
.hero-swap > span {
  grid-area: 1 / 1;
  /* Aussteigende Woerter duerfen keinen Platz mehr beanspruchen,
     sonst zieht der Stapel waehrend des Wechsels die Zeile hoch. */
  opacity: 0; visibility: hidden;
  transform: translateY(.14em) scale(1.05);
  transform-origin: left center;
}
/* Einrasten, kein Federn: steps() laesst das Wort in drei harten
   Stufen auf seine Position fallen und dort stehen. Kein
   Ueberschwingen, kein Nachwippen. */
.js .hero-swap > span {
  transition: opacity var(--dur-reveal) var(--ease),
              transform var(--dur-reveal) var(--ease),
              visibility 0s linear var(--dur-reveal);
}
.hero-swap > span.is-on {
  opacity: 1; visibility: visible; transform: none;
  transition-delay: 0s;
}
/* Ohne JS traegt das erste Wort die Zeile. */
.hero-swap > span:first-child { opacity: 1; visibility: visible; transform: none; }
.js .hero-swap > span:first-child:not(.is-on) { opacity: 0; visibility: hidden; transform: translateY(.14em) scale(1.05); }

.hero-ctas { display: flex; flex-wrap: wrap; gap: var(--s-12); margin-top: var(--s-32); }

/* Nur fuer Screenreader: der Satz einmal vollstaendig, damit der
   zyklische Wechsel nicht vorgelesen wird. */
.sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* Schmale Viewports: der Text laeuft ueber die volle Breite, ein
   seitlicher Verlauf hilft dort nichts. Also flaechig deckend —
   das Foto bleibt als Stimmung erkennbar, aber unter dem Text
   steht ueberall dieselbe Deckung. */
/* Ab hier ist die Zeile breit genug, dass Text und Foto
   nebeneinander passen, ohne dass eines von beiden zu schmal wird.
   Dieselbe Schwelle, ab der auch die Kopfnavigation ausklappt. */
@media (min-width: 900px) {
  /* Kein Mindestmass fuer die Hoehe mehr: die Hoehe des Heros ist
     die Hoehe des Bildes. So entsteht weder ein Streifen Druckfarbe
     ueber dem Foto noch ein Anschnitt — das Bild gibt das Mass, der
     Text stellt sich mittig daneben. Wird der Textblock einmal
     hoeher als das Bild, waechst die Zeile mit und das Foto sitzt
     unten buendig. Der Schnitt liegt exakt in der Mitte: halbe
     Zeile Druckfarbe, halbe Zeile Bild.

     Der Parallax-Versatz fuers Foto faellt damit weg: er braucht
     Ueberstand, und Ueberstand hiesse wieder Anschnitt. Die
     Bewegung tragen die zwei Korn-Ebenen, die ohnehin darueber
     laufen (initHeroGrain deckelt den Versatz am vorhandenen
     Ueberstand — hier also auf null). */
  .hero { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); min-height: 0; }
  /* Ohne Deckel waere der Hero so hoch wie die halbe Fensterbreite —
     auf breiten, flachen Schirmen reicht er dann unter die Falz. Die
     10rem sind der Platz, den die drei Kopfleisten darueber brauchen;
     solange das Quadrat da hineinpasst, greift der Deckel gar nicht
     und das Bild fuellt die Haelfte exakt. Greift er doch, steht das
     Bild rechtsbuendig — der Rest Druckfarbe faellt an die Naht zur
     Schrift, nicht an den Bildschirmrand. Beschnitten wird nie. */
  .hero-photo { grid-area: 1 / 2; max-height: calc(100svh - 10rem); align-self: stretch; }
  .hero-photo i { background-position: right center; }

  /* Der Textblock steht mittig in seiner Haelfte, nicht an der
     Satzkante der Seite: gleicher Rand links wie rechts. Die
     Flucht zum Header geht dabei verloren — dafuer sitzt der Block
     im Kasten, und es bleibt mehr Breite fuer die Schrift. */
  .hero-inner {
    grid-area: 1 / 1; max-width: none; margin: 0;
    padding-inline: clamp(var(--gutter), 7%, 6rem);
    padding-block: var(--s-48);
  }
  /* Massgeblich ist die laengste Zeile: „Nonnas Rezept." misst bei
     49px rund 537px und muss einzeilig bleiben. Mit dem mittig
     stehenden Block sind es je nach Fenster 500 bis 800px Breite —
     der obere Deckel bei 3.4rem haelt die Zeile ueberall in einer
     Zeile und die Schrift trotzdem gross. */
  .hero-head { font-size: clamp(1.9rem, 3.7vw, 3.4rem); }
}

@media (prefers-reduced-motion: reduce) {
  /* Alle drei Ebenen stehen still — die Geschwindigkeitsdifferenz
     ist der Effekt, ohne sie bleibt schlicht Textur. Der Wortwechsel
     laeuft weiter, aber ohne Uebergang (regelt initHeroSwap). */
  .hero-grain i, .hero-photo i { transform: none !important; }
}

/* ------------------------------------------------------------
   KONFIGURATOR — Anlass + Menü + Personen + Extras in einem
   Bestellzettel. Links die Schritte, rechts der live-Beleg.
   ------------------------------------------------------------ */
.konf { display: grid; gap: var(--s-32); margin-top: var(--s-32); }
@media (min-width: 960px) {
  /* stretch, nicht start: die rechte Spalte muss so hoch werden wie
     die Schritte links, sonst hat der klebende Beleg keine Strecke,
     auf der er mitlaufen kann. */
  .konf { grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); align-items: stretch; }
  .konf .receipt-col { align-content: start; }

  /* Nur der Beleg klebt. Der Aufklapper, die Teilen-Knöpfe und die
     Stimme stehen unter dem Raster und müssen gar nicht mitlaufen. */
  .konf .receipt-sticky {
    position: sticky; top: 76px;
    /* fit-content, sonst zieht die 1fr-Zeile den Beleg immer auf
       die volle Fensterhöhe — auch wenn er kurz ist. So bleibt er
       so hoch wie sein Inhalt und wird erst am Anschlag begrenzt. */
    height: fit-content;
    max-height: calc(100svh - 76px - var(--s-24));
    grid-template-rows: auto minmax(0, 1fr) auto;
  }

  /* Wird der Beleg im Extremfall länger als das Fenster (viele
     Extras, Wünsche, weite Anfahrt), scrollen NUR die Positionen.
     Richtpreis, Preis pro Gast und der CTA bleiben stehen — sonst
     verschwindet ausgerechnet die Zahl, um die es geht. */
  .konf .receipt-sticky .receipt { min-height: 0; display: grid; grid-template-rows: auto auto minmax(0, 1fr); }
  .konf .receipt-sticky .receipt-body { min-height: 0; display: grid; grid-template-rows: minmax(0, 1fr) auto auto auto auto; }
  .konf .receipt-sticky .rc-rows { overflow: auto; overscroll-behavior: contain; scrollbar-width: thin; }
}
.konf-steps { display: grid; gap: var(--s-24); }
/* min-width: 0 ist Pflicht, kein Feinschliff. Browser geben
   <fieldset> von Haus aus `min-inline-size: min-content` — das
   Feld schrumpft dann nicht auf seine Rasterspalte, sondern bleibt
   breiter und schiebt alle Knöpfe darin unter die Beleg-Spalte. */
.konf-step {
  border: var(--min-line) solid var(--ink); padding: var(--s-16);
  margin: 0; min-width: 0;
}
.konf-legend {
  font-family: var(--font-accent); font-weight: 700;
  font-size: var(--size-label); letter-spacing: var(--track-claim);
  text-transform: uppercase; color: var(--ink);
  padding: 0 8px; margin-left: -8px;
}

/* Anlass-Chips — kompakt, Zustand wie abgestempelt */
.chips { display: flex; flex-wrap: wrap; gap: var(--s-8); }
.chip {
  display: inline-flex; align-items: center; gap: 8px;
  min-height: var(--tap); padding: 8px 14px;
  background: var(--paper); color: var(--ink);
  border: var(--min-line) solid var(--ink); border-radius: 0;
  cursor: pointer; font-size: var(--size-micro); font-weight: 700;
  letter-spacing: var(--track-label); text-transform: uppercase;
  transition: transform var(--dur-fast) var(--ease-press),
              background-color var(--dur-base) var(--ease), color var(--dur-base) var(--ease);
}
.chip:hover { transform: translateY(-2px); }
.chip[aria-pressed="true"] { background: var(--ink); color: var(--paper); transform: rotate(var(--tilt)); }

/* Menü-Karten — reine Textkarten, kein Bild.
   Kleine Bilder gibt es auf dieser Seite nicht. */
.menus { display: grid; gap: var(--s-12); grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 700px) { .menus { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.menu-card {
  position: relative; display: flex; flex-direction: column; gap: 6px; text-align: left;
  background: var(--paper); color: var(--text);
  border: var(--min-line) solid var(--ink); border-radius: 0;
  padding: var(--s-16) var(--s-12) var(--s-12);
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-press), box-shadow var(--dur-base) var(--ease);
}
.menu-card:hover { transform: translateY(-2px) rotate(var(--tilt-alt)); }
.menu-card b {
  font-family: var(--font-display); font-weight: 400; font-size: 15px;
  color: var(--ink); line-height: 1;
}
.menu-card .price {
  font-variant-numeric: tabular-nums;
  font-size: var(--size-small); font-weight: 700; color: var(--rosso);
}
.menu-card .price small { font-weight: 400; color: var(--text-quiet); letter-spacing: 0; }
.menu-card p { margin: 0; font-size: var(--size-micro); line-height: 1.5; color: var(--text-quiet); }
.menu-card[aria-pressed="true"] {
  transform: rotate(var(--tilt));
  box-shadow: 0 0 0 var(--frame-w) var(--ink);
}
.menu-card[aria-pressed="true"]::after {
  content: "✓"; position: absolute; top: 6px; right: 6px; z-index: 3;
  width: 26px; height: 26px; display: grid; place-items: center;
  background: var(--ink); color: var(--paper); font-weight: 700;
  transform: rotate(var(--tilt-alt));
}
/* Badges: „Beliebt" in Rosso, „1 Extra inklusive" in Giallo */
.menu-badge {
  position: absolute; top: -9px; left: 8px; z-index: 3;
  padding: 3px 8px; font-size: 10px; font-weight: 700;
  letter-spacing: var(--track-label); text-transform: uppercase;
  transform: rotate(var(--tilt));
}
.menu-badge--rosso { background: var(--rosso); color: var(--cream); }
.menu-badge--giallo { background: var(--giallo); color: var(--grey); }

/* ------------------------------------------------------------
   ABLAUF — gepinnte horizontale Slide-Sektion.
   Wrapper hoch, Bühne sticky, Track fährt per JS-Transform.
   Ohne JS / mit reduzierter Bewegung: normale vertikale Liste.
   ------------------------------------------------------------ */
.flow { position: relative; }
.flow.is-pinned { height: calc(100vh + var(--pin-step) * 3); }
.flow-stage { position: relative; padding: var(--s-32) 0; }

/* Grünes Druckfeld hinter dem Ablauf.

   Dieselbe Technik wie .atwork-field: radialer Verlauf in EINER
   Druckfarbe, darüber der vorhandene --grain-Filter, verrechnet
   per background-blend-mode. Kein zweiter Farbton, keine
   Creme-Blau-Mischung — eine Fläche, eine Farbe.

   Die Ebene sitzt in der Bühne, nicht in der Sektion: gepinnt
   ist die Bühne sticky und einen Bildschirm hoch, die Sektion
   dagegen vier. In der Sektion würde das Feld unter der
   stehenden Bühne wegscrollen. Sticky überschreibt das
   position: relative von oben — die Pin-Mechanik in
   renderFlow() bleibt unberührt, sie misst Höhen, keine
   Positionierung.

   Deckkraft niedrig halten: die Karten darauf sind gerahmt und
   müssen es bleiben. Richtwert wie bei .atwork-field, hier eine
   Spur kräftiger, weil der Verlauf selbst weicher ausläuft. */
.flow-stage::before {
  content: ""; position: absolute; inset: 0; z-index: 0;
  pointer-events: none;
  background-image:
    var(--grain),
    radial-gradient(78% 62% at 16% 8%,
      rgb(var(--green-rgb) / .95) 0%, rgb(var(--green-rgb) / .45) 46%,
      rgb(var(--green-rgb) / 0) 78%),
    radial-gradient(72% 58% at 88% 96%,
      rgb(var(--green-rgb) / .8) 0%, rgb(var(--green-rgb) / 0) 72%);
  background-size: 180px 180px, cover, cover;
  background-repeat: repeat, no-repeat, no-repeat;
  background-blend-mode: overlay, normal, normal;
  opacity: calc(var(--foto-grain) + .1);
}
/* Kopf, Karten und Fortschrittsleiste liegen darüber. */
.flow-stage > * { position: relative; z-index: 1; }
.flow.is-pinned .flow-stage {
  position: sticky; top: 0; height: 100svh;
  display: flex; flex-direction: column; justify-content: center;
  /* clip statt hidden: hidden ließe sich programmatisch scrollen
     (scrollIntoView, Fokus) und würde die Bühne verschieben */
  overflow: clip; padding: 0;
}
.flow-head { padding-top: var(--s-24); }
.flow-track { display: grid; gap: var(--s-16); margin-top: var(--s-32); }
.flow.is-pinned .flow-track {
  display: flex; gap: var(--s-24);
  will-change: transform;
  padding-right: var(--gutter);
}
/* Schritte als Kartuschen: doppelte Kontur (Amici „Stap 1") */
.flow-step {
  border: var(--frame-w) solid var(--ink); background: var(--paper);
  padding: var(--s-24);
  box-shadow: inset 0 0 0 var(--min-line) var(--paper),
              inset 0 0 0 calc(var(--min-line) + var(--frame-gap)) var(--ink);
  display: flex; flex-direction: column; gap: var(--s-12);
}
.flow-step > * { margin: var(--frame-gap); }
.flow.is-pinned .flow-step {
  flex: none; width: min(78vw, 460px);
}
.flow-step .num {
  font-family: var(--font-display); font-size: 2.2rem; line-height: 1; color: var(--ink);
}
.flow-step h3 {
  font-family: var(--font-display); font-weight: 400;
  font-size: var(--size-h3); color: var(--ink);
}
.flow-step p { font-size: var(--size-small); max-width: 40ch; }
.flow-step:nth-child(even) { transform: rotate(var(--tilt-alt)); }
.flow-step:nth-child(3) { transform: rotate(var(--tilt)); }
/* Fortschritt: Perforationslinie füllt sich */
.flow-progress { margin-top: var(--s-24); height: 10px; position: relative;
  background-image: radial-gradient(rgb(var(--green-rgb) / .3) 1.8px, transparent 2.4px);
  background-size: 12px 10px; background-position: left center; }
.flow-progress i {
  position: absolute; inset: 0; width: 0; overflow: hidden; display: block;
  background-image: radial-gradient(var(--ink) 1.8px, transparent 2.4px);
  background-size: 12px 10px; background-position: left center;
}
.flow:not(.is-pinned) .flow-progress { display: none; }

/* ------------------------------------------------------------
   PREISRECHNER — Bestellzettel
   ------------------------------------------------------------ */
.field-label {
  display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
  font-size: var(--size-label); font-weight: 700;
  letter-spacing: var(--track-label); text-transform: uppercase;
  margin-bottom: 8px;
}
.field-hint { font-size: var(--size-micro); color: var(--text-quiet); letter-spacing: 0; text-transform: none; font-weight: 400; }

.persons-row { display: grid; grid-template-columns: minmax(0, 1fr) 92px; gap: var(--s-12); align-items: center; }

/* Slider im Stempel-Stil */
input[type="range"] {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: var(--tap); background: transparent; margin: 0; cursor: pointer;
}
input[type="range"]::-webkit-slider-runnable-track {
  height: var(--min-line); background: var(--ink);
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 26px; height: 26px; margin-top: -12px;
  background: var(--ink); border: var(--frame-w) solid var(--ink);
  box-shadow: inset 0 0 0 3px var(--paper); border-radius: 0;
  transform: rotate(var(--tilt-alt));
}
input[type="range"]::-moz-range-track { height: var(--min-line); background: var(--ink); }
input[type="range"]::-moz-range-thumb {
  width: 20px; height: 20px; background: var(--ink);
  border: 3px solid var(--paper); box-shadow: 0 0 0 var(--frame-w) var(--ink); border-radius: 0;
}

.input {
  width: 100%; min-height: var(--field-h); padding: 10px 14px;
  font: inherit; color: var(--text); background: var(--paper);
  border: var(--min-line) solid var(--ink); border-radius: 0;
  background-image: repeating-linear-gradient(90deg, rgb(var(--green-rgb) / .16) 0 1px, transparent 1px 34px);
  transition: box-shadow var(--dur-base) var(--ease), border-color var(--dur-base) var(--ease);
}
.input:focus { outline: 0; box-shadow: inset 0 0 0 var(--frame-w) var(--ink); }
.input[aria-invalid="true"] { border-color: var(--ink-alt); border-style: dashed; }
textarea.input { min-height: 120px; resize: vertical; background-image: none; }
.input::placeholder { color: rgb(var(--grey-rgb) / .42); }

/* Extras — zwei Pakete mit sichtbarem Inhalt */
.extras { display: grid; gap: var(--s-12); }
@media (min-width: 560px) { .extras { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.extra {
  position: relative; display: flex; flex-direction: column; gap: 6px;
  padding: var(--s-12) var(--s-16);
  border: var(--min-line) solid var(--ink); color: var(--text);
  background: var(--paper); cursor: pointer;
  transition: background-color var(--dur-base) var(--ease), transform var(--dur-fast) var(--ease-press), box-shadow var(--dur-base) var(--ease);
}
.extra:hover { transform: translateY(-2px); }
/* Die Checkbox liegt unsichtbar unter dem Etikett — der Fokus
   muss trotzdem sichtbar sein, sonst tappt die Tastatur im Dunkeln. */
.extra input { position: absolute; opacity: 0; pointer-events: none; }
.extra:has(input:focus-visible) { outline: var(--min-line) solid var(--ink); outline-offset: 3px; }
.extra .extra-head {
  display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
  font-size: var(--size-small); font-weight: 700; color: var(--ink);
  letter-spacing: var(--track-label); text-transform: uppercase;
  flex-wrap: wrap;
}
.extra .price-tag { font-variant-numeric: tabular-nums; letter-spacing: 0; color: var(--rosso); white-space: nowrap; }
.extra .extra-items { font-size: var(--size-micro); line-height: 1.55; color: var(--text-quiet); }
.extra.is-on { box-shadow: 0 0 0 var(--frame-w) var(--ink); transform: rotate(var(--tilt-alt)); }
.extra.is-on::after {
  content: "✓"; position: absolute; top: -10px; right: -8px;
  width: 24px; height: 24px; display: grid; place-items: center;
  background: var(--ink); color: var(--paper); font-weight: 700;
  transform: rotate(var(--tilt));
}

/* Beleg */
.receipt {
  border: var(--frame-w) solid var(--ink); background: var(--paper);
  padding: var(--s-24); transform: rotate(var(--tilt-alt));
}
.receipt-title {
  font-family: var(--font-accent); font-weight: 700; font-size: var(--size-label);
  letter-spacing: var(--track-claim); text-transform: uppercase; color: var(--ink);
}
.rc-rows { margin-top: var(--s-16); }
.rc-row {
  display: flex; align-items: baseline; gap: 8px; padding: 7px 0;
  font-size: var(--size-small); font-variant-numeric: tabular-nums;
}
.rc-row .dots { flex: 1; border-bottom: var(--min-line) dotted rgb(var(--grey-rgb) / .45); }
.rc-row .val { font-weight: 700; min-width: 84px; text-align: right; }
.receipt-perf { height: 10px; margin: var(--s-12) calc(var(--s-24) * -1);
  background-image: radial-gradient(var(--ink) var(--pat-band-r), transparent calc(var(--pat-band-r) + .6px));
  background-size: var(--pat-band) 10px; background-position: center; }
/* Climate Crisis ist für Preise gesperrt (Typo-Regel: „Nie für
   Fließtext oder Preise"). Die Summe läuft in Space Grotesk 700,
   tabellarisch und rechtsbündig — und bleibt unter 40px. */
.receipt-sum { display: flex; align-items: baseline; gap: 8px; padding-top: var(--s-8); }
.receipt-sum b {
  font-family: var(--font-accent); font-weight: 700;
  font-size: var(--size-label); letter-spacing: var(--track-claim);
  text-transform: uppercase; color: var(--ink);
}
.receipt-sum .dots { flex: 1; border-bottom: var(--min-line) dotted rgb(var(--grey-rgb) / .45); }
.receipt-sum .val {
  font-family: var(--font-body); font-weight: 700;
  font-size: clamp(1.5rem, 5vw, 2.1rem); color: var(--ink);
  font-variant-numeric: tabular-nums; white-space: nowrap;
  text-align: right;
}
.receipt-note { margin-top: var(--s-12); font-size: var(--size-micro); color: var(--text-quiet); }
.receipt .btn { margin-top: var(--s-16); }
.receipt-occasion {
  display: none; margin-top: var(--s-8);
  font-size: var(--size-micro); font-weight: 700; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink);
  border: var(--min-line) solid var(--ink); padding: 4px 10px; width: fit-content;
  transform: rotate(var(--tilt));
}
.receipt-occasion.is-on { display: block; }

/* ------------------------------------------------------------
   STIMMEN — gestempelte Zitate
   ------------------------------------------------------------ */
.quotes { display: grid; gap: var(--s-16); margin-top: var(--s-32);
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.quote { border: var(--min-line) solid var(--ink); padding: var(--s-24); background: var(--paper); }
.quote:nth-child(2) { transform: rotate(var(--tilt-alt)); }
.quote p { font-size: var(--size-body); }
.quote footer {
  margin-top: var(--s-16); font-size: var(--size-micro); font-weight: 700;
  letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink);
}
.quote footer span { display: block; font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--text-quiet); margin-top: 2px; }
/* Auf dem Grey-Segment: eine Farbe — Cream-Linien, Giallo-Absender */
.grey-surface .quote { background: transparent; border-color: rgb(var(--cream-rgb) / .55); color: var(--cream); }
.grey-surface .quote footer { color: var(--giallo); }
.grey-surface .quote footer span { color: rgb(var(--cream-rgb) / .7); }

/* ------------------------------------------------------------
   FORMULAR
   ------------------------------------------------------------ */
.form-grid { display: grid; gap: var(--s-24); margin-top: var(--s-32); }
@media (min-width: 900px) { .form-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr); align-items: start; } }

.calcbox { border: var(--frame-w) solid var(--paper); padding: var(--s-24); }
.calcbox h3 {
  font-family: var(--font-accent); font-weight: 700; font-size: var(--size-label);
  letter-spacing: var(--track-claim); text-transform: uppercase;
}
.calcbox ul { list-style: none; margin: var(--s-12) 0 0; padding: 0; font-size: var(--size-small); font-variant-numeric: tabular-nums; }
.calcbox li { display: flex; gap: 8px; padding: 5px 0; }
.calcbox li .dots { flex: 1; border-bottom: var(--min-line) dotted rgb(var(--cream-rgb) / .45); }
.calcbox li.sum { font-weight: 700; }
.calcbox .empty { margin-top: var(--s-12); font-size: var(--size-small); opacity: .8; }

.iform { display: grid; gap: var(--s-16); }
.iform .row2 { display: grid; gap: var(--s-16); }
@media (min-width: 560px) { .iform .row2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* Nur die Feldbeschriftung, nicht die Fehlermeldung daneben —
   sonst gewinnt diese Regel gegen .field-error { display:none } */
.iform label > span:first-child {
  display: block; margin-bottom: 6px;
  font-size: var(--size-micro); font-weight: 700;
  letter-spacing: var(--track-label); text-transform: uppercase;
}
.on-ink .input { border-color: var(--paper); background-color: rgb(var(--cream-rgb) / .96); }
.on-ink .input:focus { box-shadow: inset 0 0 0 var(--frame-w) var(--grey); }
.field-error {
  display: none; margin-top: 6px; font-size: var(--size-micro);
  font-weight: 700; letter-spacing: .04em;
}
.field-error.is-on { display: block; }
.on-ink .field-error { color: var(--paper); }
.on-ink .field-error::before { content: "→ "; }

.form-success {
  border: var(--frame-w) solid var(--paper); padding: var(--s-32);
  text-align: center; transform: rotate(var(--tilt));
}
.form-success h3 { font-family: var(--font-display); font-weight: 400; font-size: 2rem; }
.form-success p { margin-top: var(--s-12); }

/* ------------------------------------------------------------
   BILD-BEHANDLUNGEN
   .foto  = echtes Foto: Pflichtrahmen, Korn, leichte Kippung.
   .druck = Ein-Farb-Druck für dekorative Flächen.
   Platzhalter zeigen Format + erwarteten Dateinamen.
   ------------------------------------------------------------ */
.foto {
  display: block;
  position: relative; isolation: isolate; overflow: hidden;
  border: var(--frame-w) solid var(--ink);
  background: rgb(var(--grey-rgb) / .06);
}
.foto img { width: 100%; height: 100%; object-fit: cover; display: block; }
.foto::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 2;
  background-image: var(--grain); opacity: var(--foto-grain); mix-blend-mode: multiply;
}
.foto--tilt { transform: rotate(var(--tilt)); }

/* ------------------------------------------------------------
   KURZ GESAGT — vier Karten im Zickzack, direkt unter dem Hero.

   ⚠ DRITTER ENTWURF (22.08.2026). Vorher lief die Sektion
     gepinnt seitwärts (dieselbe Mechanik wie #ablauf, geteilt
     über initPinLauf). Zwei gepinnte Sektionen kurz
     hintereinander haben den Scrollweg zerhackt — jetzt steht
     die Sektion still und die Karten stehen versetzt.

   Die Komposition macht die Arbeit, nicht die Bewegung: Karte 1
   links oben, jede folgende rutscht auf die andere Seite und
   ein Stück tiefer. Der negative Zeilenabstand lässt die Karten
   vertikal überlappen — sie stehen dabei nie nebeneinander in
   derselben Spalte, es kollidiert also nichts.

   Kartenform, Rahmen, Kippung und Ziffer kommen unverändert von
   .flow-step. Neu ist nur die Anordnung und die Linie dahinter.
   ------------------------------------------------------------ */
.fakten { position: relative; background: var(--paper); }
/* Nur senkrecht polstern — die Kurzform würde die seitliche
   Polsterung von .wrap überschreiben und die Karten an die
   Fensterkante schieben. */
.fakten-stage { padding-top: var(--s-48); padding-bottom: var(--s-64); }
.fakten-head { padding-top: var(--s-24); }
.fakten .kicker { display: block; }

/* Bezugsrahmen für die absolut liegende Linie. */
.fakten-zickzack { position: relative; margin-top: var(--s-32); }
.fakten-track { display: grid; gap: var(--s-24); }

/* Die Zelle trägt Position und Reveal, die Karte darin die
   Kippung — zwei Transforms auf einem Element würden sich
   gegenseitig überschreiben. */
.fakt-zelle { position: relative; z-index: 1; }

@media (min-width: 760px) {
  /* Zwölf Spalten als Lineal: die Karten hängen an relativen
     Spaltenlinien, nicht an gepixelten Offsets. Zeilenabstand 0,
     der Versatz kommt aus dem negativen margin-top — so bleibt
     die Treppenstufe unabhängig von der Texthöhe gleich groß. */
  .fakten-track {
    grid-template-columns: repeat(12, 1fr);
    column-gap: var(--s-24); row-gap: 0;
  }
  /* Fünf von zwölf Spalten, nicht sechs: erst die freie Gasse
     zwischen linker und rechter Karte macht die Verbindungslinie
     sichtbar. Bei je sechs Spalten stoßen die Karten fast
     aneinander und verdecken die Linie bis auf einen Stummel. */
  .fakt-zelle:nth-child(1) { grid-row: 1; grid-column: 1 / span 5; }
  .fakt-zelle:nth-child(2) { grid-row: 2; grid-column: 8 / span 5; }
  .fakt-zelle:nth-child(3) { grid-row: 3; grid-column: 2 / span 5; }
  .fakt-zelle:nth-child(4) { grid-row: 4; grid-column: 8 / span 5; }
  .fakt-zelle:nth-child(n + 2) { margin-top: calc(var(--s-48) * -1); }
}

/* Dieselbe Kartusche wie die Ablauf-Schritte: doppelte Kontur,
   nichts sitzt exakt gerade. Die Papierfläche ist Pflicht — sie
   deckt die Linie ab, die hinter den Karten durchläuft. */
.fakt {
  border: var(--frame-w) solid var(--ink); background: var(--paper);
  padding: var(--s-24);
  box-shadow: inset 0 0 0 var(--min-line) var(--paper),
              inset 0 0 0 calc(var(--min-line) + var(--frame-gap)) var(--ink);
  display: flex; flex-direction: column; gap: var(--s-8);
  transform: rotate(var(--tilt));
}
.fakt > * { margin: var(--frame-gap); }
.fakt-zelle:nth-child(even) .fakt { transform: rotate(var(--tilt-alt)); }

/* Ziffer links, Bildmarke rechts. Der Slot ist bei allen vier
   gleich hoch, damit Kopf und Text auf einer Linie sitzen —
   auch bei den drei Karten ohne Marke. */
.fakt-zeichen {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-16); min-height: 62px; color: var(--ink);
}
.fakt-num {
  font-family: var(--font-display); font-size: 2.2rem; line-height: 1;
  color: var(--ink);
}
.fakt-mark { width: 62px; height: 62px; flex: none; }
/* Ohne eigene Zeilenhöhe — genau wie .flow-step h3, das hier das
   Vorbild ist. Die 1.0 aus --lead-display stammt aus dem alten
   Entwurf mit durchweg einzeiligen Köpfen; zweizeilig stoßen die
   Versalien darin aneinander. */
.fakt-kopf {
  font-family: var(--font-display); font-weight: 400;
  font-size: var(--size-h3); color: var(--ink);
  margin-top: var(--s-8);
}
.fakt-text {
  font-size: var(--size-small); color: var(--text-quiet);
  max-width: 40ch;
}

/* Die Verbindungslinie. Zwei identische Pfade übereinander:
   „Spur" liegt blass da, „Zug" in voller Druckfarbe und wird
   von einem Rechteck freigegeben, das am Scrollstand nach unten
   wächst (initFaktenLinie). Dasselbe Prinzip wie .flow-progress,
   nur an einem Pfad statt an einer Leiste.

   Bewusst kein laufendes Strichmuster: die Linie zeigt einen
   Stand an, sie animiert nicht von selbst. */
.fakten-linie {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%; pointer-events: none;
  overflow: visible;
}
.fakten-linie path {
  stroke-width: var(--min-line);
  stroke-dasharray: 9 7; stroke-linecap: butt;
  vector-effect: non-scaling-stroke;
}
.fakten-linie-spur { stroke: rgb(var(--green-rgb) / .28); }
.fakten-linie-zug  { stroke: var(--ink); }
/* Ohne JS bekommt kein Pfad ein `d` und die Linie entfällt
   ersatzlos — die Karten stehen dann untereinander und brauchen
   keine Führung. */

/* ------------------------------------------------------------
   AM OFEN — Video-Sektion zwischen Hero und Konfigurator.
   Rahmen, Korn und Kippung kommen aus .foto, nicht von hier.
   ------------------------------------------------------------ */
.atwork { position: relative; overflow: hidden; padding: var(--section-pad) 0; }

/* Die Druckfläche hinter dem Medium. Sie läuft links aus dem
   Bild und endet an der Kante — kein Kasten, kein Rahmen.

   Raster und Korn stehen einmal und gelten für beide Auftritte:
   auf dem Desktop als eigene Fläche neben der Bildspalte, auf
   Mobile als Untergrund des Textblocks (Regel weiter unten).
   Unterschiedlich sind nur Lage und Deckkraft — das Muster ist
   an beiden Stellen dasselbe. */
.atwork-field { display: none; }
.atwork-field,
.atwork-text::before {
  background-image:
    var(--grain),
    radial-gradient(rgb(var(--green-rgb) / .16) var(--pat-mid-dot),
                    transparent calc(var(--pat-mid-dot) + .6px));
  background-size: 180px 180px, var(--pat-mid) var(--pat-mid);
  background-blend-mode: overlay, normal;
}
@media (min-width: 700px) {
  .atwork-field {
    display: block; position: absolute; z-index: 0;
    left: calc(var(--gutter) * -1); width: 52%;
    top: var(--s-48); bottom: var(--s-48);
    opacity: calc(var(--foto-grain) + .7);
  }
}

/* Mobile trägt dieselbe Fläche, aber an anderer Stelle: das Layout
   ist einspaltig gestapelt, die Desktop-Koordinaten (seitlich neben
   der Bildspalte) treffen dort nichts. Statt der eigenen Fläche
   liegt das Raster direkt hinter dem Textblock und läuft nach beiden
   Seiten aus dem Satzspiegel bis an die Seitenkante — .atwork
   schneidet den Überstand ab.

   Getragen wird es hier von einem Pseudoelement statt von .atwork-
   field: das Div hängt an .atwork, kann also nicht am Textblock
   ausgerichtet werden, ohne dessen Höhe zu kennen.

   Deutlich blasser als auf dem Desktop (dort + .7) — der Text steht
   hier unmittelbar darauf und muss lesbar bleiben. + .33 ist am Gerät
   ausgemessen: darunter bleibt nur ein Farbstich ohne erkennbares
   Raster, darüber wird das Papier spürbar grau. */
@media (max-width: 699px) {
  .atwork-text { position: relative; }
  .atwork-text::before {
    content: ""; position: absolute; z-index: -1; pointer-events: none;
    top: calc(var(--s-24) * -1); bottom: calc(var(--s-24) * -1);
    left: calc(var(--gutter) * -1); right: calc(var(--gutter) * -1);
    opacity: calc(var(--foto-grain) + .33);
  }
}

.atwork-grid { position: relative; z-index: 1; display: grid; gap: var(--s-48); }
@media (min-width: 700px) {
  .atwork-grid {
    grid-template-columns: minmax(260px, 380px) auto minmax(0, 1fr);
    align-items: center;
  }
}

.atwork-media { position: relative; margin: 0; }
/* Das Seitenverhältnis kommt aus dem CSS, nicht aus den Attributen
   des Videos. So steht die Geometrie ab dem ersten Frame — egal,
   welches Format JS gleich einhängt. Kein Sprung beim Wechsel. */
.atwork-clip { display: block; aspect-ratio: 4 / 5; }
@media (min-width: 700px) { .atwork-clip { aspect-ratio: 9 / 16; } }
/* Mobil steht das Video größer als der Satzspiegel erlaubt (25.08.2026):
   es soll auf dem Telefon prominenter wirken als der Text darunter.

   Breite allein reicht dafür nicht: der Viewport deckelt sie. Der Kasten
   steht deshalb seit 25.08.2026 auf 4:5 statt 1:1 (+25% Fläche), und
   zusätzlich tritt er 12px aus dem Satzspiegel heraus. Zusammen sind das
   rund +44% Fläche gegenüber dem alten Quadrat im Satzspiegel.

   Mehr als diese 12px gehen nicht. Der Rahmen ist gekippt (--tilt); bei
   4:5 braucht seine Diagonale W x 1.026 an Platz — mehr als beim Quadrat,
   weil die Kippung mit der Höhe teurer wird. Bei 375px Viewport bleiben
   danach gut 3px bis zur Kante. Wer weiter aufdreht, schneidet die
   gekippten Ecken an `overflow-x: clip` ab, und der Rahmen ist
   Markenpflicht, kein Zierrat.

   Der Ausbruch nur bis 480px — das deckt jedes Telefon im Hochformat ab
   (das breiteste liegt bei 430px). Darüber wird der Rest bis zur Diagonale
   zu knapp: die Kippung kostet W x 0.026, der Ausbruch gibt aber konstante
   24px dazu. Die Bildunterschrift bleibt im Satzspiegel stehen — sie
   gehört zum Text, nicht zum Bild. */
@media (max-width: 480px) {
  .atwork-clip { margin-inline: -12px; }
  /* Der Scroll-Reveal setzt von scale(1.06) auf 1 auf. Mit der
     ausgebrochenen Breite ragt der gekippte Rahmen in genau diesem
     Moment über die Kante und wird von `overflow-x: clip` angeschnitten
     — sichtbar als kurz abgesägte Ecke. Die Fläche setzt hier deshalb
     nur mit der Deckkraft auf. Die Kippung sitzt auf .atwork-clip und
     bleibt davon unberührt. */
  .js .atwork-media.reveal { transform: none; }
}
.atwork-clip video { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Senkrechte Perforation — trennt Bild und Text wie beim Beleg */
.atwork-perf { display: none; }
@media (min-width: 700px) {
  .atwork-perf {
    display: block; width: var(--pat-band); align-self: stretch;
    background-image: radial-gradient(var(--hair) var(--pat-band-r),
                                      transparent calc(var(--pat-band-r) + .6px));
    background-size: var(--pat-band) var(--pat-band);
    background-position: center;
  }
}

.atwork-cap {
  margin-top: var(--s-24); width: fit-content;
  font-family: var(--font-accent); font-weight: 700;
  font-size: var(--size-micro); letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--text-quiet);
  transform: rotate(var(--tilt-alt));
}

/* Die Überschrift sitzt als aufgesetzter Stempel: gekippt, und die
   Farbfläche bricht in Fehlstellen auf — dasselbe Korn wie bei
   .ink-surface, nur umgekehrt herum. Eine Druckfarbe, nie zwei. */
.headline--stamp {
  position: relative; width: fit-content;
  transform: rotate(var(--tilt)); transform-origin: left center;
}
.headline--stamp::before {
  content: ""; position: absolute; inset: -2px 0; z-index: 1;
  pointer-events: none;
  background-image: var(--grain);
  background-size: 180px 180px;
  opacity: var(--ink-cover);
  mix-blend-mode: screen;
}
/* Die Doppellinie bleibt darunter unangetastet */
.headline--stamp::after { position: relative; z-index: 2; }

.atwork-say { max-width: var(--measure); margin-top: var(--s-24); }
.atwork-say p { line-height: var(--lead-body); }
.atwork-say p + p { margin-top: var(--s-24); }

/* Die Brille als Absatzmarke — das Sekundärzeichen aus B5.
   Der einzige Schmuck in der Textspalte. */
.atwork-say .glasses {
  display: flex; justify-content: center; align-items: center; gap: 4px;
  width: 34px; margin: var(--s-32) auto;
  color: var(--text-quiet);
}
.atwork-say .glasses i {
  display: block; width: 13px; height: 9px;
  border: var(--min-line) solid currentColor; border-radius: 50%;
}

.druck img { filter: var(--img-filter); }
.druck::before {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: var(--ink); mix-blend-mode: multiply; opacity: .78;
}
/* Platzhalter, solange die Datei fehlt */
.ph-note {
  position: absolute; inset: 0; z-index: 3;
  display: grid; place-content: center; gap: 4px; text-align: center;
  background-image:
    linear-gradient(45deg, transparent calc(50% - .5px), rgb(var(--grey-rgb) / .3) 50%, transparent calc(50% + .5px)),
    linear-gradient(-45deg, transparent calc(50% - .5px), rgb(var(--grey-rgb) / .3) 50%, transparent calc(50% + .5px));
}
.ph-note b, .ph-note small {
  justify-self: center; background: var(--cream); padding: 3px 8px;
  border: 1px solid var(--hair); font-size: var(--size-micro);
  letter-spacing: var(--track-label); text-transform: uppercase;
}
.ph-note small { text-transform: none; letter-spacing: 0; }
.foto.has-img .ph-note { display: none; }

/* ------------------------------------------------------------
   SHOWCASE — grosse Bilder, Schrift daneben.
   Keine Miniaturen: jedes Bild fuellt mindestens eine Spalte.
   ------------------------------------------------------------ */
/* ------------------------------------------------------------
   MOMENTE — Hover-Akkordeon (Neubau 15.08.2026).

   Fünf Karten in einer Reihe, jede schmal. Beim Hover weitet
   sich eine Karte und die Nachbarn weichen zurück; der Text
   liegt direkt auf dem Bild, nicht darunter.

   Grundzustand (Mobil, Touch, ohne JS): gestapelt, volle Breite,
   Text durchgehend sichtbar. Das Akkordeon ist ein Aufschlag für
   Zeigergeräte, kein Fundament — auf Touch gäbe es sonst eine
   Interaktion, die niemand auslösen kann.

   Rahmen, Korn und Bildverhalten kommen aus .foto.
   ------------------------------------------------------------ */
.momente { display: grid; gap: var(--s-24); margin-top: var(--s-48); }

.moment {
  position: relative; isolation: isolate;
  /* Hochformat: die Karten sind im Akkordeon schmal und hoch. */
  aspect-ratio: 4 / 5;
}
.moment img { width: 100%; height: 100%; object-fit: cover; display: block; }
.moment picture { display: block; height: 100%; }

/* Der Scrim macht den Text lesbar, ohne einen Kasten zu bauen.
   Grey, nicht Schwarz — die Marke kennt kein Schwarz. */
.moment-scrim {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: linear-gradient(to top,
    rgb(var(--grey-rgb) / .90) 0%,
    rgb(var(--grey-rgb) / .62) 34%,
    rgb(var(--grey-rgb) / .10) 68%,
    transparent 100%);
}

.moment-body {
  position: absolute; z-index: 3;
  left: 0; right: 0; bottom: 0;
  padding: var(--s-24) var(--s-16) var(--s-16);
  color: var(--cream);
}
/* Giallo für den Kicker, wie auf jeder anderen dunklen Fläche
   der Seite (.grey-surface .kicker). */
.moment-body .kicker { display: block; color: var(--giallo); opacity: 1; }
/* Satzform, nicht Versalien — Climate Crisis ist dafür gesperrt. */
.moment-body h3 {
  margin-top: var(--s-8);
  font-family: var(--font-body); font-weight: 700;
  font-size: var(--size-h3); line-height: 1.25; color: var(--cream);
}
.moment-body p {
  margin-top: var(--s-8); font-size: var(--size-small);
  color: rgb(var(--cream-rgb) / .84); max-width: 42ch;
}

/* ---------- Akkordeon: nur mit echtem Zeiger und genug Breite ---------- */
@media (hover: hover) and (min-width: 900px) {
  .momente {
    display: flex; gap: var(--frame-w);
    height: min(74svh, 640px); margin-top: var(--s-48);
  }
  .moment {
    flex: 1 1 0; min-width: 0; aspect-ratio: auto;
    /* Mechanisch, nicht flüssig: die Karte rastet in drei Stufen
       auf, sie gleitet nicht. Dieselbe Bewegungssprache wie das
       Wechselwort im Hero. */
    transition: flex-grow var(--dur-reveal) var(--ease);
  }
  /* Erst wenn die Reihe angefasst wird, weichen die anderen
     zurück. Ohne Hover stehen alle fünf gleich breit. */
  .momente:hover .moment { flex-grow: .74; }
  .momente .moment:hover,
  .momente .moment:focus-within { flex-grow: 3.6; }

  /* Schmal ist kein Platz für Headline und Subline. Der Kicker
     bleibt immer stehen — er ist die Beschriftung der Karte —,
     der Rest faltet sich auf Höhe 0 zusammen und kommt beim
     Aufweiten dazu.

     0fr → 1fr statt max-height: die Zeile misst sich selbst, es
     muss kein Maximalwert geraten werden, der bei langen Texten
     abschneidet oder bei kurzen nachhängt.

     Der Trick braucht genau EIN Kind — Headline und Sub liegen
     deshalb noch einmal in einem inneren <div>. Mit zwei Kindern
     legt der Browser eine zweite, implizite Zeile auf `auto` an
     und nichts faltet sich zusammen. */
  .moment-body { padding: var(--s-32) var(--s-16) var(--s-24); }
  .moment-more {
    display: grid; grid-template-rows: 0fr; overflow: hidden; opacity: 0;
    transition: grid-template-rows var(--dur-reveal) var(--ease),
                opacity var(--dur-base) var(--ease);
  }
  .moment-more > * { min-height: 0; }
  .momente .moment:hover .moment-more,
  .momente .moment:focus-within .moment-more { grid-template-rows: 1fr; opacity: 1; }

  /* Der Kicker bricht in der schmalen Karte sonst mitten im Wort. */
  .moment-body .kicker { white-space: nowrap; }
}

/* Ohne JS werden die Karten nicht gestaffelt eingeblendet — das
   regelt .reveal, das renderMomente mitgibt. */

/* ------------------------------------------------------------
   GALERIE — die aufklappende Bildmatrix (16.08.2026).
   ⚠ ERSTER ENTWURF, Platzhalterbilder.

   Die einzige dunkle Fläche der Seite. Sie ist bewusst der
   Bruch: zwischen zwei Cream-Sektionen steht ein Block, in dem
   die Bilder ohne Papier drumherum schweben.

   Die Höhe der Sektion IST der Scrollweg. Die Bühne darin
   klebt; alles, was sich bewegt, sitzt in ihr. Deshalb steht
   hier nur der Ruhezustand — die laufenden Werte schreibt
   initGalerie direkt auf die Elemente.
   ------------------------------------------------------------ */
.galerie {
  --nacht:  #0D0B09;          /* warmes Schwarz, aus --grey heruntergezogen */
  --kachel: #1A1613;          /* Fläche, solange ein Bild noch lädt         */
  position: relative;
  height: 450svh;
  background: var(--nacht);
}
@media (min-width: 900px) { .galerie { height: 600svh; } }

.galerie-stage {
  position: sticky; top: 0;
  height: 100svh; width: 100%;
  display: grid; place-items: center;
  overflow: hidden;
}
/* Startwerte = erster Frame des Effekts. Ohne sie blitzt beim
   Laden kurz der Endzustand auf. */
.galerie-rahmen {
  position: relative;
  width: 90vw; height: 80svh;
  max-width: 1920px;
  border-radius: 48px;
  border: 4px solid rgb(var(--green-rgb) / .55);
  background: #000;
  overflow: hidden;
}
.galerie-tiefe {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  perspective: 1000px;
  pointer-events: none;
}
.galerie-matrix {
  display: flex; gap: var(--s-12);
  justify-content: center; align-items: center;
  width: 120vw; height: 150svh;
  transform-style: preserve-3d;
  will-change: transform;
}
.galerie-spalte {
  display: flex; flex-direction: column; gap: var(--s-12);
  width: 22vw; min-width: 128px;
  will-change: transform;
}
.galerie-kachel {
  flex: none; width: 100%; height: 160px;
  background: var(--kachel);
  overflow: hidden;
}
.galerie-kachel picture { display: block; height: 100%; }
.galerie-kachel img {
  width: 100%; height: 100%;
  object-fit: cover; display: block;
  opacity: .82;
}

/* Zwei Innenschatten legen den Rand in den Schatten, damit die
   Matrix am Rahmen ausläuft statt abgeschnitten zu wirken. */
.galerie-vignette {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  box-shadow:
    inset 0  100px 150px -50px var(--nacht),
    inset 0 -100px 150px -50px var(--nacht),
    inset  150px 0 150px -50px var(--nacht),
    inset -150px 0 150px -50px var(--nacht);
}
/* Der Titel gehört zur ersten Phase — initGalerie blendet ihn
   aus, sobald die Matrix kippt. */
.galerie-titel {
  position: absolute; z-index: 3;
  left: 0; right: 0; bottom: var(--s-48);
  padding: 0 var(--gutter);
  text-align: center; pointer-events: none;
}
.galerie-titel .kicker { color: var(--giallo); }
.galerie-titel .headline { color: var(--cream); margin-top: var(--s-8); }

@media (min-width: 700px) {
  .galerie-matrix { gap: var(--s-24); }
  .galerie-spalte { gap: var(--s-24); min-width: 200px; }
  .galerie-kachel { height: 300px; }
}
@media (min-width: 1000px) {
  .galerie-kachel { height: 400px; }
}

/* Ohne JS und bei reduzierter Bewegung: kein Kleben, kein
   Kippen, kein Scrollweg. Die Matrix wird zum ruhigen Raster,
   die Bilder bleiben vollständig sichtbar. */
.galerie.is-still,
html:not(.js) .galerie { height: auto; }
.galerie.is-still .galerie-stage,
html:not(.js) .galerie .galerie-stage { position: static; height: auto; padding: var(--section-pad) 0; }
.galerie.is-still .galerie-rahmen,
html:not(.js) .galerie .galerie-rahmen {
  width: 100%; height: auto; border-radius: 0; border-width: 0;
  background: transparent;
}
.galerie.is-still .galerie-tiefe,
html:not(.js) .galerie .galerie-tiefe { position: static; perspective: none; pointer-events: auto; }
.galerie.is-still .galerie-matrix,
html:not(.js) .galerie .galerie-matrix {
  display: grid; grid-template-columns: repeat(2, 1fr);
  width: 100%; height: auto; transform: none;
  padding: 0 var(--gutter);
}
.galerie.is-still .galerie-spalte,
html:not(.js) .galerie .galerie-spalte { width: auto; min-width: 0; transform: none; }
.galerie.is-still .galerie-vignette,
html:not(.js) .galerie .galerie-vignette { display: none; }
/* Die zweite Lage füllt nur die Parallax-Wege. Im Raster stünde
   jedes Bild dadurch zweimal. */
.galerie.is-still .galerie-kachel--kopie,
html:not(.js) .galerie .galerie-kachel--kopie { display: none; }
.galerie.is-still .galerie-titel,
html:not(.js) .galerie .galerie-titel {
  position: static; text-align: left; margin-bottom: var(--s-32); opacity: 1;
}
@media (min-width: 900px) {
  .galerie.is-still .galerie-matrix,
  html:not(.js) .galerie .galerie-matrix { grid-template-columns: repeat(4, 1fr); }
}

/* ------------------------------------------------------------
   FOOTER
   ------------------------------------------------------------ */
.footer { border-top: var(--min-line) solid var(--ink); padding: var(--s-32) 0; }
.footer-inner {
  display: flex; flex-wrap: wrap; gap: var(--s-12) var(--s-24);
  justify-content: space-between; align-items: baseline;
  font-size: var(--size-micro); font-weight: 700;
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--text-quiet);
}
.footer a { color: var(--ink); text-underline-offset: 3px; }

/* ------------------------------------------------------------
   SCROLL-REVEAL — setzt auf (Skalierung 1.06 → 1), fährt
   nicht ein. Ohne JS: alles sichtbar.
   ------------------------------------------------------------ */
.js .reveal {
  opacity: 0; transform: scale(var(--reveal-scale));
  transition: opacity var(--dur-reveal) var(--ease-soft),
              transform var(--dur-reveal) var(--ease-press);
  transition-delay: calc(var(--i, 0) * var(--stagger));
}
.js .reveal.is-in { opacity: 1; transform: none; }
/* Gekippte Elemente behalten ihre Kippung nach dem Reveal */
.js .occasion.reveal.is-in:hover { transform: translateY(-2px) rotate(var(--tilt-alt)); }

@media (prefers-reduced-motion: reduce) {
  .js .reveal { opacity: 1; transform: none; }
}

/* ============================================================
   ENTWURF 2 — NEUE BAUSTEINE
   Alles hier zieht Farbe, Schrift, Abstand, Rahmen, Kippung und
   Bewegung ausschliesslich aus css/tokens.css.
   ============================================================ */

/* ------------------------------------------------------------
   DOPPELLINIE — 2px, 5px Lücke, 2px. Sitzt unter der Headline.
   (brand-tokens.json → lines.double)
   ------------------------------------------------------------ */
.headline::after {
  content: ""; display: block; width: 64px; margin-top: var(--s-16);
  height: calc(var(--min-line) * 2 + var(--rule-double-gap));
  background-image: linear-gradient(currentColor 0 0), linear-gradient(currentColor 0 0);
  background-size: 100% var(--min-line);
  background-position: 0 0, 0 100%;
  background-repeat: no-repeat;
}

/* Kartonfaser — Papierwirkung ohne Bilddatei. Faser, nicht
   Karopapier: nur eine Richtung, und sehr zurückgenommen. */
.karton {
  background-image:
    repeating-linear-gradient(92deg, rgb(var(--grey-rgb) / .045) 0 1px, transparent 1px var(--pat-fine));
}

/* Buttons: dokumentierte Varianten vervollständigt */
.btn--grey { background: var(--grey); border-color: var(--grey); color: var(--cream); }
.btn--small { min-height: var(--tap); padding: 9px 14px; font-size: var(--size-micro); }
.linkbtn {
  background: none; border: 0; padding: 0; cursor: pointer; color: inherit;
  font: inherit; font-weight: 700; text-decoration: underline;
  text-underline-offset: 3px; min-height: var(--tap);
}

/* ------------------------------------------------------------
   DIE BRILLE ALS SEKUNDÄRZEICHEN
   Zwei Ovale — Trenner, Aufzählungspunkt, Scroll-Fortschritt.
   ------------------------------------------------------------ */
.list-brille { list-style: none; margin: var(--s-12) 0 0; padding: 0; }
.list-brille li { position: relative; padding-left: 32px; margin-top: var(--s-8); }
.list-brille li::before {
  content: ""; position: absolute; left: 0; top: .58em;
  width: 9px; height: 7px; border-radius: 50%;
  background: currentColor; box-shadow: 12px 0 0 currentColor;
}

.brille-rule {
  display: flex; align-items: center; justify-content: center; gap: 5px;
  padding: var(--s-48) var(--gutter); color: var(--ink);
}
.brille-rule::before, .brille-rule::after {
  content: ""; height: var(--min-line); background: currentColor;
  flex: 1 1 auto; max-width: 200px;
}
.brille-rule i { display: block; width: 26px; height: 18px; border: var(--min-line) solid currentColor; border-radius: 50%; }

/* Scroll-Fortschritt im Header: ihr echter Kopf, nicht
   nachgezeichnet. Das <symbol id="nonna"> ist ein einziger
   zusammengesetzter Pfad — die Brille lässt sich daraus nicht
   isoliert ansteuern. Deshalb liegen zwei einfache Ovale als
   Overlay auf den gezeichneten Gläsern; das Icon selbst bleibt
   unangetastet. */
.nonna-scroll {
  position: relative; display: block; flex: none;
  width: 34px; height: 34px; color: var(--ink);
}
.nonna-scroll-icon {
  display: block; width: 100%; height: 100%; fill: currentColor;
}
.nonna-scroll-lens {
  position: absolute; overflow: hidden; border-radius: 50%;
  background: transparent;
}
/* Die Werte sind nicht geschätzt, sondern am gerasterten Pfad
   gemessen (Zeilenscan über das 100×100-viewBox, Suche nach den
   beiden breiten Vollflächen): linkes Glas x 10,3–39,8 / y 43–65,
   rechtes Glas x 43,5–72,8 / y 43–64,5. Die Gläser stehen leicht
   schräg, und zwar gegenläufig — deshalb zwei Vorzeichen.
   Wer das Symbol austauscht, muss neu messen. */
.nonna-scroll-lens--l {
  left: 10.3%; top: 43.1%; width: 29.4%; height: 21.8%;
  transform: rotate(8.6deg);
}
.nonna-scroll-lens--r {
  left: 43.4%; top: 43.2%; width: 29.5%; height: 21.2%;
  transform: rotate(-9.3deg);
}
/* Deckendes Rot, kein Blend-Modus: das Glas ist Green, ein
   multiply darüber ergäbe fast Schwarz auf Schwarz. So wandert
   sichtbar Pomodoro über die Gläser, von links nach rechts. */
.nonna-scroll-lens i {
  display: block; width: 100%; height: 100%;
  background: var(--rosso);
  transform: scaleX(0); transform-origin: left center;
}
/* 520 statt vormals 480: seit die Wortmarke im mobilen Header mittig
   steht und bis 28px wächst, stieß sie bei 481px genau an die Brille
   (nachgemessen: 1px Überlappung). Ab 520px liegen wieder 19px
   dazwischen. */
@media (max-width: 520px) { .nonna-scroll { display: none; } }

/* ------------------------------------------------------------
   DIE NONNA an den Einwürfen und in der Unterschrift.
   Dieselbe Bildmarke wie im Header, nur kleiner. Grey ist die
   Konturfarbe der Marke; auf der Druckfarbe wird daraus Cream.
   Mindestgröße 32px, nicht gedreht, nicht beschnitten.
   ------------------------------------------------------------ */
.nonna-face { flex: none; width: var(--nn-size, 46px); color: var(--grey); }
.nonna-face--sm  { --nn-size: 46px; }
.nonna-face--sig { --nn-size: 48px; }
.on-ink .nonna-face { color: var(--paper); }

/* ------------------------------------------------------------
   IHRE EINWÜRFE — fünf, kein sechster.
   Sprechblase im Stempelstil: Rahmen Pflicht, leicht gekippt,
   eine Druckfarbe. Grey auf Cream oder Cream auf Green.
   ------------------------------------------------------------ */
.nonna-say { display: flex; align-items: flex-start; gap: var(--s-12); margin-bottom: var(--s-16); }
.nonna-say--wide { margin-top: var(--s-24); }
.nonna-say--center { justify-content: center; margin-top: var(--s-24); margin-bottom: 0; }

.nonna-bubble {
  position: relative; width: fit-content; max-width: 34ch;
  padding: 10px 14px;
  font-family: var(--font-accent); font-weight: 700;
  font-size: var(--size-small); line-height: 1.4;
  background: var(--paper); color: var(--grey);
  border: var(--frame-w) solid var(--grey);
  transform: rotate(var(--tilt-alt));
}
.nonna-bubble::after {
  content: ""; position: absolute; left: 18px; bottom: calc(var(--frame-w) * -1 - 9px);
  width: 12px; height: 12px;
  background: var(--paper);
  border-left: var(--frame-w) solid var(--grey);
  border-bottom: var(--frame-w) solid var(--grey);
  transform: rotate(-45deg);
}
.nonna-bubble--ink {
  background: var(--green); color: var(--cream); border-color: var(--cream);
}
.nonna-bubble--ink::after { background: var(--green); border-color: var(--cream); }
.nonna-bubble--inline { margin-top: var(--s-16); transform: rotate(var(--tilt)); }

/* ------------------------------------------------------------
   KONFIGURATOR — Ergänzungen
   ------------------------------------------------------------ */
.konf-pair { display: grid; gap: var(--s-16); margin-top: var(--s-16); }
@media (min-width: 560px) { .konf-pair { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.konf-pair .field-hint { display: block; margin-top: 6px; }
.field-hint.is-warn { color: var(--rosso); font-weight: 700; }
.step-note { margin-top: var(--s-8); font-size: var(--size-small); color: var(--text-quiet); }

.restore {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-8) var(--s-12);
  margin-top: var(--s-16); padding: var(--s-8) var(--s-12); width: fit-content;
  border: var(--min-line) dashed var(--ink); color: var(--ink);
  font-size: var(--size-small);
}

/* Sortenwahl — Textkarten. Kleine Bilder gibt es hier nicht. */
.sorten { display: grid; gap: var(--s-8); margin-top: var(--s-16);
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); }
.sorte {
  position: relative; display: flex; flex-direction: column; gap: 4px;
  min-height: var(--tap); padding: var(--s-12);
  text-align: left; cursor: pointer;
  background: var(--paper); color: var(--text);
  border: var(--min-line) solid var(--ink); border-radius: 0;
  transition: transform var(--dur-fast) var(--ease-press),
              box-shadow var(--dur-base) var(--ease), opacity var(--dur-base) var(--ease);
}
.sorte:hover:not(:disabled) { transform: translateY(-2px); }
.sorte b { font-size: var(--size-small); font-weight: 700; color: var(--ink); }
.sorte-belag { font-size: var(--size-micro); line-height: 1.5; color: var(--text-quiet); }
.sorte-veg {
  width: fit-content; margin-top: 2px; padding: 1px 6px;
  font-size: 10px; font-weight: 700; letter-spacing: var(--track-label);
  text-transform: uppercase; background: var(--giallo); color: var(--grey);
}
.sorte[aria-pressed="true"] { box-shadow: 0 0 0 var(--frame-w) var(--ink); }
.sorte[aria-pressed="true"]::after {
  content: "✓"; position: absolute; top: -10px; right: -8px;
  width: 24px; height: 24px; display: grid; place-items: center;
  background: var(--ink); color: var(--paper); font-weight: 700;
}
/* Kontingent voll: ausgrauen, nicht ausblenden */
.sorte.is-off { opacity: .42; cursor: not-allowed; border-style: dashed; }
.sorten-fix { margin-top: var(--s-16); color: var(--text); font-size: var(--size-small); }
.sorten-fix b { color: var(--ink); }

/* Karten-Überschrift in der Sortenwahl — läuft über alle Spalten */
.sorten-karte {
  grid-column: 1 / -1; margin-top: var(--s-8);
  font-family: var(--font-accent); font-weight: 700;
  font-size: var(--size-micro); letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink);
  padding-bottom: 4px; border-bottom: var(--min-line) solid var(--ink);
}
.sorten-karte:first-child { margin-top: 0; }

/* „ab NONNA-Paket wählbar" — steht nur an gesperrten Sorten */
.sorte-lock {
  display: none; width: fit-content; margin-top: 2px; padding: 1px 6px;
  font-size: 10px; font-weight: 700; letter-spacing: var(--track-label);
  text-transform: uppercase;
  border: var(--min-line) dashed var(--ink); color: var(--ink);
}
.sorte.is-locked { opacity: .55; cursor: not-allowed; border-style: dashed; }
.sorte.is-locked .sorte-lock { display: block; }
.sorte.is-locked .sorte-veg { display: none; }

/* Wunschposition ohne Preis */
.extra--wunsch .price-tag {
  color: var(--ink); font-variant-numeric: normal;
  letter-spacing: var(--track-label); text-transform: uppercase;
  font-size: var(--size-micro);
}

/* Kapazitätshinweis bei PIZZA SATT */
.satt-warn {
  margin-top: var(--s-16); padding: var(--s-8) var(--s-12);
  border: var(--min-line) dashed var(--ink); color: var(--ink);
  font-size: var(--size-small);
}



/* Einrasten beim Klick — Stempel, höchstens 1.04 */
@keyframes sn-stamp { 0% { transform: scale(1.04); } 100% { transform: none; } }
.js .is-stamp { animation: sn-stamp var(--dur-fast) var(--ease-press); }

/* ------------------------------------------------------------
   BELEG — aufgeschlüsselt. Wer sieht, wofür er zahlt,
   diskutiert nicht über den Preis.
   ------------------------------------------------------------ */
.receipt-col { display: grid; gap: var(--s-16); }
.receipt-sticky { display: grid; gap: var(--s-16); align-content: start; }

/* Was nicht mitkleben muss, steht unter dem Raster: Aufklapper und
   Teilen-Knöpfe unter den Schritten, die Stimme unter dem Beleg. */
.konf-after { display: grid; gap: var(--s-16); margin-top: var(--s-32); }
@media (min-width: 960px) {
  .konf-after {
    grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
    column-gap: var(--s-32); align-items: start;
  }
  .konf-after .why,
  .konf-after .receipt-actions { grid-column: 1; }
  .konf-after .quote--price { grid-column: 2; grid-row: 1 / span 2; }
}
.rc-group + .rc-group { margin-top: var(--s-16); }
.rc-head {
  font-family: var(--font-accent); font-weight: 700; font-size: var(--size-micro);
  letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink);
  padding-bottom: 4px; border-bottom: var(--min-line) solid var(--ink);
}
.rc-incl { padding-top: 4px; }
.rc-line {
  display: flex; align-items: baseline; gap: 8px; padding: 3px 0;
  font-size: var(--size-micro); color: var(--text-quiet);
}
.rc-line .dots { flex: 1; border-bottom: 1px dotted rgb(var(--grey-rgb) / .3); }
.rc-amount {
  padding-top: 6px; text-align: right;
  font-weight: 700; font-size: var(--size-small);
  font-variant-numeric: tabular-nums;
}
.rc-note { padding: 6px 0; font-size: var(--size-micro); color: var(--text-quiet); }
.rc-row.is-strong { font-weight: 700; }
.rc-per {
  margin-top: 4px; text-align: right;
  font-size: var(--size-small); font-weight: 700;
  font-variant-numeric: tabular-nums; color: var(--rosso);
}

/* Neue Zeilen rattern von oben ein — sechs Rasten, wie Papier */
@keyframes rc-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
.js .rc-row.is-new { animation: rc-in var(--dur-reveal) steps(6, end) both; }

/* Preistransparenz — funktioniert auch ohne JS */
.why {
  border: var(--min-line) solid var(--ink); padding: var(--s-12) var(--s-16);
  font-size: var(--size-small);
}
.why > summary {
  cursor: pointer; list-style: none; min-height: var(--tap);
  display: flex; align-items: center; gap: 10px;
  font-family: var(--font-accent); font-weight: 700;
  letter-spacing: var(--track-label); text-transform: uppercase;
  font-size: var(--size-label); color: var(--ink);
}
.why > summary::-webkit-details-marker { display: none; }
.why > summary::before {
  content: "+"; flex: none;
  width: 22px; height: 22px; display: grid; place-items: center;
  border: var(--min-line) solid var(--ink);
}
.why[open] > summary::before { content: "–"; }
.why > p { margin-top: var(--s-12); color: var(--text-quiet); }

.receipt-actions { display: grid; gap: var(--s-12); }
.share-row { display: flex; flex-wrap: wrap; gap: var(--s-8); }
.share-row .btn { flex: 1 1 auto; }
.share-note {
  display: none; font-size: var(--size-micro); color: var(--ink); font-weight: 700;
}
.share-note.is-on { display: block; }

.quote--price { background: var(--paper); }
.quote--price p { font-size: var(--size-small); }

/* ------------------------------------------------------------
   ABLAUF — Ofentemperatur. Kein Flammen-Motiv, nur die Zahl.
   ------------------------------------------------------------ */
.oven { display: flex; align-items: baseline; gap: 8px; margin-top: auto; padding-top: var(--s-12); }
.oven-label {
  font-family: var(--font-accent); font-weight: 700; font-size: var(--size-micro);
  letter-spacing: var(--track-label); text-transform: uppercase; color: var(--text-quiet);
}
.oven-val {
  font-family: var(--font-display); font-weight: 400; font-size: 2.4rem;
  line-height: 1; color: var(--ink); font-variant-numeric: tabular-nums;
}
.oven-unit { font-weight: 700; color: var(--ink); }

/* ------------------------------------------------------------
   ZAHLUNG UND STORNO
   ------------------------------------------------------------ */
.pay-card {
  border: var(--frame-w) solid var(--ink); background: var(--paper);
  padding: var(--s-32) var(--s-24); transform: rotate(var(--tilt));
  background-image:
    repeating-linear-gradient(92deg, rgb(var(--grey-rgb) / .045) 0 1px, transparent 1px var(--pat-fine));
}
.pay-grid { display: grid; gap: var(--s-32); margin-top: var(--s-24); }
@media (min-width: 700px) { .pay-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.pay-grid h3 {
  font-family: var(--font-accent); font-weight: 700; font-size: var(--size-label);
  letter-spacing: var(--track-claim); text-transform: uppercase; color: var(--ink);
}
.pay-grid .list-brille { color: var(--text); }
.pay-grid .list-brille li::before { background: var(--ink); box-shadow: 12px 0 0 var(--ink); }
.pay-note { margin-top: var(--s-24); font-size: var(--size-small); color: var(--text-quiet); max-width: var(--measure); }

/* ------------------------------------------------------------
   FORMULAR — zwei Schritte, Fortschritt als Brillengläser
   ------------------------------------------------------------ */
.fsteps { display: flex; align-items: center; gap: 5px; margin-bottom: var(--s-8); }
.fstep-lens {
  display: block; width: 26px; height: 18px; overflow: hidden;
  border: var(--min-line) solid currentColor; border-radius: 50%;
}
.fstep-lens i {
  display: block; width: 100%; height: 100%; background: currentColor;
  transform: scaleX(0); transform-origin: left center;
  transition: transform var(--dur-base) var(--ease);
}
.fstep-lens.is-on i { transform: none; }
.fstep-bridge { width: 5px; height: var(--min-line); background: currentColor; }
.fstep-label {
  margin-left: var(--s-8); font-size: var(--size-micro); font-weight: 700;
  letter-spacing: var(--track-label); text-transform: uppercase; opacity: .85;
}
/* Auch hier: fieldset ohne min-width: 0 sprengt seine Spalte. */
.fpart { border: 0; padding: 0; margin: 0; min-width: 0; display: grid; gap: var(--s-16); }
.fpart > legend {
  padding: 0; margin-bottom: var(--s-8);
  font-family: var(--font-accent); font-weight: 700; font-size: var(--size-label);
  letter-spacing: var(--track-claim); text-transform: uppercase;
}
.fnav { display: flex; flex-wrap: wrap; gap: var(--s-12); }

/* Honeypot-Feld: aus dem Blickfeld, aber nicht display:none —
   manche Bots ignorieren ausgeblendete Felder gezielt. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.next-steps { list-style: none; margin: var(--s-24) 0 0; padding: 0; text-align: left; display: grid; gap: var(--s-12); }
.next-steps li { display: grid; grid-template-columns: 84px minmax(0, 1fr); gap: var(--s-12); align-items: baseline; }
.next-steps b {
  font-family: var(--font-accent); font-weight: 700; font-size: var(--size-micro);
  letter-spacing: var(--track-label); text-transform: uppercase;
  border: var(--min-line) solid currentColor; padding: 3px 6px; text-align: center;
}
.next-steps span { font-size: var(--size-small); }

/* ------------------------------------------------------------
   UNTERSCHRIFT
   ------------------------------------------------------------ */
.signature { border-top: var(--min-line) solid var(--ink); padding: var(--s-32) 0 var(--s-24); }
.signature-inner { display: flex; align-items: center; justify-content: flex-end; gap: var(--s-16); }
.signature-name {
  font-family: var(--font-accent); font-weight: 700; font-style: italic;
  font-size: var(--size-h3); color: var(--ink);
  transform: rotate(var(--tilt));
}

/* ------------------------------------------------------------
   KLEBENDE PREISLEISTE + BOTTOM SHEET (nur Mobil)
   ------------------------------------------------------------ */
.pricebar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: var(--layer-header);
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-12);
  min-height: var(--tap);
  padding: 10px var(--gutter);
  padding-bottom: calc(10px + env(safe-area-inset-bottom));
  background: var(--ink); color: var(--paper);
  border: 0; border-top: var(--frame-w) solid var(--ink);
  font: inherit; text-align: left; cursor: pointer;
}
.pricebar-l { display: grid; }
.pricebar-l b { font-size: var(--size-lead); font-weight: 700; font-variant-numeric: tabular-nums; }
.pricebar-l small { font-size: var(--size-micro); opacity: .85; font-variant-numeric: tabular-nums; }
.pricebar-r {
  font-size: var(--size-micro); font-weight: 700;
  letter-spacing: var(--track-label); text-transform: uppercase;
  border: var(--min-line) solid var(--paper); padding: 8px 12px;
}
body.has-pricebar { padding-bottom: 84px; }
@media (min-width: 700px) { .pricebar { display: none; } body.has-pricebar { padding-bottom: 0; } }

.sheet {
  position: fixed; inset: auto 0 0 0; z-index: var(--layer-preload);
  width: 100%; max-width: none; max-height: 88svh;
  margin: 0; padding: 0; border: 0;
  background: var(--paper); color: var(--text);
  border-top: var(--frame-w) solid var(--ink);
  overflow: auto;
}
.sheet::backdrop { background: rgb(var(--grey-rgb) / .62); }
.sheet-inner { padding: var(--s-16) var(--gutter) calc(var(--s-24) + env(safe-area-inset-bottom)); }
.sheet-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-12); }
.sheet-close {
  min-width: var(--tap); min-height: var(--tap);
  background: var(--paper); color: var(--ink);
  border: var(--min-line) solid var(--ink); border-radius: 0;
  font-size: 1.4rem; line-height: 1; cursor: pointer;
}
/* Die Perforation reißt beim Öffnen */
.sheet-perf {
  height: 10px;
  background-image: radial-gradient(var(--ink) var(--pat-band-r), transparent calc(var(--pat-band-r) + .6px));
  background-size: var(--pat-band) 10px; background-position: left center;
  transform-origin: left center;
}
.sheet.is-open .sheet-perf { animation: perf-tear var(--dur-reveal) steps(4, end) both; }
@keyframes perf-tear { from { transform: scaleX(.15); } to { transform: none; } }
.sheet .btn { margin-top: var(--s-16); }

@media (prefers-reduced-motion: reduce) {
  .js .rc-row.is-new,
  .js .is-stamp,
  .sheet.is-open .sheet-perf { animation: none; }
}

/* ------------------------------------------------------------
   FAQ — zehn Fragen am Seitenende (15.08.2026).

   Gebaut aus <details>/<summary>: native Tastaturbedienung,
   Screenreader-Semantik und Seitensuche kommen mit. Fällt das JS
   nach dem Rendern aus, klappt jede Frage weiterhin für sich auf.
   Mit JS übernimmt initFaq die Höhen-Transition und sorgt dafür,
   dass nur eine Frage offen ist.

   Kein Chevron, sondern der +/– Stempel aus .why — die Seite hat
   diese Marke für Aufklapper schon, eine zweite wäre eine zweite
   Sprache für dieselbe Sache.
   ------------------------------------------------------------ */
.faq {
  margin-top: var(--s-48);
  max-width: 78ch;
  border-top: var(--min-line) solid var(--ink);
}
.faq-item { border-bottom: var(--min-line) solid var(--ink); }

.faq-q {
  display: flex; align-items: center; gap: var(--s-16);
  min-height: var(--tap); padding: var(--s-16) 0;
  list-style: none; cursor: pointer;
  font-family: var(--font-body); font-weight: 700;
  font-size: var(--size-body); line-height: 1.35; color: var(--ink);
}
.faq-q::-webkit-details-marker { display: none; }
.faq-q:focus-visible { outline: var(--min-line) solid var(--ink); outline-offset: 3px; }

/* Der Stempel steht rechts: die Fragen sind unterschiedlich lang,
   links würde er die Frageflucht zerhacken. */
.faq-mark {
  flex: none; margin-left: auto;
  width: 26px; height: 26px; display: grid; place-items: center;
  border: var(--min-line) solid var(--ink);
  font-family: var(--font-body); font-weight: 700; font-size: 1rem;
  line-height: 1;
}
.faq-mark::before { content: "+"; }
.faq-item[open] .faq-mark::before { content: "–"; }
/* Aufsetzen, nicht drehen: der Stempel kippt beim Öffnen in die
   Gegenrichtung, in Stufen wie alles andere. */
.js .faq-mark { transition: transform var(--dur-base) var(--ease); }
.faq-item[open] .faq-mark { transform: rotate(var(--tilt-alt)); }

/* Der Deckel, dessen Höhe JS animiert. Ohne JS hat er keine
   Inline-Höhe und steht einfach offen da. */
.faq-a { overflow: hidden; }
.js .faq-a { transition: height var(--dur-reveal) var(--ease-soft); }
.faq-a p {
  padding-bottom: var(--s-24); padding-right: var(--s-48);
  max-width: var(--measure);
  color: var(--text-quiet); line-height: var(--lead-body);
}

@media (min-width: 700px) {
  .faq-q { font-size: var(--size-lead); }
}

@media (prefers-reduced-motion: reduce) {
  .js .faq-a { transition: none; }
}

/* ------------------------------------------------------------
   UPSELLS — Liste + Kartenstapel über dem Konfigurator
   (25.08.2026).

   Struktur: eine Liste, die einen Eintrag markiert, und ein
   Stapel, der dazu eine Karte groß zeigt. Beide hängen an
   demselben Index (main.js → setUpsell).

   Der Stapel ist ein Grid mit EINER Zelle — alle Karten liegen
   in `grid-area: 1 / 1` übereinander. Dadurch ist die Höhe des
   Stapels die der höchsten Karte, ohne feste Maße oder
   aspect-ratio: die Texte sind verschieden lang, und ein
   gedeckelter Kasten würde sie abschneiden. Absolute
   Positionierung wäre der übliche Weg und genau hier der
   falsche — sie nimmt die Karten aus dem Fluss und lässt den
   Stapel auf null zusammenfallen.

   Bewegung: nur transform und opacity, Kurve steps(3, end).
   Kein Layout rührt sich, wenn der Stapel weiterschaltet.
   ------------------------------------------------------------ */
/* Die Zweispaltigkeit schaltet erst bei 860px, nicht bei den 700px der
   übrigen Seite: bei 700 blieben nach Liste (280) und Spalte (48) nur
   271px für die Karte — schmaler als auf dem Telefon, wo sie die volle
   Breite hat. Eine Umschaltung, die das Ding kleiner macht, ist keine.
   Bis dahin gilt das gestapelte Layout. */
.upsell-grid { display: grid; gap: var(--s-24); margin-top: var(--s-32); }
@media (min-width: 860px) {
  .upsell-grid {
    grid-template-columns: minmax(200px, 280px) minmax(0, 1fr);
    gap: var(--s-48); align-items: start;
  }
  /* Auf dem Desktop steht die Liste links. Im Markup kommt sie
     nach dem Stapel, weil das die mobile Lesereihenfolge ist —
     hier dreht das Grid nur die Optik um, nicht die
     Tab-Reihenfolge. */
  .upsell-list  { order: 1; }
  .upsell-stage { order: 2; }
}

/* ---- Liste ---- */
.upsell-list { display: flex; gap: var(--s-8); }
@media (min-width: 860px) {
  .upsell-list { flex-direction: column; align-items: stretch; }
}
/* Mobil eine Reihe, die seitlich wegläuft. Die Pillen rasten ein,
   damit keine halbe Pille am Rand stehen bleibt. Der Ausbruch in
   den Gutter ist Absicht: eine Reihe, die genau am Satzspiegel
   endet, sieht aus, als wäre sie zu Ende — so sieht man, dass es
   weitergeht. */
@media (max-width: 859px) {
  .upsell-list {
    overflow-x: auto; scroll-snap-type: x proximity;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter); padding-bottom: var(--s-8);
    overscroll-behavior-x: contain;
  }
  .upsell-pill { flex: none; scroll-snap-align: start; }
}

.upsell-pill {
  display: inline-flex; align-items: center; text-align: left;
  min-height: var(--tap); padding: 8px 14px;
  background: var(--paper); color: var(--ink);
  border: var(--min-line) solid var(--ink); border-radius: 0;
  cursor: pointer; font-size: var(--size-micro); font-weight: 700;
  letter-spacing: var(--track-label); text-transform: uppercase;
  transition: transform var(--dur-fast) var(--ease-press),
              background-color var(--dur-base) var(--ease),
              color var(--dur-base) var(--ease);
}
.upsell-pill:hover { transform: translateY(-2px); }
.upsell-pill[aria-pressed="true"] {
  background: var(--ink); color: var(--paper); transform: rotate(var(--tilt));
}

/* ---- Stapel ---- */
.upsell-stage { display: grid; position: relative; isolation: isolate; }
/* Damit die Nachbarn überhaupt zu sehen sind, muss die aktive Karte
   schmaler sein als die Bühne — sonst liegen die kleiner skalierten
   Nachbarn vollständig hinter ihr und der Stapel sieht aus wie eine
   einzelne Karte. Die 6% Luft links und rechts sind genau der Streifen,
   in dem sie hervorschauen. */
@media (min-width: 860px) { .upsell-stage { padding-inline: 6%; } }

.upsell-card {
  grid-area: 1 / 1; position: relative;
  display: flex; flex-direction: column; gap: var(--s-12);
  text-align: left; font: inherit; color: var(--text);
  background: var(--paper);
  border: var(--min-line) solid var(--ink); border-radius: 0;
  padding: var(--s-12) var(--s-12) var(--s-16);
  transition: transform var(--dur-reveal) var(--ease),
              opacity var(--dur-reveal) var(--ease);
}
.upsell-card--add { cursor: pointer; }

/* Drei Zustände, mehr gibt es nicht: die Karte, ihre beiden
   Nachbarn, und alles Übrige. Der Versatz der Nachbarn ist klein
   — sie sollen andeuten, dass da mehr liegt, nicht selbst gelesen
   werden wollen. */
.upsell-card[data-upsell-pos="0"] {
  opacity: 1; transform: none; z-index: 3;
}
/* Mobil gibt es keinen Guckstreifen: die Breite ist zu knapp, um sie
   an zwei Nachbarn zu verschenken. Dort blendet der Stapel nur um —
   dass es mehr Karten gibt, sagt die Pillenreihe darunter. */
.upsell-card[data-upsell-pos="-1"],
.upsell-card[data-upsell-pos="1"],
.upsell-card[data-upsell-pos="weg"] {
  opacity: 0; z-index: 1; pointer-events: none; transform: scale(.9);
}
@media (min-width: 860px) {
  .upsell-card[data-upsell-pos="-1"],
  .upsell-card[data-upsell-pos="1"] { opacity: .3; z-index: 2; }
  /* 11% = die halbe Differenz der Skalierung (5%) plus 6% Überstand.
     Weniger, und der Nachbar verschwindet hinter der aktiven Karte. */
  .upsell-card[data-upsell-pos="-1"] { transform: translateX(-11%) scale(.9); }
  .upsell-card[data-upsell-pos="1"]  { transform: translateX(11%)  scale(.9); }
}

.upsell-card:has(:focus-visible),
.upsell-card:focus-visible { outline: var(--min-line) solid var(--ink); outline-offset: 3px; }
.upsell-card--add[data-upsell-pos="0"]:hover { transform: translateY(-2px); }

/* .foto liefert Rahmen, Korn und das Bildverhalten. Hier steht nur,
   wie hoch die Fläche wird — und das ist eine Frage der Spaltenbreite:
   in der breiten Desktop-Spalte würde 4:3 eine Karte von über 700px
   Höhe ergeben, deren Text unter der Kante des Bildschirms landet.
   Der Text ist aber der Punkt der Karte. Also flacher, je breiter. */
.upsell-shot { display: block; position: relative; aspect-ratio: 4 / 3; }
@media (min-width: 860px) { .upsell-shot { aspect-ratio: 3 / 2; } }
.upsell-shot picture,
.upsell-shot img { display: block; width: 100%; height: 100%; object-fit: cover; }

/* Der Textteil nimmt den Rest der Karte, der Knopf wird darin nach
   unten geschoben. Grund: die Bühne ist so hoch wie die HÖCHSTE Karte
   (grid-area 1/1), also hat jede kürzere Karte Luft übrig. Ohne diese
   Regel hinge sie unter dem Knopf und sähe aus wie ein Fehler; so
   steht sie zwischen Text und Knopf, und die Knöpfe aller neun Karten
   sitzen auf derselben Linie — beim Weiterschalten wandert der Knopf
   dann nicht auf und ab. */
.upsell-body { display: flex; flex-direction: column; gap: 6px; flex: 1; }
.upsell-head {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 10px; flex-wrap: wrap;
}
.upsell-head b {
  font-family: var(--font-display); font-weight: 400; font-size: var(--size-h3);
  line-height: 1; color: var(--ink);
}
.upsell-text { font-size: var(--size-small); line-height: var(--lead-body); color: var(--text-quiet); }

/* Die Marke unten sagt, was die Karte ist — und auf den klickbaren
   Karten muss sie aussehen wie das, was sie ist: eine Handlung.
   Als Kleintext mit Unterstrich (bis 25.08.2026) las sie sich wie
   eine Bildunterschrift; niemand tippt auf eine Bildunterschrift.

   Die ganze Karte ist die Klickfläche, nicht nur diese Marke — sie
   ist die Ansage dafür. Deshalb kein eigenes <button> darin: ein
   Knopf im Knopf wäre ungültiges Markup und für die Tastatur ein
   zweites Ziel mit derselben Wirkung. */
.upsell-flag {
  align-self: start; margin-top: auto;
  font-family: var(--font-accent); font-weight: 700; font-size: var(--size-small);
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--paper); background: var(--ink);
  border: var(--frame-w) solid var(--ink);
  padding: 10px 18px;
}
/* Info-Karten tragen keine Handlung — die Marke bleibt still und
   umrandet, damit sie nicht zum Tippen einlädt. */
.upsell-card:not(.upsell-card--add) .upsell-flag {
  color: var(--ink); background: transparent; border-color: var(--hair);
}

/* Ausgewählt: derselbe Haken wie an den Extras im Konfigurator.
   Zwei Ansichten desselben Zustands sollen auch gleich aussehen.
   Der Knopf dreht dabei um — gefüllt heißt „noch zu tun",
   ausgeräumt heißt „erledigt". */
.upsell-card.is-on { box-shadow: 0 0 0 var(--frame-w) var(--ink); }
.upsell-card.is-on .upsell-flag {
  background: var(--paper); color: var(--ink); border-color: var(--ink);
}
.upsell-card.is-on[data-upsell-pos="0"]::after {
  content: "✓"; position: absolute; top: -10px; right: -8px;
  width: 24px; height: 24px; display: grid; place-items: center;
  background: var(--ink); color: var(--paper); font-weight: 700;
  transform: rotate(var(--tilt)); z-index: 4;
}

@media (prefers-reduced-motion: reduce) {
  /* Die Automatik steht ohnehin still (main.js → startUpsell).
     Hier fällt zusätzlich der Übergang weg: der Stapel wechselt
     dann hart auf Klick. */
  .upsell-card { transition: none; }
  .upsell-list { scroll-behavior: auto; }
}

/* ------------------------------------------------------------
   THEMENSEITEN IM FUSSBEREICH (29.08.2026)
   Eigene Zeile unter den Rechtslinks, bewusst zurückhaltend:
   Die vier Seiten sollen erreichbar sein, ohne den Abschluss
   der Startseite zu belasten. Etwas kleiner und ruhiger als
   die Zeile darüber — sie sind ein Angebot, keine Navigation.
   ------------------------------------------------------------ */
.footer-themen {
  display: flex; flex-wrap: wrap; gap: var(--s-8) var(--s-12);
  margin-top: var(--s-16);
  font-size: var(--size-micro); letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--text-quiet);
}
.footer-themen a { color: var(--text-quiet); text-underline-offset: 3px; }
.footer-themen a:hover { color: var(--ink); }
