/* ===== SoulKeeper - Basis/Reset ===================================== */

@import url('https://fonts.googleapis.com/css2?family=VT323&family=Press+Start+2P&display=swap');

* {
    box-sizing: border-box;
}

/* ===== Logische Buehne (1280x720) =====================================
   --stage-scale/--uw/--uh werden von js/buehne.js auf <html> gesetzt (1 =
   Buehne bei 1280x720 real bzw. jedem anderen exakten 16:9-Viewport wie
   1920x1080 - die bisherige Referenzoptik bleibt dadurch unveraendert).
   html { font-size } skaliert dadurch JEDES bestehende rem-Mass (Buttons,
   Dialoge, Panels, Schriftgroessen in css/menues.css, css/namenseingabe.css
   etc.) automatisch proportional mit, ohne dass diese Dateien angefasst
   werden muessen. Fallback 1 (Attribut noch nicht gesetzt, z.B. falls JS
   deaktiviert waere) haelt die bisherige Optik als Notnagel bei. */
html {
    font-size: calc(16px * var(--stage-scale, 1));
}

/* Verhindert Mobile Safaris automatische Schriftvergrößerung ("Text Size
   Adjust"/Font-Boosting): iOS vergrößert eigenständig Textblöcke, die es
   als schwer lesbaren Fließtext einstuft, kurze Einzeiler aber nicht -
   dadurch wirken auf dem iPhone gleich große rem-Werte je nach Textblock
   unterschiedlich groß (siehe TODO-20260901-030, uebernommen aus dev0/iv/
   css/base.css). Mit 100% rendert jeder Text exakt in der hier per rem/
   --stage-scale festgelegten Größe, auf allen Geräten identisch. */
html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

/* body zentriert #story (siehe unten) per Flexbox im echten Viewport -
   #story bekommt dort eine reale Pixel-Box, die sich direkt aus --uw/--uh
   (je 1% von 1280/720, bereits mit --stage-scale multipliziert) ergibt.
   Der dadurch bei Nicht-16:9-Viewports sichtbare Rand links/rechts bzw.
   oben/unten (Letterboxing) zeigt automatisch die body-Hintergrundfarbe
   (--sky-black), passt optisch zum Nachthimmel. Kein zusaetzlicher
   Wrapper-Div noetig - #story ist bereits der alleinige Bühnen-Container
   (alle HUD-/Dialog-/Overlay-Elemente mit vormals position:fixed liegen
   schon als Nachfahren von #story im Markup, siehe dort - sie wechseln
   lediglich auf position:absolute, um sich jetzt an #story statt am
   echten Viewport auszurichten). */
body {
    display: flex;
    align-items: center;
    justify-content: center;
}

html, body {
    margin: 0;
    padding: 0;
    height: 100%;
    /* Kein freies Scrollen - die Reise nach unten wird ausschließlich vom
       Spiel gesteuert (Start-Klick, bestandene Level, Story-Sequenzen).
       Sobald spätere Ebenen ergänzt werden, übernimmt js/scenes.js das
       programmatische Scrollen zwischen ihnen. */
    overflow: hidden;
    background: var(--sky-black);
    color: var(--text);
    font-family: 'Press Start 2P', monospace;
    /* Touch-/Gesten-Sperren (v.a. iOS Safari, siehe js/touch-schutz.js für
       das JS-Gegenstück): manipulation statt none, damit die weiter unten
       per overflow-y:auto scrollbaren Panels (Highscore/Settings/
       Endauswertung, siehe css/menues.css/css/uebergang.css) touch-scrollbar
       bleiben - "kein Scrollen" gilt nur für die STARRE Spiel-Bühne selbst
       (die hat ohnehin nichts zum Scrollen, siehe overflow:hidden oben),
       nicht für deren Inhalte. manipulation unterbindet trotzdem zuverlässig
       Doppeltipp-/Pinch-Zoom. overscroll-behavior verhindert zusätzlich das
       Gummiband-/Bounce-"Verschieben" der ganzen Seite an den Rändern, das
       auf iOS auch bei overflow:hidden sonst noch auftreten kann. */
    touch-action: manipulation;
    overscroll-behavior: none;
    -webkit-touch-callout: none;
}

/* Verhindert versehentliches Ziehen von Bildern (Boss-/Gegner-Sprites, Mond,
   Seelen, HUD-Icons etc.) - global, nicht nur während aktiven Gameplays
   (siehe .level1/2/3-gameplay-aktiv weiter unten für die dortige, engere
   Ergänzung um touch-action:none). Berührt click-/pointer-events nicht,
   Dragging ist eine komplett separate Browser-Geste. */
