/* @import MUSS als allererstes in der Datei stehen (CSS-Spezifikation) -
   stand vorher weiter unten bei .oberwelt-inhalt und wurde dadurch vom
   Browser komplett ignoriert (kein Fehler, kein Log - einfach lautlos
   nicht geladen), Creepster/Nunito kamen nie an, Titel/Button liefen auf
   Schriftart-Fallback statt der Original-Halloween-Schrift. */
@import url('https://fonts.googleapis.com/css2?family=Creepster&family=Nunito:wght@400;600;800&display=swap');

/* ===== Einladung - Welt-Grundgerüst =====================================
   Adaptiert aus SoulKeeper/css/base.css (#story/#story-track) und
   SoulKeeper/css/himmel.css (Sterne, Farbverlauf, Theme-/Audio-HUD).
   Einziger bewusster Unterschied zum Original: #welt-track bewegt sich
   NICHT per transform (keine Kamerafahrt) - die vier Szenen liegen als
   normale Blockelemente im Dokumentfluss untereinander, ihre Höhe ergibt
   sich dadurch automatisch aus 4 x 100vh. Mond und hintere Friedhofsebene
   bleiben wie im Original direkte, vh-positionierte Kinder von
   #welt-track (nicht in eine einzelne Szene eingesperrt), damit sie an
   der Nahtstelle Himmel/Oberwelt nicht von deren overflow:hidden
   abgeschnitten werden - siehe css/level1.css. */

#welt-track {
    position: relative;
    width: 100%;
    /* height explizit (nicht auto aus dem Inhalt) - Himmel + Oberwelt +
       Höllentor x je 100vh, MINUS --oberwelt-kuerzung (Oberwelt wird per
       negativem margin-bottom verkürzt, siehe css/level1.css:
       #scene-level1), PLUS die Höhe der Unterwelt-Szene. Die Unterwelt hat
       seit der content-driven Umstellung (siehe css/level2.css: .unterwelt
       hat nur noch min-height:100svh, keine feste height mehr) selbst
       keine feste Höhe mehr - ihre tatsächliche Höhe ist
       max(100vh, Höhe von #programm), je nachdem ob der übernommene
       Programm-Text die Mindesthöhe der Höhlenkulisse über- oder
       unterschreitet. max() statt einer festen Addition, weil sich die
       Höhle sonst bei kurzem Text sinnlos zusätzlich zur eigenen
       Mindesthöhe verlängern würde. Nötig, damit height:auto hier keinen
       Zirkelbezug mit content-visibility:auto auf den Kind-Szenen auslöst
       (deren "bin ich relevant"-Berechnung hängt an der Position, die
       wiederum von der Höhe von #welt-track abhängt) - führte dazu, dass
       die gesamte Seite auf 0 Höhe kollabierte. --programm-hoehe wird per
       ResizeObserver (+ Erstmessung, siehe js/programm-hoehe.js) aus der
       echten, gerenderten Höhe von #programm gepflegt - reine vh-Werte
       könnten die variable (Zeilenumbruch-/Schriftart-abhängige) Höhe
       dieses übernommenen Textinhalts nicht abbilden. Muss mit
       #scene-level1 (css/level1.css, dieselbe --oberwelt-kuerzung)
       synchron bleiben - trotzdem nur eine Frage der Optik (keine
       Lücke/kein Überlapp zum Höllentor), nicht der Funktion: siehe
       overflow-y unten, das schützt unabhängig davon vor einem zweiten
       Scrollbalken. */
    /* TEST: + --hades-extra - der Höllentor-Sektion (siehe css/level3.css)
       wächst am unteren Ende um --hades-extra, sobald der darin
       eingebettete Seiteninhalt (siehe css/hades-inhalt.css) mehr Platz
       braucht als bis zur Brückenmitte standardmäßig verfügbar ist. */
    height: calc(calc(300 * var(--uh)) - var(--oberwelt-kuerzung) + max(calc(100 * var(--uh)), var(--programm-hoehe, 0px)) + var(--hades-extra, 0px));
    /* Tatsächliche Höhe der Unterwelt-Sektion, wiederverwendet von
       .level2-uebergang-hoellentor (Ebene5_4), #scene-hades (Fallstrecke der
       Seelen) UND .level2-ebene5-front (Ebene 5, siehe unten) - alle drei
       müssen dieselbe Zahl wie oben (max(100vh, --programm-hoehe)) kennen,
       um sich exakt an der echten Unterwelt-Oberkante/-Unterkante
       auszurichten. Eine gemeinsame Variable statt dreifacher Kopien
       derselben Formel verhindert, dass sie bei einer künftigen Änderung
       auseinanderlaufen (genau die Art Bug, die vorher schon zu Spalten an
       den Nahtstellen geführt hat). */
    --unterwelt-hoehe: max(calc(100 * var(--uh)), var(--programm-hoehe, 0px));
    overflow-x: hidden;
    /* Explizit statt implizit: overflow-x:hidden allein würde den
       Browser laut CSS-Spezifikation zwingen, overflow-y automatisch als
       "auto" zu berechnen (jeder nicht-"visible"-Wert auf einer Achse
       erzwingt "auto" auf der anderen, siehe css/base.css für dieselbe
       Regel bei html/body) - das hätte bei jeder noch so kleinen
       Abweichung zwischen der oben berechneten Höhe und der tatsächlich
       gerenderten (Sub-Pixel-Rundung von vh-Werten, Browser-Zoom,
       Schriftart-Rendering) einen ZWEITEN, unerwünschten Scrollbalken
       direkt an #welt-track erzeugt, zusätzlich zum normalen Seiten-
       Scrollbalken. hidden statt der impliziten Vorgabe schneidet
       stattdessen jede solche Abweichung lautlos ab (unsichtbar, kein
       Scrollbalken) - die Höhen-Berechnung oben bleibt trotzdem wichtig,
       aber nur noch für die Optik (keine Lücke/kein Überlapp zum
       Höllentor), nicht mehr für einen funktionalen Bug. */
    overflow-y: hidden;
}

