/* ============================================================
   Stil C — „Quiet Grid“: hell, Albra/Muli (weindel.co-Branding), Passepartout,
   Justified Grid mit Heroes. Qualitäts-Floor: responsive,
   focus-visible, prefers-reduced-motion.
   ============================================================ */

@font-face {
    font-family: 'Albra';
    src: url('/assets/fonts/albra.woff2') format('woff2');
    font-weight: 400;
    font-display: swap;
}
@font-face {
    font-family: 'Muli';
    src: url('/assets/fonts/muli.woff2') format('woff2');
    font-weight: 400;
    font-display: swap;
}

:root {
    --bg: #faf8f3;
    --ink: #16140f;
    --ink-soft: #6b665d;
    --line: #e2ddd4;
    --green: #3E5B47;
    --passepartout: #ffffff;
    --shadow: 0 1px 2px rgba(28, 27, 25, .06), 0 6px 24px rgba(28, 27, 25, .07);
    --font-display: 'Albra', Georgia, serif;
    --font-body: 'Muli', system-ui, sans-serif;
    /* Muss mit GAP in gallery.js übereinstimmen — dort wird die Zeilenbreite
       damit gerechnet. Läuft es auseinander, passen die Zeilen nicht mehr genau
       in die Bahn und der rechte Rand franst aus. */
    --gap: 18px;
    /* Grid-Bahn: max. Breite und seitlicher Einzug. Serif-Bühne (.gstage-inner)
       und kompakte Sticky-Leiste (.gsticky-inner) richten ihren Inhalt exakt an
       dieser Kante aus, damit Titel/Zähler/Buttons bündig zum Bildraster stehen —
       alle lesen dieselben Variablen, damit sie dauerhaft gekoppelt bleiben. */
    --gal-max: 1360px;
    --gal-pad: clamp(1rem, 4vw, 3.5rem);
    /* Passepartout-Rahmenbreiten — theme.json layout.passepartout=false setzt sie auf 0 */
    --pp-cover: 12px;
    --pp-grid: 8px;
    --pp-lb: 10px;
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }
/* Pull-to-Refresh auf /view/ WIEDER ZUGELASSEN (Live-Wunsch 24.07.2026): der
   frühere overscroll-behavior-y:none-Riegel stammte aus dem Proofing (Schutz für
   den Drawer / 2184-Kachel-Neuaufbau) und war hier fälschlich mit übernommen. In
   der öffentlichen Galerie soll man wieder ganz normal nach unten wischen und
   neu laden können — CSS-Riegel raus, der JS-touchmove-Blocker ist ebenfalls
   entfernt (siehe gallery.js). */

body {
    margin: 0;
    background: var(--bg);
    color: var(--ink);
    font-family: var(--font-body);
    line-height: 1.5;
    /* Nichts darf die Seite seitlich überlaufen lassen — v. a. ein sehr langer
       Szenenname in den Tabs (die selbst horizontal scrollen + ausfaden, #2). */
    overflow-x: hidden;
    -webkit-font-smoothing: antialiased;
    /* Kein Doppeltipp-Zoom (nervt beim Antippen von Bildern); Pinch-Zoom bleibt erhalten. */
    touch-action: manipulation;
}

/* Kein sichtbarer Fokusrahmen (Sebo 24.07.2026: der schwarze Rahmen am aktiven
   Element soll systemweit NIRGENDS erscheinen — er tauchte auf Touch immer
   wieder auf). Gilt global; die Einzelregeln unten setzen ihn NICHT wieder. */
:focus-visible {
    outline: none;
}

@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    *, *::before, *::after {
        animation-duration: .01ms !important;
        transition-duration: .01ms !important;
    }
}

.kicker {
    font-family: var(--font-display);
    font-size: .8rem;
    letter-spacing: .22em;
    text-transform: uppercase;
    color: var(--ink-soft);
    margin: 0 0 .75rem;
}

/* ---------- Admin-Vorschau-Hinweis ---------- */
.admin-preview-note {
    position: sticky;
    top: 0;
    z-index: 50;
    background: #2b2a27;
    color: #f5f3ef;
    text-align: center;
    font-size: .85rem;
    padding: .5rem 1rem;
}

/* ---------- Cover ---------- */
.cover {
    position: relative;
    min-height: 90svh;
    display: grid;
    place-items: center;
    padding: 8vmin;
    background: var(--bg);
    /* Seiten-Gutter des Coverbildes = --gal-pad, exakt der horizontale Innen-
       abstand des Grids. Unterhalb der Galerie-Maximalbreite (--gal-max) hat das
       Grid keinen Zentrier-Rand, seine Kante liegt also ebenfalls bei --gal-pad
       → Cover und Grid FLUCHTEN (mobil bis ~1360px). Oberhalb --gal-max
       zentriert sich das 1360er-Band und bekommt wachsende Seitenränder, während
       das Cover bei --gal-pad stehen bleibt → das Cover ragt dann bewusst BREITER
       als das Grid hinaus (Wunsch 24.07.2026: fluchten wo möglich, nur über der
       Maximalbreite darf das Cover weiter rausgehen). Oben/unten bleibt 5vmin. */
    --cover-edge: var(--gal-pad);
    /* Kein Parallax mehr (permanent raus, siehe gallery.js): das Coverbild
       (::before) steht fest im Passepartout-Rahmen und wird beim Scrollen nicht
       mehr verschoben. overflow:hidden bleibt als Sicherheitsnetz, damit selbst
       ein gerundeter Rahmen nie ueber den Folgeabschnitt blutet. */
    overflow: hidden;
}

.cover::before {
    content: '';
    position: absolute;
    /* Oben/unten bleibt 5vmin; nur die Seiten fluchten mit dem Grid (s. --cover-edge). */
    inset: 5vmin var(--cover-edge);
    background-image: var(--cover-url);
    background-size: cover;
    /* Fokuspunkt kommt inline vom View (--focus, Prozent x/y); Rueckfall 50%/50%
       (Bildmitte), falls die Migration auf diesem Server noch nicht gelaufen ist. */
    background-position: var(--focus, 50% 50%);
    /* Zoom (weiter unten) soll um denselben Punkt herum stattfinden, sonst
       legt er bei randnahem Fokus (z.B. 0%/100%) sichtbar Rand frei. */
    transform-origin: var(--focus, 50% 50%);
    /* Passepartout: Bild sitzt in einem hellen Rahmen */
    border: var(--pp-cover) solid var(--passepartout);
    box-shadow: var(--shadow);
    /* Kein Lade-Zoom mehr (Rückmeldung 22.07.2026: die langsame Zoom-Einblendung
       machte schwindelig — zumal ihre 7s-Transition auch den Scroll-Parallax
       träge nachziehen ließ). Der Ausschnitt steht fest; die einzige Bewegung ist
       der Parallax (transform weiter unten, OHNE Transition, damit er dem Scrollen
       direkt folgt statt zu driften). */
    transition: none;
}

/* Titelzeilen gestaffelt einblenden (Kicker → Titel → Beschreibung → CTA).
   Reihenfolge/Timing per transition-delay, ausgelöst durch dieselbe .is-revealed-Klasse. */
.cover-content .kicker,
.cover-content h1,
.cover-content .cover-intro,
.cover-content .cover-desc,
.cover-content .cover-scroll {
    opacity: 0;
    transform: translateY(8px);
    transition: opacity .5s ease, transform .5s ease;
}
.cover.is-revealed .kicker { opacity: 1; transform: none; transition-delay: 0s; }
.cover.is-revealed h1 { opacity: 1; transform: none; transition-delay: .12s; }
.cover.is-revealed .cover-intro { opacity: 1; transform: none; transition-delay: .2s; }
.cover.is-revealed .cover-desc { opacity: 1; transform: none; transition-delay: .28s; }
.cover.is-revealed .cover-scroll { opacity: 1; transform: none; transition-delay: .36s; }

.cover-veil {
    position: absolute;
    /* Deckungsgleich mit der BILDFLÄCHE, nicht mit dem ::before-Kasten: das Foto
       sitzt bei inset 5vmin PLUS 12px Passepartout-Rahmen. Mit nur 5vmin lag der
       (im text-light-Modus dunkle) Verlauf 12px daneben, blutete auf den weißen
       Rahmen und endete nicht bündig mit dem Bild (Live-Fund 24.07.2026, #1). */
    inset: calc(5vmin + var(--pp-cover)) calc(var(--cover-edge) + var(--pp-cover));
    /* Nochmals zurückgenommen: Deckkraft grob halbiert, Reichweite auf gut
       ein Drittel der Höhe verkürzt. Das Foto soll wirken — der Verlauf trägt
       nur noch so viel, dass der Titel lesbar bleibt (siehe .cover h1 text-shadow). */
    background: linear-gradient(to top, rgba(250, 249, 247, .32) 0%, rgba(250, 249, 247, .08) 18%, rgba(250, 249, 247, 0) 34%);
}

.cover-content {
    position: relative;
    text-align: center;
    max-width: 44rem;
    align-self: end;
    padding-bottom: 6vmin;
}

.cover h1 {
    /* Verlauf trägt kaum noch — der weiche Schatten hält den Titel lesbar */
    text-shadow: 0 1px 2px rgba(250, 249, 247, .5), 0 2px 22px rgba(250, 249, 247, .85);
    font-family: var(--font-display);
    font-weight: 500;
    font-size: clamp(2rem, 6vw, 4rem);
    letter-spacing: -.01em;
    margin: 0 0 .5rem;
}

.cover-intro {
    color: var(--ink-soft);
    font-size: .85rem;
    letter-spacing: .02em;
    max-width: 36rem;
    margin: 0 auto .8rem;
}

.cover-desc {
    color: var(--ink-soft);
    max-width: 36rem;
    margin: 0 auto 1.5rem;
}

.cover-scroll {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: .5rem;
    margin-top: 1.5rem;
    color: var(--ink);
    text-decoration: none;
    cursor: pointer;
    font-family: var(--font-display);
    font-size: .8rem;
    letter-spacing: .18em;
    text-transform: uppercase;
    transition: opacity .15s ease, transform .15s ease;
}
.cover-scroll:active { transform: scale(.96); opacity: .8; }

/* Bouncender Abwärts-Pfeil: klare „hier geht's weiter / antippbar"-Affordance,
   ersetzt die statische Linie (die wie ein bloßes Label wirkte). */
.cover-scroll-arrow {
    display: block;
    animation: cover-bounce 1.8s ease-in-out infinite;
}
@keyframes cover-bounce {
    0%, 100% { transform: translateY(0); opacity: .85; }
    50%      { transform: translateY(5px); opacity: 1; }
}
/* Altbestand-Linie (falls noch im Markup) unsichtbar halten. */
.cover-scroll-line { display: none; }

/* Cover-Höhe 90svh (Wunsch 24.07.2026 „wie vorhin besprochen"): das Cover ist
   bewusst kürzer als der volle Viewport, damit unten schon der nächste Inhalt
   (Studio-Eyebrow + Meta-Zeile) ANSCHNEIDET — „hier geht's weiter" statt
   Sackgasse. Gilt jetzt einheitlich für Desktop wie Mobil (Basiswert in .cover),
   die frühere mobile calc(100svh - 56px)-Sonderregel ist damit entfallen. */