img {
    -webkit-user-drag: none;
    user-drag: none;
    -webkit-touch-callout: none;
}

/* Generische Sichtbarkeits-Klasse - ersetzt 17 wortgleiche
   .komponente.hidden { display: none; }-Kopien, die über CSS-Dateien im
   Projekt verstreut waren (siehe log/SK_SELF_CHECK_2026-08-23.md: SK-036).
   HTML nutzt die Klasse `hidden` bereits überall zusätzlich zur
   Komponentenklasse, keine Markup-Änderung nötig. Einzige bewusste
   Ausnahme bleibt .dialog-hinweis.hidden (css/dialog.css, visibility:hidden
   statt display:none, dort eigens kommentiert, bleibt unangetastet - zwei
   Klassen sind dort ohnehin spezifischer als die Regel hier).

   Bugfix (2026-08-24): SK-036 hat einen Teil seiner eigenen Begründung
   uebersehen - "keine Komponentenklasse braucht hoehere Spezifitaet"
   stimmte fuer die KOMPONENTENKLASSE selbst, nicht aber fuer Faelle, in
   denen dieselbe Komponentenklasse SELBST bereits einen eigenen
   display-Wert setzt (z.B. .panel { display: flex; ... }, siehe
   css/menues.css). ".panel" und ".hidden" sind beide einfache
   Ein-Klassen-Selektoren mit IDENTISCHER Spezifitaet - bei Gleichstand
   gewinnt die im Ladeauftrag (siehe index.html) SPAETER eingebundene
   Regel, unabhaengig vom "Sinn" der Regel. base.css laedt vor menues.css/
   dialog.css/uebergang.css/level1-3-gameplay.css, wodurch deren jeweils
   eigene display-Deklarationen (.panel, u.a.) die Regel hier bei
   gesetzter .hidden-Klasse STILL ueberschrieben haben - alle sechs
   Panels (Highscore/Settings/Spielwerte/Seelenpfad/Jukebox/
   Namenseingabe) blieben dadurch dauerhaft display:flex sichtbar, auf
   dem Start-Bildschirm sichtbar uebereinandergestapelt (auf Desktop wegen
   der schieren Bildschirmgroesse kaum auffaellig, auf schmaleren
   Viewports wie iPhone-Querformat aber blockierend). "html " davor hebt
   die Spezifitaet minimal auf (0,1,1) an - hoeher als jede reine
   Ein-Klassen-Komponentenregel (0,1,0), unabhaengig von der Ladereihenfolge,
   ohne 17 Komponentenklassen-Kopien zurueckzubauen und ohne !important. */
html .hidden {
    display: none;
}

img, svg {
    display: block;
    max-width: 100%;
}

button {
    font-family: inherit;
}

/* ===== Story-Container =====
   #story ist das Sichtfenster (immer genau 100vh, schneidet alles
   Weitere ab). #story-track darin ist so hoch wie alle Ebenen zusammen
   (Himmel, Oberwelt, Unterwelt, Hades) und wird per transform:translateY
   bewegt - das ist die "Kamerafahrt". Kein echtes Scrollen, deshalb auch
   kein Konflikt mit overflow:hidden auf html/body, und die Bewegung lässt
   sich per CSS-Transition beliebig langsam/weich timen statt wie
   gewöhnliches Scrollen zu wirken. */
#story {
    position: relative;
    flex: none;
    width: calc(100 * var(--uw));
    height: calc(100 * var(--uh));
    overflow: hidden;
}

#story-track {
    position: relative;
    width: 100%;
    /* Explizit gesetzt (nicht nur implizit durch die Kinder bestimmt) -
       sonst kann #himmel-hintergrund darin nicht zuverlässig height:100%
       auflösen. --ebenen-anzahl bei jeder neuen Ebene (Unterwelt, Hades)
       um 1 erhöhen. */
    --ebenen-anzahl: 4;
    height: calc(var(--ebenen-anzahl) * 100 * var(--uh));
    transition: transform 4s cubic-bezier(0.65, 0, 0.35, 1);
}

/* Kamerafahrt zu Level 1 - bewusst NICHT die vollen 100vh (eine ganze
   Ebene), sondern nur 80vh: der Himmel darf beim Übergang nicht komplett
   verschwinden, der obere Bildrand soll noch dunklen Himmel zeigen. Das
   passt exakt zum Verlauf in css/himmel.css (#himmel-hintergrund geht von
   sky-black bei 0 zu horizon-color bei 100vh) - bei -80vh zeigt der obere
   Bildschirmrand also noch die letzten 20vh Himmel-Schwarz, geht darunter
   weich in die Horizontfarbe über und der Rest ist durchgehend Orange,
   statt eines harten Schnitts auf reines Orange. Weitere Level später als
   zusätzliche --levelN-Klasse ergänzen (Distanz ggf. neu abstimmen). */