/* Früher stand hier .nahtabdeckung - ein solider 22vh-hoher schwarzer
   Balken, der drei Gradient-Reste an der Nahtstelle Oberwelt->Unterwelt
   kaschiert hat (Details siehe git-Historie). Seit #programm (Was dich
   erwartet/Der Ablauf der Nacht) dort mit eigenem, deckendem Hintergrund
   direkt auf die Oberwelt folgt (siehe index.html, css/programm.css),
   übernimmt dessen Hintergrund diese Kaschier-Aufgabe von selbst - der
   Balken wurde dadurch nicht nur überflüssig, sondern (da die Oberwelt
   jetzt kürzer ist, siehe css/level1.css: #scene-level1) auch störend
   auffällig sichtbar. Entfernt statt nur unsichtbar gemacht. */

/* Durchgehender Hintergrund (Sterne + Farbverlauf) über die volle Höhe
   von #welt-track - liegt hinter allen Szenen. */
#himmel-hintergrund {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: 0;
}

/* Verlauf wie SoulKeeper/css/himmel.css: oben tiefschwarz mit Sternen,
   Übergang zur Theme-Horizontfarbe an der Nahtstelle Himmel->Oberwelt
   (100vh), durchgängig bis zur Nahtstelle Oberwelt->Unterwelt (200vh),
   danach Schwarz (Unterwelt/Höllentor decken ab dort ohnehin blickdicht
   mit eigenem Hintergrund ab - siehe css/level2.css/css/level3.css). */
#himmel-hintergrund {
    background: linear-gradient(to bottom,
        var(--sky-black) 0,
        var(--sky-black) calc(85 * var(--uh)),
        var(--horizon-color) calc(100 * var(--uh)),
        var(--horizon-color) calc(190 * var(--uh)),
        var(--level2-hoehle-bg) calc(200 * var(--uh))
    );
    transition: background var(--theme-transition);
}

.scene {
    position: relative;
    z-index: 1;
    width: 100%;
    height: calc(100 * var(--uh));
    overflow: hidden;
    background: transparent;
}

/* Performance: echtes, freies Scrollen bedeutet (anders als im Spiel, wo
   Höhle/Höllentor nie während des Scrollens sichtbar sind - die Kamera
   steht dort immer still), dass alle vier Welten inklusive ihrer vielen
   maskierten/animierten Effekte durchgehend gerendert würden, auch weit
   außerhalb des Bildschirms. content-visibility:auto lässt den Browser
   Layout/Paint für Szenen außerhalb des Viewports überspringen, ohne dass
   optisch etwas fehlt, sobald man wieder hinscrollt - contain-intrinsic-
   size verhindert dabei Sprünge in der Seitenhöhe, solange eine Szene noch
   nicht gerendert wurde.

   WICHTIG: NICHT auf #scene-level1 - content-visibility:auto erzwingt
   immer Paint-Containment (wirkt wie overflow:hidden fürs Malen), auch
   während die Szene sichtbar/gerendert ist. Das hätte die Äste
   (css/level1.css: .level1-kamera-ebene, ragen über die Oberkante der
   Oberwelt in den Himmel-Bereich hinein) hart an der Sektionsgrenze
   abgeschnitten. #scene-hades braucht diese Ausnahme NICHT (mehr) - seine
   eigene Box ist jetzt bewusst um 100vh nach oben vergrößert (siehe
   dortiger Kommentar unten), schließt die dort hineinragenden Seelen
   also bereits vollständig ein, ganz ohne überschüssiges Malen außerhalb
   der eigenen Box. Bekommt trotzdem eine eigene, größere
   contain-intrinsic-size (200vh statt 100vh), passend zur eigenen
   Boxhöhe.

   AUCH NICHT auf #scene-unterwelt - seit der content-driven Umstellung
   (#programm bestimmt per normalem Dokumentfluss die Höhe von .unterwelt,
   die Höhlenkulisse liegt dahinter absolut mit inset:0, siehe
   css/level2.css) hat sie keine feste 100vh-Höhe mehr, sondern wächst mit
   dem übernommenen Textinhalt. contain-intrinsic-size ist dabei nur ein Platzhalterwert für
   die Zeit, in der die Szene off-screen ist (nicht gerendert wird) - ein
   fixer 100vh-Platzhalter für eine tatsächlich variable, oft deutlich
   größere Box würde beim Herausscrollen zu einem sichtbaren Höhensprung
   führen. Performance-Kosten (Unterwelt wird immer gemalt, auch weit
   außerhalb des Viewports) sind bei dieser kleinen, privaten Party-Seite
   vertretbar. */
.scene:not(#scene-level1):not(#scene-unterwelt) {
    content-visibility: auto;
    contain-intrinsic-size: calc(100 * var(--uw)) calc(100 * var(--uh));
}

/* ===== Sterne (wie SoulKeeper/css/himmel.css) ===== */