/* Dunkles Cover-Bild (JS-Helligkeitsanalyse): heller Text + dunkler Schleier */
.cover.text-light .cover-veil {
    background: linear-gradient(to top, rgba(22, 20, 15, .26) 0%, rgba(22, 20, 15, .07) 18%, rgba(22, 20, 15, 0) 34%);
}
.cover.text-light .cover-content { color: #faf8f3; }
.cover.text-light h1 { text-shadow: 0 1px 2px rgba(22, 20, 15, .45), 0 2px 24px rgba(22, 20, 15, .8); }
.cover.text-light .cover-intro { color: rgba(250, 248, 243, .8); }
.cover.text-light .cover-desc { color: rgba(250, 248, 243, .85); }
.cover.text-light .kicker { color: rgba(250, 248, 243, .8); }
.cover.text-light .cover-scroll { color: #faf8f3; }
.cover.text-light .cover-scroll-line { background: #faf8f3; }

/* ---------- Cover-Varianten (Migration 46, Design-Handoff „Galerie-Hero" 11.09.2026) ----------
   Waehlbar je Galerie in der Werkbank (Details → Cover-Ansicht). Beide Varianten
   lesen dieselben Custom Properties wie das klassische Cover (--cover-url,
   --focus) und dieselbe .is-revealed-Klasse fuer die Einblendung. Das
   klassische ::before-Bild wird abgeschaltet; das Foto sitzt in einem eigenen
   Element (.cv-photo / .cv-bg). Schriften: Albra statt DM Serif Display, Muli
   statt Manrope (Stand-ins laut Handoff → Hausschriften der Galerie). */
.cover--split, .cover--dark {
    display: block;
    padding: 0;
    place-items: initial;
}
.cover--split::before, .cover--dark::before { content: none; }

/* Gestaffelte Einblendung: --i je Element, 80 ms Versatz, 600 ms Fahrt
   (Handoff: fade + translateY 16px → 0, cubic-bezier(.2,.7,.2,1)). Kein
   Foto-Zoom — die Lade-Zoom-Rueckmeldung vom 22.07.2026 (schwindelig) gilt
   auch hier; das Bild steht von Anfang an fest. */
.cover .cv-in {
    opacity: 0;
    transform: translateY(16px);
    transition: opacity .6s cubic-bezier(.2, .7, .2, 1), transform .6s cubic-bezier(.2, .7, .2, 1);
    transition-delay: calc(var(--i, 0) * 80ms);
}
.cover.is-revealed .cv-in { opacity: 1; transform: none; }

/* Gemeinsame Typo-Bausteine */
.cover .cv-kicker {
    font-family: var(--font-body);
    font-size: 11px;
    line-height: 1;
    letter-spacing: .22em;
    text-transform: uppercase;
    margin: 0;
}
.cover--split h1, .cover--dark h1 {
    font-family: var(--font-display);
    font-weight: 400;
    margin: 0;
    text-wrap: balance;
    text-shadow: none;
}
.cover .cv-desc {
    font-family: var(--font-body);
    font-size: 14px;
    line-height: 1.55;
    max-width: 34rem;
    margin: 24px 0 0;
}

/* --- 1a Editorial Split: Typo links auf Papier, Foto rechts --- */
.cover--split {
    --cv-ink: #111;
    --cv-ink-soft: #3d3a36;
    --cv-text-2: #5a5651;
    --cv-text-3: #8a8580;
    display: grid;
    /* Handoff: 560px | 1fr bei 1440px. Albra laeuft breiter als der DM-Serif-
       Stand-in — die Spalte darf deshalb bis 42% wachsen und der Titel bleibt
       unter 84px, sonst ragt ein langes Einzelwort („Versicherung") aus der
       Spalte (gemessen: 528px bei 92px auf 464px Platz). */
    grid-template-columns: minmax(360px, 44%) 1fr;
    min-height: min(820px, 90svh);
    color: var(--cv-ink);
    /* In der Grid-Bahn (Sebo 12.09.2026: „muss im Grid konsistent sein"):
       dieselbe max-width wie .gallery-main/.gstage-inner, Text links und
       Foto rechts enden an der Bahn-Kante (--gal-pad). Das Papier ist der
       Seitenhintergrund, ein Vollbreiten-Streifen brächte hier nichts. */
    max-width: var(--gal-max);
    margin: 0 auto;
}
.cover--split .cv-text {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: 36px;
    padding: 40px clamp(28px, 3.3vw, 48px) 44px var(--gal-pad);
    min-width: 0;
}
.cover--split .cv-top {
    display: flex;
    justify-content: space-between;
    gap: 20px;
    font-family: var(--font-body);
    font-weight: 500;
    font-size: 11px;
    line-height: 1.3;
    letter-spacing: .22em;
    text-transform: uppercase;
}
.cover--split .cv-top-r { color: var(--cv-text-3); text-align: right; }
.cover--split .cv-kicker { color: var(--cv-text-3); margin-bottom: 28px; }
.cover--split h1 {
    font-size: clamp(44px, 5.8vw, 82px);
    line-height: .98;
    letter-spacing: -.02em;
    /* Sicherheitsnetz fuer sehr lange Einzelwoerter: lieber trennen als
       unter overflow:hidden des Covers abgeschnitten werden. */
    hyphens: auto;
    overflow-wrap: break-word;
}
.cover--split h1 em { font-style: italic; color: var(--cv-ink-soft); }
.cover--split .cv-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 16px 28px;
    margin: 40px 0 0;
    font-family: var(--font-body);
    font-size: 13px;
    line-height: 1.5;
    color: var(--cv-text-2);
}
.cover--split .cv-meta dt {
    font-size: 11px;
    letter-spacing: .18em;
    text-transform: uppercase;
    color: var(--cv-text-3);
}
.cover--split .cv-meta dd { margin: 0; }
.cover--split .cv-desc { color: var(--cv-text-2); }
.cover--split .cv-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    align-items: center;
}
.cover .cv-btn {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    font-family: var(--font-body);
    font-weight: 500;
    font-size: 14px;
    line-height: 1;
    padding: 16px 24px;
    border-radius: 999px;
    border: 1px solid transparent;
    background: transparent;
    color: inherit;
    cursor: pointer;
    text-decoration: none;
    transition: background .15s ease, border-color .15s ease, transform .12s ease;
}
.cover .cv-btn:active { transform: scale(.97); }
.cover .cv-btn-primary { background: var(--cv-ink); border-color: var(--cv-ink); color: #fff; }
.cover .cv-btn-primary:hover { background: #2a2825; border-color: #2a2825; }
.cover .cv-btn-ghost { padding: 16px 20px; border-color: rgba(17, 17, 17, .18); color: var(--cv-ink); }
.cover .cv-btn-ghost:hover { border-color: rgba(17, 17, 17, .4); }
.cover .cv-btn-ghost[aria-pressed="true"] { background: var(--cv-ink); border-color: var(--cv-ink); color: #fff; }
.cover .cv-btn-ghost[aria-pressed="true"] svg { fill: currentColor; }
.cover--split .cv-photo {
    position: relative;
    overflow: hidden;
    margin: 20px var(--gal-pad) 20px 0;
    min-height: 60svh;
    background-color: #e9e5de;
    background-image: var(--cover-url);
    background-size: cover;
    background-position: var(--focus, 50% 50%);
}
.cover--split .cv-photo-n {
    position: absolute;
    left: 20px;
    bottom: 18px;
    font-family: var(--font-body);
    font-size: 11px;
    line-height: 1;
    letter-spacing: .2em;
    text-transform: uppercase;
    color: #fff;
    opacity: .85;
    text-shadow: 0 1px 8px rgba(0, 0, 0, .35);
}
@media (max-width: 1023px) {
    /* Stapeln: Foto oben (~60vh), darunter der Textblock (Handoff-Empfehlung). */
    .cover--split {
        grid-template-columns: 1fr;
        min-height: 0;
    }
    .cover--split .cv-photo {
        order: -1;
        margin: 20px var(--gal-pad) 0;
        min-height: 0;
        height: 60svh;
    }
    .cover--split .cv-text { padding: 28px var(--gal-pad) 36px; gap: 28px; }
    .cover--split h1 { font-size: clamp(40px, 9vw, 92px); }
    .cover--split .cv-kicker { margin-bottom: 20px; }
    .cover--split .cv-meta { margin-top: 28px; }
}
@media (max-width: 640px) {
    .cover--split .cv-photo { margin: 12px var(--gal-pad) 0; height: 56svh; }
    .cover--split .cv-actions .cv-btn { padding: 14px 18px; font-size: 13px; }
}

/* --- 1b Cinematic Dark: Vollbild-Foto, Titel unten links, dunkler Verlauf --- */
.cover--dark {
    position: relative;
    height: 100svh;
    min-height: 520px;
    background: #111;
    color: #fff;
}
.cover--dark .cv-bg {
    position: absolute;
    inset: 0;
    background-image: var(--cover-url);
    background-size: cover;
    background-position: var(--focus, 50% 40%);
}
.cover--dark .cv-shade {
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, rgba(10, 10, 10, .35) 0%, rgba(10, 10, 10, 0) 30%, rgba(10, 10, 10, 0) 45%, rgba(10, 10, 10, .82) 100%);
}
/* Foto bleibt Vollbild, Topbar und untere Zone laufen aber in der Grid-Bahn
   (max-width + margin auto zentriert ein absolut gesetztes Element mit
   left/right 0): Titel fluchtet mit dem Studio-Eyebrow der Buehne, der
   Ghost-Button mit dem Download-Chip darunter. */
.cover--dark .cv-topbar {
    position: absolute;
    top: 0; left: 0; right: 0;
    max-width: var(--gal-max);
    margin: 0 auto;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 20px;
    padding: 28px var(--gal-pad);
    font-family: var(--font-body);
    font-weight: 500;
    font-size: 11px;
    line-height: 1;
    letter-spacing: .22em;
    text-transform: uppercase;
    /* Der Verlauf oben ist bewusst schwach (.35) — bei hellem Himmel traegt
       ein weicher Schatten die weisse Schrift mit. */
    text-shadow: 0 1px 10px rgba(0, 0, 0, .4);
}
.cover--dark .cv-topbar-r { display: flex; gap: 28px; align-items: center; }
.cover--dark .cv-link {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    color: #fff;
    background: none;
    border: 0;
    padding: 10px 0;          /* Trefferflaeche ≥ 44px trotz 11px-Schrift */
    margin: -10px 0;
    cursor: pointer;
    opacity: .92;
    transition: opacity .15s ease;
}
.cover--dark .cv-link:hover { opacity: 1; }
.cover--dark .cv-link[aria-pressed="true"] svg { fill: currentColor; }
.cover--dark .cv-bottom {
    position: absolute;
    left: 0; right: 0; bottom: 48px;
    max-width: var(--gal-max);
    margin: 0 auto;
    padding: 0 var(--gal-pad);
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    gap: 40px;
}
.cover--dark .cv-bottom-l { min-width: 0; }
.cover--dark .cv-kicker { opacity: .7; margin-bottom: 20px; text-shadow: 0 1px 10px rgba(0, 0, 0, .4); }
.cover--dark h1 {
    font-size: clamp(44px, 7.8vw, 112px);
    line-height: .95;
    letter-spacing: -.025em;
    color: #fff;
}
.cover--dark .cv-desc { color: rgba(255, 255, 255, .8); }
.cover--dark .cv-bottom-r {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 18px;
    flex: none;
    text-align: right;
    font-family: var(--font-body);
    font-size: 13px;
    line-height: 1.4;
}
.cover--dark .cv-count { opacity: .75; }
/* Ghost-Button „Galerie ansehen ↓" — ueberschreibt die Spalten-Optik des
   klassischen .cover-scroll (dort Text ueber huepfendem Pfeil). Der Klick-
   Handler in gallery.js ist derselbe (weiche Fahrt zum Grid-Anfang). */
.cover--dark .cover-scroll {
    flex-direction: row;
    gap: 10px;
    margin: 0;
    padding: 16px 22px;
    border: 1px solid rgba(255, 255, 255, .4);
    border-radius: 999px;
    color: #fff;
    font-family: var(--font-body);
    font-weight: 500;
    font-size: 13px;
    letter-spacing: .14em;
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    transition: border-color .15s ease, transform .15s ease, opacity .15s ease;
}
.cover--dark .cover-scroll:hover { border-color: rgba(255, 255, 255, .8); }
@media (max-width: 899px) {
    .cover--dark .cv-topbar { padding: 20px var(--gal-pad); }
    /* Topbar-Aktionen auf Icons (+ Zaehler beim Herz) zusammenziehen. */
    .cover--dark .cv-link-label { display: none; }
    .cover--dark .cv-topbar-r { gap: 18px; }
    .cover--dark .cv-bottom {
        bottom: 32px;
        flex-direction: column;
        align-items: flex-start;
        gap: 22px;
    }
    .cover--dark h1 { font-size: clamp(40px, 11vw, 112px); }
    .cover--dark .cv-kicker { margin-bottom: 14px; }
    .cover--dark .cv-bottom-r {
        flex-direction: row;
        align-items: center;
        gap: 16px;
        text-align: left;
    }
}

@keyframes scroll-hint {
    0%, 100% { transform: scaleY(1); transform-origin: top; opacity: .9; }
    50% { transform: scaleY(.55); opacity: .5; }
}

/* ---------- Sticky Leiste: Szenen-Nav, Favoriten-Filter, Download (Handoff 8a) ---------- */
@keyframes fadeUp {
    from { opacity: 0; transform: translateY(6px); }
    to { opacity: 1; transform: none; }
}

/* Schlanke Buehne bei den Cover-Varianten split/dark: Studio, Bildanzahl,
   Datum, Favoriten und Download stehen dort schon im Cover — die Buehne
   zeigt das nicht noch einmal 100px tiefer (Doppel in anderer Reihenfolge
   und anderem Stil, Live-Fund 12.09.2026). Es bleiben nur die Szenen-Tabs.
   Der Kopf bleibt im DOM (display:none, nicht entfernt): gallery.js liest
   knownTotal aus #gal-count und schaltet den Favoriten-Filter ueber
   #gb-fav-toggle; die Sticky-Leiste uebernimmt beim Scrollen wie bisher.
   Ohne Szenen ist die Buehne dann nur noch ihre Trennlinie. */
.gstage--slim .gstage-head { display: none; }
.gstage--slim .gstage-inner { padding-top: 0; }
.gstage--slim .gstage-tabs, .gstage--slim .gstage-scenerow { margin-top: 0; padding-top: 14px; }

/* --- Serif-Bühne (Design 15a): Ruhezustand des Kopfs, im Fluss unter dem Cover --- */
.gstage {
    background: var(--bg);
    border-bottom: 1px solid var(--line);
}
.gstage-inner {
    max-width: var(--gal-max);
    margin: 0 auto;
    /* Variante A: schlanke Ruhezeile (kein großer Titel — der lebt im Cover und,
       verkleinert, in der Sticky-Leiste). Seitlich an die Grid-Bahn gekoppelt,
       unten 0 (Tabs tragen die Trennlinie). */
    padding: clamp(1.25rem, 3vw, 1.75rem) var(--gal-pad) 0;
}
.gstage-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 1.5rem;
}
.gstage-lead { min-width: 0; }
.gstage-eyebrow {
    margin: 0 0 .45rem;
    font-family: var(--font-body);
    font-size: 11px;
    letter-spacing: .24em;
    text-transform: uppercase;
    color: #8A8375;
    font-weight: 600;
}
.gstage-meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 9px;
    margin: 0;
    font-size: 13.5px;
    color: #8A8375;
}
.gstage-dot {
    width: 3px;
    height: 3px;
    border-radius: 50%;
    background: #C9C0AE;
    flex: 0 0 auto;
}
.gstage-actions {
    display: flex;
    gap: 8px;
    flex-shrink: 0;
    padding-bottom: 4px;
}

/* Szenen-Tabs: unterstrichene Navigation statt Chips (Handoff 15a). */
.gstage-tabs {
    display: flex;
    gap: 22px;
    margin-top: 18px;
    border-bottom: 1px solid #E7E1D6;
    overflow-x: auto;
    scrollbar-width: none;
}
.gstage-tabs::-webkit-scrollbar { display: none; }
/* Weiche Kante NUR, wenn die Tabs wirklich überlaufen (JS setzt is-scroll) —
   sonst fadete auch ein kurzer Szenenname („Sommerfest") grundlos am rechten
   Rand aus. Gilt für beide Tab-Leisten (Bühne + Sticky). */
.gstage-tabs.is-scroll, .gsticky-tabs.is-scroll {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent 100%);
    mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent 100%);
}
.gstage-tab {
    flex: 0 0 auto;
    padding: 0 2px 12px;
    font-size: 13px;
    font-weight: 600;
    color: #8A8375;
    text-decoration: none;
    white-space: nowrap;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    transition: color .15s ease;
}
.gstage-tab-n {
    color: #B0A895;
    font-weight: 500;
}
.gstage-tab.is-active {
    color: var(--ink);
    border-bottom-color: var(--green);
}
@media (hover: hover) {
    .gstage-tab:hover { color: var(--ink); }
}
.gstage-tab[hidden] { display: none; }