#story-track.story-track--level1 {
    transform: translateY(calc(-80 * var(--uh)));
}

/* Kamerafahrt zu Level 2 (Unterwelt) - bewusst OHNE Vorschau der letzten
   20vh von Level 1 (anders als beim Himmel→Level1-Übergang): dort ist der
   "Blick zurück" unauffälliger dunkler Sternenhimmel, hier wäre es der
   untere Friedhofsbereich mit Bäumen/Grabsteinen - würde wie ein
   Bild-Fehler aussehen ("Baum in der Höhle"). Deshalb harter Schnitt auf
   genau -200vh (Level 1 komplett durchgelaufen, Level 2 startet exakt am
   oberen Bildschirmrand). Ersetzt (nicht kumulativ mit)
   .story-track--level1, da beide dieselbe Eigenschaft setzen - bei
   gleicher Selektor-Spezifität gewinnt die spätere Regel (siehe
   js/levelkarte.js: springeZuEbene setzt beim Sprung zu Level 2 trotzdem
   auch .story-track--level1 mit, für konsistenten Zustand beim
   Zurückspringen). */
#story-track.story-track--level2 {
    transform: translateY(calc(-200 * var(--uh)));
}

/* Kamerafahrt zu Level 3 (Das Höllentor) - selber harter-Schnitt-Ansatz wie
   Level 2: Level 2 komplett durchgelaufen, Level 3 startet exakt am oberen
   Bildschirmrand. Muss NACH .story-track--level2 stehen (gleiche
   Spezifität, spätere Regel gewinnt), da js/levelkarte.js beim Sprung nach
   Level 3 weiterhin auch .story-track--level1/--level2 gesetzt lässt
   (konsistenter Zustand für ein Zurückspringen). */
#story-track.story-track--level3 {
    transform: translateY(calc(-300 * var(--uh)));
}

/* Ein durchgehender Hintergrund (Sterne + Verlauf) über die gesamte Höhe
   von #story-track - liegt hinter allen Szenen, die selbst transparent
   sind (siehe css/himmel.css, css/level1.css). Details zum Verlauf dort. */
#himmel-hintergrund {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: 0;
}

.scene {
    position: relative;
    z-index: 1;
    width: 100%;
    height: calc(100 * var(--uh));
    overflow: hidden;
    background: transparent;
}

/* ===== Debug: Trefferradius-Marker (?hitbox=1) =====
   Zeigt den tatsächlichen Trefferradius jedes Schusses (siehe
   js/hitbox-debug.js, genutzt von js/level1/2/3-gameplay.js) an der
   echten Klickposition - gefüllter Kreis bei Treffer, gestrichelter Ring
   bei Fehlschuss. Rein visuell, ohne Einfluss auf die Trefferlogik selbst
   (die läuft unabhängig davon in beiKlick). Eine gemeinsame Regel für
   alle drei Level statt drei Kopien - Änderungen am Aussehen wirken sich
   dadurch automatisch überall aus. */
.feind-treffer-debug {
    position: absolute;
    transform: translate(-50%, -50%);
    border-radius: 50%;
    z-index: 9;
    pointer-events: none;
    animation: feind-treffer-debug-fade 1s ease-out forwards;
}

.feind-treffer-debug--treffer {
    background: rgba(255, 40, 40, 0.35);
    border: 2px solid #ff2a2a;
}

.feind-treffer-debug--fehlschuss {
    background: transparent;
    border: 2px dashed rgba(255, 42, 42, 0.75);
}

@keyframes feind-treffer-debug-fade {
    0%   { opacity: 1; }
    65%  { opacity: 1; }
    100% { opacity: 0; }
}

/* LP-Debug-Anzeige (?hitbox=1), gemeinsam für alle drei Level (bisher
   dreifach als .l1-feind-lp-debug/.l2-feind-lp-debug/.l3-soulkeeper-lp-debug
   kopiert) - js/level{1,2,3}-gameplay.js weisen jetzt zusätzlich zur
   levelspezifischen Klasse (bleibt als reiner querySelector-Hook bestehen)
   diese gemeinsame Klasse zu. Level 3 überschreibt font-size/text-shadow
   weiterhin per eigenem, kleinerem Modifier (siehe css/level3-gameplay.css). */