.stars-layer {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.stars-far {
    opacity: 0.55;
    animation: sterne-atmen 6s ease-in-out infinite;
}

.stars-mid .stern,
.stars-near .stern {
    position: absolute;
    border-radius: 50%;
    background: var(--star-color);
    animation: funkeln var(--funkel-dauer, 3.5s) ease-in-out infinite;
    animation-delay: var(--funkel-versatz, 0s);
}

.stars-near .stern {
    box-shadow: 0 0 4px rgba(242, 236, 226, 0.8);
}

@keyframes funkeln {
    0%, 100% { opacity: var(--funkel-min, 0.35); }
    50% { opacity: 1; }
}

@keyframes sterne-atmen {
    0%, 100% { opacity: 0.5; }
    50% { opacity: 0.65; }
}

.sternschnuppe {
    position: absolute;
    width: 3px;
    height: 3px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 0 6px 1px rgba(255, 255, 255, 0.9);
    opacity: 0;
    animation: sternschnuppe-flug var(--flug-dauer, 1.2s) linear forwards;
}

.sternschnuppe::after {
    content: '';
    position: absolute;
    top: 50%;
    right: 50%;
    width: 110px;
    height: 1.5px;
    background: linear-gradient(to left, rgba(255, 255, 255, 0.85), transparent);
    transform: translateY(-50%) rotate(var(--schweif-winkel, 0deg));
    transform-origin: right center;
}

@keyframes sternschnuppe-flug {
    0%   { opacity: 0; transform: translate(0, 0); }
    8%   { opacity: 1; }
    75%  { opacity: 1; }
    100% { opacity: 0; transform: translate(var(--flug-x, 300px), var(--flug-y, 160px)); }
}

@media (prefers-reduced-motion: reduce) {
    .stars-far,
    .stars-mid .stern,
    .stars-near .stern {
        animation: none;
    }
    .sternschnuppe {
        display: none;
    }
}

/* ===== Theme-Umschalter (oben rechts) =====
   Wie SoulKeeper/css/himmel.css: .hud/.theme-switch - hier bewusst
   position:fixed statt absolute (im Original reichte absolute, weil sich
   #story nie scrollt; hier gibt es echtes Scrollen). */

.hud {
    position: fixed;
    top: 0;
    /* right:0 waere die echte Fensterkante - bei Letterboxing (siehe
       css/base.css: --letterbox-x) liegt die BUEHNEN-Kante aber weiter
       innen, dorthin soll der Umschalter kleben. */
    right: var(--letterbox-x, 0px);
    z-index: 50;
    padding: clamp(0.8rem, calc(2.5 * var(--uw)), 1.6rem);
}

.theme-switch {
    display: flex;
    gap: 0.4rem;
    background: var(--panel-bg);
    border: 1px solid var(--panel-border);
    border-radius: 8px;
    padding: 0.4rem;
    backdrop-filter: blur(3px);
}

.theme-swatch {
    width: 1.6rem;
    height: 1.6rem;
    border-radius: 50%;
    border: 2px solid transparent;
    cursor: pointer;
    padding: 0;
    transition: border-color 0.2s ease, transform 0.15s ease;
}

.theme-swatch:hover {
    transform: scale(1.1);
}

.theme-swatch[aria-pressed="true"] {
    border-color: #fff;
}

.theme-swatch--purple { background: #7a4fd6; }
.theme-swatch--orange { background: #c9500f; }
.theme-swatch--grey   { background: #6b7280; }
.theme-swatch--white  { background: #e5e5ea; }

/* ===== Button "Erneut verfluchen" (unten links) =====
   Dauerhafte Möglichkeit, den Fluch erneut heraufzubeschwören (siehe
   js/fluch-status.js) - von index.php nur gerendert, wenn die Welt gerade
   frei ist ($weltIstFrei). Bewusst klein/unauffällig gehalten, gleiche
   Panel-Optik wie .theme-switch/.audio-controls. */
.btn-welt-zuruecksetzen {
    position: fixed;
    /* Wie .hud: left addiert den Letterbox-Rand, damit der Button an der
       Buehnenkante statt der echten Fensterkante klebt (siehe css/base.css). */
    left: calc(var(--letterbox-x, 0px) + clamp(0.8rem, calc(2.5 * var(--uw)), 1.6rem));
    /* Sicherheitszone wie bei .audio-controls - dort die Begruendung. */
    bottom: calc(clamp(0.8rem, calc(2.5 * var(--uw)), 1.6rem) + env(safe-area-inset-bottom, 0px));
    z-index: 50;
    font-family: 'Press Start 2P', monospace;
    font-size: 0.55rem;
    color: var(--text-dim);
    background: var(--panel-bg);
    border: 1px solid var(--panel-border);
    border-radius: 8px;
    padding: 0.5rem 0.7rem;
    backdrop-filter: blur(3px);
    cursor: pointer;
    opacity: 0.6;
    transition: opacity 0.2s ease;
}

.btn-welt-zuruecksetzen:hover {
    opacity: 1;
}

/* ===== Audio-Steuerung (unten rechts) =====
   Wie SoulKeeper/css/himmel.css: .audio-controls - ebenfalls fixed statt
   absolute, aus demselben Grund wie oben. */

.audio-controls {
    position: fixed;
    /* Wie .hud: right addiert den Letterbox-Rand (siehe css/base.css). */
    right: calc(var(--letterbox-x, 0px) + clamp(0.8rem, calc(2.5 * var(--uw)), 1.6rem));
    /* env(safe-area-inset-bottom) addiert: auf iPhones faengt der Streifen
       des Home-Indikators am unteren Rand Tipper ab, bevor sie die Seite
       erreichen. Diese Leiste sass mit rund 17px Abstand mitten darin - der
       Ton liess sich in iv und gl am Telefon deshalb kaum einschalten,
       waehrend er in ch ging (dessen Ton-Knopf steht im normalen Fluss
       innerhalb der Buehne, nicht fixed am Fensterrand). Ohne Sicherheits-
       zone (Desktop, aeltere Browser) ist der Zuschlag 0. */
    bottom: calc(clamp(0.8rem, calc(2.5 * var(--uw)), 1.6rem) + env(safe-area-inset-bottom, 0px));
    z-index: 50;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    background: var(--panel-bg);
    border: 1px solid var(--panel-border);
    border-radius: 8px;
    padding: 0.5rem 0.8rem;
    backdrop-filter: blur(3px);
}

.btn-mute {
    background: none;
    border: none;
    color: var(--text-dim);
    cursor: pointer;
    font-size: 1.1rem;
    line-height: 1;
    padding: 0;
}

.btn-mute:hover {
    color: var(--text);
}

.volume-slider {
    width: 90px;
    accent-color: var(--accent-bright);
}

/* Positions-Tausch (rechts -> links) per @media(max-width) entfernt
   (2026-08-24, Bühnen-Nachbesserung) - siehe css/dialog.css fuer die
   Begruendung. .audio-controls haengt bereits ueber --letterbox-x korrekt
   an der Buehnenkante (siehe oben), unabhaengig vom echten Viewport. */

/* ===== Übergang Unterwelt -> Höllentor =====
   Zusätzliche Bergkamm-Silhouette (img/level2/Ebene5_4.svg, neu, nicht aus
   SoulKeeper übernommen). Gehört inhaltlich zur UNTERWELT, liegt aber wie
   Mond/hintere Ebene/Nahtabdeckung als eigenständiges Kind von #welt-track
   (siehe index.html) - NICHT als Kind von .unterwelt/#programm, siehe
   Kommentar dort. bottom:100vh bezieht sich auf die GESAMTHÖHE von
   #welt-track - unabhängig davon, wie hoch die Unterwelt-Szene durch
   #programm tatsächlich wird (siehe oben: max(100vh, --programm-hoehe)),
   ist #scene-hades als letzte Szene immer exakt 100vh hoch, macht die
   Unterkante dieses Elements also immer exakt zur Nahtstelle Unterwelt->
   Höllentor, derselben Kante wie die Oberkante von css/level3.css:
   .e5-top/Ebene5.svg (dort top:0 von #scene-hades). Reicht von dort nach
   OBEN in die Unterwelt hinein - liegt also oberhalb von Ebene5.svg, nicht
   darüber. z-index höher als .nahtabdeckung (20) UND höher als alles in
   #scene-hades, da diese Sektion jetzt overflow:visible hat (siehe unten)
   und sonst über der eigenen Sektionsgrenze hinausgemalter Höllentor-
   Inhalt (z.B. der Schein hinter den Seelen) diesen Kamm überdecken würde.
   Bewusst OHNE Glüh-Filter: der Feuerschein gehört zum Höllentor, an den
   Berg-Silhouetten der Unterwelt gibt es das nicht (wie css/level2.css:
   .level2-cave-ebene5-*-wrap, ebenfalls ungeglüht).

   Seitenverhaeltnis (576:48 = 12:1) IMMER erhalten, Hoehe bei
   --stage-scale:1 (Referenzgeraet) weiterhin exakt 225px: 2700px ist
   exakt 225px * 12 (das Seitenverhaeltnis), reines aspect-ratio:auto-height
   uebernimmt den Rest.
   FRUEHER width:max(100%, 2700px) - eine feste UNTERE px-Grenze, die bei
   breiten, aber niedrigen Fenstern (--stage-scale klein trotz grosser
   Fensterbreite) nicht mitschrumpfte und das Element deutlich zu gross
   wirken liess (TODO-20260901-039). JETZT width:calc(2700px *
   var(--stage-scale)) - skaliert wie jedes andere Buehnenelement rein
   ueber --stage-scale, keine Fallunterscheidung nach realer Fensterbreite
   mehr. Bleibt die Box (weiterhin oft) breiter als das sichtbare
   #welt-track, wird der Ueberhang weiterhin von dessen
   overflow-x:hidden seitlich abgeschnitten - das ist ausdruecklich
   erlaubt. left:50%+translateX(-50%) statt left:0, damit dieser
   Ueberhang symmetrisch auf beiden Seiten abgeschnitten wird statt nur
   rechts. */
.level2-uebergang-hoellentor {
    position: absolute;
    left: 50%;
    /* TEST: + --hades-extra - die Höllentor-Sektion danach ist jetzt nicht
       mehr immer exakt 100vh hoch (siehe css/level3.css:
       --hades-box-hoehe), sondern wächst um --hades-extra, sobald ihr
       eingebetteter Seiteninhalt (siehe css/hades-inhalt.css) mehr Platz
       braucht. Ohne diese Ergänzung würde dieser Bergkamm-Übergang bei
       --hades-extra > 0 zu weit oben (mitten im eingebetteten Inhalt statt
       an der echten Nahtstelle Unterwelt->Höllentor) landen. */
    bottom: calc(calc(100 * var(--uh)) + var(--hades-extra, 0px) - 10px);
    transform: translateX(-50%);
    width: calc(2700px * var(--stage-scale));
    aspect-ratio: 576 / 48;
    height: auto;
    background-image: url('../img/level2/Ebene5_4.svg');
    background-size: 100% 100%;
    background-repeat: no-repeat;
    /* War 25, jetzt 27 (die höchste der drei Nahtstellen-Ebenen) - auf
       Wunsch liegt Ebene5_4 jetzt VOR allem: vor dem Boden-Nebel (siehe
       .level2-bodennebel-front, 26) und vor Ebene5_1/5_2 (siehe
       .level2-ebene5-front, jetzt 25). Reihenfolge von vorne nach hinten:
       Ebene5_4 (27) > Boden-Nebel (26) > Ebene5_1/5_2 (25). */
    z-index: 27;
    pointer-events: none;
}

/* ===== Ebene 5 (Unterwelt-Vordergrund) - eigenständiger Layer =====
   .level2-cave-ebene5-1/2-wrap (die vorderste Höhlenschicht, siehe
   css/level2.css) sitzen NICHT mehr in .level2-decor/.unterwelt, sondern
   hier drin - aus demselben Grund wie Mond/hintere Ebene/Nahtabdeckung/
   .level2-uebergang-hoellentor: ein eigenständiges Kind von #welt-track
   statt ein in seine Szene eingesperrtes Kind. Ebene5_3-wrap ist die
   Ausnahme und sitzt bewusst wieder in .level2-decor/.unterwelt (siehe
   css/level2.css: Kommentar bei .level2-cave-ebene5-3-wrap) - deckte auf
   schmalen Screens sonst #programm-Inhalte zu.

   Grund: die fallenden Seelen (css/level3.css: .soul, siehe unten) starten
   jetzt oben in der Unterwelt und fallen quer durch deren komplette Höhe -
   sie sind aber DOM-technisch Nachfahren von #scene-hades, einer eigenen
   Sektion mit eigenem Stacking-Context (.scene hat z-index:1). Zwei
   Stacking-Contexts mit GLEICHEM z-index (#scene-unterwelt und #scene-hades
   haben beide z-index:1) werden nach DOM-Reihenfolge sortiert - #scene-hades
   steht später im Dokument und gewinnt deshalb komplett, unabhängig davon,
   wie hoch z-index:21/22 von Ebene 5 INNERHALB von #scene-unterwelt ist:
   ein höherer z-index INNERHALB eines Stacking-Contexts kann diesen nicht
   über einen GESCHWISTER-Stacking-Context heben, der selbst schon gewinnt.
   Die Seelen malten dadurch trotz Ebene 5s z-index:21/22 VOR ihr statt
   dahinter.

   Lösung: Ebene 5 bekommt (wie Ebene5_4 direkt darüber) einen eigenen
   Stacking-Context auf #welt-track-Ebene, mit einem z-index (26) der
   direkt gegen #scene-hades (z-index:1) antritt statt indirekt über den
   Umweg von #scene-unterwelt - schlägt #scene-hades jetzt unabhängig von
   dessen DOM-Position. #scene-unterwelts übriger Inhalt (Hintergrund,
   Ebene 1-4, #programm) bleibt UNVERÄNDERT hinter #scene-hades zurück -
   die Seelen bleiben dadurch weiterhin vor der Höhlen-Kulisse/dem Text
   sichtbar, verschwinden nur noch hinter Ebene 5 selbst.

   bottom:100vh (OHNE die -10px-Überlappung von .level2-uebergang-hoellentor
   - die war eine bewusste Übermalung für die Kamm-Silhouette, hier soll die
   Box exakt (nicht überlappend) an der echten Unterwelt-Kante sitzen) +
   height:var(--unterwelt-hoehe) ergibt exakt dieselbe Box wie vorher
   .unterwelt selbst (dessen eigentliche, gerenderte Höhe ebenfalls
   max(100vh, --programm-hoehe) ist) - .level2-cave-ebene5-*-wrap brauchen
   dadurch selbst KEINE einzige Zeile Code-Änderung (weiterhin top:0/
   bottom:0/left:0/right:0 relativ zu dieser Box, siehe css/level2.css),
   nur ihr Eltern-Element ändert sich. overflow:hidden hier uebernimmt die
   Klemm-Aufgabe, die vorher .unterwelt (via .scene) erledigt hat. */
.level2-ebene5-front {
    position: absolute;
    left: 0;
    /* TEST: + --hades-extra, aus demselben Grund wie
       .level2-uebergang-hoellentor oben. */
    bottom: calc(calc(100 * var(--uh)) + var(--hades-extra, 0px));
    width: 100%;
    height: var(--unterwelt-hoehe);
    overflow: hidden;
    /* War 26, dann 27, jetzt 25 (die niedrigste der drei Nahtstellen-Ebenen,
       aber immer noch klar über #scene-hades 1 - schlägt die Seelen also
       weiterhin zuverlässig, siehe Begründung oben). Auf Wunsch liegen
       Ebene5_1/5_2 jetzt GANZ HINTEN: hinter dem Boden-Nebel (siehe
       .level2-bodennebel-front, 26) und hinter Ebene5_4 (siehe oben, 27).
       Betrifft rein technisch auch Ebene5_1 (oberer Bergkamm, teilt sich
       dieses eine Element mit Ebene5_2) - unproblematisch, da Ebene5_1
       oben in der Höhle sitzt und sich mit Nebel/Ebene5_4 (beide ganz
       unten) ohnehin nie überschneidet. */
    z-index: 25;
    pointer-events: none;
}

/* ===== Boden-Nebel-Ebene: zwischen Ebene5_4 (Kammlinie, vorne) und
   Ebene5_1/5_2 (vorderste Höhlenschicht, jetzt hinten) ====================
   Auftrag: der Nebel an der Kammlinie (css/level2.css: .level2-smoke-bottom-*)
   soll HINTER Ebene5_4.svg liegen (.level2-uebergang-hoellentor, jetzt
   z-index:27 - die vorderste der drei), aber VOR Ebene5_1/5_2.svg
   (.level2-ebene5-front, jetzt z-index:25 - die hinterste). Dafür braucht
   der Nebel einen eigenen z-index GENAU zwischen beiden (26) UND denselben
   Trick wie Ebene5_4/Ebene5_1/5_2 selbst: ein eigenständiges Kind von
   #welt-track statt Kind von #scene-unterwelt, sonst würde der lokale
   z-index (siehe css/level2.css) nie gegen diese beiden GESCHWISTER-
   Stacking-Contexts antreten können (siehe ausführliche Begründung oben bei
   .level2-ebene5-front). bottom-Formel 1:1 von .level2-uebergang-hoellentor
   übernommen (exakt dieselbe Nahtstelle) - height:0 reicht, die Kind-
   Elemente positionieren sich ohnehin über bottom:Xvh (siehe
   css/level2.css: .level2-smoke-bottom-*), nicht über Prozent der
   Elternhöhe. */
.level2-bodennebel-front {
    position: absolute;
    left: 0;
    bottom: calc(calc(100 * var(--uh)) + var(--hades-extra, 0px) - 10px);
    width: 100%;
    height: 0;
    overflow: visible;
    z-index: 26;
    pointer-events: none;
}

/* ===== Seelen: Fallstrecke bis zur Decke der Unterwelt verlängert =====
   Original (css/level3.css): --soul-start setzt den Startpunkt nur knapp
   oberhalb von Ebene5.svg (--e5-fence-height + 60px, ca. 230-340px) - dort
   reichte das, weil die Kamera Level 2 nie gleichzeitig mit Level 3 zeigt.
   Hier reicht die Unterwelt direkt sichtbar bis an die Nahtstelle heran,
   die Seelen sollen deshalb von GANZ OBEN in der Unterwelt (der Grenze
   Oberwelt->Unterwelt) bis in den Fluss fallen.

   --soul-overflow (auf #scene-hades, vererbt sich auf .level3-hoellentor)
   ist NICHT der frühere feste Wert -100vh: die Unterwelt hatte in
   SoulKeeper eine feste 100vh-Höhe, ist hier aber content-driven (siehe
   css/level2.css: .unterwelt hat nur min-height, die echte Höhe kommt aus
   #programm) und je nach Textmenge/Fensterbreite oft 250-500vh hoch -
   -100vh hätte die Seelen dadurch nur ein kurzes Stück über der Nahtstelle
   starten lassen (irgendwo MITTEN im Inhalt), nicht an der echten Oberkante.
   max(100vh, var(--programm-hoehe, 0px)) ist dieselbe Formel wie für die
   tatsächliche Höhe der Unterwelt-Sektion (siehe oben: Gesamthöhe von
   #welt-track) - --soul-start darauf zu setzen heißt also "genau so viele
   Pixel über der Nahtstelle wie die Unterwelt hoch ist" = exakt ihre
   Oberkante, ganz gleich wie hoch #programm gerade tatsächlich ist.
   Überschreibt NUR --soul-start - Ziel (--river-mid), Anzahl (5 Seelen,
   siehe index.html) und Fallzeiten bleiben unverändert, --soul-fall-distance
   in css/level3.css berechnet sich automatisch neu daraus (calc() greift
   auf den kaskadierten Wert zu, nicht auf den zum Zeitpunkt der Deklaration
   in level3.css). Braucht
   #scene-hades { overflow: visible; } (statt des Original-overflow:hidden
   von .scene), sonst würde der Teil des Falls über der Nahtstelle
   abgeschnitten - wie bei den Ästen der Oberwelt (css/level1.css).

   Selektor bewusst #scene-hades .level3-hoellentor (höhere Spezifität als
   das einfache .level3-hoellentor in css/level3.css) - css/welt.css wird
   VOR css/level3.css geladen (siehe index.html), ohne die höhere
   Spezifität würde die spätere, schwächere Kaskade-Position von
   level3.css sonst gewinnen und diesen Override wirkungslos machen.

   ZWEI FEHLGESCHLAGENE VERSUCHE, bevor diese Lösung stand (der Vollständigkeit
   halber dokumentiert, falls das Problem in anderer Form wiederkehrt):
   1. #scene-hades { overflow: visible; } - hat zusätzlich auch das
      bewusste ~80px-Überstehen von .bridge-frame/.bridge-texture UNTER
      der Sektionsgrenze freigegeben (vorher vom normalen overflow:hidden
      unsichtbar mitgeklippt) und dadurch einen unechten, zusätzlichen
      Scrollbalken erzeugt.
   2. clip-path: inset(-100vh 0 0 0) statt overflow - verhindert zwar das
      Malen außerhalb der Box, aber NICHT den Beitrag zur "scrollable
      overflow"-Berechnung des Elements (anders als overflow:hidden/clip)
      - body bekam dadurch trotzdem eine um 81px zu große scrollHeight,
      wertete das als eigenen Inhalt und reservierte dafür einen eigenen,
      unsichtbaren 15px-Scrollbalken (kürzte width:100%-Elemente wie
      .level2-uebergang-hoellentor entsprechend).

   LÖSUNG: #scene-hades bekommt eine ECHTE, um --soul-overflow nach OBEN
   vergrößerte Box (height:calc(100vh + --soul-overflow) + margin-top:
   calc(-1 * --soul-overflow) - hebt die Box um --soul-overflow an, ohne
   die Flusshöhe von #welt-track zu verändern: margin-top + height ergibt
   IMMER exakt 100vh, unabhängig vom Wert von --soul-overflow, die negative
   margin gleicht die zusätzliche Höhe im normalen Fluss also immer wieder
   vollständig aus). Innerhalb davon bleibt .level3-hoellentor exakt 100vh
   hoch und unten angedockt (bottom:0 statt inset:0 - sonst würde sein
   eigenes height:100vh überschrieben und alle vh-Formeln darin, z.B.
   --river-mid, verzerren). Die Seelen (top:-soul-overflow relativ zu
   .level3-hoellentor) landen dadurch exakt an der neuen, ECHTEN Oberkante
   der jetzt vergrößerten Box - normales overflow:hidden (siehe .scene in
   css/welt.css) reicht damit wieder aus, kein Sonderfall mehr nötig, und
   .bridge-frame/.bridge-texture werden an der unteren (unveränderten)
   Kante wieder ganz normal mitgeklippt. */
#scene-hades {
    /* Dieselbe Formel wie --unterwelt-hoehe auf #welt-track (siehe oben,
       jetzt auch von .level2-ebene5-front genutzt) - eigener Name hier
       beibehalten, weil er innerhalb dieses Kommentarblocks (und in
       .level3-hoellentor unten) den SPEZIFISCHEN Zweck "Fallstrecken-
       Überstand der Seelen" beschreibt, nicht nur "Höhe der Unterwelt".

       TEST: + --hades-extra in height/contain-intrinsic-size - die
       Box wächst jetzt zusätzlich zum Seelen-Überstand auch um den
       Platzbedarf des eingebetteten Seiteninhalts (siehe
       css/hades-inhalt.css). margin-top bleibt UNVERÄNDERT (nur
       -soul-overflow) - --hades-extra soll den normalen Dokumentfluss
       tatsächlich verlängern (damit nachfolgender Inhalt wie der Footer
       weiter nach unten rutscht), --soul-overflow dagegen soll (wie
       bisher) unsichtbar bleiben. */
    --soul-overflow: var(--unterwelt-hoehe);
    height: calc(calc(100 * var(--uh)) + var(--soul-overflow) + var(--hades-extra, 0px));
    margin-top: calc(-1 * var(--soul-overflow));
    contain-intrinsic-size: calc(100 * var(--uw)) calc(calc(100 * var(--uh)) + var(--soul-overflow) + var(--hades-extra, 0px));
}

/* ===== Erlösung: Seelen steigen bis in den Himmel auf ====================
   Ist der Fluch gebrochen (html.fluch-gebrochen, siehe js/fluch-status.js),
   fliegen die Seelen nicht nur bis zur Unterwelt-Oberkante hoch (das wäre
   die normale --soul-overflow von oben), sondern durch die GESAMTE Oberwelt
   hindurch bis ganz an die Spitze von #welt-track (= Oberkante von
   #scene-himmel) - "sie sind frei und können in den Himmel". +200vh -
   --oberwelt-kuerzung ist exakt Oberwelt (100vh - --oberwelt-kuerzung) +
   Himmel (100vh); die Rechnung geht exakt bis Y:0 von #welt-track auf (kein
   Überstand über dessen eigenes overflow:hidden hinaus, siehe Beleg im
   Commit-Kommentar). --soul-overflow steuert automatisch DREI Dinge
   gleichzeitig, ohne dass hier noch etwas anderes angefasst werden muss:
   1. Die reale, um --soul-overflow vergrößerte Box von #scene-hades oben
      (kein Clipping des längeren Aufstiegs durch dessen eigenes
      overflow:hidden).
   2. --soul-start auf .level3-hoellentor unten (calc(-1 * --soul-overflow))
      - die Ruheposition, an der @keyframes soul-rise (css/level3.css) endet.
   3. Dadurch automatisch auch die Fallstrecke (--soul-fall-distance in
      css/level3.css, aus --soul-start berechnet) - spielt aber keine Rolle
      mehr, da bei gebrochenem Fluch ohnehin nur noch soul-rise läuft. */
html.fluch-gebrochen #scene-hades {
    --soul-overflow: calc(var(--unterwelt-hoehe) + calc(200 * var(--uh)) - var(--oberwelt-kuerzung));
}

/* TEST: von unten- auf oben-verankert (vorher inset:auto 0 0 0, exakt die
   UNTEREN 100vh von #scene-hades). Der bisher sichtbare Bereich musste
   fix an der UNTERKANTE von #scene-hades sitzen, weil #scene-hades nur
   nach OBEN vergrößert wurde (für den Seelen-Überstand, siehe
   --soul-overflow oben) - die eigentliche Höllentor-Fläche war dabei
   immer exakt 100vh. Jetzt kommt zusätzlich --hades-extra dazu (Platz für
   eingebetteten Inhalt, siehe css/hades-inhalt.css), um den die sichtbare
   Fläche selbst wächst - top:var(--soul-overflow) verankert ihre OBERKANTE
   weiterhin exakt an derselben Stelle (direkt nach der Unterwelt, wo
   Ebene5.svg/.e5-top sitzt - siehe css/level3.css, dort weiterhin top:0),
   height wächst um --hades-extra nach UNTEN. Alle Elemente innerhalb, die
   per bottom: positioniert sind (Tor, Zäune, Ufer, Brücke, Lavafluss,
   vordere Ebene-5-Elemente - praktisch alles außer .e5-top), rutschen
   dadurch automatisch mit nach unten, OHNE dass ihre einzelnen Formeln in
   css/level3.css angefasst werden müssen. */
#scene-hades .level3-hoellentor {
    --soul-start: calc(-1 * var(--soul-overflow));
    position: absolute;
    top: var(--soul-overflow);
    right: 0;
    bottom: auto;
    left: 0;
    height: calc(calc(100 * var(--uh)) + var(--hades-extra, 0px));
}

/* ===== Effekt-Umschalter (vorerst deaktiviert) =====
   Schaltet den Glow auf Pilzen/Kristallen (Unterwelt) und die
   Lavafluss-Bewegung (Höllentor) aus, ohne css/level2.css bzw.
   css/level3.css selbst anzufassen - beide bleiben unverändert als
   Referenz erhalten, falls die Effekte später wieder gebraucht werden.
   Aktiv, solange <body> die Klasse "effekte-reduziert" trägt (siehe
   index.html). WIEDER EINSCHALTEN: einfach die Klasse von <body> in
   index.html entfernen, an dieser Stelle muss dafür nichts geändert
   werden.

   Selektoren bewusst mit body.effekte-reduziert-Präfix (höhere
   Spezifität als die einfachen Klassen in level2.css/level3.css) - wie
   beim --soul-start-Override oben wird css/welt.css VOR den beiden
   geladen, ohne die höhere Spezifität würde die spätere, schwächere
   Kaskade-Position der Originaldateien sonst gewinnen. */
body.effekte-reduziert .level2-mushroom-glow {
    filter: none;
}

body.effekte-reduziert .level2-crystal-ambient-wrap {
    display: none;
}

body.effekte-reduziert .level2-crystal-outline-wrap {
    animation: none;
    filter: none;
}

/* Färbt den Felsen unter dem Kristall pulsierend ein (mix-blend-
   mode:screen) - liest sich optisch als Teil des Kristall-Scheins, obwohl
   es technisch am Felsen (Ebene4-3/Ebene5-3) hängt, nicht am Kristall
   selbst. */
body.effekte-reduziert .level2-crystal-rock-tint {
    display: none;
}

body.effekte-reduziert .lava-mover {
    animation: none;
}

/* ===== Oberwelt-Inhalt =====
   1:1 aus Halloween/index.php (.hero-content: Zeile, Titel, Untertitel,
   Countdown, CTA-Button) übernommen - inklusive der dortigen Original-
   Schriftarten (Creepster für den Titel, Nunito für Fließtext/Zeile/
   Countdown-Beschriftung; siehe assets/css/einladung/hero.css) statt der
   Press-Start-2P-Pixelschrift, die sonst auf dieser Seite läuft: das hier
   ist übernommener Party-Inhalt, kein SoulKeeper-Weltelement. EINZIGE
   Änderung gegenüber dem Original: dort war alles fest auf Orange kodiert
   (--orange/--orange-bright aus assets/css/base.css) - hier stattdessen
   die Theme-Variablen dieser Seite (--accent/--accent-bright), reagieren
   also auf den Theme-Umschalter. Positioniert im oberen Drittel der
   Oberwelt-Szene, damit Mond (oben rechts) und Friedhof-Silhouette
   (unten) frei bleiben. */

.hidden {
    display: none !important;
}

.oberwelt-inhalt {
    position: absolute;
    left: 50%;
    top: calc(1 * var(--uh));
    transform: translateX(-50%);
    z-index: 2;
    /* 90% direkt ueber --uw statt als Prozentwert des Containers - siehe
       css/dialog.css: .dialog-box fuer die allgemeine Begruendung dieses
       Musters. 640px bleibt bewusster absoluter Lesbarkeits-Deckel. */
    width: min(calc(90 * var(--uw)), 640px);
    text-align: center;
    pointer-events: none;
    font-family: 'Nunito', system-ui, sans-serif;
}

.oberwelt-inhalt > * {
    pointer-events: auto;
}

.oberwelt-topline {
    margin: 0 0 0.5rem;
    font-size: clamp(1rem, calc(2.5 * var(--uw)), 1.3rem);
    letter-spacing: 0.35em;
    text-indent: 0.35em;
    text-transform: uppercase;
    color: var(--text-dim);
}

.oberwelt-titel {
    margin: 0;
    font-family: 'Creepster', cursive;
    font-weight: 400;
    font-size: clamp(2.2rem, calc(7 * var(--uw)), 4.6rem);
    line-height: 0.95;
    letter-spacing: 0.05em;
    color: var(--accent-bright);
    text-shadow:
        0 0 18px var(--accent),
        0 0 60px var(--accent);
    transition: color var(--theme-transition), text-shadow var(--theme-transition);
}

/* Bezugsrahmen fuer die dauerhafte Seele (index.php: #seele-dauerhaft,
   css/verlorene-seele.css: .seele-dauerhaft) - inline-block schmiegt diesen
   Wrapper per shrink-to-fit GENAU an die tatsaechlich gerenderte Breite des
   Zweizeilers an (statt der vollen 640px-Breite von h1/.oberwelt-inhalt),
   deren top:50%/left:0 landen dadurch bei jeder Bildschirmbreite exakt auf
   der echten Buchstabenkante, nicht auf einem geschaetzten Abstand.
   text-align wird von .oberwelt-inhalt geerbt (center) - bei ungleich
   breiten Zeilen ("Halloween" vs. "Party 2026") bestimmt die BREITERE Zeile
   die Wrapper-Breite, die schmalere zentriert sich weiterhin darin.
   position:relative erzeugt bewusst KEINEN eigenen Stacking-Context (kein
   eigener z-index) - die Seele haengt sich mit ihrem negativen z-index
   deshalb weiterhin in den naechsten tatsaechlichen Stacking-Context hoch
   (.oberwelt-inhalt, z-index:2) und liegt dadurch trotzdem hinter dem hier
   gerenderten Titeltext. */
.oberwelt-titel-text {
    display: inline-block;
    position: relative;
}

.oberwelt-sub {
    margin: 0.7rem 0 1.2rem;
    font-weight: 600;
    font-size: clamp(0.9rem, calc(2.2 * var(--uw)), 1.1rem);
    color: var(--text);
    text-shadow: 0 2px 8px rgba(0, 0, 0, 0.8);
}

/* ===== Countdown =====
   .oberwelt-inhalt begrenzt seine Breite bewusst auf einen festen 640px-
   Lesbarkeits-Deckel (siehe dort) - der skaliert NICHT mit --stage-scale.
   Der Countdown ist als Block-Kind sonst genauso breit wie sein Elternteil
   und wuerde dadurch bei hohem --stage-scale (z.B. 4K-Monitor) auf 640px
   echte Pixel zusammengequetscht, obwohl seine Schriftgroessen (rem-basiert)
   proportional mitwachsen - Folge: abgeschnittene Ziffern. Fix: eigene
   Breite nach Inhalt (max-content, nie schmaler erzwungen) statt geerbter
   Elternbreite, Zentrierung ueber "left:50% der Elternbreite + eigene
   -50%" - da .oberwelt-inhalt selbst exakt auf der Buehnenmitte zentriert
   ist, faellt dieser Punkt unabhaengig von beiden Breiten immer mit der
   Buehnenmitte zusammen. */
.countdown {
    display: flex;
    justify-content: center;
    flex-wrap: nowrap;
    gap: clamp(0.5rem, calc(2 * var(--uw)), 1.25rem);
    position: relative;
    left: 50%;
    transform: translateX(-50%);
    width: max-content;
    max-width: calc(100 * var(--uw));
}

.count-box {
    background: rgba(13, 11, 20, 0.65);
    border: 1px solid var(--accent);
    border-radius: 14px;
    padding: clamp(0.4rem, calc(1.5 * var(--uw)), 0.75rem) clamp(0.6rem, calc(2 * var(--uw)), 1.1rem);
    min-width: clamp(56px, calc(14 * var(--uw)), 90px);
    backdrop-filter: blur(4px);
    box-shadow: 0 0 25px rgba(0, 0, 0, 0.15), inset 0 0 20px rgba(0, 0, 0, 0.4);
    transition: border-color var(--theme-transition);
}

.count-num {
    display: block;
    font-family: 'Creepster', cursive;
    font-size: clamp(1.8rem, calc(6 * var(--uw)), 3.2rem);
    color: var(--accent-bright);
    text-shadow: 0 0 14px var(--accent);
    font-variant-numeric: tabular-nums;
    transition: color var(--theme-transition), text-shadow var(--theme-transition);
}

.count-label {
    display: block;
    font-size: clamp(0.65rem, calc(1.5 * var(--uw)), 0.8rem);
    text-transform: uppercase;
    letter-spacing: 0.15em;
    color: var(--text-dim);
    margin-top: 0.3rem;
}

.countdown-done {
    font-family: 'Creepster', cursive;
    font-size: clamp(1.6rem, calc(5 * var(--uw)), 2.6rem);
    color: var(--accent-bright);
    text-shadow: 0 0 16px var(--accent);
    margin: 1rem 0 0;
}

/* Repositionierung per @media(max-width) entfernt (2026-08-24, Bühnen-
   Nachbesserung) - siehe css/dialog.css fuer die Begruendung. Mond und
   Hero-Text skalieren beide proportional mit der Buehne (siehe
   css/level1.css: .level1-mond, oben: .oberwelt-inhalt), ihr Abstand
   zueinander bleibt dadurch bei jeder Buehnengroesse gleich wie am
   1920x1080-Referenzbild. */