/* --- Kompakte Sticky-Leiste (Design 15s): kollabierter Zustand --- */
.gsticky {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 41;
    /* DECKEND, KEIN backdrop-filter blur. Eine fixe, geblurrte Leiste zwingt iOS
       beim Momentum-Scroll, das ganze Viewport je Frame neu zu kompositieren →
       das gesamte Bild flackert (Regression-Fund 24.07.2026; die 8a-Bar war
       position:sticky und deckte anders). Deckende Fläche + Layer-Promotion
       (will-change) hält den Scroll flüssig. */
    background: var(--bg);
    border-top: 1px solid #E7E1D6;
    border-bottom: 1px solid #E7E1D6;
    box-shadow: 0 8px 20px -14px rgba(40, 30, 10, .3);
    /* Ausgeblendet, bis der Sentinel oben aus dem Bild scrollt (JS toggelt
       is-visible). Variante A: die Leiste schiebt sich von oben herein (nicht
       aus dem Nichts) — translateY(-100%) → 0. */
    opacity: 0;
    transform: translateY(-100%);
    will-change: transform, opacity;
    pointer-events: none;
    visibility: hidden;
    transition: opacity .25s ease, transform .25s ease, visibility 0s linear .25s;
}
.gsticky.is-visible {
    opacity: 1;
    transform: none;
    pointer-events: auto;
    visibility: visible;
    transition: opacity .25s ease, transform .25s ease;
}
/* Lightbox offen: die fixe Leiste SOFORT weg, ohne Fade. Waehrend des
   250ms-Opacity-Fades der Lightbox schien die deckend weisse Leiste sonst
   durch die halbtransparente Flaeche und lag optisch UEBER Zaehler und X im
   Lightbox-Kopf (Video-Befund 24.07.2026). JS setzt body.lb-open in open()
   und entfernt es erst im closeTimer (nach dem Ausblende-Fade) — die Leiste
   kehrt dann mit ihrem normalen Fade zurueck. */
body.lb-open .gsticky {
    opacity: 0 !important;
    visibility: hidden !important;
    transition: none;
}
.gsticky-inner {
    display: flex;
    align-items: center;
    gap: 16px;
    max-width: var(--gal-max);
    margin: 0 auto;
    padding: 9px var(--gal-pad);
    min-height: 52px;
}
.gsticky-info {
    display: flex;
    align-items: baseline;
    min-width: 0;
    flex: 0 1 auto;
}
.gsticky-title {
    font-family: var(--font-display);
    font-size: 16px;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    /* Lange Namen laufen an einer weichen Kante aus (kein hartes „…"); den vollen
       Namen liefert das title-Tooltip. Maske nur, wenn wirklich abgeschnitten
       wird — sonst würden auch kurze Namen am Rand ausfaden (JS setzt is-clipped). */
    max-width: min(40vw, 420px);
}
.gsticky-title.is-clipped {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent 100%);
    mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent 100%);
}
.gsticky-tabs {
    display: flex;
    gap: 18px;
    margin-left: 8px;
    overflow-x: auto;
    scrollbar-width: none;
    min-width: 0;
}
.gsticky-tabs::-webkit-scrollbar { display: none; }
.gsticky-tab {
    flex: 0 0 auto;
    padding: 14px 2px;
    font-size: 12.5px;
    font-weight: 600;
    color: #8A8375;
    text-decoration: none;
    white-space: nowrap;
    transition: color .15s ease, box-shadow .15s ease;
}
.gsticky-tab.is-active {
    color: var(--ink);
    box-shadow: inset 0 -2px 0 var(--green);
}
@media (hover: hover) {
    .gsticky-tab:hover { color: var(--ink); }
}
.gsticky-tab[hidden] { display: none; }
.gsticky-spacer { flex: 1 1 auto; }
.gsticky-fav, .gsticky-dl {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    border-radius: 99px;
    font-size: 12.5px;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    flex: 0 0 auto;
}
/* Fav + Download als EIN Paar (Live-Fund 24.07.2026): vorher trug die
   Fav-Pille einen sichtbaren Rahmen-Ring, die Download-Pille keinen — das wirkte
   uneinheitlich, und im aktiven Zustand kam ein dunkler Ring dazu. Jetzt beide
   randlos, gleiche Höhe; nur die Füllung unterscheidet Sekundär (soft) von
   Primär (ink). Aktiv = volle ink-Füllung ohne Ring. */