.feind-lp-debug {
    position: absolute;
    top: 2px;
    left: 50%;
    transform: translateX(-50%);
    font-size: 0.85rem;
    color: #fff;
    background: rgba(0, 0, 0, 0.55);
    padding: 0.15em 0.4em;
    border-radius: 3px;
    text-shadow: 1px 1px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000;
    white-space: nowrap;
    pointer-events: none;
}

/* ===== Fadenkreuz während des aktiven Gameplays, gemeinsam für alle drei
   Level (bisher wortgleich dreifach als .level1/2/3-gameplay-aktiv kopiert,
   siehe css/level1-gameplay.css für den ausführlichen Herleitungs-
   Kommentar). Klasse wird von js/level{1,2,3}-gameplay.js exakt während
   laeuft===true gesetzt/entfernt. ===== */
.level1-gameplay-aktiv,
.level2-gameplay-aktiv,
.level3-gameplay-aktiv {
    /* Fallback für den kurzen Moment, bevor waehleWaffe() das erste Mal
       [data-waffe] gesetzt hat - siehe die spezifischere Regel direkt
       darunter für die tatsächliche Waffenauswahl. */
    cursor: var(--cursor-pistole);
    user-select: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    -webkit-user-drag: none;
    user-drag: none;
    /* Strenger als das globale touch-action:manipulation (siehe html/body
       oben) - während der eigentlichen Ballerei gibt es auf der Bühne
       selbst nichts zu scrollen/zoomen, deshalb hier bewusst "none" statt
       "manipulation": unterbindet zusätzlich jedes versehentliche
       Ein-Finger-Wischen/Verschieben genau während des Zielens/Schießens,
       ohne die separaten scrollbaren Menü-Panels zu berühren (die liegen
       außerhalb dieser Klasse). */
    touch-action: none;
}

.level1-gameplay-aktiv[data-waffe="pumpgun"],
.level2-gameplay-aktiv[data-waffe="pumpgun"],
.level3-gameplay-aktiv[data-waffe="pumpgun"] {
    cursor: var(--cursor-pumpgun);
}

/* ===== Fadenkreuz als eigenes Element (Bugfix TODO-20260908-006) =====
   Die Cursor-Regeln oben bleiben unverändert bestehen und sind weiterhin
   die Rückfallebene ohne JavaScript. Sichtbar gezeichnet wird das
   Fadenkreuz aber von js/fadenkreuz.js (siehe dort für die Herleitung:
   ein Browser entscheidet unabhängig vom berechneten CSS-Wert, ob er ein
   Custom-Cursor-BILD überhaupt zeichnet - Blink verwirft Bilder über
   32 CSS-Pixel, sobald ihr Rechteck nicht vollständig im sichtbaren
   Viewport liegt). Das Element folgt der Mausposition und ist dadurch von
   dieser Beschränkung nicht betroffen.

   Der Wrapper ist ein 0x0-Punkt an der Mausposition (JS setzt nur
   transform: translate3d, eine reine Compositor-Eigenschaft - kein Reflow
   pro Mausbewegung); die beiden Bilder hängen sich per negativem margin
   mittig daran auf, sodass die Bildmitte exakt auf clientX/clientY liegt -
   dieselbe Lage wie der bisherige Cursor-Hotspot (16 16 bzw. 24 24) und
   damit weiterhin genau dort, wo ein Treffer zählt. */
#fadenkreuz {
    position: fixed;
    left: 0;
    top: 0;
    width: 0;
    height: 0;
    z-index: 9000;
    pointer-events: none;
    display: none;
}

/* Die Klasse setzt ausschließlich js/fadenkreuz.js, und nur solange es
   tatsächlich ein Fadenkreuz an einer bekannten Mausposition zeichnet. */
html.fadenkreuz-aktiv #fadenkreuz {
    display: block;
}

#fadenkreuz .fadenkreuz-bild {
    position: absolute;
    left: 0;
    top: 0;
    display: none;
    /* Hebt die globale Regel `img, svg { max-width: 100% }` (siehe oben)
       wieder auf: der Wrapper ist absichtlich 0 Pixel breit, das Bild wäre
       damit sonst ebenfalls 0 breit. Breite/Höhe zusätzlich explizit, damit
       die Größe nicht allein an den SVG-Attributen hängt. */
    max-width: none;
}

#fadenkreuz[data-waffe="pistole"] .fadenkreuz-bild--pistole,
#fadenkreuz[data-waffe="pumpgun"] .fadenkreuz-bild--pumpgun {
    display: block;
}

.fadenkreuz-bild--pistole {
    width: 32px;
    height: 32px;
    margin: -16px 0 0 -16px;
}

.fadenkreuz-bild--pumpgun {
    width: 48px;
    height: 48px;
    margin: -24px 0 0 -24px;
}

/* Nur wenn das eigene Fadenkreuz gezeichnet wird, verschwindet der native
   Zeiger - sonst gäbe es zwei Zeiger. Läuft js/fadenkreuz.js nicht (kein
   JavaScript, Datei entfernt, Fehler), fehlt die Klasse und es bleibt beim
   bisherigen Cursor-Bild aus den Regeln oben; "gar kein Zeiger" kann
   dadurch nicht entstehen. Höhere Spezifität als die Waffen-Regel oben
   (0,2,1 gegen 0,2,0), deshalb greift sie auch bei [data-waffe]. */
html.fadenkreuz-aktiv .level1-gameplay-aktiv,
html.fadenkreuz-aktiv .level2-gameplay-aktiv,
html.fadenkreuz-aktiv .level3-gameplay-aktiv,
html.fadenkreuz-aktiv .countdown-overlay {
    cursor: none;
}

/* ===== Punkte-Popup bei Treffer, gemeinsam für alle drei Level (bisher
   bis auf den Namen/z-index wortgleich dreifach kopiert als
   .l1-punkte-popup/.l2-punkte-popup/.l3-punkte-popup samt eigener
   @keyframes) - einzig inhaltlicher Unterschied war der z-index (8, 8, 45),
   bleibt als levelspezifischer Zusatz erhalten. ===== */
.l1-punkte-popup,
.l2-punkte-popup,
.l3-punkte-popup {
    position: absolute;
    transform: translate(-50%, -100%);
    pointer-events: none;
    font-size: clamp(0.55rem, calc(1.6 * var(--uw)), 0.85rem);
    color: var(--accent-bright);
    text-shadow: 0 0 4px var(--accent-bright), 0 0 2px #fff;
    white-space: nowrap;
    animation: punkte-auf 0.8s ease-out forwards;
}

.l1-punkte-popup,
.l2-punkte-popup {
    z-index: 8;
}

.l3-punkte-popup {
    z-index: 45;
}

@keyframes punkte-auf {
    0%   { opacity: 0; transform: translate(-50%, -100%) scale(0.6); text-shadow: none; }
    12%  { opacity: 1; transform: translate(-50%, -100%) scale(1.2); text-shadow: 0 0 16px var(--accent-bright), 0 0 6px #fff; }
    35%  { transform: translate(-50%, -100%) scale(1); text-shadow: 0 0 6px var(--accent-bright), 0 0 2px #fff; }
    100% { opacity: 0; transform: translate(-50%, calc(-100% - 36px)) scale(1); }
}

/* ===== Mündungsfeuer (js/muendungsfeuer.js) =====
   Kurzer Lichtblitz bei jedem Schuss, an der echten Klickposition -
   position:fixed statt einem levelbezogenen Container (dieselbe Technik
   wie der alte Prototyp, siehe js/muendungsfeuer.js für die Herleitung),
   deshalb hier keine transform:translate(-50%,-50%)-Zentrierung nötig wie
   beim Trefferradius-Marker oben - margin übernimmt das stattdessen (JS
   setzt nur left/top auf die Klickposition selbst). Animiert wird
   ausschließlich transform:scale()+opacity (reine Compositor-
   Eigenschaften) - kein Reflow/Repaint der übrigen Seite pro Schuss. */
.muendungsfeuer {
    position: fixed;
    /* Bugfix: war fest 60px/-30px - auf der Buehne skaliert (--stage-scale,
       siehe js/buehne.js) sonst NICHT mit, anders als praktisch jedes
       andere Element im Spiel (Fadenkreuz, Gegner, HUD...). Bei kleinem
       Handy-Viewport (deutlich niedrigerer --stage-scale als am Desktop)
       wirkte der Blitz dadurch unproportional groß/"falsch dran" - jetzt
       wie überall sonst per --uh skaliert (8 * var(--uh) ergibt bei
       stage-scale:1 ca. 57.6px, nahezu identisch zum bisherigen Wert). */
    width: calc(8 * var(--uh));
    height: calc(8 * var(--uh));
    margin: calc(-4 * var(--uh)) 0 0 calc(-4 * var(--uh));
    z-index: 47;
    pointer-events: none;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(255, 240, 190, 0.95) 0%, rgba(255, 150, 30, 0.6) 40%, transparent 72%);
    animation: muendungsfeuer-aus 0.16s ease-out forwards;
}

@keyframes muendungsfeuer-aus {
    from { transform: scale(0.6); opacity: 1; }
    to   { transform: scale(1.4); opacity: 0; }
}