.gsticky-fav, .gsticky-dl { min-height: 38px; border: 0; }
.gsticky-fav {
    padding: 8px 14px;
    background: #ECE5D9;
    color: var(--ink-soft);
    transition: background .15s ease, color .15s ease;
}
.gsticky-fav[aria-pressed="true"] {
    background: var(--ink);
    color: #fff;
}
.gsticky-fav[aria-pressed="true"] .ico-heart { fill: #fff; }
.gsticky-dl {
    padding: 8px 15px;
    background: var(--ink);
    color: #fff;
    transition: background .15s ease;
}
.gsticky-dl.is-busy { background: var(--green); }
.gsticky-fav:active, .gsticky-dl:active { transform: scale(.96); }

/* --- Micro-Interactions beim Klick --- */
/* Herz „pumpt" kurz, wenn der Favoriten-Filter aktiviert wird (Bühne + Leiste). */
@keyframes heart-pop {
    0%   { transform: scale(1); }
    38%  { transform: scale(1.32); }
    100% { transform: scale(1); }
}
.gb-fav-pill[aria-pressed="true"] .ico-heart,
.gsticky-fav[aria-pressed="true"] .ico-heart {
    transform-box: fill-box;
    transform-origin: center;
    animation: heart-pop .3s cubic-bezier(.34, 1.56, .64, 1);
}
/* Tabs: kurzer Press + sanft einlaufende Unterstreichung. */
.gstage-tab, .gsticky-tab { transition: color .15s ease, border-color .2s ease, box-shadow .2s ease, transform .12s ease; }
.gstage-tab:active, .gsticky-tab:active { transform: scale(.97); }

.gb-fav-pill, .gb-dl-pill {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    border-radius: 99px;
    font-size: 12.5px;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    min-height: 44px;
}

/* Randloses Paar wie in der Sticky-Leiste (siehe dort): Fav soft, Download ink,
   gleiche Höhe, kein Rahmen-Ring — auch nicht im aktiven Zustand. */
.gb-fav-pill {
    padding: 8px 14px;
    border: 0;
    background: #ECE5D9;
    color: var(--ink-soft);
    transition: background .15s ease, color .15s ease;
}
.gb-fav-pill[aria-pressed="true"] {
    background: var(--ink);
    color: #fff;
}
.gb-fav-pill[aria-pressed="true"] .ico-heart { fill: #fff; }
.gb-fav-pill:active, .gb-dl-pill:active { transform: scale(.96); }

.gb-dl-pill {
    padding: 9px 16px;
    border: 0;
    background: var(--ink);
    color: #fff;
    transition: background .15s ease, transform .15s ease;
}
.gb-dl-pill.is-busy { background: #3E5B47; }

/* Spinner beim ZIP-Vorbereiten: kurzer Bogen mit bewusst UNgleichmäßigem Tempo
   (mal schneller, mal langsamer), damit auch mobil — wo das Textlabel
   ausgeblendet ist — sichtbar bleibt, dass etwas passiert. */
@keyframes gb-dl-spin-var {
    0%   { transform: rotate(0deg); }
    25%  { transform: rotate(60deg); }
    55%  { transform: rotate(300deg); }
    75%  { transform: rotate(330deg); }
    100% { transform: rotate(360deg); }
}
.gb-dl-spin-arc {
    transform-box: fill-box;
    transform-origin: center;
    animation: gb-dl-spin-var 1.4s cubic-bezier(.6, 0, .4, 1) infinite;
}
/* Weiches Ein-/Ausblenden beim Wechsel Download-Icon ↔ Spinner ↔ Icon. */
@keyframes gb-dl-appear { from { opacity: 0; } to { opacity: 1; } }
.gb-dl-pill svg { animation: gb-dl-appear .18s ease; }
@media (prefers-reduced-motion: reduce) {
    .gb-dl-spin-arc { animation-duration: 1.4s; animation-timing-function: linear; }
    .gb-dl-pill svg { animation: none; }
}

.gb-fav-badge { display: none; }
/* Serifen-Ziffern: Favoriten-Zähler in Pille und mobiler Plakette */
#gb-fav-count, .gb-fav-badge { font-family: var(--font-display); }

/* ============================================================
   Szenen-Pille (≥ 5 Szenen, Test-Schalter ?pille=1). gallery.js baut
   die Pille + Menü NUR dann; ohne Flag/genug Szenen existiert kein
   Pille-DOM und diese Regeln sind wirkungslos (Handoff „Szenen-Navigation").
   ============================================================ */
.gstage-scenerow {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 18px;
    border-bottom: 1px solid #E7E1D6;
    padding-bottom: 12px;
}
.scene-menu-anchor { position: relative; min-width: 0; }
.scene-pill {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    max-width: 260px;
    min-width: 0;
    padding: 8px 14px;
    border-radius: 99px;
    border: 1px solid #E0D9CC;
    background: #fff;
    font-family: var(--font-body);
    font-size: 12.5px;
    font-weight: 600;
    color: #17150F;
    cursor: pointer;
    transition: border-color .15s ease, transform .12s ease;
}
@media (hover: hover) { .scene-pill:hover { border-color: #C9C0AE; } }
.scene-pill:active { transform: scale(.97); }
/* Name füllt die (per JS fixierte) Pillenbreite; Zähler + Chevron rechts
   angeheftet — so bleibt die Pille pro Galerie gleich breit und die Pfeile
   dahinter wandern beim Szenenwechsel nicht. */
.scene-pill .sp-name { flex: 1 1 auto; min-width: 0; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.scene-pill .sp-pos { flex: 0 0 auto; color: #B0A895; font-weight: 500; font-size: 12px; }
.scene-pill .sp-chev { flex: 0 0 auto; color: #8A8375; transition: transform .2s ease; }
.scene-pill[aria-expanded="true"] .sp-chev { transform: rotate(180deg); }

.scene-nav { display: inline-flex; gap: 4px; flex: 0 0 auto; }
.scene-nav button {
    width: 30px; height: 30px;
    display: inline-grid; place-items: center;
    border-radius: 8px; border: 0; background: transparent;
    color: #8A8375; cursor: pointer;
    transition: background .15s ease, color .15s ease, transform .12s ease;
}
@media (hover: hover) { .scene-nav button:hover { background: #EFEAE0; color: var(--ink); } }
.scene-nav button:active { transform: scale(.94); }
/* „Hier ist Schluss": kurzer Prall in Sperrrichtung statt Ausgrauen. */
@keyframes scene-bump-next { 0%, 100% { transform: none; } 35% { transform: translateX(4px); } 70% { transform: translateX(-1px); } }
@keyframes scene-bump-prev { 0%, 100% { transform: none; } 35% { transform: translateX(-4px); } 70% { transform: translateX(1px); } }
.scene-nav button.is-bump-next { animation: scene-bump-next .32s cubic-bezier(.36, .07, .19, .97); }
.scene-nav button.is-bump-prev { animation: scene-bump-prev .32s cubic-bezier(.36, .07, .19, .97); }

/* Sticky-Variante: Pille (+ Pfeile) stehen anstelle des Tab-Streifens. */
.gsticky-scenerow { display: flex; align-items: center; gap: 8px; min-width: 0; }
.gsticky .scene-pill { max-width: 230px; }

/* Popover (Desktop) unter der Pille. */
.scene-pop {
    position: absolute;
    top: calc(100% + 8px);
    left: 0;
    z-index: 60;
    min-width: 280px;
    max-height: 50vh;
    overflow: auto;
    background: #fff;
    border: 1px solid #E7E1D6;
    border-radius: 13px;
    box-shadow: 0 16px 40px -16px rgba(40, 30, 10, .35);
    padding: 6px;
    opacity: 0;
    transform: translateY(6px);
    pointer-events: none;
    transition: opacity .2s ease, transform .2s ease;
}
.scene-pop.open { opacity: 1; transform: none; pointer-events: auto; }

.scene-row {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    /* Linkes Padding (8) so gewählt, dass der Name exakt unter dem Pillen-Namen
       steht: Popover-Border 1 + Popover-Padding 6 + 8 = 15 = Pillen-Border 1 +
       Pillen-Padding 14. Gleiche Schriftgröße wie die Pille (Sebo 24.07.). */
    padding: 11px 12px 11px 8px;
    border: 0;
    border-bottom: 1px solid #F0EBE1;
    border-radius: 9px;
    background: transparent;
    text-align: left;
    font-family: var(--font-body);
    cursor: pointer;
}
.scene-row:last-child { border-bottom: 0; }
@media (hover: hover) { .scene-row:hover { background: #FBF9F4; } }
/* Häkchen der aktiven Szene rechts (order:3), damit die NAMEN links bündig mit
   dem Pillen-Namen stehen (kein Häkchen-Gutter davor). Platz bleibt reserviert. */
.scene-row .sr-check { order: 3; width: 16px; height: 16px; flex: 0 0 auto; color: #3E5B47; opacity: 0; }
.scene-row.is-active { background: #F5F6F1; }
.scene-row.is-active .sr-check { opacity: 1; }
.scene-row .sr-name {
    flex: 1 1 auto; min-width: 0;
    font-size: 13px; font-weight: 500; color: #5A5448;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.scene-row.is-active .sr-name { font-weight: 600; color: #17150F; }
.scene-row .sr-n { order: 2; flex: 0 0 auto; font-size: 12px; color: #B0A895; }

@media (max-width: 640px) {
    /* Pfeile bleiben auch mobil sichtbar (Sebo 24.07.: „immer drin lassen"),
       nur kompakter, damit die Sticky-Leiste auch auf ~375px nicht überläuft.
       Deckel skaliert mit der Breite: mehr Platz auf 500px, sicher auf 375px. */
    .scene-pill { max-width: min(240px, 52vw); }
    .gstage-scenerow { margin-top: 16px; }
    .gsticky-inner { gap: 10px; }
    .gsticky-scenerow { gap: 4px; }
    .scene-nav { gap: 2px; }
    .scene-nav button { width: 30px; height: 30px; }
    /* Popover kommt auch mobil direkt aus der Pille (kein Bottom-Sheet). Nie
       breiter als der Schirm; die JS-Klemme in openPop() schiebt es zusätzlich
       horizontal in den sichtbaren Bereich. */
    .scene-pop { min-width: 0; width: max(240px, 62vw); max-width: calc(100vw - 24px); }
    .scene-pop .scene-row { padding: 13px 12px 13px 8px; min-height: 44px; }  /* linkes 8 = Buendigkeit mit Pillen-Name */
}
@media (prefers-reduced-motion: reduce) {
    .scene-pop { transition: none; }
    .scene-pill:active, .scene-nav button:active { transform: none; }
    .scene-nav button.is-bump-next, .scene-nav button.is-bump-prev { animation: none; }
}

.fav-empty {
    text-align: center;
    color: var(--ink-soft);
    padding: 4rem 0;
    animation: fadeUp .25s ease;
}
.fav-empty[hidden] { display: none; }

/* Überschrift über den gefilterten Favoriten (Sebo 24.07.2026). Sitzt ganz oben
   in #galerie; die Szenen-Köpfe darunter gruppieren wie gewohnt. */
.fav-heading {
    margin: 0 0 clamp(2rem, 3vw + 1rem, 3.5rem);
    animation: fadeUp .25s ease;
}
.fav-heading[hidden] { display: none; }
.fav-heading-title {
    font-family: var(--font-display);
    font-weight: 500;
    font-size: clamp(2.125rem, 1.5rem + 2vw, 2.75rem);
    letter-spacing: -.005em;
    color: var(--ink);
    margin: 0 0 .5rem;
}
.fav-heading-meta {
    font-family: var(--font-body);
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--ink-soft);
    margin: 0;
}
.fav-heading-meta .num {
    font-family: var(--font-display);
    font-weight: 500;
    font-size: .9rem;
    letter-spacing: 0;
    text-transform: none;
}

.grid-item.fadeup { animation: fadeUp .25s ease; }

@media (max-width: 640px) {
    /* Bühne mobil = Sticky-Formen (24.07.2026): kompaktes Icon-Paar (♥+Zahl,
       ↓) rechts NEBEN dem Textblock statt breiter Label-Pillen in eigener
       Zeile — spart die dritte Zeile, und beim Einkleben der Sticky-Leiste
       wirkt es, als klebten dieselben Knöpfe einfach oben fest. Der lange
       Galeriename (.gstage-eyebrow) darf dafür umbrechen. */
    .gstage-head {
        flex-direction: row;
        align-items: center;
        gap: 1rem;
    }
    .gstage-actions { padding-bottom: 0; justify-content: flex-end; }
    /* Labels aus, Maße wie .gsticky-fav/.gsticky-dl (Padding/Höhe identisch). */
    .gb-fav-label, .gb-dl-label { display: none; }
    .gb-fav-pill { min-height: 38px; padding: 9px 13px; }
    .gb-dl-pill { min-height: 38px; padding: 10px; min-width: 44px; justify-content: center; }
    /* Zähler wie in der Sticky (Herz + Zahl): die Badge übernimmt, weil der
       Zähler im ausgeblendeten Label steckt; bei 0 versteckt JS die Badge —
       dann steht das Herz allein. */
    .gb-fav-badge { display: inline; }
    .gb-fav-badge[hidden] { display: none; }
    /* Trefferfläche trotz 38px-Optik ≥44px — unsichtbar aufgeweitet, Muster
       wie .lb-close (beide Pillen sind bereits position:relative). */
    .gb-fav-pill::after, .gb-dl-pill::after {
        content: '';
        position: absolute;
        top: 50%; left: 50%;
        width: 100%; min-width: 44px; height: 44px;
        transform: translate(-50%, -50%);
    }

    /* Sticky-Leiste mobil: Galeriename raus (zu lang), Szenen zuerst; die Knöpfe
       nur als Icons (Label + Favoriten-Zähler ausgeblendet). */
    .gsticky-info { display: none; }
    .gsticky-tabs { margin-left: 0; }
    .gsticky-tab { padding-block: 15px; }
    .gsticky-fav { padding: 9px 13px; }
    .gsticky-dl { padding: 10px; min-width: 44px; justify-content: center; }
    /* Fav-Zähler bleibt mobil sichtbar (Herz + Zahl, informativ, #9); nur das
       längere Download-Label wird zum reinen Icon. */
    .gsticky-dl-label { display: none; }
}

/* ---------- Galerie / Szenen ---------- */
.gallery-main {
    /* Bahn schmaler als vorher (1440px): eine engere Spalte hält auch die
       volle-Breite-Zeile (.is-hero) und die Querformat-Reihen kleiner, statt
       sie über die ganze Bildschirmbreite laufen zu lassen. 1360px liegt am
       unteren Rand des vereinbarten Korridors (1360–1440px) — bewusst eng,
       weil genau das Wirken „zu groß" der Auslöser war. Cover bleibt
       ausdrücklich außerhalb (eigener Vollbreiten-Akzent, siehe .cover). */
    max-width: var(--gal-max);
    margin: 0 auto;
    padding: 6rem var(--gal-pad);
}

.scene {
    /* Mehr Luft zwischen den Szenen, auf schmalen Screens moderater als am Desktop */
    margin-bottom: clamp(3.5rem, 6vw + 1rem, 8rem);
    /* Kapitel-Auftakt: deutlich mehr Weißraum vor dem Szenentitel als vorher. */
    padding-top: clamp(2rem, 5vw, 4.5rem);
    /* Anker-Ziel darf nicht unter der Sticky-Leiste landen */
    scroll-margin-top: var(--gallery-bar-h, 70px);
}
.scene:last-of-type { margin-bottom: 0; }
/* Erste Szene folgt direkt auf das Cover — kein doppeltes Loch. */
.scene:first-of-type { padding-top: 0; }

.scene-head { margin: 0 0 clamp(2rem, 3vw + 1rem, 3.5rem); }

.scene-title {
    font-family: var(--font-display);
    font-weight: 500;
    font-size: clamp(2.125rem, 1.5rem + 2vw, 2.75rem);
    letter-spacing: -.005em;
    color: var(--ink);
    margin: 0 0 .5rem;
}
.scene-meta {
    font-family: var(--font-body);
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--ink-soft);
    margin: 0 0 1.25rem;
}
/* Serifen-Ziffern: die Zahl sticht als Albra aus dem gesperrten Muli-Text hervor. */
.scene-meta .num {
    font-family: var(--font-display);
    font-weight: 500;
    font-size: .9rem;
    letter-spacing: 0;
    text-transform: none;
}
.scene-rule {
    display: block;
    height: 1px;
    background: var(--line);
}
/* Quittung für den Szenen-Sprung: Titel dunkelt kurz nach, dann zurück.
   Bestätigt den Sprung auch dann, wenn die Zielszene noch nicht fertig geladen ist. */
.scene-title.js-jumped {
    animation: scene-jump-flash .3s ease-out;
}
@keyframes scene-jump-flash {
    from { color: var(--ink-soft); }
}

/* Justified Grid: JS setzt flex-basis je Zeile; Fallback ohne JS = einfache Spalten */
.justified-grid {
    display: flex;
    flex-wrap: wrap;
    gap: var(--gap);
}

/* Block-Hülle (blockweises Laden, blocks.js): jeder Block nimmt die volle Breite
   und ist selbst ein Justified-Container. So bleibt die Zeilen-Packung und der
   18px-Rhythmus identisch zum früheren einteiligen Grid — nur zerlegt in Blöcke,
   damit ferne Blöcke geleert werden können, ohne dass sich das Aussehen ändert.
   Eine leere, ferne Block-Hülle trägt nur eine per JS gesetzte exakte Höhe. */
.blk {
    flex: 0 0 100%;
    display: flex;
    flex-wrap: wrap;
    gap: var(--gap);
    align-content: flex-start;
}
/* Geleerter Block (blocks.js applyHullState → .blk-hull): ein geleerter Block ist
   transparent und zeigte beim Szenensprung den near-white Body (#fafafa) — ein
   WEISSER Blitz, bis die Kacheln neu rendern (Live-bestätigt 24.07.2026). Ein
   warmes Skeleton (wie der Kachel-Schimmer / wie im Proofing) macht den Umbau
   ruhig statt weiß. Gilt nur /view/ — Proofing lädt diese CSS nicht. */
.blk-hull {
    border-radius: 12px;
    background: linear-gradient(100deg, #ece5d8 40%, #f2ece0 50%, #ece5d8 60%);
    background-size: 200% 100%;
    animation: shimmer 1.4s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
    .blk-hull { animation: none; background: #ece5d8; }
}

.grid-item {
    margin: 0;
    flex-grow: 1;
    position: relative;
    overflow: hidden; /* Hochformat-Bilder dürfen die Zeilenhöhe nicht überlaufen */
    /* Höhendeckel: kein Bild in der Rasteransicht wird höher als 76vh — sonst
       läuft ein formatfüllendes Hochformat über zwei Bildschirmhöhen (der
       konkrete Auslöser dieser Regel). vh ist bewusst gewählt statt % (Lehre 4
       im HANDOFF: max-height:% braucht eine definite Bezugsgröße in der
       Vorfahrenkette; vh bezieht sich direkt auf den Viewport und ist immer
       definit, auch als Flex-Item in .justified-grid). Wirkt für alle Kacheln,
       ist aber praktisch nur beim vollbreiten .is-hero-Bild und bei zwei
       nebeneinanderstehenden Hochformaten in einer schmalen Zeile spürbar —
       normale Reihen bleiben durch TARGET_ROW in gallery.js ohnehin darunter.
       76 statt der ursprünglichen 82: Bei 82vh füllt ein Bild praktisch den
       ganzen Bildschirm und es ist nicht mehr zu sehen, dass darunter etwas
       weitergeht. Der schmale Streifen der nächsten Zeile am unteren Rand ist
       kein Rest, sondern die Einladung weiterzuscrollen. */
    /* --gal-cap kommt aus blocks.js und ist exakt die Zahl, mit der die
       Layout-Rechnung deckelt (window.innerHeight * 0.76). Bewusst NICHT 76vh
       oder 76svh: beide Einheiten legt der Browser selbst aus, mobil abweichend
       von innerHeight — dann laufen Rechnung und Darstellung wieder auseinander
       und die Platzhalterhöhen stimmen nicht. 76vh bleibt nur Rückfall, solange
       JS die Variable noch nicht gesetzt hat. */
    max-height: var(--gal-cap, 76vh);
}

/* Rhythmus gilt nur der ungefilterten Ansicht: die Sichtbarkeits-Klasse am
   Grid-Container (JS setzt .is-filtered bei aktivem Favoriten-Filter) schaltet
   die Vollbreiten-Regel ab, damit gefilterte Kacheln nicht künstlich groß bleiben. */
.justified-grid:not(.is-filtered) .grid-item.is-hero {
    flex-basis: 100% !important;
    margin-block: clamp(1.5rem, 3vw, 3.5rem);
}

/* Greift der 76vh-Deckel oben bei .is-hero, wäre object-fit:cover ein
   Beschnitt — in einer Fotogalerie zeigen wir das Bild lieber vollständig,
   mittig, mit Luft links/rechts (Passepartout-Farbe als Rand) statt oben/unten
   abzuschneiden. Ungedeckelt (Höhe passt exakt zum Seitenverhältnis) sieht
   contain identisch zu cover aus — kein Nachteil im Normalfall. */
.grid-item.is-hero img {
    object-fit: contain;
    background: var(--bg);
}

.grid-open {
    display: block;
    width: 100%;
    height: 100%; /* füllt die vom JS gesetzte Zeilenhöhe, damit img height:100% greift */
    padding: 0;
    border: 0;
    background: none;
    cursor: zoom-in;
}

.grid-item img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* Passepartout je Bild */
    border: var(--pp-grid) solid var(--passepartout);
    box-shadow: var(--shadow);
    opacity: 0;
    /* Weiches Einblenden wie im Proofing (.pf-img img, .3s) — GENAU das lässt den
       Szenensprung dort „smooth" wirken statt zu flackern (Live-Vergleich
       24.07.2026). Beide Seiten decoden vorher identisch (watchImage → img.decode()
       → .loaded), nur /view/ schaltete bisher hart auf opacity:1 statt zu faden:
       beim Szenensprung poppten viele frisch dekodierte Kacheln zeitversetzt auf =
       Flackern. Der Fade glättet genau diese Kaskade. Der Skeleton-Platzhalter
       darunter verhindert weiterhin Layout-Sprünge. */
    transition: opacity .3s ease;
}
.grid-item img.loaded { opacity: 1; }

/* Skeleton-Schimmer, bis das Bild da ist (v5 §5.3). Warme Beige-Töne (identisch
   zu .blk-hull) statt des früheren near-white #f6f2ea — sonst blitzte während des
   .3s-Einblendens der halbtransparenten Kachel wieder Weiß durch. */
.grid-open {
    background: linear-gradient(100deg, #ece5d8 40%, #f2ece0 50%, #ece5d8 60%);
    background-size: 200% 100%;
    animation: shimmer 1.4s linear infinite;
}
.grid-item.img-done .grid-open { animation: none; background: none; }
/* Bild kam auch nach dem zweiten Versuch nicht: ruhige Fläche statt
   Defekt-Symbol, mit einem kleinen erklärenden Hinweis statt einer stummen
   Fläche. Das Bild bleibt unsichtbar (opacity 0). */
.grid-item.img-failed .grid-open {
    background: #eee9e0;
    position: relative;
}
.grid-item.img-failed .grid-open::after {
    content: "Bild derzeit nicht verfügbar";
    position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center;
    padding: 1rem; text-align: center;
    font-size: .74rem; color: var(--ink-soft);
}
.grid-item.img-failed img { opacity: 0 !important; }
@keyframes shimmer { to { background-position: -200% 0; } }

/* Ansichtsgalerie: kein Hover-Lift — Bilder stehen ruhig (v5-Feedback).
   Kein schwarzer Fokusrahmen (Sebo 24.07.2026). */
.grid-open:focus-visible {
    outline: none;
}

.empty-note {
    text-align: center;
    color: var(--ink-soft);
    padding: 4rem 0;
}

/* ---------- Schluss-Moment (Abspann) ---------- */
.outro {
    max-width: 1440px;
    margin: 0 auto;
    padding: clamp(4rem, 10vw, 7rem) 1rem clamp(2.5rem, 5vw, 4rem);
    text-align: center;
    opacity: 0;
    transform: translateY(14px);
    transition: opacity .5s ease, transform .5s ease;
}
.outro.is-visible { opacity: 1; transform: none; }
.outro-rule {
    display: block;
    width: 2.5rem;
    height: 1px;
    background: var(--line);
    margin: 0 auto clamp(2rem, 4vw, 3rem);
}
.outro-title {
    font-family: var(--font-display);
    font-weight: 500;
    font-size: clamp(1.75rem, 1.3rem + 2vw, 2.5rem);
    margin: 0 0 .75rem;
}
.outro-credit {
    color: var(--ink-soft);
    font-size: .85rem;
    margin: 0 0 2rem;
}
.outro-dl { margin: 0 auto; }

.gallery-footer {
    text-align: center;
    color: var(--ink-soft);
    font-size: .85rem;
    padding: 3rem 1rem 4rem;
    border-top: 1px solid var(--line);
}

/* ---------- Lightbox ---------- */
.lightbox {
    position: fixed;
    inset: 0;
    z-index: 100;
    background: rgba(250, 249, 247, .97);
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    overflow: hidden;                 /* Lightbox selbst scrollt nicht */
    overscroll-behavior: contain;     /* kein Durchscrollen zum Hintergrund */
    touch-action: none;               /* Wischen = blättern, keine Seitengesten */
    /* Weiches Ein-/Ausblenden statt hartem display-Toggle; JS setzt .is-open
       nach dem Entfernen von [hidden] (siehe open()/close()). */
    opacity: 0;
    transition: opacity .25s ease-out;
}

.lightbox.is-open { opacity: 1; }

.lightbox[hidden] { display: none; }

.lb-stage {
    position: relative;
    margin: 0;
    display: grid;
    justify-items: center;
    gap: 1rem;
    padding: 3rem 1rem 2rem;
    min-width: 0;
}

/* Lightbox-Spinner, solange das Web-Derivat lädt */
.lb-stage::before {
    content: "";
    position: absolute; top: 45%; left: 50%; width: 2rem; height: 2rem; margin: -1rem;
    border-radius: 50%;
    border: 2px solid rgba(250, 248, 243, .25);
    border-top-color: #faf8f3;
    animation: lb-spin .7s linear infinite;
    opacity: 0; pointer-events: none;
    transition: opacity .15s;
}
.lightbox.loading .lb-stage::before { opacity: 1; }
/* Das sichtbare Bild wird beim Laden NICHT mehr abgedunkelt (frueher opacity:.35
   → heller Kasten/Blitz beim schnellen Blaettern). Das alte Bild bleibt voll
   stehen, bis das neue dekodiert ist (siehe applyImage in gallery.js). */
@keyframes lb-spin { to { transform: rotate(360deg); } }

.lb-stage img {
    /* Bildwechsel: kurzer Crossfade (JS setzt/entfernt .is-swapping); FLIP beim
       Öffnen/Schließen ersetzt transform kurzzeitig, siehe gallery.js open()/close(). */
    transition: opacity .18s ease;
    /* Schutz gegen eine erzwungene aspect-ratio: Steht auf dem Element eine
       Ratio, die nicht der des Bildes entspricht (fehlende Masse im Verzeichnis),
       gewinnt sonst die Box und das Motiv wird gestaucht. contain zeigt das Bild
       in jedem Fall vollstaendig und unverzerrt. */
    object-fit: contain;
    max-width: min(92vw, 1600px);
    max-height: 78svh;
    border: var(--pp-lb) solid var(--passepartout);
    box-shadow: var(--shadow);
}
.lb-stage img.is-swapping { opacity: 0; }

.lb-caption {
    display: flex;
    gap: 1.25rem;
    align-items: baseline;
    font-size: .85rem;
    color: var(--ink-soft);
    font-family: var(--font-display);
    letter-spacing: .04em;
}

.lb-counter { opacity: .7; font-family: var(--font-display); }

.lb-download {
    color: var(--ink);
    text-decoration: underline;
    text-underline-offset: 3px;
}

.lb-close, .lb-prev, .lb-next {
    background: none;
    border: 0;
    cursor: pointer;
    color: var(--ink);
    font-size: 2.2rem;
    line-height: 1;
    padding: 1rem;
}

.lb-close {
    /* Über der Bild-Stage (kommt im DOM früher) — sonst verdeckt das
       Fullscreen-Bild den Button und Taps treffen nur das Bild. */
    z-index: 7;
    position: absolute;
    top: .75rem;
    right: 1rem;
    /* Kompakter Kreis statt riesigem Glyph: 2,6rem Schriftgröße plus 1rem
       Innenabstand ergaben einen Button, der das Bild dominierte. */
    width: 2.4rem; height: 2.4rem; padding: 0;
    font-size: 1.4rem;
    display: grid; place-items: center;
    border-radius: 50%;
    color: var(--ink-soft);
    transition: background .15s ease, color .15s ease, transform .15s ease;
}
.lb-close:hover { background: rgba(28, 27, 25, .08); color: var(--ink); }
.lb-close:active { transform: scale(.92); }

.lb-prev, .lb-next { font-size: 3rem; }
/* Anfang/Ende: Pfeil sichtbar inaktiv, statt nur funktional zu blockieren. */
.lb-prev:disabled, .lb-next:disabled {
    opacity: .3;
    cursor: default;
}

/* Gummiband: kurzer, gedämpfter Ausschlag in die versuchte Richtung, wenn am
   Anfang/Ende weiter geblättert wird (Pfeiltaste oder Wisch — die Pfeil-Knöpfe
   selbst sind an den Enden disabled, siehe oben, und lösen es daher nicht aus). */
@keyframes lb-edge-bounce-prev {
    30% { transform: translateX(24px); }
}
@keyframes lb-edge-bounce-next {
    30% { transform: translateX(-24px); }
}
.lb-stage img.edge-bounce-prev { animation: lb-edge-bounce-prev .18s ease-out; }
.lb-stage img.edge-bounce-next { animation: lb-edge-bounce-next .18s ease-out; }

/* Mobile Aktionsleiste — Desktop aus (dort Caption + Pfeile) */
.lb-bar { display: none; }

@media (max-width: 640px) {
    /* Fullscreen-Viewer (Pixieset-Muster): randloses Bild, Chrome per Tap ein-/ausblendbar */
    /* Drei echte Zeilen statt einer: Kopf, Buehne, Fussleiste. Das Bild teilt sich
       den Platz nicht mehr mit dem Chrome, sondern bekommt eine eigene Zeile —
       damit kann der Zaehler das Motiv nicht mehr ueberlagern. Die Kopfhoehe ist
       bewusst knapp und an der optischen Hoehe der unteren .lb-bar orientiert,
       damit oben und unten wie ein Paar wirken. */
    .lightbox {
        grid-template-columns: 1fr;
        grid-template-rows: calc(2.75rem + env(safe-area-inset-top)) 1fr auto;
        height: 100svh;
        background: var(--bg);
    }
    /* Der Kopfstreifen selbst: ein Pseudo-Element als Grid-Item der ersten Zeile.
       So bekommt der Kopf eine deckende Flaeche im Papierton, ohne dass am Markup
       etwas geaendert werden muesste (die .php sind hier tabu). */
    .lightbox::before {
        content: '';
        grid-row: 1; grid-column: 1;
        background: var(--bg);
        border-bottom: 1px solid var(--line);
        padding-top: env(safe-area-inset-top);
        z-index: 2;
    }
    /* min-height:0 ist Pflicht: ohne das waechst ein Grid-Item ueber seine Zeile
       hinaus (Default min-height:auto) und die Buehne schoebe die Fussleiste raus. */
    /* grid-column:1 ist Pflicht, nicht Deko: Seit die Pfeile (.lb-prev/.lb-next)
       explizit in grid-row:2/grid-column:1 wohnen, wuerde die Buehne per
       Auto-Placement in eine IMPLIZITE zweite Spalte ausweichen — Bild halb so
       breit rechts aussen, Pfeile und Fussleiste in einer linken Halbspalte
       (Mobil-Befund 22.07.). Explizit dieselbe Zelle = bewusste Ueberlagerung. */
    .lb-stage { grid-row: 2; grid-column: 1; padding: 0; height: auto; min-height: 0; place-items: center; overflow: hidden; }
    .lb-stage img {
        max-width: 100vw; max-height: 100%;
        border: 0; box-shadow: none;
        /* Im Vollbild-Viewer bleibt die BOX bei jedem Bild exakt gleich gross —
           nur der gemalte Inhalt wechselt. Vorher aenderte das <img> bei jedem
           Wisch seine Groesse (JS setzt inline eine aspect-ratio); iOS/Safari
           verwarf die alten Compositor-Kacheln des geschrumpften Bereichs nicht,
           und ober- und unterhalb blieben Reste des vorherigen Bildes stehen
           (frueher: weisse Flaechen). Ohne Groessenaenderung gibt es nichts mehr,
           was stehenbleiben koennte.
           Das !important ist hier notwendig und kein Versehen: die aspect-ratio
           kommt aus einem Inline-Style (gallery.js, applyImage) und waere sonst
           nicht zu ueberstimmen. Bitte nicht "aufraeumen". Auf dem Desktop bleibt
           die aspect-ratio wirksam — dort haengt das Passepartout an der
           tatsaechlichen Bildgroesse. */
        width: 100%;
        height: 100%;
        aspect-ratio: auto !important;
        object-fit: contain;
    }
    /* Spinner auf hellem Grund sichtbar (dunkel) */
    .lb-stage::before { border-color: rgba(28,27,25,.2); border-top-color: var(--ink); }
    /* Blättern per Wischen bleibt der Hauptweg, aber ohne sichtbare Pfeile fand
       ihn niemand ohne Wisch-Routine (UX-Audit). Die Buttons leben in derselben
       Grid-Zeile wie die Bühne (grid-row:2 wie .lb-stage) und werden dort links/
       rechts an den Rand gesetzt — sie liegen also NICHT über Kopf- oder
       Fussleiste und stehlen dem Tap-Toggle (der auf dem Bild sitzt) keine
       Fläche, da sie schmal und randständig bleiben. Optisch klein und
       halbtransparent, damit sie das Bild nicht erschlagen; die Trefferfläche
       wird wie bei .lb-close/.tile-fav per ::after unsichtbar auf 44px
       aufgeweitet (position:relative + zentriertes Pseudo-Element). */
    .lb-prev, .lb-next {
        display: grid; place-items: center;
        grid-row: 2; grid-column: 1; align-self: center;
        position: relative; z-index: 3;
        width: 2.3rem; height: 2.3rem; padding: 0;
        font-size: 1.5rem;
        border-radius: 50%;
        background: rgba(250, 249, 247, .5);
        color: var(--ink);
        -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
    }
    .lb-prev { justify-self: start; margin-left: .5rem; }
    .lb-next { justify-self: end; margin-right: .5rem; }
    .lb-prev::after, .lb-next::after {
        content: '';
        position: absolute;
        top: 50%; left: 50%;
        width: 44px; height: 44px;
        transform: translate(-50%, -50%);
    }
    /* X kompakt als Kreis (war viel zu groß). Sichtbare Größe bleibt 2.1rem —
       die Trefferfläche wird per ::after unsichtbar auf 44px aufgeweitet
       (position:relative + zentriertes Pseudo-Element, kein Layout-Einfluss). */
    .lb-close {
        top: calc(env(safe-area-inset-top) + .32rem); right: .55rem; left: auto; z-index: 4; width: 2.1rem; height: 2.1rem; padding: 0;
        font-size: 1.35rem; display: grid; place-items: center; border-radius: 50%;
        background: rgba(28,27,25,.08);
        position: absolute;
    }
    .lb-close::after {
        content: '';
        position: absolute;
        top: 50%; left: 50%;
        width: 44px; height: 44px;
        transform: translate(-50%, -50%);
    }

    /* Chrome: X, Zähler, Aktionsleiste — Tap aufs Bild blendet ein/aus */
    /* Bewusste Entscheidung: Der Tap blendet das Chrome nur aus, die beiden
       Grid-Zeilen behalten ihren Platz. Wuerden die Zeilen kollabieren, aendert
       sich die Buehnenhoehe — das Bild springt bei jedem Tap und iOS muss neu
       komponieren (genau die Klasse Fehler, die uns die weissen Reste gebracht
       hat). Ein paar verschenkte Millimeter sind ruhiger als ein huepfendes Bild. */
    .lb-close, .lb-caption, .lb-bar, .lb-prev, .lb-next, .lightbox::before { transition: opacity .25s ease; }
    .lightbox.chrome-hidden::before,
    .lightbox.chrome-hidden .lb-close,
    .lightbox.chrome-hidden .lb-caption,
    .lightbox.chrome-hidden .lb-bar,
    .lightbox.chrome-hidden .lb-prev,
    .lightbox.chrome-hidden .lb-next { opacity: 0; pointer-events: none; }
    /* Kein "Gefangen"-Gefühl: derselbe Tap aufs Bild, der die Pfeile ausblendet,
       holt sie (mit dem Rest des Chromes) wieder zurück — siehe toggleChrome()
       in gallery.js. Es gibt also immer einen Weg zurück, nur einen Tap entfernt. */

    /* Der Zaehler steht im Markup in der .lb-stage, gehoert optisch aber in den
       Kopf. position:fixed holt ihn dort hin, ohne das Markup anzufassen — und
       entkommt zugleich dem overflow:hidden der Buehne, das ihn sonst abschneiden
       wuerde. Bezug ist das Viewport, das passt zur ebenfalls fixen .lightbox. */
    .lb-caption {
        position: fixed; top: calc(env(safe-area-inset-top) + .78rem);
        left: 50%; transform: translateX(-50%); bottom: auto;
        font-size: .78rem; z-index: 4;
    }
    /* Name, Download und Favorit wandern in die Bottom-Leiste */
    .lb-caption #lb-name, .lb-caption .lb-download, .lb-caption .lb-fav { display: none; }

    .lb-bar {
        display: flex; position: static; grid-row: 3; grid-column: 1; z-index: 3;
        justify-content: space-evenly; align-items: stretch;
        padding: .5rem 0 calc(.6rem + env(safe-area-inset-bottom));
        background: color-mix(in srgb, var(--bg) 92%, transparent);
        -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
        border-top: 1px solid var(--line);
    }
    .lb-action {
        display: flex; flex-direction: column; align-items: center; gap: .25rem;
        border: 0; background: none; color: var(--ink); text-decoration: none;
        font-size: .72rem; font-family: var(--font-body); padding: .3rem 1.2rem; cursor: pointer;
    }
    /* Aktiver Favorit: gefülltes Herz in Ink — neutral, kämpft nicht mit den Bildern */
    .lb-action[aria-pressed="true"] .ico-heart { fill: var(--ink); stroke: var(--ink); }
}

/* Favoriten-Herz auf den Kacheln — Desktop bei Hover/Fokus, gesetzt immer sichtbar */
.tile-fav {
    position: absolute; right: calc(var(--pp-grid) + 8px); bottom: calc(var(--pp-grid) + 8px);
    width: 2rem; height: 2rem; display: grid; place-items: center; z-index: 2;
    border: 0; border-radius: 50%; cursor: pointer; padding: 0;
    background: rgba(250, 249, 247, .82); color: var(--ink);
    -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
    box-shadow: 0 1px 4px rgba(28, 27, 25, .18);
    opacity: 0; transform: scale(.9);
    transition: opacity .15s ease, transform .15s ease, color .15s ease;
}
/* Sichtbare Größe bleibt 2rem (32px) — Trefferfläche wird per ::after
   unsichtbar auf 44px aufgeweitet, ohne das Layout zu beeinflussen. */
.tile-fav::after {
    content: '';
    position: absolute;
    top: 50%; left: 50%;
    width: 44px; height: 44px;
    transform: translate(-50%, -50%);
}
.grid-item:hover .tile-fav,
.grid-item:focus-within .tile-fav,
.tile-fav[aria-pressed="true"] { opacity: 1; transform: none; }
.tile-fav:hover { transform: scale(1.08); }
.tile-fav:active { transform: scale(.92); }
.tile-fav svg { transition: fill .15s ease; }
.tile-fav[aria-pressed="true"] svg { fill: var(--ink); }
.tile-fav:focus-visible { outline: none; opacity: 1; }
@media (hover: none) { .tile-fav { opacity: 1; transform: none; } }  /* Touch: immer sichtbar */
/* Kurzer Pop beim Setzen — ein Ansatz, kein Hüpfen. Wird von JS beim Aktivieren
   erneut getriggert (Klasse entfernen/reflow/setzen). */
.tile-fav.js-pop { animation: tile-fav-pop .22s ease-out; }
@keyframes tile-fav-pop {
    from { transform: scale(1); }
    50% { transform: scale(1.14); }
}

/* ---------- Verstecken (Kurator-Modus: Main-Client / Admin-Vorschau) ---------- */
/* Auge unten links, spiegelbildlich zum Favoriten-Herz unten rechts. */
.tile-hide {
    position: absolute; left: calc(var(--pp-grid) + 8px); bottom: calc(var(--pp-grid) + 8px);
    width: 2rem; height: 2rem; display: grid; place-items: center; z-index: 2;
    border: 0; border-radius: 50%; cursor: pointer; padding: 0;
    background: rgba(250, 249, 247, .82); color: var(--ink);
    -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
    box-shadow: 0 1px 4px rgba(28, 27, 25, .18);
    opacity: 0; transform: scale(.9);
    transition: opacity .15s ease, transform .15s ease;
}
.tile-hide::after { content: ''; position: absolute; top: 50%; left: 50%; width: 44px; height: 44px; transform: translate(-50%, -50%); }
.grid-item:hover .tile-hide,
.grid-item:focus-within .tile-hide,
.tile-hide[aria-pressed="true"] { opacity: 1; transform: none; }
.tile-hide:hover { transform: scale(1.08); }
.tile-hide:active { transform: scale(.92); }
.tile-hide:focus-visible { outline: none; opacity: 1; }
@media (hover: none) { .tile-hide { opacity: 1; transform: none; } }
/* Auge = sichtbar (Standard), Auge-durchgestrichen = ausgeblendet (aria-pressed). */
.tile-hide .ico-eye-off { display: none; }
.tile-hide[aria-pressed="true"] .ico-eye { display: none; }
.tile-hide[aria-pressed="true"] .ico-eye-off { display: block; }

/* Ausgeblendete Kachel: abgedunkelt + Badge, aber weiter sichtbar/klickbar
   (rück-einblendbar). Für Nicht-Kuratoren existiert die Kachel gar nicht. */
.grid-item.is-hidden img { opacity: .3; filter: grayscale(.45); }
.tile-hidden-badge {
    position: absolute; top: calc(var(--pp-grid) + 8px); left: calc(var(--pp-grid) + 8px);
    z-index: 2; display: none; padding: 3px 9px; border-radius: 999px;
    font-size: 11px; font-weight: 600; letter-spacing: .02em;
    background: rgba(28, 27, 25, .8); color: #faf8f3;
}
.grid-item.is-hidden .tile-hidden-badge { display: inline-block; }

/* Lightbox-Verstecken-Icon (Auge/Auge-durchgestrichen). */
.lb-hide .ico-eye-off { display: none; }
.lb-hide[aria-pressed="true"] .ico-eye { display: none; }
.lb-hide[aria-pressed="true"] .ico-eye-off { display: block; }
.lb-hide[aria-pressed="true"] { background: var(--ink); color: #faf8f3; border-color: var(--ink); }

/* Kurator-Banner (oben): erklärt die Verstecken-Funktion, in der Admin-Vorschau
   mit Ausstiegslink. */
.curator-banner {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    padding: 10px 16px; background: #2b2a27; color: #faf8f3;
    font-size: 13.5px; line-height: 1.4; text-align: left;
}
.curator-banner .curator-eye { display: inline-flex; flex: none; opacity: .85; }
.curator-banner strong { font-weight: 600; }
.curator-banner .curator-exit { margin-left: auto; color: #faf8f3; font-weight: 600; text-decoration: underline; white-space: nowrap; }
/* Links im Fließtext des Banners (Anmelde-Hinweis bei ?kuratieren=1 ohne
   gültige Main-Anmeldung) — gleicher Ton wie der Banner, nur unterstrichen. */
.curator-banner span a { color: #faf8f3; text-decoration: underline; }

/* Lightbox-Aktionen: reine Icon-Buttons */
.lb-icon {
    display: inline-grid; place-items: center; width: 2.1rem; height: 2.1rem; padding: 0;
    border: 1px solid var(--line); border-radius: 50%; cursor: pointer;
    background: var(--passepartout); color: var(--ink); text-decoration: none;
    transition: border-color .15s ease, background .15s ease, transform .15s ease;
}
.lb-icon:hover { border-color: var(--ink); }
.lb-icon:active { transform: scale(.94); }
.lb-fav[aria-pressed="true"] svg { fill: var(--ink); }
.lb-download { border-color: var(--ink); background: var(--ink); color: var(--bg); }
.lb-download:hover { border-color: var(--ink); opacity: .9; }

/* Aktionen im Cover-Header — nur Icons */
.cover-actions { display: flex; gap: .7rem; justify-content: center; align-items: center; margin: 1.5rem 0 .2rem; }
.cover-actions form { margin: 0; }
.cov-item[hidden] { display: none; }
.cover-actions .cov-btn {
    position: relative; display: inline-grid; place-items: center;
    width: 2.6rem; height: 2.6rem; padding: 0;
    border: 1px solid currentColor; background: transparent; color: inherit;
    border-radius: 50%; cursor: pointer;
    transition: background .15s ease, transform .15s ease;
}
/* Cover-Text ist je nach Bild hell oder dunkel — deshalb neutral abheben */
.cover-actions .cov-btn:hover { background: rgba(128, 128, 128, .25); }
.cover-actions .cov-btn:active { transform: scale(.95); }
/* Eigener Chip statt currentColor — bleibt auf hellen wie dunklen Covern lesbar */
.cov-badge {
    position: absolute; top: -.2rem; right: -.2rem; min-width: 1.1rem; height: 1.1rem;
    display: grid; place-items: center; padding: 0 .25rem;
    border-radius: 999px; background: rgba(28, 27, 25, .82); color: #fff;
    font-size: .64rem; font-weight: 700; line-height: 1;
}

/* Größen-Menü (Web / Original) — öffnet erst auf Klick */
.dl-menu {
    position: fixed; z-index: 140; min-width: 15rem;
    background: var(--passepartout); border: 1px solid var(--line); border-radius: 12px;
    box-shadow: 0 10px 34px rgba(28, 27, 25, .22); padding: .35rem;
    animation: dl-in .15s ease;
}
.dl-menu[hidden] { display: none; }
@keyframes dl-in { from { opacity: 0; transform: translateY(-4px); } }
.dl-menu button {
    display: block; width: 100%; text-align: left; border: 0; background: transparent;
    border-radius: 9px; padding: .55rem .7rem; cursor: pointer; font: inherit; color: var(--ink);
}
.dl-menu button:hover { background: var(--bg); }
.dl-menu button:disabled { cursor: not-allowed; opacity: .45; }
.dl-menu button:disabled:hover { background: transparent; }
.dl-menu strong { display: block; font-size: .87rem; font-weight: 600; }
.dl-menu span { display: block; font-size: .74rem; color: var(--ink-soft); margin-top: .1rem; }

/* ---------- Unlock ---------- */
.unlock-page {
    display: grid;
    place-items: center;
    min-height: 100svh;
    padding: 1.5rem;
}

.unlock-card {
    background: var(--passepartout);
    box-shadow: var(--shadow);
    padding: 3rem;
    max-width: 26rem;
    width: 100%;
    text-align: center;
}

.unlock-card h1 {
    font-family: var(--font-display);
    font-weight: 500;
    margin: 0 0 1.5rem;
}

.unlock-card label {
    display: block;
    text-align: left;
    font-size: .85rem;
    color: var(--ink-soft);
    margin-bottom: .35rem;
}

.unlock-card input {
    width: 100%;
    padding: .7rem .8rem;
    border: 1px solid var(--line);
    background: var(--bg);
    font-size: 1rem;
    margin-bottom: 1.25rem;
}

.unlock-card button {
    width: 100%;
    padding: .8rem;
    background: var(--ink);
    color: var(--bg);
    border: 0;
    font-family: var(--font-display);
    letter-spacing: .1em;
    text-transform: uppercase;
    font-size: .8rem;
    cursor: pointer;
}

.alert-error {
    color: #a13d2d;
    font-size: .9rem;
}

/* Eingeklappter Login-Bereich für Galerie-Inhaber:innen unterhalb des Codes */
.unlock-owner {
    margin-top: 1.5rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--line);
    text-align: left;
}

.unlock-owner summary {
    cursor: pointer;
    text-align: center;
    font-size: .85rem;
    color: var(--ink-soft);
    list-style: none;
}

.unlock-owner summary::-webkit-details-marker { display: none; }

.unlock-owner summary:hover { color: var(--ink); }

.unlock-owner form { margin-top: 1rem; }

.unlock-owner-sent {
    margin-top: 1.5rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--line);
    font-size: .85rem;
    color: var(--ink-soft);
}

/* Footer: Betreiber-Credit zuerst (etwas präsenter, nicht fett) + Rechtslinks (aus theme.json) */
.footer-credit { margin:0 0 .5rem; }
.footer-credit a {
    color: var(--ink); text-decoration: none;
    font-family: var(--font-display); font-size: .92rem; letter-spacing: .03em;
    border-bottom: 1px solid var(--line); padding-bottom: .1rem;
    transition: border-color .15s;
}
.footer-credit a:hover { border-bottom-color: var(--ink); }
.footer-legal { display:flex; gap:1.25rem; justify-content:center; flex-wrap:wrap; margin-top:.4rem; }
.footer-legal a { color: var(--ink-soft); text-decoration: none; border-bottom:1px solid transparent; transition:color .15s, border-color .15s; font-size:.78rem; }
.footer-legal a:hover { color: var(--ink); border-bottom-color: var(--line); }
/* Einstieg in den Kurator-Modus — bewusst so ruhig wie „Abmelden": eine
   Handlung für die Haupt-Person, kein Angebot an alle Gäste. */
.footer-curate { margin-top:.4rem; text-align:center; }
.footer-curate a {
    color: var(--ink-soft); text-decoration:none; border-bottom:1px solid transparent;
    font-size:.78rem; transition:color .15s, border-color .15s;
}
.footer-curate a:hover { color: var(--ink); border-bottom-color: var(--line); }
.footer-logout { margin-top:.4rem; text-align:center; }
.footer-logout button {
    background:none; border:none; padding:0; cursor:pointer;
    color: var(--ink-soft); text-decoration:none; border-bottom:1px solid transparent;
    font-family:inherit; font-size:.78rem; transition:color .15s, border-color .15s;
}
.footer-logout button:hover { color: var(--ink); border-bottom-color: var(--line); }
.unlock-legal { position:fixed; bottom:1rem; left:0; right:0; }

/* ---------- Print-Paket: Dialog für die Benachrichtigungs-Adresse ---------- */
.pkg-overlay {
    position: fixed; inset: 0; z-index: 120;
    display: grid; place-items: center; padding: 1.2rem;
    background: rgba(22, 20, 15, .55);
    animation: fadeUp .2s ease;
}
.pkg-overlay[hidden] { display: none; }
.pkg-box {
    width: min(30rem, 100%);
    background: var(--passepartout); color: var(--ink);
    border: 1px solid var(--line); border-radius: 16px;
    padding: 1.6rem 1.5rem; box-shadow: 0 20px 60px -20px rgba(22, 20, 15, .5);
}
.pkg-box h2 {
    font-family: var(--font-display); font-weight: 400;
    font-size: 1.3rem; margin: 0 0 .5rem;
}
.pkg-note, .pkg-done { color: var(--ink-soft); font-size: .9rem; line-height: 1.55; margin: 0 0 1.1rem; }
.pkg-done { margin: 0 0 1rem; color: var(--ink); font-size: .95rem; }
.pkg-form label { display: block; font-size: .82rem; font-weight: 600; margin-bottom: .35rem; }
.pkg-form input[type="email"] {
    width: 100%; font: inherit; font-size: 1rem;
    padding: .7rem .8rem; border-radius: 10px;
    border: 1px solid var(--line); background: var(--bg); color: var(--ink);
}
.pkg-form input[type="email"]:focus { outline: none; border-color: var(--ink-soft); }
.pkg-err { color: #b3452f; font-size: .85rem; margin: .5rem 0 0; }
.pkg-err[hidden] { display: none; }
.pkg-actions { display: flex; justify-content: flex-end; gap: .6rem; margin-top: 1.1rem; }
.pkg-cancel, .pkg-go {
    font: inherit; font-size: .9rem; font-weight: 600; cursor: pointer;
    border-radius: 999px; padding: .6rem 1.2rem; min-height: 44px;
    transition: background .15s ease, border-color .15s ease, opacity .15s ease;
}
.pkg-cancel { border: 1px solid var(--line); background: transparent; color: var(--ink-soft); }
.pkg-cancel:hover { border-color: var(--ink-soft); color: var(--ink); }
.pkg-go { border: 0; background: var(--ink); color: var(--bg); }
.pkg-go:hover { background: #3E5B47; }
.pkg-go[disabled] { opacity: .6; cursor: default; }
/* „Bereit — jetzt laden": ersetzt das Formular, sobald der Poll ein fertiges
   Paket meldet, statt allein auf die Mail zu warten. Eigenständiger Erfolgs-Button
   (grün = fertig/los, wie die ZIP-Bereit-Semantik), mit Download-Icon (#6). */
.pkg-ready-link {
    display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
    width: 100%; box-sizing: border-box;
    font: inherit; font-size: .95rem; font-weight: 600;
    text-decoration: none; cursor: pointer;
    padding: .85rem 1.2rem; min-height: 48px;
    border: 0; border-radius: 999px;
    background: var(--green); color: #fff;
    box-shadow: 0 10px 24px -12px rgba(62, 91, 71, .7);
    transition: background .15s ease, transform .1s ease;
}
.pkg-ready-link:hover { background: #34503c; }
.pkg-ready-link:active { transform: scale(.98); }
.pkg-ready-link[hidden] { display: none; }

/* ---------- Kurzmeldung (Toast), z. B. bei gescheitertem Download ---------- */
.toast {
    /* iPhones mit Home-Indicator: Safe-Area einrechnen, sonst wird der Toast
       vom Indicator verdeckt/abgeschnitten. */
    position: fixed; left: 50%; bottom: calc(1.6rem + env(safe-area-inset-bottom)); z-index: 160;
    max-width: min(26rem, calc(100% - 2.4rem));
    transform: translateX(-50%) translateY(.4rem);
    background: var(--ink); color: var(--bg);
    border-radius: 12px; padding: .8rem 1.1rem;
    font-size: .86rem; line-height: 1.5;
    box-shadow: 0 14px 34px -10px rgba(22, 20, 15, .5);
    opacity: 0; transition: opacity .25s ease, transform .25s ease;
}
.toast[hidden] { display: none; }
.toast.is-visible { opacity: 1; transform: translateX(-50%) translateY(0); }

/* ---------- „Nach oben" ------------------------------------------------
   Erscheint erst nach reichlich Scroll (siehe gallery.js), unten rechts,
   dezent im Stil der Galerie. z-index unter Toast/Overlay/Größen-Menü/
   Lightbox, damit es die nie verdeckt. */
.to-top {
    position: fixed;
    right: clamp(1rem, 3vw, 1.75rem);
    bottom: calc(1.1rem + env(safe-area-inset-bottom));
    z-index: 60;
    width: 2.6rem; height: 2.6rem;
    display: grid; place-items: center; padding: 0;
    border: 1px solid var(--line); border-radius: 50%;
    background: color-mix(in srgb, var(--bg) 90%, transparent);
    -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
    color: var(--ink-soft); cursor: pointer;
    box-shadow: 0 3px 10px -6px rgba(28, 27, 25, .3);
    opacity: 0; transform: translateY(8px);
    pointer-events: none;
    transition: opacity .25s ease, transform .25s ease, color .15s ease, border-color .15s ease;
}
.to-top.is-visible { opacity: 1; transform: none; pointer-events: auto; }
.to-top:hover { color: var(--ink); border-color: var(--ink-soft); }
.to-top:focus-visible { outline: none; opacity: 1; pointer-events: auto; }

@media (max-width: 640px) {
    /* Sichtbare Größe bleibt 2.4rem (38,4px) — Trefferfläche wird per ::after
       unsichtbar auf 44px aufgeweitet, ohne das Layout zu beeinflussen. */
    .to-top {
        right: 1rem; bottom: calc(1rem + env(safe-area-inset-bottom)); width: 2.4rem; height: 2.4rem;
        position: fixed;
    }
    .to-top::after {
        content: '';
        position: absolute;
        top: 50%; left: 50%;
        width: 44px; height: 44px;
        transform: translate(-50%, -50%);
    }
}

/* ---------- Favoriten sichern: Dialog für anonyme Gäste ----------
   Bewusst dieselbe Form wie der Paket-Dialog: derselbe Kontext, dieselbe
   Erwartung — zwei verschiedene Dialogformen in einer Ansicht wären Unruhe
   ohne Gewinn. Die Selektoren bleiben getrennt, damit sich die beiden
   unabhängig entwickeln können. */
.fav-save-overlay {
    position: fixed; inset: 0; z-index: 170;
    display: grid; place-items: center; padding: 1.2rem;
    background: rgba(22, 20, 15, .42);
    -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
}
.fav-save-overlay[hidden] { display: none; }
.fav-save-box {
    width: min(30rem, 100%); background: var(--passepartout);
    border: 1px solid var(--line); border-radius: 14px;
    box-shadow: 0 18px 50px rgba(22, 20, 15, .22); padding: 1.5rem 1.5rem 1.3rem;
}
.fav-save-box h2 { font-family: var(--font-display); font-weight: 500; font-size: 1.25rem; margin: 0 0 .5rem; }
.fav-save-note, .fav-save-done { color: var(--ink-soft); font-size: .9rem; line-height: 1.55; margin: 0 0 1.1rem; }
.fav-save-done { margin: 0; color: var(--ink); }
.fav-save-form label { display: block; font-size: .82rem; font-weight: 600; margin-bottom: .35rem; }
.fav-save-form input[type="email"] {
    width: 100%; padding: .6rem .7rem; border: 1px solid var(--line);
    border-radius: 9px; background: var(--bg); color: var(--ink);
    font: inherit; font-size: 16px;   /* unter 16px zoomt iOS beim Fokus hinein */
}
.fav-save-form input[type="email"]:focus { outline: none; border-color: var(--ink-soft); }
.fav-save-err { color: #b3452f; font-size: .85rem; margin: .5rem 0 0; }
.fav-save-err[hidden] { display: none; }
.fav-save-actions { display: flex; justify-content: flex-end; gap: .6rem; margin-top: 1.1rem; }
.fav-save-cancel, .fav-save-go {
    min-height: 44px; padding: .55rem 1.1rem; border-radius: 999px;
    font: inherit; font-size: .9rem; font-weight: 600; cursor: pointer;
    transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.fav-save-cancel { border: 1px solid var(--line); background: transparent; color: var(--ink-soft); }
.fav-save-cancel:hover { border-color: var(--ink-soft); color: var(--ink); }
.fav-save-go { border: 0; background: var(--ink); color: var(--bg); }
.fav-save-go:hover { background: #3E5B47; }
.fav-save-go[disabled] { opacity: .6; cursor: default; }
.fav-save-cancel:active, .fav-save-go:active { transform: scale(.97); }
@media (prefers-reduced-motion: reduce) { .fav-save-cancel:active, .fav-save-go:active { transform: none; } }

/* Aktions-Knopf im Toast („Favoriten sichern") */
.toast-action {
    margin-left: .7rem; border: 0; background: none; padding: .2rem .1rem;
    color: inherit; font: inherit; font-size: .85rem; font-weight: 700;
    text-decoration: underline; cursor: pointer;
}

/* Parallax ENTFERNT (24.07.2026, PERMANENT): Das Coverbild sitzt in einem
   Passepartout-Rahmen (::before inset 5vmin + 12px Rand). Verschiebt die
   Parallax dieses Element, wandert die RAHMENKANTE sichtbar mit — ein gerahmtes
   Bild kann nicht parallaxen, ohne seine Kante zu zeigen (Live-Fund, mehrfach
   gemeldet). Das Cover steht ruhig. Der Scroll-Listener und --cover-shift sind
   in gallery.js ersatzlos entfernt; transform:none hier bleibt als Riegel. */
.cover::before { transform: none; }
