@charset "UTF-8";
/* ---- WARUM DIESE ZEILE GANZ OBEN STEHT -------------------------------------------------------
   Gemeldet: im Shop stand vor jedem Preis "â—ˆ" statt "◈".

   Das ist kein Textfehler, das ist eine Kodierung. Das Rautezeichen ist U+25C8, in UTF-8 die
   drei Bytes E2 97 88. Liest ein Browser die Datei als Windows-1252, werden daraus drei
   Zeichen: â (E2), — (97) und ˆ (88) — genau das, was auf dem Bildschirm stand.

   Die HTML-Datei sagt ihre Kodierung selbst (<meta charset=utf-8>), ein Stylesheet ist aber
   eine EIGENE Ressource: dort entscheidet der HTTP-Kopf des Servers, und viele Mietpakete
   schicken fuer .css noch immer iso-8859-1. api/_htaccess setzt bislang keinen Zeichensatz.

   @charset macht die Datei unabhaengig davon. Sie MUSS das allererste im Dokument sein — vor
   jedem Kommentar, sonst gilt sie nicht.
   ============================================================================================ */
/* ============================================================ SCHRIFT
   Selbst gehostet, bewusst. Google Fonts laedt die Datei vom Google-Server und uebertraegt
   dabei die IP des Besuchers in die USA — das LG Muenchen I hat das im Januar 2022
   (Az. 3 O 17493/20) als DSGVO-Verstoss gewertet. Bei einer Seite mit Impressum,
   Datenschutzerklaerung und Kaufabwicklung ist das kein theoretisches Risiko.
   Die Dateien liegen unter fonts/, es geht keine Anfrage nach draussen.

   JetBrains Mono, SIL Open Font License 1.1 (fonts/OFL.txt liegt bei — sie muss mitgeliefert
   werden). Auf Latin-1 plus die im Spiel benutzten Symbole reduziert: 15 KB je Schnitt statt
   93 KB.

   Zwei Schnitte, EINE Familie: 400 fuers HUD, 800 fuer die grossen Momente. Die Trennung
   laeuft ueber Gewicht, Groesse und Sperrung, nicht ueber verschiedene Schriften — deshalb
   klingt alles nach demselben Spiel.

   'BG Mono' steht in jedem Stapel VORNE, die alten Angaben bleiben dahinter stehen. Laedt die
   Datei nicht, sieht alles exakt so aus wie vorher. Zum Rueckbau reicht es, diesen Block zu
   loeschen. */
@font-face {
  font-family: 'BG Mono';
  src: url('fonts/jbmono-regular.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'BG Mono';
  src: url('fonts/jbmono-extrabold.woff2') format('woff2');
  font-weight: 800; font-style: normal; font-display: swap;
}

/* One source of truth for the top strip's inset and the minimap's box. The corner
     pause badge has to live in #touchui for tap reasons — see #tpause — so it
     cannot be positioned against the minimap's corners by nesting. They compute those corners
     from these variables instead, which means changing --mmw or --mmpad moves the badges with
     it. --mmh is the canvas height at the matching width (the canvas is 135x117). */
  :root { --hudx: 14px; --hudy: 12px; --mmw: 135px; --mmh: 117px; --mmpad: 8px; --badge: 36px; }

  * { margin: 0; padding: 0; box-sizing: border-box; }
  html, body {
    width: 100%; height: 100%; overflow: hidden; background: #05070d;
    color: #e8edf7; -webkit-font-smoothing: antialiased;
    font-family: 'BG Mono', ui-monospace, "SF Mono", "JetBrains Mono", "Menlo", "Consolas", monospace;
    /* dragging the mouse to aim must never start a text selection */
    user-select: none; -webkit-user-select: none; -moz-user-select: none;
    -webkit-touch-callout: none; -webkit-tap-highlight-color: transparent;
  }
  #c { position: fixed; inset: 0; display: block; -webkit-user-drag: none; }

  /* fake post-processing: vignette + subtle grade */
  /* Der Schadensrand. z-index 3: ueber der festen Vignette (2), unter dem Fadenkreuz (4) — der
     Rand erreicht die Mitte ohnehin nicht. Schnell an, langsam aus. */
  #hurtvig {
    position: fixed; inset: 0; pointer-events: none; z-index: 3; opacity: 0;
    background: radial-gradient(circle at 50% 48%,
      rgba(255,30,40,0) 36%, rgba(255,30,40,.28) 76%, rgba(255,45,55,.55) 100%);
    transition: opacity .42s ease-out;
  }
  #hurtvig.on { opacity: 1; transition: opacity .04s linear; }
  #vignette {
    position: fixed; inset: 0; pointer-events: none; z-index: 2;
    background:
      radial-gradient(circle at 50% 46%, rgba(0,0,0,0) 42%, rgba(0,0,0,.35) 82%, rgba(0,0,0,.62) 100%),
      radial-gradient(circle at 50% 50%, rgba(120,160,255,.04), rgba(0,0,0,0) 55%);
    mix-blend-mode: normal;
  }
  /* shield ring shown while shield/invuln active */
  #shieldring {
    position: fixed; inset: 0; pointer-events: none; z-index: 2; opacity: 0;
    transition: opacity .18s ease;
    box-shadow: inset 0 0 120px 20px rgba(90,180,255,.35), inset 0 0 40px 4px rgba(150,210,255,.28);
  }
  #shieldring.on { opacity: 1; }

  #panic {
    position: fixed; inset: 0; pointer-events: none; z-index: 3; opacity: 0;
    box-shadow: inset 0 0 150px 34px rgba(255,26,38,.5);
    transition: opacity .3s ease;
  }
  #panic.on { animation: panicPulse .85s ease-in-out infinite; }
  @keyframes panicPulse { 0%, 100% { opacity: .22; } 50% { opacity: .62; } }
  /* ---- UND DIE STUFE DARUEBER ---------------------------------------------------------------
     Die Panik-Vignette schaltete auf `lives === 1`. Im Last Stand steht lives auf 0 — sie ging
     also genau in dem Moment AUS, in dem es am knappsten ist. Der Rand wurde ruhig, waehrend
     der Spieler um sein Leben schoss.
     `stand` ist enger, roter und schlaegt schneller. Es ist dieselbe Sprache, eine Stufe
     lauter — kein zweites Motiv. */
  #panic.stand {
    box-shadow: inset 0 0 210px 58px rgba(255,20,30,.66);
    animation: standPulse .5s ease-in-out infinite;
  }
  @keyframes standPulse { 0%, 100% { opacity: .42; } 50% { opacity: .86; } }

  #respawnring {
    position: fixed; inset: 0; pointer-events: none; z-index: 4; opacity: 0;
    box-shadow: inset 0 0 130px 34px rgba(120,255,240,.5), inset 0 0 46px 8px rgba(170,255,250,.4);
  }
  #respawnring.on { animation: respawnFade 1.7s ease-out; }
  /* Der Levelstart ist Routine, kein Ereignis — derselbe Ring, nur halb so laut. Die Dauer
     setzt grantInvuln() inline, passend zur Laenge der jeweiligen Schutzphase. */
  #respawnring.soft { opacity: 1; }
  #respawnring.soft.on { animation-name: respawnFadeSoft; }
  @keyframes respawnFadeSoft {
    0%   { opacity: .5; }
    100% { opacity: 0; }
  }
  @keyframes respawnFade { 0% { opacity: 0; } 12% { opacity: .85; } 100% { opacity: 0; } }

  #respawnfade {
    position: fixed; inset: 0; pointer-events: none; z-index: 6; opacity: 0;
    background: radial-gradient(circle at 50% 55%, rgba(10,14,22,.55), rgba(4,6,10,.96));
  }
  #respawnfade.on { animation: respawnBlackIn .55s ease-out; }
  @keyframes respawnBlackIn { 0% { opacity: 1; } 55% { opacity: .7; } 100% { opacity: 0; } }

  #crosshair {
    position: fixed; left: 50%; top: 50%; width: 5px; height: 5px;
    margin: -2.5px 0 0 -2.5px; border-radius: 50%; background: #fff; opacity: .75;
    box-shadow: 0 0 0 2px rgba(0,0,0,.45); pointer-events: none; z-index: 4;
  }
  /* ---- DER LADERING -----------------------------------------------------------------------
     Die Anzeige IST hier die Mechanik, nicht ihre Verzierung: ohne sie kann niemand wissen,
     dass Halten etwas anderes tut als Tippen. Deshalb gehoert sie in die erste Fassung und
     nicht in einen spaeteren Politur-Durchgang.

     Um das Fadenkreuz herum, weil dort das Auge ohnehin steht — und weil dieselbe Geste bei
     der gekochten Bombe schon so gelehrt wird (#cookbar direkt darunter). Der Spieler lernt
     kein neues Zeichen, er sieht ein bekanntes an einer neuen Stelle.

     conic-gradient statt eines Balkens: ein Ring fuellt sich sichtbar von einem Anfang zu einem
     Ende und braucht dafuer keine zweite Farbe. Bernstein, dieselbe wie der Bolzen, damit man
     die beiden zusammen liest. */
  #chargering {
    position: fixed; left: 50%; top: 50%; width: 34px; height: 34px; margin: -17px 0 0 -17px;
    border-radius: 50%; pointer-events: none; z-index: 4; opacity: 0;
    transition: opacity .1s ease;
    background: conic-gradient(#b45cff calc(var(--k, 0) * 360deg), rgba(180,92,255,.14) 0);
    -webkit-mask: radial-gradient(circle, transparent 12px, #000 13px);
            mask: radial-gradient(circle, transparent 12px, #000 13px);
  }
  #chargering.on { opacity: .95; }
  /* Das Fadenkreuz faerbt mit. Es ist der einzige Punkt, auf den der Spieler waehrend des
     Ladens ohnehin schaut — und die Farbe ist die Botschaft, nicht der Ring. Sonst bleibt
     alles unberuehrt: kein umgefaerbtes HUD, keine getoente Welt. */
  /* body.laedt und NICHT `#chargering.on ~ #crosshair`: das Fadenkreuz steht im Dokument VOR
     dem Ring, und ~ waehlt nur nachfolgende Geschwister. Der Selektor haette nie gegriffen. */
  body.laedt #crosshair {
    background: #e8ccff; box-shadow: 0 0 0 2px rgba(0,0,0,.45), 0 0 10px rgba(180,92,255,.9);
  }
  /* Voll: ein kurzer Stoss nach aussen. Er sagt "jetzt", ohne den Ring zu verlassen — und die
     Deckkraft bleibt unangetastet, damit Lesbarkeit nicht an einer Animation haengt. */
  #chargering.voll { animation: chargeVoll .32s ease-out; }
  @keyframes chargeVoll {
    0%   { transform: scale(1);    filter: brightness(1); }
    45%  { transform: scale(1.28); filter: brightness(1.9); }
    100% { transform: scale(1);    filter: brightness(1); }
  }
  @media (prefers-reduced-motion: reduce) { #chargering.voll { animation: none; } }
  #cookbar {
    position: fixed; left: 50%; top: calc(50% + 22px); transform: translateX(-50%);
    width: 128px; height: 7px; border-radius: 4px; z-index: 4; pointer-events: none;
    background: rgba(10,14,20,.7); border: 1px solid rgba(255,255,255,.25);
    overflow: hidden; opacity: 0; transition: opacity .12s ease;
  }
  #cookbar.on { opacity: 1; }
  #cookfill { height: 100%; width: 0%; background: #8dff6a; box-shadow: 0 0 8px currentColor; transition: width .05s linear, background .1s linear; }

  #hud { position: fixed; inset: 0; pointer-events: none; z-index: 5; display: flex; flex-direction: column; }

  /* Top strip. A grid — the blocks get their own tracks, so they can never
     slide under each other however wide the stats bar grows.

     Column 2 is the flexible one and holds the boss bar. When there is no boss it is simply
     an empty 1fr spacer, which is exactly what column 1 used to be — so the strip looks
     identical to before on every screen where no boss is alive. */
  #topzone {
    display: grid; grid-template-columns: minmax(0, auto) 1fr auto auto;
    align-items: start; gap: 10px; padding: var(--hudy) var(--hudx) 0;
  }
  #stats  { grid-column: 1; }
  #topbar { grid-column: 3; }
  #mmwrap { grid-column: 4; }
  #stats { min-width: 0; display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
  .bar {
    display: flex; gap: 4px; align-items: center;
    /* ---- WARUM HIER KEIN backdrop-filter MEHR STEHT --------------------------------------
       Das war der teuerste Posten des ganzen Bildschirms, und er stand auf SECHS dauerhaft
       sichtbaren Elementen: dieser Leiste, jeder Pille in #bar2, dem #topbar (zweimal, Desktop
       und Touch) und dem Minimap-Rahmen.

       backdrop-filter ist kein statischer Effekt. Der Hintergrund dieser Elemente ist die
       Spiel-Leinwand, und die aendert sich in JEDEM Bild. Der Browser muss also je Element und
       je Bild: den Bereich darunter auslesen, ihn mehrfach weichzeichnen, zurueckschreiben.
       Sechs Bereiche, sechzig Mal je Sekunde — und die Kosten wachsen mit der FLAECHE, weshalb
       es auf 2K in Vollbild rund doppelt so schwer wiegt wie auf 1080p. Genau dort wurde es
       gemeldet.

       Das ist die Antwort auf "warum haengt es so oft und warum sind die Uebergaenge nicht
       fluessig": das Spiel hat ohnehin einen Fuellraten-Engpass — deshalb gibt es ueberhaupt
       eine adaptive Aufloesung. Diese Weichzeichner nahmen von JEDEM Bild einen festen Anteil
       weg, und zwar bevor das Spiel selbst etwas gezeichnet hat. Es blieb kein Puffer: jedes
       Ereignis, das zusaetzlich Arbeit macht (ein Riss, ein Banner, ein Bombenwurf), kippte das
       Bild ueber die Grenze. Deshalb haengt es immer bei EREIGNISSEN und nie im Leerlauf.

       Der Ersatz ist eine dichtere Fuellung (.55 -> .82). Auf einem dunklen HUD ueber einem
       dunklen Spiel ist der Unterschied kaum zu sehen; im Bildbudget ist er erheblich. */
    background: rgba(8,11,20,.82); padding: 7px 9px; border-radius: 11px;
    border: 1px solid rgba(120,140,190,.18);
    width: auto; max-width: 100%; flex-wrap: wrap; row-gap: 2px;
  }
  /* row 2: situational pills + synergy chips, each carrying its own chrome */
  #bar2 { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; max-width: 100%; }
  #bar2 .pill {
    background: rgba(8,11,20,.84); border: 1px solid rgba(120,140,190,.18);
    border-radius: 999px; padding: 4px 10px;   /* kein backdrop-filter — siehe .bar */
  }
  #bar2 .pill + .pill { border-left: 1px solid rgba(120,140,190,.18); }
  /* ---- NUR NOCH SYMBOL UND ZAHL -----------------------------------------------------------
     Die Beschriftungen (Bomben, Reichw., Tempo, Schuss) sind raus. Auf Touch war das laengst so
     (siehe .pill .lbl weiter unten) — am Desktop standen sie noch, obwohl der farbige Punkt
     dieselbe Information traegt: rund fuer Bomben, orange fuer Reichweite, gruener Pfeil fuer
     Tempo, cyan leuchtend fuer den Blaster. Vier Woerter, die man nach dem ersten Lauf nicht
     mehr liest, aber in jedem Bild ueberspringen muss.

     Der frei gewordene Platz geht in die GROESSE: alles in dieser Leiste ist rund 15 % groesser.
     Das war der zweite Teil der Bitte, und beides zusammen geht auf — die Leiste ist trotz der
     groesseren Zahlen schmaler als vorher. */
  .pill .lbl { display: none; }
  /* Nachgebessert: +15 % war einen Tick zu viel. Rund +7 % gegenueber dem Original —
     gross genug, um im Augenwinkel zu funktionieren, klein genug, um nicht zu draengen. */
  .pill {
    display: flex; align-items: center; gap: 6px; padding: 3px 9px;
    font-size: 13px; letter-spacing: .5px; color: #9fb0d0;
  }
  .pill + .pill { border-left: 1px solid rgba(120,140,190,.15); }
  .pill b { color: #fff; font-size: 15px; font-variant-numeric: tabular-nums; }   /* 14 -> 15 (+7 %) */
  .pill.fire b { color: #ff8a4c; }
  .pill.speed b { color: #8dff6a; }
  /* 9 -> 10.5: der Punkt ERSETZT jetzt die Beschriftung, also muss er auch als Zeichen taugen. */
  .dot { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }
  .dot.bomb { background: #cfd7ea; border-radius: 50%; }
  .dot.fire { background: #ff8a1e; }
  .dot.speed { background: #8dff6a; clip-path: polygon(0 0,100% 50%,0 100%,35% 50%); }
  .dot.blaster { background: #8ffcff; border-radius: 50%; box-shadow: 0 0 6px #8ffcff; }

  /* hearts */
  #hearts { display: flex; gap: 3px; align-items: center; }
  #hearts .hp { font-size: 16px; line-height: 1; filter: drop-shadow(0 0 4px rgba(255,60,90,.5)); }   /* 15 -> 16 (+7 %) */
  #hearts .hp.on { color: #ff435f; }
  #hearts .hp.off { color: #3a2030; filter: none; opacity: .7; }

  /* ability badges */
  #abilities { display: flex; gap: 5px; align-items: center; }
  .ab {
    width: 24px; height: 24px; border-radius: 7px; display: flex; align-items: center; justify-content: center;
    font-size: 12px; font-weight: 800; letter-spacing: 0; color: #6a748f;   /* 22/11 -> 24/12 (+8 %) */
    background: rgba(20,26,44,.7); border: 1px solid rgba(120,140,190,.16);
    transition: transform .12s ease;
  }
  .ab.on { color: #fff; }
  .ab.shield.on { background: rgba(40,90,150,.85); border-color: #6ab6ff; box-shadow: 0 0 10px rgba(90,180,255,.55); }
  .ab.remote.on { background: rgba(80,50,130,.85); border-color: #b78dff; box-shadow: 0 0 10px rgba(150,110,255,.5); }
  .ab.pierce.on { background: rgba(140,70,20,.85); border-color: #ffab4a; box-shadow: 0 0 10px rgba(255,150,60,.5); }
  /* Der Notzuender traegt eine Zahl, also darf er breiter werden als die drei Buchstaben-
     Badges. min-width statt fester Breite: bei einer Ladung bleibt er schmal. Er wird per
     display gesteuert (siehe updateHUD) — bei null ist er gar nicht da, statt dunkel
     danebenzustehen. Ein Platzhalter fuer etwas, das man nicht hat, ist Unruhe ohne Nutzen. */
  .ab.panic {
    display: none; width: auto; min-width: 22px; padding: 0 5px; gap: 2px;
    color: #ff8a7a; background: rgba(64,18,14,.8); border-color: rgba(255,90,60,.35);
  }
  .ab.panic.on { display: flex; }
  .ab.panic b { font-size: 12px; font-weight: 900; color: #fff; font-variant-numeric: tabular-nums; }
  /* Die letzte Ladung pocht. Ab hier ist es keine Reserve mehr, sondern die Reserve. */
  .ab.panic.last { border-color: #ff5a3a; box-shadow: 0 0 10px rgba(255,90,50,.55); animation: panicLast 1.5s ease-in-out infinite; }
  @keyframes panicLast { 0%,100% { opacity: 1; } 50% { opacity: .62; } }

  #topbar {
    display: flex; gap: 0; align-items: stretch; justify-self: center;
    background: rgba(8,11,20,.82); border-radius: 11px;
    border: 1px solid rgba(120,140,190,.18); overflow: hidden;   /* kein backdrop-filter — siehe .bar */
  }
  .seg { padding: 6px 14px; text-align: center; }
  .seg + .seg { border-left: 1px solid rgba(120,140,190,.15); }
  .seg .k { font-size: 8px; letter-spacing: 2px; text-transform: uppercase; color: #7f8db0; }
  .seg .v { font-size: 15px; font-weight: 800; font-variant-numeric: tabular-nums; color: #eef3ff; }
  .seg.level .v { color: #6ad1ff; }
  /* ---- WANN IST DIE WELLE VORBEI? ----------------------------------------------------------
     Der Tester wusste nicht, wann die laufende Welle endet. Der Grund ist strukturell: in der
     Arena gibt es kein "Abschliessen" — arenaWave ist 1 + floor(t / 65), also eine UHR und
     keine Runde. Man kann eine Welle nicht raeumen, sie laeuft ab. Die ehrliche Anzeige dafuer
     ist kein Zaehler, sondern ein Balken.

     ERSTER VERSUCH, UND WARUM ER UNSICHTBAR WAR: die Leiste sass mit `bottom: -3px` DREI PIXEL
     UNTERHALB des Segments — also ausserhalb der Leiste, in der es steckt. Dort war sie 2 px
     hoch, zu 55 % durchsichtig und stand auf dem dunklen Hintergrund der Seite. Technisch
     vorhanden, praktisch nicht zu sehen.

     Jetzt INNERHALB des Segments am unteren Rand, 3 px hoch, voll deckend, mit einer schwach
     sichtbaren Schiene dahinter — sonst weiss man bei fast leerer Leiste nicht, ob sie leer
     ist oder fehlt. Nur in der Arena (.welle), weil es in der Kampagne nichts abzulaufen gibt. */
  .seg.level { position: relative; }
  .seg.level.welle::before {
    content: ''; position: absolute; left: 9px; right: 9px; bottom: 3px; height: 3px;
    background: rgba(106,209,255,.16); border-radius: 2px;
  }
  .seg.level.welle::after {
    content: ''; position: absolute; left: 9px; bottom: 3px; height: 3px;
    /* --w ist eine reine ZAHL von 0 bis 1, kein Prozentwert: calc() darf eine Laenge mit einer
       Zahl multiplizieren, aber nicht Prozent durch Prozent teilen. */
    width: calc((100% - 18px) * var(--w, 0));
    background: #6ad1ff; border-radius: 2px;
    box-shadow: 0 0 6px rgba(106,209,255,.5);
    transition: width .25s linear;
  }
  /* ---- IN DER ARENA BLEIBEN NUR ZWEI SEGMENTE -------------------------------------------
     Score, Schrott und Zeit sind dort nicht handlungsrelevant (Begruendung in hudZeichnen) und
     stehen jetzt auf dem Endbildschirm. Ueber eine Klasse statt ueber display:none je Element,
     damit die Kampagne unveraendert bleibt und der Wechsel eine Zeile ist. */
  /* Gilt jetzt in BEIDEN Modi (siehe hudZeichnen). Der Fortschrittsbalken darunter bleibt an
     .welle gebunden und erscheint deshalb weiterhin nur in der Arena, wo es eine Wellenuhr gibt. */
  #topbar.nurwelle .seg:not(.level):not(.enemy) { display: none; }
  .seg.enemy .v { color: #ff5a6e; }
  .seg.time .v { color: #ffd23a; }
  .seg.scrap .v { color: #ffce54; }

  /* Everything below the top strip stacks in one centred column. Persistent items
     (eye, boss bar, doom clock, curse) sit above the transient pop-ins, so a combo
     flashing in can never nudge a timer the player is reading. */
  #centercol {
    display: flex; flex-direction: column; align-items: center; gap: 7px;
    padding-top: 10px; pointer-events: none; min-width: 0;
  }
  #combo {
    display: none; font-size: 22px; font-weight: 900; letter-spacing: 1px; color: #ffcf3a;
    text-shadow: 0 2px 16px rgba(255,160,20,.7), 0 0 3px rgba(0,0,0,.6);
  }
  #combo.show { display: block; animation: comboPop .16s ease-out; }
  @keyframes comboPop { from { opacity: 0; transform: scale(.6); } to { opacity: 1; transform: scale(1); } }

  /* ---- rival duel ------------------------------------------------------------------
     The target from a ?duell= link, carried top-centre for the whole run so the number you
     are chasing is never more than a glance away. Turns green the moment you pass it. */
  #rival {
    display: none; align-items: center; gap: 7px; white-space: nowrap;
    padding: 5px 13px; border-radius: 999px;
    background: rgba(30,12,40,.72); border: 1px solid rgba(200,120,255,.45);
    box-shadow: 0 0 16px rgba(180,70,255,.26);
    font-size: 12px; font-weight: 800; letter-spacing: .5px; color: #e9d4ff;
  }
  #rival.on { display: flex; }
  #rival b { color: #fff; font-variant-numeric: tabular-nums; }
  #rival i { font-style: normal; font-weight: 600; color: #b9a4d6; }
  #rival.beat {
    background: rgba(12,40,22,.72); border-color: rgba(125,255,154,.5);
    color: #8fffa8; box-shadow: 0 0 16px rgba(60,255,120,.3);
  }

  /* ---- der Schwur -------------------------------------------------------------------
     Dasselbe Muster wie das Duell: eine Zeile, die den ganzen Lauf oben steht, damit die
     Absicht nie mehr als einen Blick entfernt ist. Sein Gold, weil er sie verlangt hat.
     Gruen sobald erfuellt; ausgegraut und durchgestrichen, sobald rechnerisch verloren —
     ein Ziel, das laengst weg ist und trotzdem hoffnungsvoll dasteht, ist schlimmer als
     eine ehrliche Absage. */
  #oath {
    display: none; align-items: center; gap: 7px; white-space: nowrap;
    padding: 5px 13px; border-radius: 999px;
    background: rgba(40,30,8,.72); border: 1px solid rgba(255,210,74,.45);
    box-shadow: 0 0 16px rgba(255,190,60,.22);
    font-size: 12px; font-weight: 800; letter-spacing: .5px; color: #ffe9a8;
  }
  #oath.on { display: flex; }
  #oath i { font-style: normal; font-weight: 600; color: #d8c48a; }
  #oath b { color: #fff; font-variant-numeric: tabular-nums; }
  #oath.done {
    background: rgba(12,40,22,.72); border-color: rgba(125,255,154,.5);
    color: #8fffa8; box-shadow: 0 0 16px rgba(60,255,120,.3);
  }
  #oath.done i { color: #7fd79a; }
  #oath.lost {
    background: rgba(26,26,32,.7); border-color: rgba(140,150,170,.3);
    color: #8d95a6; box-shadow: none; text-decoration: line-through;
  }
  #oath.lost i { color: #6c7484; }
  /* Seine Weltregel — dieselbe Pille wie der Schwur, aber violett (seine Farbe) und immer
     kurz: Name plus Symbol. Die Beschreibung steht im title-Attribut und beim Levelstart
     im Banner; im Bild waere sie eine zweite lange Zeile, und eine reicht. */
  #wrule {
    display: none; align-items: center; gap: 6px; white-space: nowrap;
    padding: 4px 11px; border-radius: 999px;
    background: rgba(30,12,40,.72); border: 1px solid rgba(200,120,255,.4);
    box-shadow: 0 0 14px rgba(180,90,255,.2);
    font-size: 11px; font-weight: 800; letter-spacing: .6px; color: #e6c4ff;
  }
  #wrule .wri { font-size: 13px; }
  /* Das Angebot des Haendlers. Wechselt alle zwei Sekunden; gruen, wenn man es sich leisten
     kann. Bewusst dieselbe Pillenform wie Schwur und Weltregel — es ist eine Information,
     die den ganzen Kernzonen-Moment ueber gilt, kein Popup. */
  #traderhud {
    display: none; align-items: center; gap: 7px; white-space: nowrap;
    padding: 4px 12px; border-radius: 999px;
    background: rgba(48,32,4,.78); border: 1px solid rgba(255,210,74,.5);
    box-shadow: 0 0 16px rgba(255,190,60,.26);
    font-size: 12px; font-weight: 800; letter-spacing: .5px; color: #ffe9a8;
  }
  #traderhud .ti { font-size: 14px; }
  #traderhud b { color: #fff; font-variant-numeric: tabular-nums; }
  #traderhud.can { border-color: rgba(141,255,106,.6); color: #d8ffc8; }
  #traderhud.can b { color: #8dff6a; }
  /* BLINDFLUG blendete die Minimap KOMPLETT aus — und damit sah es aus, als waere sie kaputt.
     Ein Feature, das sich nicht von einem Fehler unterscheiden laesst, ist ein Fehler. Der
     Rahmen bleibt jetzt stehen, nur der Inhalt verschwindet, und an seiner Stelle steht das
     Symbol der Regel. Der Spieler sieht damit, dass ihm etwas GENOMMEN wurde. */
  body.blind #mmwrap canvas { visibility: hidden; }
  body.blind #mmwrap { position: relative; }
  body.blind #mmwrap::after {
    content: '\1F441'; position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center;
    font-size: 26px; opacity: .28; filter: grayscale(1);
  }
  /* Kurzform auf schmalen Bildschirmen: nach ein paar Sekunden bleiben nur Symbol und
     Fortschritt stehen. Der volle Satz kommt bei jeder Aenderung kurz zurueck — das ist
     der Moment, in dem er zaehlt. Die Breite faehrt mit, sonst bliebe eine leere Pille. */
  #oath .ot { transition: opacity .18s ease; }
  #oath.mini { padding: 5px 11px; gap: 4px; }
  #oath.mini .ot { display: none; }
  /* Erfuellt und gefeiert: nach ein paar Sekunden verschwindet die Zeile ganz. Das Ergebnis
     steht ohnehin auf der Game-Over-Karte — waehrend des Laufs ist ein abgehaktes Ziel nur
     noch Platzverbrauch. Verfehlte bleiben in der Kurzform stehen: die Information, WARUM
     nichts mehr geht, ist bis zum Ende nuetzlich. */
  #oath.gone { opacity: 0; transform: translateY(-6px); pointer-events: none; }
  #oath { transition: opacity .5s ease, transform .5s ease; }

  /* ---- boss health bar -------------------------------------------------------------
     Moved out of the centre column and into the top strip (grid column 2, between the
     stats and the minimap). It used to sit dead centre of the screen, directly over the
     boss you were trying to read — worst on a phone, where the arena is only ~380px tall.

     It is also slimmer now, and the label lives INSIDE the track instead of on a second
     line below it. That halves the vertical footprint and, because the text is centred on
     the bar, the whole thing reads as one object rather than a bar with a caption. */
  #bossbar {
    position: relative;                 /* the label is absolutely placed inside the track */
    grid-column: 2; grid-row: 1;
    justify-self: center; align-self: start;
    margin-top: 8px;                    /* lines the bar's centre up with the stats pills */
    width: min(34vw, 340px); max-width: 100%;
    /* Column 2 is a plain 1fr, i.e. its automatic minimum is the min-content of whatever is
       in it. With no boss the bar is display:none, the track has no contents, the minimum is
       zero and the spacer collapses — the strip looks exactly as it did before. With a boss
       alive this min-width becomes that minimum, so between roughly 950 and 1400px, where
       stats + info + minimap already fill the row, the STATS BAR wraps to its second line
       (which it is built to do) instead of the health bar being squeezed out of existence. */
    min-width: 150px;
    display: none;
  }
  #bossbar.on { display: block; animation: bossIn .35s ease; }
  @keyframes bossIn { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: translateY(0); } }
  #bossbar .bl {
    height: 15px; border-radius: 8px; background: rgba(22,8,12,.72);
    border: 1px solid rgba(255,90,70,.55); overflow: hidden;
    box-shadow: 0 0 16px rgba(255,60,40,.4), inset 0 1px 2px rgba(0,0,0,.6);
  }
  #bossbar .bfill {
    height: 100%; width: 100%;
    background: linear-gradient(90deg, #ff2a44 0%, #ff5a2e 60%, #ffb14a 100%);
    box-shadow: 0 0 12px rgba(255,90,40,.8); transition: width .18s ease;
  }
  /* White with a hard dark contour, because the text has to stay legible over BOTH the
     bright end of the gradient and the near-black empty part of the track as the boss
     drains. A soft glow alone loses against #ffb14a; a 1px outline does not. */
  #bossbar .bt {
    position: absolute; inset: 0; margin: 0;
    display: flex; align-items: center; justify-content: center;
    font-size: 9px; font-weight: 900; letter-spacing: 2px; text-transform: uppercase;
    color: #fff; pointer-events: none;
    text-shadow:
      0 1px 0 rgba(28,4,2,.95), 0 -1px 0 rgba(28,4,2,.95),
      1px 0 0 rgba(28,4,2,.95), -1px 0 0 rgba(28,4,2,.95),
      0 0 7px rgba(0,0,0,.85);
  }

  #mmwrap {
    padding: var(--mmpad);
    background: rgba(8,11,20,.84); border-radius: 11px;
    border: 1px solid rgba(120,140,190,.18);   /* kein backdrop-filter — siehe .bar */
  }
  #mmwrap .label {
    font-size: 9px; letter-spacing: 2px; color: #7f8db0; text-transform: uppercase;
    text-align: center; margin-bottom: 6px;
  }
  /* explicit height, not auto: the record badge derives the frame's bottom edge from it */
  #mm { display: block; width: var(--mmw); height: var(--mmh); border-radius: 4px; image-rendering: pixelated; }

  /* Onboarding, nicht Dauereinblendung — ab dem zweiten Lauf weg. Ausgeblendet statt entfernt,
     damit setLang() weiter darauf zugreifen kann und der Pausenbildschirm sie zeigen koennte. */
  #hint.weg { display: none; }
  #hint {
    position: absolute; left: 50%; bottom: 12px; transform: translateX(-50%);
    font-size: 10px; letter-spacing: 1px; color: #67718f; text-align: center;
    background: rgba(8,11,20,.4); padding: 5px 12px; border-radius: 8px;
  }
  #hint b { color: #9fb0d0; }

  /* floating score popups */
  /* Der Versatz unter das Banner sitzt am BEHAELTER (siehe bannerVersatz in game.js) und wird
     ueberblendet — vorher rechnete popText ihn einmal je Text aus, und ein Banner, das eine
     Zehntelsekunde spaeter aufging, lag dann ueber der Meldung. */
  #pops { position: fixed; left: 50%; top: 50%; z-index: 4; pointer-events: none;
          transition: transform .22s cubic-bezier(.2,.7,.3,1); }
  @media (prefers-reduced-motion: reduce) { #pops { transition: none; } }
  .pop {
    position: absolute; left: 0; top: 0; transform: translate(-50%,-50%);
    font-size: 16px; font-weight: 800; white-space: nowrap;
    text-shadow: 0 2px 10px rgba(0,0,0,.7); animation: popUp .85s ease-out forwards;
  }
  /* Zweite Stufe, bewusst nur fuer eine Handvoll Aufrufe. Die 30 popTexts teilen sich in
     "+Feuer / +250 / BOSS -1" (beilaeufig, wiederkehrend) und "SYNERGIE / NOTZUENDER / FLUCH"
     (einmalige Ereignisse). Waeren alle gleich gross, waere das Problem nur verschoben. */
  .pop.big {
    font-size: 26px; font-weight: 900; letter-spacing: .5px;
    text-shadow: 0 2px 14px rgba(0,0,0,.85), 0 0 22px currentColor;
  }
  @keyframes popUp {
    0% { opacity: 0; transform: translate(-50%,-30%) scale(.7); }
    18% { opacity: 1; }
    100% { opacity: 0; transform: translate(-50%,-160%) scale(1.05); }
  }

  /* ============================================================ LEXIKON
     Vorher war das eine einzige lange Liste mit Inline-Styles. Zwanzig Systeme untereinander
     liest niemand — jetzt Reiter. Die Seiten liegen alle im DOM und werden nur ein- und
     ausgeblendet: ein Reiterwechsel zeichnet die Karte nicht neu, die Einblendung laeuft
     also nicht jedes Mal von vorn. */
  .lextabs {
    display: flex; flex-wrap: wrap; gap: 6px; justify-content: center;
    margin: 4px 0 12px; padding-bottom: 10px;
    border-bottom: 1px solid rgba(120,140,190,.16);
  }
  .lextab {
    font: 700 11.5px/1 inherit; letter-spacing: .08em; text-transform: uppercase;
    padding: 7px 12px; border-radius: 999px; cursor: pointer;
    background: rgba(16,20,31,.7); border: 1px solid rgba(120,140,190,.2); color: #8894ae;
  }
  .lextab:hover { color: #dfe7f7; border-color: rgba(150,180,255,.4); }
  .lextab.on { background: rgba(201,168,255,.16); border-color: rgba(201,168,255,.55); color: #e9d4ff; }
  .lexbody { text-align: left; max-height: 52vh; overflow: auto; padding-right: 6px; }
  .lexpage { display: none; }
  .lexpage.on { display: block; }
  .lexrow {
    display: flex; gap: 10px; align-items: baseline;
    padding: 5px 0; border-bottom: 1px solid rgba(120,140,190,.10);
  }
  .lexic { width: 1.6em; flex: none; text-align: center; font-size: 15px; }
  .lextx { flex: 1; font-size: 13px; }
  .lextx b { color: #dfe7f7; }
  .lexds { font-size: 11.5px; opacity: .74; }
  .lexkopf {
    opacity: .5; letter-spacing: .16em; font-size: 10px; margin: 16px 0 4px; text-transform: uppercase;
  }
  .lexkopf span { letter-spacing: 0; opacity: .7; text-transform: none; }
  .lexon { color: #8dff6a; font-weight: 400; font-size: 11px; }
  .lexmul { opacity: .6; font-weight: 400; font-size: 11px; }
  .lextag {
    font-size: 10px; padding: 1px 7px; margin-left: 6px; border-radius: 999px;
    border: 1px solid rgba(106,209,255,.4); color: #6ad1ff; font-weight: 400;
  }
  .lexnote {
    margin-top: 12px; padding: 8px 11px; border-radius: 8px;
    background: rgba(120,140,190,.07); font-size: 11.5px; line-height: 1.45; color: #9fb0d0;
  }
  @media (max-width: 620px) {
    .lextab { padding: 6px 9px; font-size: 10.5px; }
    .lexbody { max-height: 46vh; }
  }

  /* ---- Die Vorzeichen-Zeile im Uebergangsbildschirm -------------------------------------
     Sie steht bewusst UNTER der Ueberschrift und UEBER dem Angebot: sie ist der Grund, aus dem
     man sich fuer eine bestimmte Belohnung entscheidet, nicht eine Fussnote danach. */
  .vorschau {
    display: flex; flex-wrap: wrap; align-items: baseline; justify-content: center;
    gap: 6px 12px; margin: 0 0 16px; padding: 9px 14px;
    background: rgba(12,16,26,.55); border: 1px solid rgba(120,140,190,.18); border-radius: 10px;
  }
  .vorschau .vzl { font-size: 10px; letter-spacing: .18em; text-transform: uppercase; color: #67718f; }
  .vorschau b { font-size: 15px; color: #dfe7f7; font-weight: 800; }
  .vorschau .vzt { display: flex; flex-wrap: wrap; gap: 6px; }
  .vorschau .vzt span {
    font-size: 11.5px; padding: 2px 8px; border-radius: 999px;
    border: 1px solid currentColor; opacity: .92;
  }
  .vorschau .vzh { color: #6ad1ff; }   /* Gefahr der Welt */
  .vorschau .vzb { color: #ff8a4a; }   /* Boss */
  .vorschau .vzc { color: #c9a8ff; }   /* Verderbnis */
  .vorschau .vzx { color: #ffce54; }   /* Chaos-Modifikator */

  /* center banner (level transitions) */
  /* max-width fehlte komplett. Der Kasten war so breit wie sein Inhalt und wird um seine
     MITTE verschoben — zu langer Text ragte also an BEIDEN Seiten hinaus. Die Auge-Unterzeile
     ("MAGNETSTURM . Stell dich in den Kreis.") misst bei 11 px und 2 px Sperrung rund 330 px;
     auf 360 px passt das knapp, auf 320 nicht mehr. Eine Zeile sichert alle Banner auf einmal. */
  /* ---- WARUM HIER will-change UND contain STEHEN ---------------------------------------------
     Gemeldet: "wenn die Texteinblendungen kommen, dann haengt es kurz". Zwei Ursachen, beide
     hier oben:

     1. Das Banner hatte keine eigene Compositing-Ebene. Bei jedem Einblenden musste der Browser
        eine anlegen, den Text hineinrastern und sie danach wieder verwerfen. will-change haelt
        sie dauerhaft — das Element ist ohnehin immer im DOM und kostet als Ebene nichts.
     2. Das Neusetzen des Textes loeste ein Layout des GANZEN Dokuments aus (bannerPaint erzwingt
        einen Reflow, damit die Animation neu startet). contain kapselt das auf dieses Element:
        was sich hier drin aendert, kann ausserhalb nichts verschieben, also muss der Browser
        auch nichts anderes neu vermessen. */
  #banner {
    position: fixed; left: 50%; top: 40%; transform: translate(-50%,-50%) scale(.85);
    z-index: 6; pointer-events: none; text-align: center; opacity: 0;
    max-width: min(92vw, 620px);
    transition: opacity .25s ease, transform .25s ease;
    will-change: opacity, transform;
    contain: layout style paint;
  }
  #banner.show { opacity: 1; transform: translate(-50%,-50%) scale(1); }
  #banner .big {
    font-family: 'BG Mono', ui-monospace, "Arial Black", system-ui, sans-serif;
    font-weight: 800; letter-spacing: .06em;
    /* Als Variable, damit die kleine Stufe daran rechnen kann. Vorher stand unten .62em —
       und em rechnet gegen die ELTERN-Groesse (#banner erbt ~16px), nicht gegen diese 52px.
       Ergebnis waren ~10px statt der dort genannten "62 % Groesse". */
    --bnbig: 52px;
    font-size: var(--bnbig); font-weight: 900; letter-spacing: 2px; line-height: 1;
    /* ---- 40 PIXEL WEICHZEICHNUNG AUF 52-PIXEL-SCHRIFT ------------------------------------
       Hier stand `0 6px 40px`. Ein Schattenradius von 40 px muss fuer JEDES Zeichen einzeln
       gerastert werden, und zwar neu, sobald sich der Text aendert — also bei jeder einzelnen
       Einblendung. Das ist der eigentliche Preis, nicht der Text selbst.

       Zwei Schatten statt einem: ein enger, harter fuer die Lesbarkeit (der traegt die Arbeit)
       und ein weiter, aber schwacher fuer die Tiefe. Zusammen kosten sie einen Bruchteil und
       sehen naeher am Original aus als ein einzelner mittlerer Radius. */
    color: #eef3ff; text-shadow: 0 2px 6px rgba(0,0,0,.9), 0 5px 16px rgba(0,0,0,.55);
  }
  #banner .small { margin-top: 8px; font-size: 13px; letter-spacing: 3px; text-transform: uppercase; color: #ffb14a; }
  /* ---- DER WELTNAME NIMMT SEINEN PLATZ EIN ---------------------------------------------------
     Nur fuer Prioritaet 2 — den Namen einer Welt, die man zum ersten Mal betritt. Die Buchstaben
     ziehen von eng nach weit auseinander, ueber dieselben 0.9 s, in denen sich auch Licht und
     Nebel oeffnen (LEVEL_IN in game.js). Text und Welt kommen damit GEMEINSAM an.
     Dieselbe Geste, mit der der Startbildschirm-Hinweis geht — hier umgekehrt. */
  #banner.arrive.show .big { animation: bnArrive .9s cubic-bezier(.16,1,.3,1) both; }
  #banner.arrive.show .small { animation: bnArriveSm .9s .12s cubic-bezier(.16,1,.3,1) both; }
  /* ---- DER WEICHZEICHNER WAR EIN TEIL DES RUCKLERS ------------------------------------------
     `filter: blur(6px)` auf einer 52-px-Zeile zwingt den Browser, fuer diese Animation eine
     eigene Filterebene anzulegen und den Text darin bei JEDEM Bild neu zu rastern — auf 2K in
     Vollbild ist das die teuerste Zeile im ganzen Banner.

     Der Effekt, den sie erzeugt hat ("kommt aus dem Unscharfen"), traegt hier ohnehin die
     Laufweite: -6px auf 2px liest sich als Zusammenziehen. Ein Skalieren von 1.06 auf 1.0
     ergaenzt den Tiefeneindruck und laeuft im Compositor, kostet also nichts. */
  @keyframes bnArrive {
    from { letter-spacing: -6px; opacity: 0; transform: scale(1.06); }
    to   { letter-spacing: 2px;  opacity: 1; transform: scale(1); }
  }
  @keyframes bnArriveSm {
    from { letter-spacing: 12px; opacity: 0; }
    to   { letter-spacing: 3px;  opacity: 1; }
  }
  @media (prefers-reduced-motion: reduce) {
    #banner.arrive.show .big, #banner.arrive.show .small { animation: none; }
  }

  /* One-time teaching prompt. Sits high, well clear of the crosshair and of the touch
     buttons at the bottom right, and never takes pointer events — it explains a control,
     so covering that control would be its own joke. */
  #teach {
    position: fixed; left: 50%; top: 27%; transform: translate(-50%, -50%) scale(.92);
    z-index: 9; pointer-events: none; opacity: 0;
    width: max-content; max-width: min(88vw, 430px);
    padding: 12px 20px; border-radius: 14px; text-align: center;
    background: rgba(10,14,26,.92); border: 1px solid rgba(255,206,84,.45);
    box-shadow: 0 12px 40px rgba(0,0,0,.6), 0 0 26px rgba(255,180,60,.18);
    transition: opacity .3s ease, transform .3s ease;
  }
  #teach.on { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  #teach .tk {
    font-size: 10px; letter-spacing: 3px; text-transform: uppercase;
    font-weight: 800; color: #ffce54; margin-bottom: 6px;
  }
  #teach .tt { font-size: 14px; line-height: 1.55; color: #dce5f7; }
  #teach .tt b { color: #fff; font-size: 15px; }

  #flash {
    position: fixed; inset: 0; pointer-events: none; opacity: 0;
    transition: opacity .2s ease; background: rgba(255,40,40,.4); z-index: 5;
  }

  #overlay {
    position: fixed; inset: 0; display: flex; flex-direction: column; align-items: center;
    padding: 16px; z-index: 10; overflow-y: auto; -webkit-overflow-scrolling: touch;
    background: radial-gradient(circle at 50% 38%, rgba(12,16,30,.72), rgba(3,5,10,.95));
    backdrop-filter: blur(7px);
  }
  #overlay > .card { margin: auto; }
  /* ---- KARTEN KOMMEN AN, SIE STEHEN NICHT PLOETZLICH DA -----------------------------------
     Alle sieben Bildschirme (Menue, Shop, Ereignis, Konto, Bestenliste, Optionen, Ergebnis)
     werden ueber overlay.innerHTML NEU gebaut. Damit startet eine CSS-Animation auf .card bei
     jedem Wechsel von selbst — ohne eine einzige Zeile JavaScript an sieben Stellen.
     Bewusst kurz und ohne Ueberschwingen: 0.26 s, acht Pixel Anstieg. Es soll den Eindruck
     "das Bild wechselt weich" machen und nicht bei jedem Shop-Besuch auffallen. */
  .card { animation: cardIn .26s cubic-bezier(.16,1,.3,1) both; }
  @keyframes cardIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
  /* ---- ...ABER NUR, WENN DER BILDSCHIRM AUCH WECHSELT -------------------------------------
     Gemeldet: beim Zuruecksetzen der Optionen, bei den Trailer-Werten und beim Umschalten der
     Bestenlisten-Reiter "drueckt kurz der Spielehintergrund durch".
     Genau das tut die Animation darueber, und zwar korrekt: sie beginnt bei opacity 0. In
     diesen Sekundenbruchteilen liegt zwischen Spieler und Welt nur noch #overlay selbst — und
     dessen Hintergrund ist absichtlich halbdurchsichtig (rgba(...,.72) bis (...,.95)). Man
     sieht also fuer 0.26 s die Arena durch das Menue.
     Beim WECHSEL des Bildschirms ist das richtig und gewollt. Beim AKTUALISIEREN desselben
     Bildschirms ist es falsch: dort erwartet niemand einen Auftritt, sondern eine geaenderte
     Zahl. .nofade schaltet ihn genau dort ab — vergeben von overlaySetzen() in game.js, das
     den Bildschirmnamen mit dem vorherigen vergleicht. */
  .card.nofade, .card.acctcard.nofade { animation: none; }
  /* Der Anmeldebildschirm bekommt etwas mehr: er kommt oft direkt nach dem Vorspann, also aus
     dem Schwarzen heraus, und ist der erste Bildschirm, auf dem der Spieler etwas TUT. */
  .card.acctcard { animation: acctIn .5s cubic-bezier(.16,1,.3,1) both; }
  @keyframes acctIn {
    from { opacity: 0; transform: translateY(16px) scale(.965); filter: blur(3px); }
    to   { opacity: 1; transform: none; filter: none; }
  }
  /* Den Hintergrund NICHT mitanimieren, obwohl es naheliegt: #overlay traegt
     backdrop-filter: blur(7px) ueber die volle Flaeche, und eine Deckkraft-Animation darauf
     zwingt den Browser, den Weichzeichner in jedem Bild neu zu rechnen. Overlays oeffnen sich
     zwischen JEDEM Level — das waere ein wiederkehrender Ruckler fuer einen Effekt, den die
     Karte darueber ohnehin traegt. */
  @media (prefers-reduced-motion: reduce) {
    .card, .card.acctcard { animation: none; }
  }
  .card {
    position: relative; text-align: center; max-width: 460px; width: 100%;
    padding: 40px 40px 34px; border-radius: 20px;
    background: linear-gradient(165deg, rgba(22,28,48,.96), rgba(9,12,22,.96));
    border: 1px solid rgba(120,140,200,.22);
    box-shadow: 0 30px 90px rgba(0,0,0,.65), inset 0 1px 0 rgba(255,255,255,.05);
  }
  .card::before {
    content: ""; position: absolute; left: 0; right: 0; top: 0; height: 3px;
    border-radius: 20px 20px 0 0;
    background: linear-gradient(90deg, #ff8a1e, #ff5a3c 45%, #6ad1ff);
  }
  .eyebrow {
    font-size: 10px; letter-spacing: 4px; text-transform: uppercase;
    color: #ff8a4c; margin-bottom: 12px;
  }
  .card h1 {
    font-family: 'BG Mono', ui-monospace, "Arial Black", system-ui, sans-serif;
    font-weight: 800; letter-spacing: .06em;
    font-size: 40px; line-height: 1; font-weight: 900; letter-spacing: 1px;
    margin-bottom: 12px; color: #eef3ff;
  }
  .card h1 span { color: #ff8a1e; font-size: 30px; vertical-align: 3px; margin: 0 2px; }
  .card h1.win { color: #7dff9a; }
  .card h1.lose { color: #ff6b6b; }
  .sub { color: #aab6d2; font-size: 13px; line-height: 1.6; margin-bottom: 18px; }
  .scoreline {
    display: flex; justify-content: center; gap: 26px; margin: 6px 0 20px;
  }
  .scoreline div { text-align: center; }
  .scoreline .k { font-size: 9px; letter-spacing: 2px; text-transform: uppercase; color: #7f8db0; }
  .scoreline .v { font-size: 26px; font-weight: 900; color: #eef3ff; font-variant-numeric: tabular-nums; }
  .scoreline .v.hs { color: #ffcf3a; }
  .keys {
    display: grid; grid-template-columns: 1fr 1fr; gap: 9px 14px;
    margin: 18px 0 22px; font-size: 12px; color: #cdd6ea; text-align: left;
  }
  .keys span { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
  kbd {
    background: #222a40; border: 1px solid #3a4568; border-bottom-width: 2px;
    border-radius: 5px; padding: 2px 6px; font-family: inherit; font-size: 11px;
    color: #dfe7f7; min-width: 18px; text-align: center;
  }
  .powntro {
    display: flex; flex-wrap: wrap; gap: 6px 8px; justify-content: center; margin: 4px 0 20px;
    font-size: 10px; color: #9fb0d0;
  }
  .powntro span { display: flex; align-items: center; gap: 5px; background: rgba(30,38,60,.6); padding: 4px 8px; border-radius: 7px; }
  .swatch { width: 9px; height: 9px; border-radius: 3px; }
  button {
    background: linear-gradient(90deg, #ff7a2e, #ff5a3c);
    color: #fff; border: none; padding: 13px 30px; border-radius: 11px;
    font-family: inherit; font-size: 14px; font-weight: 700; letter-spacing: .5px;
    cursor: pointer; box-shadow: 0 10px 26px rgba(255,100,50,.35);
    transition: transform .12s ease, box-shadow .12s ease;
  }
  button:hover { transform: translateY(-2px); box-shadow: 0 14px 32px rgba(255,100,50,.5); }
  button:active { transform: translateY(0); }
  .tip { margin-top: 16px; font-size: 11px; color: #7f8db0; line-height: 1.5; }
  .legalfoot { margin-top: 18px; display: flex; flex-wrap: wrap; justify-content: center; gap: 6px 16px; }
  .legalfoot a { font-size: 11px; color: #5a6480; text-decoration: none; letter-spacing: .02em; }
  .legalfoot a:hover { color: #9aa6c4; }
  @media (max-width: 520px) {
    .card { padding: 32px 22px 26px; }
    .card h1 { font-size: 30px; }
    #banner .big { --bnbig: 38px; }
    #topbar .seg { padding: 6px 10px; }
  }

  /* mode + mutator toggles (menu) */
  .opts { margin: 2px 0 20px; }
  .optblock { margin-bottom: 12px; }
  .optlbl { font-size: 9px; letter-spacing: 3px; text-transform: uppercase; color: #7f8db0; margin-bottom: 7px; }
  .optrow { display: flex; flex-direction: column; gap: 10px; align-items: stretch; }
  .opt {
    cursor: pointer; user-select: none; font-size: 11px; font-weight: 700; letter-spacing: .3px;
    padding: 8px 14px; border-radius: 9px; color: #aab6d2;
    background: rgba(30,38,60,.55); border: 1px solid rgba(120,140,190,.2);
    transition: transform .12s ease, border-color .12s ease, color .12s ease, box-shadow .12s ease, background .12s ease;
  }
  .opt small { display: block; font-size: 8.5px; font-weight: 600; letter-spacing: 0; color: #6c7896; margin-top: 2px; }
  .opt:hover { border-color: rgba(150,180,255,.5); color: #dfe7f7; transform: translateY(-1px); }
  .opt.on { color: #fff; background: linear-gradient(90deg, rgba(255,122,46,.92), rgba(255,90,60,.92)); border-color: #ff8a4a; box-shadow: 0 0 12px rgba(255,110,50,.45); }
  .opt.on small { color: rgba(255,255,255,.8); }
  .opt.mode.on { background: linear-gradient(90deg, rgba(46,140,255,.92), rgba(90,120,255,.92)); border-color: #5aa6ff; box-shadow: 0 0 12px rgba(70,140,255,.45); }
  .opt.mode { font-size: 16px; padding: 15px 20px; border-radius: 12px; }
  .opt.mode small { font-size: 11px; margin-top: 4px; letter-spacing: .3px; }
  .btnrow { display: flex; gap: 10px; justify-content: center; align-items: center; flex-wrap: wrap; }
  button.ghost { background: rgba(30,38,60,.7); box-shadow: none; border: 1px solid rgba(120,140,190,.3); padding: 13px 22px; }
  button.ghost:hover { box-shadow: 0 8px 20px rgba(0,0,0,.35); }

  /* curse indicator */
  #curse {
    /* 12px waren zu wenig fuer eine Meldung, die 6,5 s lang die Steuerung erklaert — der
       Sekundenzaehler daneben war mit 13px sogar GROESSER als die Warnung selbst. */
    display: none; align-items: center; gap: 9px;
    font-size: 18px;
    padding: 8px 15px; border-radius: 10px;
    background: rgba(40,10,44,.72); border: 1px solid rgba(210,80,255,.55);
    box-shadow: 0 0 18px rgba(190,60,255,.4); color: #f0c8ff;
    font-size: 12px; font-weight: 800; letter-spacing: 1px; white-space: nowrap;
  }
  #curse.on { display: flex; animation: curseIn .2s ease; }
  @keyframes curseIn { from { transform: scale(.7); opacity: 0; } to { transform: scale(1); opacity: 1; } }
  /* The bar was 56x5 px — technically correct and practically invisible, so players never
     realised a curse runs out on its own and simply thought the controls had broken. It is
     now large enough to read at a glance, counts down in seconds, and turns green as relief
     approaches. */
  #curse .ct {
    width: 132px; height: 10px; border-radius: 5px; overflow: hidden;
    background: rgba(0,0,0,.45); border: 1px solid rgba(255,255,255,.18);
  }
  #curse .cf {
    height: 100%; width: 100%;
    background: linear-gradient(90deg,#d24aff,#ff4aa0);
    transition: background .3s linear;
  }
  #curse.ending .cf { background: linear-gradient(90deg,#4affa0,#a0ff4a); }
  #curse.ending { animation: curseEnd .45s ease-in-out infinite; }
  @keyframes curseEnd { 0%,100% { box-shadow: 0 0 18px rgba(190,60,255,.4); } 50% { box-shadow: 0 0 26px rgba(90,255,160,.75); } }
  #cursesec {
    font-family: 'BG Mono', ui-monospace, monospace; font-size: 13px; font-weight: 900;
    min-width: 30px; text-align: right; color: #ffd8ff;
  }
  #curse.ending #cursesec { color: #b8ffd0; }
  @media (prefers-reduced-motion: reduce) { #curse.ending { animation: none; } }
  #cursetint {
    position: fixed; inset: 0; pointer-events: none; z-index: 3; opacity: 0;
    box-shadow: inset 0 0 130px 30px rgba(190,50,255,.4);
  }
  #cursetint.on { animation: cursePulse 1.3s ease-in-out infinite; }
  @keyframes cursePulse { 0%,100% { opacity: .1; } 50% { opacity: .32; } }

  /* ---- Die Geduld des Gottes -------------------------------------------------------------
     Erscheint nur, solange hoechstens zwei Gegner leben (siehe updateStall in game.js) — also
     genau dann, wenn der Spieler entscheidet, ob er noch pluendert. Absichtlich klein und
     ruhig: es ist eine Auskunft, keine Warnung. Erst wenn es eng wird, wird es laut. */
  #stall {
    display: none; align-items: center; gap: 7px; margin-top: 4px;
    padding: 3px 10px 4px; border-radius: 999px; pointer-events: none;
    background: rgba(10,8,18,.55); border: 1px solid rgba(180,140,255,.22);
    font-size: 10px; font-weight: 800; letter-spacing: .16em; color: #b9a6dc;
  }
  #stall.on { display: inline-flex; }
  #stall .se { font-size: 12px; line-height: 1; letter-spacing: 0; filter: drop-shadow(0 0 4px rgba(200,120,255,.7)); }
  #stall .sb { width: 76px; height: 4px; border-radius: 3px; background: rgba(120,100,160,.26); overflow: hidden; }
  #stall .sf { display: block; height: 100%; width: 0; border-radius: 3px; background: #9a6cff; }
  /* Ab zwei Dritteln — dort faellt auch der Satz des Gottes. Zwei Kanaele, ein Moment. */
  #stall.warn { border-color: rgba(255,190,80,.45); color: #ffd9a0; }
  #stall.warn .sf { background: #ffb020; }
  #stall.hot { border-color: rgba(255,90,90,.55); color: #ffb6b6; animation: stallHot .48s ease-in-out infinite; }
  #stall.hot .sf { background: #ff3a3a; }
  @keyframes stallHot { 0%, 100% { opacity: .8; } 50% { opacity: 1; } }
  /* killstreak announcer */
  /* ---- DER SERIENRUF STAND IM WEG ------------------------------------------------------
     Er lag als Flex-Element in #centercol, also bei y~202-254. Zwei gemessene Folgen:

       1. #huntcount (absolut, top 21 %, 62 px gross) schwebt genau darueber. Gemessen:
          148 x 55 px Ueberlappung ueber 872 ms — "DOPPEL-KILL x2" und die pinke "17" mitten
          ineinander. Die groesste Einzelkollision im ganzen Bericht.
       2. streakPop skaliert auf 1.12 und dreht um -8 Grad. Eine Transformation waechst UEBER
          die Layoutbox hinaus, also griff der Ruf nach oben in den Weltregel-Chip hinein
          (79 x 11 px, dreimal gemessen).

     Er steht jetzt frei bei 30 % — im gemessenen Leerband zwischen huntcount (endet bei 313)
     und dem Banner (beginnt fruehestens bei 393). Zentriert ueber left/right statt ueber
     transform, damit die Skalierung der Animation nicht dagegen arbeitet. */
  #streak {
    position: fixed; left: 0; right: 0; top: 30%; z-index: 5;
    display: none; pointer-events: none;
    font-family: 'BG Mono', ui-monospace, "Arial Black", system-ui, sans-serif;
    font-weight: 800; letter-spacing: .06em;
    font-size: 30px; font-weight: 900; letter-spacing: 2px; text-align: center;
    color: #ffdf5a; text-shadow: 0 3px 20px rgba(255,120,20,.7), 0 0 4px rgba(0,0,0,.6);
  }
  #streak.show { display: block; animation: streakPop .95s ease-out; }
  @keyframes streakPop {
    0% { opacity: 0; transform: scale(.4) rotate(-8deg); }
    18% { opacity: 1; transform: scale(1.12) rotate(-2deg); }
    30% { transform: scale(1) rotate(0deg); }
    82% { opacity: 1; }
    100% { opacity: 0; transform: scale(1.05) translateY(-16px); }
  }

  /* shop (between levels) */
  .card.shop { max-width: 560px; }
  .shopbal { font-size: 13px; color: #cdd6ea; margin-bottom: 4px; }
  .shopbal b { color: #ffce54; font-size: 20px; font-variant-numeric: tabular-nums; }
  /* Das Kaufkontingent. Steht bewusst direkt unter dem Kontostand und in derselben Groesse:
     es ist die zweite Waehrung dieses Bildschirms, und ab dem zweiten Besuch die knappere.
     Gruen solange etwas offen ist, rot wenn nicht — die Karten sind dann alle grau, und ohne
     diesen einen farbigen Punkt sucht man den Grund. */
  .shopbuys { font-size: 13px; color: #cdd6ea; margin-bottom: 4px; display: flex; gap: 8px; align-items: baseline; justify-content: center; }
  .shopbuys b { color: #8dff6a; font-size: 20px; font-variant-numeric: tabular-nums; }
  .shopbuys.none b { color: #ff6a6a; }
  .shopgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: 16px 0 20px; }
  .shopitem {
    position: relative; text-align: left; padding: 12px 13px; border-radius: 12px; cursor: pointer;
    background: rgba(28,36,58,.7); border: 1px solid rgba(120,140,190,.22);
    transition: transform .1s ease, border-color .1s ease, box-shadow .1s ease, background .1s ease;
  }
  .shopitem:hover:not(.dis) { transform: translateY(-2px); border-color: rgba(150,180,255,.55); box-shadow: 0 8px 20px rgba(0,0,0,.3); }
  .shopitem .nm { font-size: 13px; font-weight: 800; color: #eef3ff; display: flex; align-items: center; gap: 7px; }
  .shopitem .nm .sw { width: 10px; height: 10px; border-radius: 3px; }
  .shopitem .de { font-size: 10.5px; color: #9fb0d0; margin: 3px 0 8px; line-height: 1.35; }
  /* "Stufe N greift ab Level M" — nur in der Werkstatt. Die Startwerte haengen an einem
     mitwachsenden Deckel (siehe upgradeLiveFrom in game.js); ohne diesen Hinweis kauft man
     fuer 150 Kerne eine Stufe, die im ersten Level keine einzige Zahl bewegt.
     Bernstein und nicht grau: das ist kein Beiwerk, sondern die Bedingung, unter der der
     Preis daneben ueberhaupt stimmt. */
  .shopitem .hint { font-size: 10px; color: #ffce54; opacity: .82; margin: -4px 0 8px; line-height: 1.3; }
  .shopitem .ct { font-size: 12px; font-weight: 800; color: #ffce54; }
  /* Als ASCII-Escape statt als Zeichen: \25C8 haengt an keiner Kodierung mehr und ueberlebt
     auch einen Server, der die @charset-Zeile oben nicht durchreicht. \A0 ist ein
     geschuetztes Leerzeichen — ein gewoehnliches wuerde die Hexziffernfolge beenden und
     dabei selbst verschluckt, das Zeichen klebte dann an der Zahl. */
  .shopitem .ct::before { content: "\25C8\A0"; }
  .shopitem .lv { position: absolute; top: 10px; right: 12px; font-size: 9px; letter-spacing: 1px; color: #7f8db0; }
  .shopitem.dis { opacity: .45; cursor: not-allowed; }
  .shopitem.max { opacity: .6; cursor: default; }
  .shopitem.max .ct { color: #7dff9a; }
  .shopitem.owned .ct { color: #7dff9a; }
  /* synergy badges (build combos) — they sit in the situational row now */
  #synergy { display: none; gap: 6px; flex-wrap: wrap; }
  .syn {
    font-size: 11px; font-weight: 800; letter-spacing: .3px; padding: 3px 9px; border-radius: 20px;
    background: rgba(10,14,26,.66); border: 1.5px solid #888; color: #fff;
    box-shadow: 0 0 10px rgba(0,0,0,.4), inset 0 0 8px rgba(255,255,255,.05);
    animation: synPop .35s ease-out;
  }
  @keyframes synPop { 0% { transform: scale(.5); opacity: 0; } 60% { transform: scale(1.12); } 100% { transform: scale(1); opacity: 1; } }

  /* ---- touch controls (mobile) ---- */
  #touchui { position: fixed; inset: 0; z-index: 7; pointer-events: none; display: none; }
  body.touch #touchui { display: block; }
  /* ---- DER STICK STEHT JETZT FEST -------------------------------------------------------
     Vorher erschien er dort, wo der Finger landete — auch bei 44 % Breite und 20 % Hoehe, also
     als 128-px-Kreis direkt neben der Arenamitte. In einem Spiel, dessen Staerke die freie
     Sicht auf die Arena ist, kaempft ein Bedienelement, das mitten hineinspringt, gegen genau
     diese Staerke.

     Der Einwand gegen einen festen Stick lautet normalerweise: der Daumen muss ihn erst
     TREFFEN. Der gilt hier nicht, aus zwei Gruenden:
       1. Er ist dauerhaft sichtbar (opacity .30), man sucht ihn also nicht.
       2. Die Beruehrungszone bleibt gross — die ganze untere linke Ecke, 45 % x 62 %. Wer
          daneben greift, steuert trotzdem; nur der Kreis wandert nicht mehr mit.
     Sichtbarer Anker, grosszuegige Flaeche: das ist die Kombination, die Muskelgedaechtnis
     erzeugt, ohne Reaktionszeit zu kosten. */
  #stickzone { position: absolute; left: 0; bottom: 0; top: auto; width: 45%; height: 62%; pointer-events: auto; touch-action: none; }
  #lookzone { position: absolute; right: 0; top: 0; width: 55%; height: 100%; pointer-events: auto; touch-action: none; }
  /* Fester Sitz, mit Rand-Abstand fuer Geraete mit Aussparung (Notch/Home-Leiste im
     Querformat). env() faellt auf 0 zurueck, wo es das nicht gibt. */
  #stick { position: absolute; left: calc(env(safe-area-inset-left, 0px) + 30px);
    bottom: calc(env(safe-area-inset-bottom, 0px) + 22px); top: auto; margin: 0;
    width: 128px; height: 128px; border-radius: 50%;
    border: 2px solid rgba(180,200,255,.35); background: radial-gradient(circle, rgba(80,110,180,.14), rgba(40,60,110,.06));
    opacity: .30; transition: opacity .12s ease; }
  #stick.on { opacity: 1; }
  #stickknob { position: absolute; left: 50%; top: 50%; width: 58px; height: 58px; margin: -29px 0 0 -29px; border-radius: 50%;
    background: radial-gradient(circle at 40% 35%, rgba(210,225,255,.9), rgba(120,150,220,.7)); box-shadow: 0 0 14px rgba(120,160,255,.6); }
  /* 210x176 -> 180x174. Der Block schluckt Beruehrungen als Ganzes (siehe den Abschnitt zur
     Touch-Ergonomie weiter unten), jedes ueberfluessige Pixel ist also tote Blickzone. Die alten
     Masse hatten 30 px Luft nach links und 2 nach oben, die niemand brauchte:
     LEGEN endet bei 94, FEUER bei 180, WURF bei 174. Das ist die Huelle, mehr nicht. */
  #tbtns { position: absolute; right: calc(env(safe-area-inset-right, 0px) + 16px);
    bottom: calc(env(safe-area-inset-bottom, 0px) + 16px); width: 180px; height: 174px; pointer-events: none; z-index: 2; }
  .tbtn { position: absolute; border-radius: 50%; pointer-events: auto; touch-action: none; user-select: none;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
    background: rgba(12,16,30,.5); border: 2px solid rgba(255,255,255,.4); box-shadow: 0 2px 12px rgba(0,0,0,.4), inset 0 0 12px rgba(255,255,255,.05);
    color: #fff; -webkit-tap-highlight-color: transparent; }
  .tbtn .g { font-size: 24px; line-height: 1; }
  .tbtn .l { font-size: 9px; font-weight: 800; letter-spacing: .4px; opacity: .85; }
  .tbtn.down { transform: scale(.9); background: rgba(60,90,150,.6); }
  .tbtn.bomb { right: 0; bottom: 0; width: 94px; height: 94px; border-color: #ffce54; }
  .tbtn.bomb .g { font-size: 32px; }
  .tbtn.fire { right: 104px; bottom: 8px; width: 76px; height: 76px; border-color: #6ad1ff; opacity: .45; }
  .tbtn.fire.avail { opacity: 1; }
  /* 70 statt 66 bzw. 62: Apples Richtwert ist 44 pt, also rund 66 px. Der Notzuender war mit
     62 px der KLEINSTE Knopf im Spiel — und der, den man in Panik trifft oder eben nicht.
     Beide passen weiter in die 210x176 des Blocks (throw endet bei 174, det bei 172). */
  .tbtn.throw { right: 10px; bottom: 104px; width: 70px; height: 70px; border-color: #9bff6a; }
  /* ---- DER NOTZUENDER, JETZT UNTER DER MINIMAP -------------------------------------------
     Vorher: right:102 / bottom:96 — die innere Ecke des Knopfblocks, also der Punkt, der am
     weitesten in die Bildmitte ragt. Genau dort setzt der rechte Daumen zum Drehen an.

     Verankert von OBEN aus denselben Variablen wie die Minimap, aus dem Grund, der beim
     Bombenzaehler weiter unten steht: so wandert er von selbst mit, wenn eine Bildschirmstufe
     --hudy, --mmh oder --mmpad aendert. Nachgerechnet fuer den engsten Fall (Hoehe <= 420 px:
     --hudy 12, --mmh 73, --mmpad 5):

       oben   12 + 73 + 10 + 12 = 107 px
       unten  107 + 62          = 169 px
       #tbtns beginnt bei       = Hoehe - (16 + 174) = 170 px

     Also ein Pixel Luft — knapp, aber ohne Ueberschneidung. Auf allen groesseren Stufen ist
     reichlich Platz. Unter 420 px Hoehe faellt er zusaetzlich auf 54 px (siehe unten). */
  .tbtn.det {
    top: calc(var(--hudy) + var(--mmh) + var(--mmpad) * 2 + 12px);
    right: var(--hudx); bottom: auto; left: auto;
    width: 62px; height: 62px; border-color: #ff7a9a; opacity: .45; z-index: 2;
  }
  .tbtn.det.avail { opacity: 1; }

  /* ---- DIE BOMBENZAHL AUF DEM KNOPF ------------------------------------------------------
     Kein eigener Platz im HUD mehr, sondern ein Abzeichen am Rand des LEGEN-Knopfes. Es zeigt
     FREI/MAX (siehe bombZaehler in game.js), deshalb Platz fuer drei Zeichen statt eines. Es liegt
     an dessen oberer AUSSENkante, also weg von der Bildmitte: dort verdeckt es nichts vom
     Spielfeld und der Daumen liegt nicht darauf.
     pointer-events: none, damit ein Tipp auf die Zahl den Knopf trifft und nicht das Abzeichen. */
  .tbtn .cnt {
    position: absolute; top: -4px; right: -4px; min-width: 38px; height: 26px; padding: 0 5px;
    display: flex; align-items: center; justify-content: center; box-sizing: border-box;
    border-radius: 13px; background: rgba(8,11,20,.92); border: 2px solid #ffce54;
    color: #ffce54; font-family: 'BG Mono', var(--mono, ui-monospace, monospace);
    font-size: 13px; font-weight: 800; line-height: 1; letter-spacing: -.02em;
    transition: color .12s ease, border-color .12s ease;
    box-shadow: 0 1px 6px rgba(0,0,0,.55); pointer-events: none;
  }
  /* ---- minimap corner badges: pause + record -------------------------------------
     Pause is centred on the minimap frame's TOP-RIGHT corner, record on its BOTTOM-LEFT.
     A quarter of each badge sits over the frame and the rest hangs outside, which reads as
     part of the panel while covering almost none of the map — the previous full-size
     buttons sat squarely ON it, which is what made the map unreadable in that corner.

     Both stay CHILDREN OF #touchui on purpose. #lookzone covers the entire right half of
     the screen at z-index 7 and #hud is at 5, so a badge nested inside #mmwrap would paint
     perfectly and then never receive a single tap. They are therefore anchored to the
     viewport edge and reconstruct the frame's corners from the same variables the frame
     itself uses (--mmw/--mmh/--mmpad + 2px of border), so the three move together. */
  #tpause {
    position: absolute; width: var(--badge); height: var(--badge);
    border-radius: 50%; pointer-events: auto; touch-action: none; z-index: 12;
    display: flex; align-items: center; justify-content: center;
    background: rgba(10,14,26,.88); border: 1px solid rgba(150,175,225,.30);
    box-shadow: 0 2px 10px rgba(0,0,0,.55); backdrop-filter: blur(4px);
    -webkit-tap-highlight-color: transparent; user-select: none;
  }
  /* --hudx/--hudy are set to --badge/2 on touch, so these land exactly in the screen's
     corner: centred on the frame corner, nothing clipped. */
  #tpause {
    top:   calc(var(--hudy) - var(--badge) / 2);
    right: calc(var(--hudx) - var(--badge) / 2);
    color: #fff; font-size: 13px; letter-spacing: 2px;
  }
  #tpause:active { background: rgba(40,60,110,.9); }
  #rotate { position: fixed; inset: 0; z-index: 20; display: none; flex-direction: column; align-items: center; justify-content: center; gap: 12px;
    background: radial-gradient(circle at 50% 45%, #0b1020, #04060c); color: #eaf0ff; text-align: center; }
  #rotate .ricon { font-size: 56px; animation: rotwig 1.6s ease-in-out infinite; }
  #rotate .rbig { font-size: 22px; font-weight: 800; }
  #rotate .rsub { font-size: 13px; opacity: .6; }
  @keyframes rotwig { 0%,100% { transform: rotate(-18deg); } 50% { transform: rotate(72deg); } }
  @media (orientation: portrait) { body.touch #rotate { display: flex; } }
  /* short landscape (phones): trim the tall menu so the start button always fits */
  @media (max-height: 520px) {
    #overlay { padding: 8px; }
    .card { padding: 16px 22px 14px !important; border-radius: 14px; }
    .card h1 { font-size: 30px !important; margin: 2px 0 6px !important; }
    .menucard .sub { display: none; }
    .menucard .tip { display: none; }
    .menucard .powntro { display: none; }
    .menucard .keys { margin: 6px 0 !important; font-size: 10px; }
    .menucard .eyebrow { margin-bottom: 2px; }
  }
  /* mobile in-game HUD tweaks */
  body.touch #hint { display: none; }                 /* keyboard hint irrelevant on touch */
  body.touch .syn .sn { display: none; }              /* synergies show icon only */
  body.touch .syn { font-size: 15px; padding: 3px 7px; }

/* One Minute Apocalypse countdown */
#apoc { display: none; font-weight: 900; font-size: 42px; letter-spacing: 2px; color: #ff3a3a; text-shadow: 0 0 18px rgba(255,40,40,.7), 0 2px 6px rgba(0,0,0,.85); pointer-events: none; animation: apocPulse 1s ease-in-out infinite; }
@keyframes apocPulse { 0%,100% { transform: scale(1); opacity: .95; } 50% { transform: scale(1.12); opacity: 1; } }

/* Der Vorhang fuer das Auge-Intro. Nur beim allerersten Start, siehe playEyeIntro().
   #godeye wird dafuer kurz nach vorn geholt statt ein zweites Auge zu bauen — man soll lernen,
   dass DAS Auge oben im Bild derselbe ist, der einen hier angesehen hat. */
#introback {
  position: fixed; inset: 0; z-index: 1150; background: #04050a;
  transition: opacity 1.1s ease;
}
/* ---- DER HINWEIS, DEN MAN DRAUSSEN NICHT LESEN KONNTE ---------------------------------------
   Hier stand: 12px, color rgba(200,215,245,.42), dazu introPulse mit Deckkraft .30 bis .75.
   Die beiden Deckkraefte MULTIPLIZIEREN sich — effektiv 0.126 bis 0.315 gegen einen Grund von
   #04050a. Nach WCAG sind das 1.24:1 bis 2.19:1; verlangt werden fuer kleinen Text 4.5:1.

   Gemeldet aus dem Sommer, im Freien: "teilweise nur einen schwarzen Bildschirm gesehen und
   konnte nicht einmal lesen, dass ich klicken muss". Bei zwei- bis dreieinhalbfacher
   Unterschreitung ist das keine Ueberraschung, sondern die Vorhersage.

   Diese Aufforderung ist seit dem Umbau nur noch der RUECKFALL — im Normalfall steht sie auf dem
   beleuchteten Bootschirm (siehe startGesteFangen in game.js). Wenn sie aber erscheint, dann in
   dem einen Moment, in dem der Spieler sonst gar nichts weiss. Also volle Deckkraft, groesser,
   in der Signalfarbe, mit Rahmen als zweiter Lesbarkeitsebene.

   Das Pulsieren fasst die DECKKRAFT nicht mehr an: Aufmerksamkeit ueber den Massstab kostet
   keine Lesbarkeit. Genau derselbe Fehler wie dreimal zuvor in diesem Projekt — ein dauerhafter
   Schreiber (die Animation) gegen den Wert, auf den es ankommt. */
#introback .hint {
  position: absolute; bottom: 12%; left: 50%; transform: translateX(-50%);
  font-family: 'BG Mono', ui-monospace, "SFMono-Regular", Menlo, monospace;
  font-size: 13px; font-weight: 700; letter-spacing: .22em; text-transform: uppercase; white-space: nowrap;
  padding: 9px 18px; border-radius: 999px;
  color: #ffd24a; border: 1px solid rgba(255,210,74,.55); background: rgba(255,210,74,.08);
  animation: introPulse 2.2s ease-in-out infinite;
}
@keyframes introPulse {
  0%, 100% { transform: translateX(-50%) scale(1); }
  50%      { transform: translateX(-50%) scale(1.045); }
}
/* Er ist seit dem Umbau nur noch der Rueckfall und startet versteckt. Ohne diese Zeile wuerde
   das hidden-Attribut vom eigenen display-Kontext ueberstimmt, sobald jemand hier spaeter
   ein display setzt — billiger als die Ursachensuche danach. */
#introback .hint[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) { #introback .hint { animation: none; } }
/* ---- WARUM DER HINWEIS NICHT VERSCHWAND ---------------------------------------------------
   playEyeIntro setzt beim Klick h.style.opacity = '0'. Das hat nie gewirkt: eine laufende
   CSS-ANIMATION steht in der Kaskade ueber dem Inline-Stil, solange sie die Eigenschaft
   bespielt. introPulse schrieb die Deckkraft also weiter zwischen .3 und .75 — unendlich.

   Derselbe Fehlertyp wie bei den Gruft-Kerzen (updateHorror ueberschrieb die Deckkraft der
   Flamme) und beim Fundstueck-Halo (die Animationsschleife ueberschrieb den berechneten Wert).
   Zum dritten Mal dieselbe Falle: ein dauerhafter Schreiber gegen eine einmalige Zuweisung.

   Die Klasse schaltet die Animation ab UND setzt die Deckkraft — damit gibt es keinen zweiten
   Besitzer mehr. */
#introback .hint.weg {
  animation: none;
  opacity: 0;
  letter-spacing: .9em;
  transition: opacity .28s ease, letter-spacing .5s ease;
}
#godeye.intro {
  display: block !important; position: fixed; left: 50%; top: 46%; z-index: 1200;
  font-size: 14vmin; opacity: 0; transform: translate(-50%,-50%) scale(.35);
  /* 2.4 s / 3.2 s waren der Grund fuer die gemeldeten "3-4 Sekunden passiert nichts": das Auge
     stand nach einer halben Sekunde noch bei rund 10 % Deckkraft. Ein Vorspann darf langsam
     ATMEN, aber er muss auf den Klick ANTWORTEN. Jetzt ist der Sprung sofort da und laeuft
     danach weich aus. */
  transition: opacity .55s ease, transform 1.05s cubic-bezier(.16,1,.3,1), filter .8s ease;
  filter: drop-shadow(0 0 10px rgba(210,110,255,.5));
  animation: none;
}
/* Stufe eins: die unmittelbare Antwort auf den Klick. Halb offen, sofort sichtbar. */
#godeye.intro.wake {
  opacity: .55; transform: translate(-50%,-50%) scale(.72);
  filter: drop-shadow(0 0 20px rgba(210,110,255,.75));
}
/* Stufe zwei: ganz offen, mit dem Donner. */
#godeye.intro.open {
  opacity: .95; transform: translate(-50%,-50%) scale(1);
  filter: drop-shadow(0 0 38px rgba(210,110,255,.98));
}
/* Der Anblitz im Moment des Klicks. */
#godeye.intro.hit { animation: introHit .42s ease-out; }
@keyframes introHit {
  0%   { filter: drop-shadow(0 0 60px rgba(255,255,255,1)) brightness(2.6); }
  100% { filter: drop-shadow(0 0 20px rgba(210,110,255,.75)) brightness(1); }
}

/* ---- DIE STIMME MUSS UEBER DEM VORHANG LIEGEN -------------------------------------------
   Der eigentliche Fehler hinter "es passiert nichts": #godvoice hat z-index 59, #introback
   aber 1150. Die beiden Zeilen des Vorspanns ("Endlich." / "Ein neuer Versuch.") wurden also
   gesprochen und waren VOLLSTAENDIG hinter dem schwarzen Vorhang versteckt. Man hoerte den
   Donner und sah dazu ein sehr langsam erscheinendes Auge — sonst nichts. */
body.introfx #godvoice { z-index: 1250; bottom: 26%; font-size: clamp(18px, 3.4vmin, 27px); }

/* ---- Der Ring, der vom Auge ausgeht ---------------------------------------------------- */
#introring {
  position: fixed; left: 50%; top: 46%; z-index: 1190; pointer-events: none;
  width: 14vmin; height: 14vmin; margin: -7vmin 0 0 -7vmin;
  border: 1px solid rgba(200,140,255,.7); border-radius: 50%; opacity: 0;
}
#introring.go { animation: introRing 1.5s cubic-bezier(.16,1,.3,1) forwards; }
@keyframes introRing {
  0%   { opacity: .85; transform: scale(.3); }
  100% { opacity: 0;   transform: scale(4.6); }
}

/* ---- Aufsteigende Partikel ---------------------------------------------------------------
   DOM statt der Sprite-Pools im Spiel: die liegen auf dem Canvas, und der Canvas ist waehrend
   des Vorspanns komplett verdeckt. Acht Elemente, reine CSS-Animation, keine Frame-Kosten. */
#intromote {
  position: fixed; inset: 0; z-index: 1160; pointer-events: none; overflow: hidden; opacity: 0;
  transition: opacity .5s ease;
}
#intromote.go { opacity: 1; }
#intromote i {
  position: absolute; bottom: -6px; width: 3px; height: 3px; border-radius: 50%;
  background: #c89aff; box-shadow: 0 0 8px rgba(200,140,255,.9);
  animation: introMote linear infinite;
}
@keyframes introMote {
  0%   { transform: translateY(0) scale(.5); opacity: 0; }
  12%  { opacity: .9; }
  80%  { opacity: .55; }
  100% { transform: translateY(-100vh) scale(1.15); opacity: 0; }
}

/* Der Vorhang geht nicht nur weg — er weicht zurueck. */
#introback { transition: opacity .9s ease, transform .9s ease, filter .9s ease; }
#introback.lift { opacity: 0; transform: scale(1.06); filter: blur(6px); }

/* Der Zündgott — his eye opens further the more you escalate (the only UI he gets) */
#godeye { display: none; font-size: 24px; pointer-events: none; opacity: .5; transition: opacity .5s ease, transform .5s ease, filter .5s ease; animation: godBlink 4.5s ease-in-out infinite; }
@keyframes godBlink { 0%, 92%, 100% { filter: none; } 95% { opacity: .15; } }

/* His voice — rare, short, never explained */
#godvoice { position: fixed; bottom: 22%; left: 50%; transform: translateX(-50%); z-index: 59; font-size: 21px; font-weight: 700; letter-spacing: 3px; color: #e9d4ff; text-shadow: 0 0 16px rgba(190,90,255,.9), 0 2px 8px rgba(0,0,0,.9); pointer-events: none; opacity: 0; }
#godvoice.show { animation: godVoice 2.8s ease-out forwards; }
@keyframes godVoice { 0% { opacity: 0; transform: translateX(-50%) translateY(8px); } 12% { opacity: .95; transform: translateX(-50%) translateY(0); } 70% { opacity: .95; } 100% { opacity: 0; } }

/* ============================================================
   RESPONSIVE PASS — one HUD bar that wraps, labels that vanish
   when space runs out, and a menu where only the modes shout.
   ============================================================ */

/* the pills never break mid-word; the bar wraps to a second line instead */
.pill { white-space: nowrap; }
.pill .lbl { font-style: normal; }
.pill.ico { gap: 4px; font-size: 15px; line-height: 1; }
.abs { display: inline-flex; gap: 5px; align-items: center; padding-left: 6px; border-left: 1px solid rgba(120,140,190,.15); }

/* secondary menu actions: present, but clearly subordinate to the two modes */
.subrow { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin: 12px 0 2px; }
.subbtn {
  cursor: pointer; user-select: none; display: inline-flex; align-items: center; gap: 6px;
  font-family: inherit; font-size: 11px; font-weight: 700; letter-spacing: .4px; color: #8e9cbd;
  background: rgba(22,28,46,.6); border: 1px solid rgba(120,140,190,.18);
  padding: 7px 12px; border-radius: 999px; box-shadow: none;
  transition: color .12s ease, border-color .12s ease, background .12s ease;
}
.subbtn:hover { color: #dbe6ff; border-color: rgba(150,180,255,.45); background: rgba(34,44,72,.7); }
.subbtn .ic { font-size: 13px; line-height: 1; }
.subbtn b { color: #cfd9f2; font-weight: 800; }

/* ------------------------------------------------------------------
   Breakpoints measured against the real column widths, not guessed:
     labelled stats 655 + info 378 + minimap 153 + chrome 48 = 1234 px
     icon-only stats 424 + info 378 + minimap 153 + chrome 48 = 1003 px
   Overlap is already impossible (the top strip is a grid) — these keep
   it from wrapping to a second line before it has to.
   ------------------------------------------------------------------ */

/* 1280: everything drops to icon + number. That includes the synergy chips and the
   mutation pill — three spelled-out build names ate more width than the whole stat bar. */
@media (max-width: 1280px) {
  .pill .lbl { display: none; }
  .pill { padding: 3px 7px; gap: 5px; }
  .seg { padding: 6px 11px; }
  .syn .sn, #s-mut .sn { display: none; }
  .syn { padding: 3px 7px; font-size: 13px; line-height: 1; }
  #bar2 .pill { padding: 4px 8px; }
}

/* 1060: the info strip no longer fits beside the stats. It moves to a SECOND GRID ROW
   rather than to the bottom edge — the bottom belongs to the touch controls, and a strip
   down there sits under the left thumb (joystick zone is the left 45% of the screen)
   and clips the action buttons on a 740px phone. Row 2 keeps the no-overlap guarantee. */
@media (max-width: 950px) {
  :root { --hudx: 10px; --hudy: 10px; }
  #topzone { grid-template-columns: minmax(0, auto) 1fr auto; gap: 8px; }
  #stats   { grid-column: 1; grid-row: 1; }
  #bossbar { grid-column: 2; grid-row: 1; }
  #mmwrap  { grid-column: 3; grid-row: 1; }
  /* out of the grid, pinned bottom-centre in the gap between the two thumbs */
  #topbar {
    position: fixed; left: 50%; bottom: 12px; transform: translateX(-50%);
    grid-row: auto; width: max-content; max-width: 52vw; flex-wrap: nowrap;
    background: rgba(6,8,14,.88); border-radius: 12px;   /* kein backdrop-filter — siehe .bar */
    border: 1px solid rgba(120,140,190,.18); overflow: hidden;
  }
  /* Der Schwur gehoert nicht in die Bildmitte — dort klebte er auf dem Fadenkreuz. Er sitzt
     direkt UEBER der Statusleiste, die hier ohnehin unten mittig haengt: dieselbe Spalte,
     dieselbe Leserichtung, und der Blick muss nur einmal nach unten statt zweimal.
     Der erste Versuch landete versehentlich in @media (max-width: 520px) — die Screenshots
     waren 740px breit, die Regel hat nie gefeuert. Hier ist derselbe Breakpoint wie #topbar,
     und oathNarrow() im JS prueft exakt dieselben 950px. */
  #oath {
    position: fixed; left: 50%; bottom: 52px; transform: translateX(-50%);
    max-width: 64vw; z-index: 4;
  }
  #oath.on { display: flex; }
  #oath.gone { transform: translateX(-50%) translateY(-6px); }
  #topbar .seg { padding: 5px 9px; border-right: 1px solid rgba(120,140,190,.14); }
  #topbar .seg:last-child { border-right: 0; }
  .seg .v { font-size: 13px; }
  .seg .k { font-size: 7px; letter-spacing: 1.5px; }
}
/* the keyboard hint only fits while there is room for it along the bottom */
@media (max-width: 900px) { #hint { display: none; } }

/* 660: shrink the minimap and tighten the bar so both still fit side by side */
@media (max-width: 660px) {
  :root { --mmw: 108px; --mmh: 94px; --mmpad: 6px; }
  .bar { padding: 5px 7px; gap: 2px; }
  .pill b { font-size: 13px; }
  .ab { width: 19px; height: 19px; font-size: 10px; }
  .ab.panic { width: auto; min-width: 19px; padding: 0 4px; }
  .ab.panic b { font-size: 11px; }
  #hearts .hp { font-size: 13px; }
  #mmwrap .label { font-size: 8px; margin-bottom: 4px; }
  #centercol { gap: 5px; padding-top: 8px; }
  #streak { font-size: 24px !important; }
  #combo { font-size: 18px; }
}

/* 460: last resort — the minimap label goes, everything gets minimal */
@media (max-width: 460px) {
  :root { --mmw: 88px; --mmh: 76px; }
  #mmwrap .label { display: none; }
  .abs { padding-left: 4px; }
  #apoc { font-size: 32px; }
}

/* touch: the pause button now lives on the minimap's corner, so the strip no longer has to
   reserve 64px of its width for it — reclaiming that is what lets the minimap sit at the
   right edge. The inset is --badge/2, which is exactly what the corner badges need to sit
   centred on the frame's corners without being clipped by the screen edge. */
body.touch { --badge: 36px; --hudx: 18px; --hudy: 18px; --mmw: 118px; --mmh: 102px; }
@media (max-width: 660px) { body.touch { --mmw: 96px; --mmh: 83px; } }
@media (max-width: 460px) { body.touch { --mmw: 84px; --mmh: 73px; } }
body.touch .pill .lbl { display: none; }
/* Same three columns as everywhere else: the boss bar belongs BETWEEN the stats and the
   minimap on a phone too. Below 950px the info strip is already pinned to the bottom edge,
   which hands its ~380px straight to the middle column — so it fits. */
body.touch #topzone { grid-template-columns: minmax(0, auto) 1fr auto; }
body.touch #stats   { grid-column: 1; grid-row: 1; }
body.touch #bossbar { grid-column: 2; grid-row: 1; min-width: 96px; width: min(40vw, 300px); margin-top: 6px; }
body.touch #mmwrap  { grid-column: 3; grid-row: 1; }
/* The minimap was taking close to a quarter of the top half of a phone screen. ~13% smaller
   with a quieter frame gives that room back. The CANVAS is deliberately untouched — its own
   background is painted in drawMinimap(), and thinning that would cost the enemy dots the
   contrast they need. Only the frame around it gets more transparent. */
body.touch #mmwrap { background: rgba(8,11,20,.42); border-color: rgba(120,140,190,.14); }
body.touch #mmwrap .label { display: none; }   /* the corner badges own that space now */
body.touch #topbar {
  position: fixed; left: 50%; bottom: 12px; transform: translateX(-50%);
  grid-row: auto; width: max-content; max-width: 52vw; flex-wrap: nowrap;
  background: rgba(6,8,14,.88); border-radius: 12px;   /* kein backdrop-filter — siehe .bar */
  border: 1px solid rgba(120,140,190,.18); overflow: hidden; z-index: 6;
}
body.touch .syn .sn, body.touch #s-mut .sn { display: none; }
body.touch #hint { display: none; }


/* ============================================================
   GAME OVER — the run card is the hero; on anything wider than a
   phone-portrait it sits beside the numbers rather than above them,
   which is what makes it readable in phone LANDSCAPE (740x360).
   ============================================================ */
.card.gameover { max-width: 780px; }
.gorow { display: flex; gap: 20px; align-items: center; justify-content: center; margin-top: 6px; }
.gorow .gocard { flex: 0 0 auto; }
.gorow .gocard img {
  display: block; width: 236px; max-width: 40vw; max-height: 66vh; object-fit: contain;
  border-radius: 14px; box-shadow: 0 10px 34px rgba(0,0,0,.55);
}
.gorow .goinfo { flex: 1 1 0; min-width: 0; text-align: left; }
.gorow .goinfo .sub { text-align: left; margin: 0 0 10px; }
.gorow .goinfo .scoreline { justify-content: flex-start; margin: 0 0 10px; }
.gostats { display: grid; grid-template-columns: 1fr auto; gap: 4px 16px; font-size: 13px; margin-bottom: 10px; }
.gostats span { opacity: .62; }
.gostats b { font-variant-numeric: tabular-nums; }
.gocores { font-size: 13px; color: #8dd0ff; margin-bottom: 10px; }
.gocores b { color: #ffd24a; }

/* one button, the platform decides what it opens */
.sharebtn {
  width: 100%; margin: 0 0 8px; display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  background: linear-gradient(90deg, #2a3550, #1c2740); border: 1px solid rgba(120,180,255,.32); color: #cfe3ff;
}
.sharebtn .ic { font-size: 15px; line-height: 1; }
/* Der Wordle-Ergebnis-Knopf: prominenter als der Bild-Teilen-Knopf, weil er in den Challenges
   der Hauptweg zum Teilen ist. Warmer Akzent, damit das Auge zuerst hier landet. */
.gridbtn {
  width: 100%; margin: 0 0 8px; display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  background: linear-gradient(90deg, #6a3fd0, #8a5cff); border: 1px solid rgba(180,150,255,.5); color: #fff;
  font-weight: 800;
}
.gridbtn .ic { font-size: 15px; line-height: 1; }
.gridbtn:hover { background: linear-gradient(90deg, #7a4fe0, #9a6cff); }
/* Steht der Wordle-Knopf daneben, tritt der Bild-Knopf optisch zurueck (sekundaer). */
.gridbtn + .sharebtn { background: rgba(20,28,46,.7); opacity: .85; }
.gridout {
  width: 100%; font-family: 'BG Mono', var(--mono, monospace); font-size: 12px; line-height: 1.5; color: #cfe3ff;
  background: rgba(10,14,26,.9); border: 1px solid rgba(120,140,190,.3); border-radius: 8px;
  padding: 8px; resize: none;
}
#sharefall { display: none; flex-wrap: wrap; gap: 6px; margin: 0 0 10px; }
#sharefall.on { display: flex; }
.shbtn {
  font-family: inherit; font-size: 11px; font-weight: 800; letter-spacing: .3px; cursor: pointer;
  text-decoration: none; padding: 7px 11px; border-radius: 999px;
  background: rgba(16,22,38,.8); border: 1px solid rgba(120,140,190,.3); box-shadow: none;
}
.shbtn:hover { background: rgba(30,40,66,.9); box-shadow: none; }

/* phone portrait / very narrow: stack it, card on top */
@media (max-width: 620px) {
  .gorow { flex-direction: column; gap: 12px; }
  .gorow .gocard img { width: 200px; max-width: 62vw; max-height: 40vh; }
  .gorow .goinfo { text-align: center; }
  .gorow .goinfo .sub, .gorow .goinfo .scoreline { text-align: center; justify-content: center; }
  .gostats { max-width: 280px; margin-left: auto; margin-right: auto; }
}
/* short landscape phones: the card must not eat the buttons */
@media (max-height: 480px) {
  .gorow .gocard img { max-height: 56vh; width: 190px; }
  .gostats { font-size: 12px; gap: 2px 14px; }
  .card.gameover h1 { margin-bottom: 4px !important; }
}

/* ============================================================
   OPTIONS — sliders, toggles, key rebinding, and the focus ring
   a controller needs when there is no mouse pointer (Steam Deck).
   ============================================================ */
.card.optcard { max-width: 560px; text-align: left; }
.card.optcard h1 { font-size: 34px; margin-bottom: 10px; text-align: center; }
.card.optcard .eyebrow { text-align: center; }
.osec {
  font-size: 10px; letter-spacing: 2.5px; text-transform: uppercase; color: #7f8db0;
  margin: 16px 0 6px; padding-bottom: 5px; border-bottom: 1px solid rgba(120,140,190,.16);
}
.orow { display: flex; align-items: center; gap: 12px; padding: 5px 0; }
.orow label { flex: 1 1 auto; font-size: 13px; color: #cdd6ea; }
.oval { flex: 0 0 52px; text-align: right; font-size: 12px; font-weight: 800; color: #6ad1ff; font-variant-numeric: tabular-nums; }

.oslide {
  flex: 0 0 190px; -webkit-appearance: none; appearance: none; height: 5px; border-radius: 3px;
  background: rgba(120,140,190,.25); outline: none; cursor: pointer;
}
.oslide::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; width: 17px; height: 17px; border-radius: 50%;
  background: #6ad1ff; border: 2px solid #0b1020; box-shadow: 0 0 10px rgba(90,190,255,.7); cursor: pointer;
}
.oslide::-moz-range-thumb {
  width: 15px; height: 15px; border-radius: 50%; background: #6ad1ff;
  border: 2px solid #0b1020; box-shadow: 0 0 10px rgba(90,190,255,.7); cursor: pointer;
}

.otog, .qbtn {
  font-family: inherit; font-size: 11px; font-weight: 800; letter-spacing: .5px; cursor: pointer;
  padding: 6px 14px; border-radius: 999px; color: #8e9cbd; box-shadow: none;
  background: rgba(22,28,46,.7); border: 1px solid rgba(120,140,190,.22);
}
.otog.on { color: #0b1020; background: #7dff9a; border-color: #7dff9a; }
.qrow { display: flex; gap: 6px; }
.qbtn.on { color: #0b1020; background: #6ad1ff; border-color: #6ad1ff; }
.otog:hover, .qbtn:hover { box-shadow: none; border-color: rgba(150,180,255,.5); }

.obinds { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.obind {
  display: flex; align-items: center; justify-content: space-between; gap: 8px; cursor: pointer;
  padding: 7px 11px; border-radius: 9px; font-size: 12px; color: #9fb0d0;
  background: rgba(22,28,46,.6); border: 1px solid rgba(120,140,190,.18);
  transition: border-color .1s ease, background .1s ease;
}
.obind:hover { border-color: rgba(150,180,255,.5); background: rgba(34,44,72,.75); }
.obind b { color: #eef3ff; font-size: 12px; white-space: nowrap; }
.obind.wait { border-color: #ffce54; background: rgba(60,48,18,.7); }
.obind.wait b { color: #ffce54; }

/* controller focus: the only thing standing between a Deck and an unplayable menu */
.padfocus {
  outline: 2px solid #7dff9a !important; outline-offset: 2px;
  box-shadow: 0 0 16px rgba(125,255,154,.45) !important;
}

@media (max-width: 620px) {
  .obinds { grid-template-columns: 1fr; }
  .oslide { flex: 0 0 130px; }
  .orow label { font-size: 12px; }
}
@media (max-height: 560px) {
  .osec { margin: 10px 0 4px; }
  .orow { padding: 3px 0; }
  .card.optcard h1 { font-size: 26px !important; margin-bottom: 4px !important; }
}

/* ---- demo build: locked options and the closing pitch ---- */
.opt.locked { opacity: .45; cursor: not-allowed; border-style: dashed; }
.opt.locked:hover { border-color: rgba(120,140,190,.22); background: rgba(22,28,46,.6); }
.teaser {
  margin: 4px 0 12px; padding: 11px 13px; border-radius: 11px; text-align: left;
  background: linear-gradient(180deg, rgba(40,26,66,.75), rgba(20,16,38,.7));
  border: 1px solid rgba(160,110,255,.28);
}
.teaser .tlbl {
  font-size: 9px; letter-spacing: 2.5px; text-transform: uppercase;
  color: #c9a8ff; margin-bottom: 7px;
}
.teaser ul { list-style: none; display: flex; flex-direction: column; gap: 5px; }
.teaser li { font-size: 12px; color: #b9c4dc; line-height: 1.45; padding-left: 14px; position: relative; }
.teaser li::before { content: '\25c8'; position: absolute; left: 0; color: #ff8a1e; font-size: 9px; top: 3px; }
.teaser b { color: #eef3ff; }
.buybtn {
  display: block; width: 100%; text-align: center; text-decoration: none;
  font-family: inherit; font-size: 15px; font-weight: 900; letter-spacing: .4px;
  padding: 13px 20px; border-radius: 11px; margin: 0 0 8px; color: #1a1005;
  background: linear-gradient(90deg, #ffb03a, #ff7a1e);
  box-shadow: 0 8px 26px rgba(255,140,30,.35);
}
.buybtn:hover { filter: brightness(1.08); }

/* language picker in the options screen */
.lbtn {
  font-family: inherit; font-size: 11px; font-weight: 800; letter-spacing: .5px; cursor: pointer;
  padding: 6px 16px; border-radius: 999px; color: #8e9cbd; box-shadow: none;
  background: rgba(22,28,46,.7); border: 1px solid rgba(120,140,190,.22);
}
.lbtn.on { color: #0b1020; background: #c9a8ff; border-color: #c9a8ff; }
.lbtn:hover { box-shadow: none; border-color: rgba(180,150,255,.6); }

/* ============================================================
   LEADERBOARD — the reason to come back that a copied folder can't fake.
   ============================================================ */
.card.boardcard { max-width: 560px; }
.card.boardcard h1 { text-align: center; font-size: 34px; margin-bottom: 4px; }
.card.boardcard .eyebrow { text-align: center; }
.boardtotal { text-align: center; font-family: 'BG Mono', var(--mono, monospace); font-size: 12px; color: #808ead; margin-bottom: 12px; letter-spacing: .04em; }
.boardlist { list-style: none; display: flex; flex-direction: column; gap: 4px; }
.boardlist li {
  display: grid; grid-template-columns: 38px 1fr 92px 88px; gap: 10px; align-items: center;
  padding: 9px 12px; border-radius: 9px; background: rgba(16,20,31,.55);
  border: 1px solid rgba(120,140,190,.12);
}
/* Keyed on the RANK, not the position in the list. With the podium and the player's own
   neighbourhood in one <ol>, nth-child would start handing out medals to whoever happens to
   sit in the 4th row of the DOM. */
.boardlist li[data-rank="1"] { border-color: rgba(255,206,84,.5); background: rgba(48,40,14,.5); }
.boardlist li[data-rank="2"] { border-color: rgba(200,210,230,.4); }
.boardlist li[data-rank="3"] { border-color: rgba(205,127,80,.4); }
.boardlist li.me { border-color: rgba(106,209,255,.6); background: rgba(14,32,48,.6); }
.boardlist .rk { font-family: 'BG Mono', ui-monospace, monospace; font-weight: 900; font-size: 15px; color: #808ead; text-align: center; }
.boardlist li[data-rank="1"] .rk { color: #ffce54; }
.boardlist .nm { font-weight: 700; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #cfe0f7; }
.boardlist li.me .nm { color: #6ad1ff; }
.boardlist .lv { font-size: 11px; color: #808ead; font-family: 'BG Mono', ui-monospace, monospace; text-align: center; }
.boardlist .sc { font-family: 'BG Mono', ui-monospace, monospace; font-weight: 900; font-size: 15px; color: #6ad1ff; text-align: right; }
/* the break between the podium and the player's own neighbourhood */
.boardlist li.bgap {
  display: flex; align-items: center; justify-content: center;
  height: 20px; padding: 0; border: 0; background: none;
  color: #4a5470; font-family: 'BG Mono', ui-monospace, monospace; font-size: 15px; letter-spacing: 5px;
}
.boardhint { margin-top: 10px; text-align: center; font-size: 12px; color: #7f8db0; }

/* The card grew and shrank as you moved between tabs: more rows on the busy boards, and a
   streak box that only two of the four tabs had. The box is on every tab now, and the list
   can no longer exceed 3 podium rows + a separator + 5 rows around the player — so reserving
   that worst case here pins the card to one height for good.
   8 rows x 38px + separator 20 + 8 gaps x 4 + the count line 27 = 383, rounded up for
   headroom, because overshooting a min-height costs a thin empty strip while undershooting
   brings the jumping straight back.
   ---- NACHGEZOGEN FUER DEN RADAR-KASTEN ----
   Der Kasten mit Platz, Perzentil und Abstand steht seit K-Runde ebenfalls in #boardbody — und
   er erscheint NUR, wenn der Spieler auf diesem Brett einen Eintrag hat. Zwischen dem Reiter
   "Tag" (Eintrag vorhanden) und "Anomalie" (noch keiner) waere die Karte damit wieder
   gesprungen, also genau der Fehler, gegen den diese Zeile geschrieben wurde. Der Kasten misst
   mit Rand und Abstand rund 78px; 400 + 78 = 478, aufgerundet. */
#boardbody { min-height: 480px; display: flex; flex-direction: column; }
.boardoff {
  flex: 1 1 auto; display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; padding: 28px 16px; color: #9fb0d0; font-size: 15px; line-height: 1.6;
}

/* game-over: rank line + name field */
.rankline {
  text-align: center; font-family: 'BG Mono', ui-monospace, monospace; font-weight: 900; font-size: 15px;
  color: #ffce54; margin: 0 0 10px; letter-spacing: .04em;
}
.namerow { display: flex; gap: 8px; margin: 0 0 8px; }
.namerow input {
  flex: 1 1 auto; min-width: 0; font-family: inherit; font-size: 14px; color: #eef3ff;
  padding: 10px 13px; border-radius: 9px; background: rgba(16,20,31,.75);
  border: 1px solid rgba(120,140,190,.28); outline: none;
}
.namerow input:focus { border-color: rgba(106,209,255,.6); }
.namebtn {
  font-family: inherit; font-size: 13px; font-weight: 800; cursor: pointer; white-space: nowrap;
  padding: 10px 16px; border-radius: 9px; color: #cfe3ff;
  background: rgba(34,44,72,.8); border: 1px solid rgba(120,180,255,.32); box-shadow: none;
}
.namebtn:hover { border-color: rgba(150,190,255,.6); box-shadow: none; }

/* ============================================================
   MODE BUTTONS — the first thing on the start screen, so they carry weight.
   Daily leads (violet, the god's colour), the others sit quieter beneath it.
   ============================================================ */
.moderow { display: flex; flex-direction: column; gap: 10px; }
.mode {
  display: grid; grid-template-columns: 52px 1fr auto; align-items: center; gap: 14px;
  padding: 16px 18px; border-radius: 14px; cursor: pointer; position: relative; overflow: hidden;
  background: rgba(16,20,31,.7); border: 1px solid rgba(120,140,190,.18);
  transition: transform .14s ease, border-color .14s ease, background .14s ease;
}
.mode:hover { transform: translateY(-2px); border-color: rgba(150,180,255,.5); background: rgba(22,28,46,.8); }
.mode:active { transform: translateY(0); }
.mode .mi { font-size: 30px; text-align: center; line-height: 1; filter: drop-shadow(0 2px 8px rgba(0,0,0,.4)); }
.mode .mt { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.mode .mt b { font-size: 18px; font-weight: 900; letter-spacing: -.01em; }
.mode .mt small { font-size: 12px; color: #8e9cbd; font-family: 'BG Mono', ui-monospace, monospace; }
.mode .mgo { font-size: 13px; color: #6677a0; transition: transform .14s ease, color .14s ease; }
.mode:hover .mgo { transform: translateX(3px); color: #a9b8dc; }

/* Daily: the headline mode. Violet edge, a soft inner glow, slightly larger type. */
.mode.daily {
  background: linear-gradient(100deg, rgba(44,28,70,.85), rgba(22,18,40,.8));
  border-color: rgba(160,110,255,.42);
  box-shadow: inset 0 0 22px rgba(160,110,255,.12), 0 6px 24px rgba(120,60,220,.14);
}
.mode.daily::before {   /* a faint sweep of the god's light along the top */
  content: ''; position: absolute; inset: 0 0 auto 0; height: 2px;
  background: linear-gradient(90deg, transparent, #c9a8ff, transparent);
}
.mode.daily:hover { border-color: rgba(180,140,255,.7); transform: translateY(-2px); }
.mode.daily .mi { font-size: 34px; }
.mode.daily .mt b { font-size: 19px; color: #f3ecff; }
.mode.daily .mgo { color: #c9a8ff; }

/* Campaign: warm ember edge, the game's core loop. */
.mode.campaign { border-color: rgba(255,138,30,.28); }
.mode.campaign:hover { border-color: rgba(255,160,60,.6); }
.mode.campaign .mi { filter: drop-shadow(0 2px 10px rgba(255,138,30,.35)); }

/* Endless: cool blue, the survival grind. */
.mode.endless { border-color: rgba(90,170,240,.28); }
.mode.endless:hover { border-color: rgba(110,200,255,.6); }

/* Locked (demo): dimmed, no pointer, no lift. */
.mode.locked { opacity: .48; cursor: not-allowed; border-style: dashed; grid-template-columns: 52px 1fr; }
.mode.locked:hover { transform: none; border-color: rgba(120,140,190,.18); background: rgba(16,20,31,.7); }

/* the currently-selected mode gets a persistent ring */
.mode.on { border-color: rgba(106,209,255,.7); box-shadow: inset 0 0 0 1px rgba(106,209,255,.3); }
.mode.daily.on { border-color: rgba(180,140,255,.8); }

/* install button + iOS hint */
.subbtn.install { color: #7dff9a; border-color: rgba(125,255,154,.35); }
.subbtn.install:hover { border-color: rgba(125,255,154,.65); }
.installhint {
  font-size: 12px; color: #9fb0d0; text-align: center; margin: 6px auto 0; max-width: 340px;
  padding: 8px 12px; border-radius: 9px; background: rgba(16,20,31,.6); border: 1px solid rgba(120,140,190,.16);
}

@media (max-width: 560px) {
  .mode { padding: 14px 15px; grid-template-columns: 44px 1fr auto; gap: 11px; }
  .mode .mi { font-size: 26px; }
  .mode.daily .mi { font-size: 28px; }
  .mode .mt b { font-size: 16px; }
  .mode.daily .mt b { font-size: 17px; }
}

/* ============================================================
   Start-screen icon buttons (below SPIELEN), pause screen, mode selection ring.
   ============================================================ */

/* subrow as compact icon tiles: glyph on top, tiny caption below */
.subrow.iconrow { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; }
.subbtn.ico {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  width: 62px; height: 60px; padding: 0; border-radius: 12px;
  background: rgba(16,20,31,.7); border: 1px solid rgba(120,140,190,.2);
}
.subbtn.ico .ic { font-size: 22px; margin: 0; }
.subbtn.ico .cap {
  font-size: 9px; font-weight: 800; letter-spacing: .3px; color: #8e9cbd;
  font-family: 'BG Mono', ui-monospace, monospace; max-width: 56px; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
.subbtn.ico:hover { border-color: rgba(150,180,255,.5); background: rgba(22,28,46,.85); }
.subbtn.ico.install { border-color: rgba(125,255,154,.35); }
.subbtn.ico.install .cap { color: #7dff9a; }
.subbtn.ico.install:hover { border-color: rgba(125,255,154,.65); }

/* SPIELEN: the primary action, given room to breathe */
#playbtn { letter-spacing: 1px; }

/* ---- FORTSETZEN ---------------------------------------------------------------------------
   Erbt sein Aussehen von #playbtn (beide sind direkte Kinder der Menuekarte und teilen deren
   Knopfregeln) und bekommt hier nur den Unterschied: er ist die Hauptaktion, sobald ein
   unterbrochener Lauf vorliegt.
   Der Startknopf tritt in dem Fall zurueck — er heisst dann "NEUER LAUF" und wuerde den
   gespeicherten Lauf ueberschreiben. Er soll erreichbar bleiben, aber nicht der Knopf sein, den
   man aus Gewohnheit trifft. */
#resumebtn { letter-spacing: 1px; }
/* Der Hinweis stand mit margin: -2px direkt an der Unterkante des Knopfes und wirkte wie ein
   Teil von ihm. Zehn Pixel machen ihn zu dem, was er ist: eine Anmerkung dazu. */
.tip.resumetip { margin: 10px 0 14px; opacity: .62; }
#playbtn.secondary {
  background: transparent; color: #9fb0d0;
  border: 1px solid rgba(120,140,190,.34);
  box-shadow: none; font-size: 14px; padding: 9px 22px; margin-top: 2px;
}
#playbtn.secondary:hover { border-color: rgba(150,180,255,.6); color: #d6e2f5; background: rgba(22,28,46,.6); }

/* ---- pause screen ---- */
.card.pausecard { max-width: 480px; }
/* "Weiter" ist die eine Handlung, um die es hier geht — daruber die Liste der Mutationen und
   Spezialbomben, darunter drei Nebenknoepfe. Ohne Luft stand er als vierter Eintrag in einer
   Reihe von Knoepfen statt als Ziel des Bildschirms. */
.card.pausecard #playbtn.resume { margin: 22px 0 18px; }
.pausestats {
  display: flex; justify-content: center; gap: 40px; margin: 8px 0 22px;
}
.pausestats .ps { display: flex; align-items: center; gap: 12px; }
.pausestats .psi { font-size: 30px; line-height: 1; }
.pausestats .pst { text-align: left; }
.pausestats .k {
  font-size: 10px; letter-spacing: 2px; text-transform: uppercase; color: #808ead;
  font-family: 'BG Mono', ui-monospace, monospace; margin-bottom: 2px;
}
.pausestats .v { font-size: 30px; font-weight: 900; letter-spacing: -.02em; }
#playbtn.resume { font-size: 17px; }

/* ---- selected mode: clearly in front of the others ---- */
.mode { opacity: .82; }                 /* unselected modes sit slightly back */
.mode.on { opacity: 1; }
.mode.on {
  border-color: rgba(106,209,255,.75);
  box-shadow: inset 0 0 0 1px rgba(106,209,255,.35), 0 8px 30px rgba(60,150,240,.18);
  transform: translateY(-1px);
}
.mode.daily.on {
  border-color: rgba(190,150,255,.85);
  box-shadow: inset 0 0 0 1px rgba(190,150,255,.4), 0 8px 30px rgba(150,80,255,.22);
}
.mode.campaign.on { border-color: rgba(255,175,70,.8); box-shadow: inset 0 0 0 1px rgba(255,175,70,.4), 0 8px 30px rgba(255,140,40,.2); }
.mode.endless.on { border-color: rgba(110,200,255,.8); box-shadow: inset 0 0 0 1px rgba(110,200,255,.4), 0 8px 30px rgba(60,160,255,.2); }
/* a small check on the selected tile's arrow */
.mode.on .mgo::after { content: ' \2713'; color: inherit; }

@media (max-width: 560px) {
  .subbtn.ico { width: 56px; height: 54px; }
  .subbtn.ico .ic { font-size: 20px; }
  .pausestats { gap: 26px; }
  .pausestats .v { font-size: 26px; }
}

/* ============================================================
   Daily challenge: streak badge, top-3 on game over, personal best.
   ============================================================ */
.mode .streak {
  display: inline-block; margin-left: 6px; color: #ff9a3a; font-weight: 800;
  font-size: 11px; white-space: nowrap;
}
.rankline { /* already styled; daily may now show best + streak inline */ }

/* today's top 3, shown on the daily game-over card */
.top3 {
  margin: 4px 0 12px; padding: 10px 12px; border-radius: 11px;
  background: rgba(16,20,31,.6); border: 1px solid rgba(255,206,84,.25);
}
.top3 .t3lbl {
  font-size: 9px; letter-spacing: 2px; text-transform: uppercase; color: #ffce54;
  font-family: 'BG Mono', ui-monospace, monospace; margin-bottom: 7px; text-align: center;
}
.top3 .t3row {
  display: grid; grid-template-columns: 24px 1fr auto; gap: 10px; align-items: center;
  padding: 4px 2px; font-size: 13px;
}
.top3 .t3r { font-family: 'BG Mono', ui-monospace, monospace; font-weight: 900; color: #ffce54; text-align: center; }
.top3 .t3row:nth-child(3) .t3r { color: #cfd6e6; }
.top3 .t3row:nth-child(4) .t3r { color: #d0955a; }
.top3 .t3n { font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #cfe0f7; }
.top3 .t3s { font-family: 'BG Mono', ui-monospace, monospace; font-weight: 900; color: #6ad1ff; }

/* ---- recording indicator ---- */
#recdot {
  position: fixed; top: 14px; left: 50%; transform: translateX(-50%); z-index: 30;
  display: flex; align-items: center; gap: 8px; pointer-events: none;
  padding: 6px 13px; border-radius: 999px; background: rgba(10,10,16,.82);
  border: 1px solid rgba(255,70,70,.5); font-family: 'BG Mono', ui-monospace, monospace;
  font-size: 13px; font-weight: 800; color: #ff5a5a; letter-spacing: .5px;
}
#recdot .rd {
  width: 11px; height: 11px; border-radius: 50%; background: #ff3a3a;
  box-shadow: 0 0 10px rgba(255,60,60,.8); animation: recpulse 1.1s ease-in-out infinite;
}
@keyframes recpulse { 0%,100% { opacity: 1; } 50% { opacity: .25; } }
@media (prefers-reduced-motion: reduce) { #recdot .rd { animation: none; } }
/* Bleibt die Aufnahme unter ihrer Bildrate, faerbt sich das Laempchen. Kein Blinken und
   kein Kasten: es soll auffallen, wenn man hinsieht, und nicht ablenken, waehrend man
   spielt — die Aufnahme laeuft ja weiter und ist brauchbar, nur eben zaeher. */
#recdot.zaeh { border-color: rgba(255,180,60,.55); color: #ffc14a; }

/* ---- CLIP-MODUS ----------------------------------------------------------------------------
   Wofuer das da ist, und wofuer NICHT:

   Der eingebaute Recorder nimmt ueber canvas.captureStream() ausschliesslich die Leinwand auf.
   Das HUD, die Banner, die Beruehrungsknoepfe — alles DOM — sind in diesen Videos ohnehin nicht
   drin. Fuer ihn braucht es hier gar nichts.

   Gemeint sind die Aufnahmen, die das GANZE BILD nehmen: OBS, ShadowPlay, ReLive, und auf dem
   Telefon der Bildschirmmitschnitt des Systems. Genau die zeigen sonst jeden Knopf, jede
   Zahl und jeden Hinweis mit. Das betrifft Telefon und Rechner gleichermassen — auf dem
   Telefon sogar staerker, weil dort die Steuerung selbst im Bild liegt.

   opacity: 0 und NICHT display: none. Der Unterschied entscheidet, ob man waehrenddessen noch
   spielen kann: unsichtbare Elemente nehmen weiterhin Beruehrungen entgegen, entfernte nicht.
   Der Daumen findet den Knopf, wo er immer war — man sieht ihn nur nicht mehr. Und weil nichts
   aus dem Fluss faellt, springt auch kein Layout.

   Das Overlay (Menue, Pause, Optionen) ist bewusst NICHT dabei: wer pausiert, will sein Menue
   sehen. Es liegt ohnehin ueber allem und ist im Clip nur, wenn man es aufruft.

   uebergang: 0.18 s. Ein harter Schnitt mitten in einer laufenden Aufnahme sieht nach Fehler
   aus, ein Ausblenden nach Absicht. */
/* ---- ZWEI DINGE BLEIBEN STEHEN -------------------------------------------------------------
   Gemeldet: im Clip-Modus war auch das Fadenkreuz weg, und damit war Zielen Raten.

   Fadenkreuz und Kochbalken sind kein Interface, sie sind WERKZEUG. Das eine sagt, wohin der
   Schuss geht, das andere, wie weit die Zuendschnur ist — ohne sie spielt man nicht sauberer,
   sondern blind. Und in einem Ego-Spiel gehoert ein Fadenkreuz ohnehin ins Bild; kein Trailer
   der Welt blendet es aus.

   Deshalb wird nicht mehr #hud als Ganzes ausgeblendet, sondern seine Kinder EINZELN — sonst
   ginge es gar nicht: eine opacity am Elternteil laesst sich von einem Kind nicht zuruecknehmen. */
body.clip #hud > *:not(#crosshair):not(#cookbar):not(#ammocross):not(#cliphearts),
body.clip #banner,
body.clip #teach,
body.clip #godvoice,
body.clip #pops,
body.clip #huntcount,
body.clip #recdot,
body.clip #touchui,
body.clip #stall {
  opacity: 0;
}
#hud > *, #banner, #teach, #godvoice, #pops, #huntcount, #recdot, #touchui, #stall {
  transition: opacity .18s ease;
}
/* Die Weltschicht-Effekte bleiben: Vignette, Schild-Ring, Notzuender-Puls, Respawn-Blitz und
   die Abdunklung im Jagdfieber sind Teil des BILDES, nicht der Anzeige. Sie gehoeren in einen
   Clip hinein — nur ihre Zahlen nicht. */

/* ---- recording / trailer section in options ---- */
.orec {
  margin-top: 16px; padding: 14px; border-radius: 12px;
  background: rgba(16,20,31,.5); border: 1px solid rgba(255,90,90,.2);
}
.orec .orlbl {
  font-size: 12px; font-weight: 800; letter-spacing: 1px; text-transform: uppercase;
  color: #ff8a8a; margin-bottom: 2px;
}

/* ---- touch recording button (sits next to pause) ---- */
/* geometry + look: see "minimap corner badges" above */
/* the indicator doubles as a stop button on touch */
#recdot { pointer-events: auto; cursor: pointer; }

/* ---- personal daily streak stats, above the online board ---- */
.streakbox {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;
  margin: 0 0 14px; padding: 12px 10px; border-radius: 12px;
  background: rgba(16,20,31,.55); border: 1px solid rgba(155,110,255,.28);
}
.streakbox .sbcell { text-align: center; }
.streakbox .sbnum {
  font-family: 'BG Mono', ui-monospace, monospace; font-weight: 900; font-size: 19px; color: #cfe0f7;
  line-height: 1.15;
}
.streakbox .sblbl {
  font-size: 9px; letter-spacing: 1.4px; text-transform: uppercase;
  color: #8fa2c4; margin-top: 3px;
}

/* ---- which chip is drawing (options) ---- */
.ogpu {
  margin: 8px 0 2px; padding: 9px 11px; border-radius: 10px; font-size: 11px; line-height: 1.5;
  background: rgba(16,20,31,.5); border: 1px solid rgba(120,150,200,.2); color: #9fb0d0;
}
/* Der Neuladen-Hinweis unter der Grafikstufe. Gleicher Kasten wie .ogpu, aber in einem Ton,
   der auffaellt ohne zu warnen: es ist keine Stoerung, sondern eine Auskunft. Wann er ueberhaupt
   erscheint, entscheidet qnoteAktualisieren() in game.js. */
.ogpu.qnote {
  border-color: rgba(120,200,255,.4); background: rgba(14,34,52,.5); color: #9fd8ff;
}
.ogpu.warn {
  border-color: rgba(255,160,60,.45); background: rgba(60,36,10,.42); color: #ffc27a;
}

/* ---- weekly challenge tile: same family as daily, cooler accent ---- */
.mode.weekly.on { border-color: rgba(110,190,255,.75); box-shadow: 0 0 0 1px rgba(110,190,255,.35), 0 8px 26px rgba(60,140,220,.22); }
.mode.weekly .mi { filter: drop-shadow(0 0 6px rgba(110,190,255,.4)); }

/* ---- short landscape screens (phones held sideways): the menu got a fourth mode tile and
   no longer fits, pushing SPIELEN below the fold. Lay the tiles out two-up and trim the
   padding so the primary action stays on screen without scrolling. ---- */
@media (max-height: 560px) and (orientation: landscape) {
  .moderow { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
  .mode { padding: 8px 10px; grid-template-columns: 32px 1fr; gap: 8px; border-radius: 11px; }
  .mode .mi { font-size: 20px; }
  .mode .mt b { font-size: 13px; }
  .mode .mt small { font-size: 10px; }
  .mode .mgo { display: none; }          /* the arrow is decoration; the tile itself is the button */
  .menucard h1 { font-size: 30px; margin: 2px 0 4px; }
  .menucard .sub { display: none; }      /* the tagline is nice-to-have, the play button is not */
  .menucard .eyebrow { margin-bottom: 2px; }
}

/* ---- restart cooldown: the play button waits out the two seconds after a run ends ---- */
#playbtn.cooling {
  opacity: .45; cursor: default; filter: saturate(.5);
  transition: opacity .35s ease, filter .35s ease;
}
#playbtn.cooling::after {
  content: ''; position: absolute; left: 0; bottom: 0; height: 2px; width: 100%;
  background: rgba(255,255,255,.5); transform-origin: left center;
  animation: cooldn 2s linear forwards;
}
#playbtn { position: relative; overflow: hidden; }
@keyframes cooldn { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@media (prefers-reduced-motion: reduce) { #playbtn.cooling::after { animation: none; opacity: .3; } }

/* ---- who the run was filed under, on the game-over card ----
   This replaced the name input + save button that used to sit here. It is a statement, not
   a control: the name is set once on the account screen and then simply reported back. */
.whoami {
  display: none; margin: 0 0 12px; padding: 8px 11px; border-radius: 9px;
  font-family: 'BG Mono', ui-monospace, monospace; font-size: 12px; color: #8fa2c4;
  background: rgba(16,20,31,.5); border: 1px solid rgba(120,150,200,.18);
}
.whoami b { color: #6ad1ff; font-weight: 800; }

/* ---- the display-name row, shared by the account screen and the first-sign-in prompt ---- */
.acctname { margin: 0 0 12px; }
.acctname .anlbl {
  font-size: 9px; letter-spacing: 2px; text-transform: uppercase; color: #808ead;
  font-family: 'BG Mono', ui-monospace, monospace; margin-bottom: 6px; text-align: left;
}
.acctname .namerow { margin: 0; }

/* ---- rival duel: the challenge button and the result line on the game-over card ----
   .rivalbtn has to reset background and box-shadow explicitly: the global `button` rule
   paints every button with the orange gradient, which is reserved for the primary action. */
.rivalbtn {
  display: block; width: 100%; margin: 0 0 10px; padding: 12px 18px;
  border: 1px solid rgba(200,120,255,.45); border-radius: 12px; cursor: pointer;
  background: rgba(40,16,54,.75); color: #e9d4ff; box-shadow: none;
  font-family: inherit; font-size: 14px; font-weight: 800; letter-spacing: .3px;
}
.rivalbtn:hover {
  background: rgba(54,22,72,.9); border-color: rgba(215,150,255,.75);
  transform: translateY(-1px); box-shadow: 0 8px 22px rgba(150,60,255,.22);
}
.duelline {
  display: none; margin: 0 0 10px; padding: 9px 12px; border-radius: 10px;
  font-size: 13px; line-height: 1.5; text-align: center;
  background: rgba(40,16,54,.5); border: 1px solid rgba(200,120,255,.3); color: #d9c4f0;
}
.duelline.won { background: rgba(14,40,24,.5); border-color: rgba(125,255,154,.35); color: #9fffb8; }
/* Ergebnis des Schwurs auf der Game-Over-Karte. Auch bei "offen" sichtbar: was man sich
   vorgenommen hatte, gehoert auf denselben Bildschirm wie das, was daraus wurde. */
.oathline {
  margin: 0 0 10px; padding: 9px 12px; border-radius: 10px;
  font-size: 13px; line-height: 1.5; text-align: center;
  background: rgba(40,30,8,.5); border: 1px solid rgba(255,210,74,.3); color: #f0dfae;
}
.oathline.won  { background: rgba(14,40,24,.5); border-color: rgba(125,255,154,.35); color: #9fffb8; }
.oathline.lost { background: rgba(26,26,32,.55); border-color: rgba(140,150,170,.28); color: #98a0b0; }
.oathline b { color: #fff; }
/* Die Weltregel auf dem Schwur-Bildschirm: sie steht UEBER den drei Schwuer-Karten, weil
   sie die Entscheidung darunter beeinflusst. Seine Farbe, damit klar ist, wer sie stellt. */
.wrcard {
  margin: 0 0 16px; padding: 12px 14px; border-radius: 12px; text-align: center;
  background: linear-gradient(180deg, rgba(48,18,64,.6), rgba(24,10,34,.5));
  border: 1px solid rgba(200,120,255,.34);
}
.wrcard.plain { background: rgba(18,22,34,.5); border-color: rgba(120,140,190,.2); }
.wrcard .wrn { font-size: 15px; font-weight: 800; letter-spacing: 1px; color: #e6c4ff; }
.wrcard.plain .wrn { color: #8d95a6; font-weight: 600; letter-spacing: .3px; }
.wrcard .wri { margin-right: 7px; }
.wrcard .wrd { margin-top: 4px; font-size: 12px; color: #b9a6cc; }
.wrcard .wrm { margin-top: 6px; font-size: 11px; font-weight: 700; color: #ffce54; }
.duelline b { color: #fff; }
/* last-resort fallback when neither the share sheet nor the clipboard is available */
.rivalink {
  width: 100%; padding: 9px 11px; border-radius: 9px; color: #cfe0f7;
  font-family: 'BG Mono', ui-monospace, monospace; font-size: 11px;
  background: rgba(10,14,26,.8); border: 1px solid rgba(120,150,200,.3);
}

/* ---- beating the campaign ----
   The win used to be the death card with a different headline. It is his now: the accent goes
   from red to his gold, and the line he says on the way out is set larger than the rest. */
.card.gameover.godwin { border-color: rgba(255,210,74,.32); box-shadow: 0 30px 90px rgba(0,0,0,.6), 0 0 60px rgba(255,190,60,.12); }
.card.gameover.godwin::before { background: linear-gradient(90deg, #ffd24a, #ff8a1e 55%, #c9a8ff); }
.card.gameover.godwin h1 { color: #ffd24a; text-shadow: 0 0 34px rgba(255,200,60,.5); }
.card.gameover.godwin .eyebrow { color: #ffce54; }
.godline {
  display: inline-block; margin-bottom: 6px;
  font-size: 19px; font-weight: 800; letter-spacing: .5px; color: #e9d4ff;
  text-shadow: 0 0 22px rgba(190,120,255,.55);
}
/* the name he gave you, if you earned one */
.gowontitle {
  display: inline-block; margin-top: 6px; padding: 3px 12px; border-radius: 999px;
  font-family: 'BG Mono', ui-monospace, monospace; font-size: 12px; letter-spacing: 3px; font-weight: 800;
  color: #ffd24a; background: rgba(60,44,8,.5); border: 1px solid rgba(255,206,84,.4);
}

/* ---- the epilogue: the HUD steps back while he speaks ---- */
body.hushed #hud, body.hushed #touchui { opacity: 0; transition: opacity 1.1s ease; pointer-events: none; }

/* ---- account screen ---- */
.acctcard { max-width: 460px; }
.acctcard .sub { margin: 2px 0 14px; }
.subbtn.ico.signedin .ic { filter: drop-shadow(0 0 6px rgba(125,255,154,.55)); }
.licbox {
  margin: 4px 0 14px; padding: 11px 13px; border-radius: 11px; font-size: 13px; font-weight: 700;
  background: rgba(16,20,31,.6); border: 1px solid rgba(255,170,70,.3); color: #ffc27a;
}
.licbox.on { border-color: rgba(125,255,154,.35); color: #8fffa8; }
.acctmsg {
  margin-top: 12px; padding: 10px 12px; border-radius: 10px; font-size: 12px; line-height: 1.55;
  background: rgba(16,20,31,.55); border: 1px solid rgba(120,150,200,.22); color: #b8c3d8;
}
.acctmsg.local { border-color: rgba(106,209,255,.35); }
/* Eine Absage ist keine Notiz. Der haeufigste Fall ist "Name schon vergeben", und der muss
   sich vom gruenen "Gespeichert" und vom blauen Lokal-Hinweis unterscheiden lassen. */
.acctmsg.warn { border-color: rgba(255,106,106,.42); color: #ffb0b0; background: rgba(40,16,20,.5); }
.acctmsg .locallink {
  display: inline-block; margin-top: 8px; padding: 8px 14px; border-radius: 9px;
  background: linear-gradient(135deg, #ff8a1e, #ff6a2e); color: #14100a;
  font-weight: 800; text-decoration: none;
}

/* ---- buy button on the account screen ---- */
.buybtn {
  display: block; width: 100%; margin: 0 0 12px; padding: 14px 20px;
  border: 0; border-radius: 12px; cursor: pointer;
  background: linear-gradient(135deg, #ffb03a, #ff6a2e); color: #17110a;
  font-family: inherit; font-size: 15px; font-weight: 900; letter-spacing: .4px;
  box-shadow: 0 8px 26px rgba(255,120,40,.28);
  transition: transform .12s ease, box-shadow .12s ease;
}
.buybtn:hover { transform: translateY(-1px); box-shadow: 0 12px 32px rgba(255,120,40,.36); }
.buybtn:disabled { opacity: .6; cursor: default; transform: none; }

/* ---- account: reassurance line and the quiet "change address" link ---- */
.staytip {
  margin: 0 0 12px; font-size: 12px; line-height: 1.5; color: #8fffa8;
  background: rgba(20,40,28,.35); border: 1px solid rgba(125,255,154,.18);
  border-radius: 9px; padding: 8px 11px;
}
.acctcard .staytip { text-align: left; }
.linkbtn {
  display: block; width: 100%; margin: 0 0 12px; padding: 9px;
  background: none; border: 0; cursor: pointer;
  color: #6ad1ff; font-family: inherit; font-size: 12px; text-decoration: underline;
  text-underline-offset: 3px;
}
.linkbtn:hover { color: #9fe4ff; }

/* ---- withdrawal consent, required before selling a download in Germany ---- */
.widerruf {
  display: flex; gap: 10px; align-items: flex-start; text-align: left;
  margin: 0 0 12px; padding: 10px 12px; border-radius: 10px; cursor: pointer;
  background: rgba(16,20,31,.5); border: 1px solid rgba(120,150,200,.22);
  font-size: 11px; line-height: 1.55; color: #b8c3d8;
}
.widerruf input { margin-top: 2px; width: 16px; height: 16px; flex: 0 0 auto; accent-color: #ff6a2e; cursor: pointer; }

/* ---- Jagdfieber ----------------------------------------------------------------------
   Die Abdunklung laeuft ueber DOM, nicht ueber die Szenenbeleuchtung. Grund: ambient/dir/torch
   werden bereits von drei Systemen gleichzeitig geschrieben (Biom, Gott-Atmosphaere, Chaos
   'Dunkelheit'). Ein vierter Schreiber dort waere die sichere Art, irgendwann in einem dauerhaft
   schwarzen Level zu landen. Eine CSS-Ebene kann nichts kaputtmachen und geht garantiert weg. */
/* Deutlich zurueckgenommen: die Verdunklung passiert jetzt in der SZENE (siehe huntDark in
   game.js — Umgebungslicht 0.14, Nebel auf 5.5). Der alte Verlauf ging bis rgba(6,0,6,.72) am
   Rand und wuerde zusammen mit der echten Dunkelheit nur noch schwarz ergeben. Was bleibt, ist
   die Faerbung: ein Rotstich, der sagt, in welchem Zustand man ist. */
#huntfx {
  position: fixed; inset: 0; pointer-events: none; z-index: 5; opacity: 0;
  transition: opacity .3s ease;
  background:
    radial-gradient(circle at 50% 50%, rgba(120,0,58,.16) 0%, rgba(255,47,160,.09) 44%, rgba(30,0,16,.34) 100%);
}
#huntfx.on { opacity: 1; }
#huntcount {
  position: absolute; left: 50%; top: 21%; transform: translateX(-50%);
  font: 700 62px/1 ui-monospace, "SFMono-Regular", Menlo, monospace;
  letter-spacing: 5px; color: #ff2fa0; opacity: .9;
  text-shadow: 0 0 28px rgba(255,47,160,.95), 0 0 60px rgba(255,47,160,.5), 0 2px 10px rgba(0,0,0,.9);
}
/* Jede volle Sekunde ein Schlag. Neustart der Animation per class-toggle im Loop. */
#huntcount.tick { animation: huntTick .45s cubic-bezier(.2,.9,.3,1); }
@keyframes huntTick {
  0%   { transform: translateX(-50%) scale(1.55); opacity: 1; }
  100% { transform: translateX(-50%) scale(1);    opacity: .9; }
}
/* Die letzten drei Sekunden: die Zahl wird weiss-heiss. */
#huntcount.last { color: #fff; text-shadow: 0 0 34px rgba(255,47,160,1), 0 0 70px rgba(255,47,160,.8), 0 2px 10px rgba(0,0,0,.9); }
@media (max-width: 720px) { #huntcount { font-size: 44px; top: 24%; } }
/* ---- Schwur: die Fussleiste ---------------------------------------------------------------
   Vorher standen "Ohne Schwur spielen" und "Hauptmenue" als zwei gleiche .ghost-Knoepfe direkt
   hintereinander im Fluss — kein Abstand, gleiche Farbe, gleiche Groesse. Der Bildschirm bot
   damit zwei gleichrangige Ausgaenge an, obwohl der eine "spiel jetzt" heisst und der andere
   "ich bin hier falsch".
   Jetzt eine klare Rangfolge: der Weg ins Spiel mittig, allein, in der Akzentfarbe. Der Weg
   zurueck darunter, kleiner und blass. */
.oathfoot { display: flex; flex-direction: column; align-items: center; gap: 12px; margin-top: 18px; }
.oathfoot .oathgo {
  min-width: 240px; padding: 12px 26px; border-radius: 11px; cursor: pointer;
  font: 700 15px/1 inherit; letter-spacing: .04em;
  color: #1a1005; border: 1px solid transparent;
  background: linear-gradient(96deg, #ffb03a, #ff8a1e);
  box-shadow: 0 6px 22px rgba(255, 138, 30, .26);
}
.oathfoot .oathgo:hover { filter: brightness(1.08); }
.oathfoot .oathback {
  padding: 7px 18px; border-radius: 9px; cursor: pointer;
  font: 600 12.5px/1 inherit; letter-spacing: .04em;
  color: #8090ad; background: transparent;
  border: 1px solid rgba(120, 140, 190, .18);
}
.oathfoot .oathback:hover { color: #d8e2f5; border-color: rgba(150, 180, 255, .38); }

/* ---- Das Zucken des Gottesauges ----------------------------------------------------------
   Kurz, hell und nach aussen: er schaut nicht genauer hin, er REAGIERT. 260 ms sind lang genug,
   um es im Augenwinkel mitzubekommen, und kurz genug, um bei einer Zehnerkette nicht zu
   flimmern. Die Skalierung ist bewusst multiplikativ ueber eine eigene Variable geloest, damit
   sie die stufenlose Groesse aus godEye() nicht ueberschreibt. */
@keyframes godFed {
  0%   { filter: brightness(1)   drop-shadow(0 0 6px rgba(210,110,255,.9)); }
  22%  { filter: brightness(2.1) drop-shadow(0 0 22px rgba(240,170,255,1)); }
  100% { filter: brightness(1)   drop-shadow(0 0 6px rgba(210,110,255,.9)); }
}
#godeye.fed { animation: godFed .26s ease-out, godBlink 4.5s ease-in-out infinite .26s; }

/* ---- Die kleine Stufe des Banners --------------------------------------------------------
   Nicht ausgeblendet, nur zurueckgenommen: 62 % Groesse, weniger Deckkraft, kein Leuchten.
   Man liest es weiterhin, wenn man hinsieht — es reisst den Blick nur nicht mehr vom Spiel weg.
   Genau das soll dem grossen Banner vorbehalten bleiben. */
/* 62 % der Grundgroesse — Desktop 32px, Mobil 24px. Und die Unterzeile MUSS darunter bleiben:
   vorher war .big 9.9px und .small 13.1px, die Ueberschrift also kleiner als ihre eigene
   Erklaerung. Das Auge las zuerst die Nebeninformation. */
#banner.lite .big   { font-size: calc(var(--bnbig, 52px) * .62); letter-spacing: .04em; opacity: .78; text-shadow: none; }
#banner.lite .small { font-size: 12px; opacity: .6; }
#banner.lite        { filter: none; }

/* ---- Munition am Fadenkreuz --------------------------------------------------------------
   28 Pixel unter der Bildmitte: nah genug, dass das Auge sie ohne Blickwechsel mitnimmt, weit
   genug, dass sie das Zielen nicht stoert. Keine Maus-Ereignisse, damit sie nie im Weg ist. */
#ammocross {
  position: fixed; left: 50%; top: calc(50% + 28px); transform: translateX(-50%);
  font: 700 15px/1 ui-monospace, monospace; letter-spacing: .06em;
  text-shadow: 0 1px 3px rgba(0,0,0,.85); pointer-events: none; z-index: 40;
  transition: opacity .18s ease, color .18s ease;
}
body.paused #ammocross, body.menu #ammocross { display: none; }

/* ---- DIE HERZEN IM CLIP-MODUS --------------------------------------------------------------
   Unten mittig, weit genug ueber dem Rand, dass kein Schnittprogramm sie anschneidet, und weit
   genug vom Fadenkreuz, dass die Mitte frei bleibt.

   Deckkraft 0 als Grundzustand: das Ein- und Ausblenden macht game.js, und ohne Schaden ist es
   schlicht nicht da. 0.35 s statt der ueblichen 0.18 — es erscheint in dem Moment, in dem man
   getroffen wird, und da ist alles andere ohnehin laut. Ein langsames Auftauchen liest sich als
   Konsequenz, ein schnelles als Zucken.

   Etwas groesser als oben im Balken: dort ist es eine Zahl unter vielen, hier ist es die einzige
   Anzeige im Bild und muss auch nach einer Verkleinerung auf 720 Zeilen noch lesbar sein. */
#cliphearts {
  position: fixed; left: 50%; bottom: 8vh; transform: translateX(-50%);
  display: flex; gap: 5px; align-items: center;
  opacity: 0; transition: opacity .35s ease; pointer-events: none; z-index: 40;
}
#cliphearts .hp { font-size: 24px; line-height: 1; filter: drop-shadow(0 0 6px rgba(255,60,90,.6)); }
#cliphearts .hp.on { color: #ff435f; }
#cliphearts .hp.off { color: #3a2030; filter: none; opacity: .55; }
body.paused #cliphearts, body.menu #cliphearts { display: none; }
/* Der Notzuender am Fadenkreuz. Eigene Farbe, damit er sich von der Munitionszahl loest —
   sonst liest man "0 2" als eine Zahl. Leer heisst leer: ohne Inhalt kein Rand, kein Abstand,
   kein Platzverbrauch. */
#ammocross i {
  font-style: normal; margin-left: 7px; color: #ff8a7a;
  transition: opacity .18s ease;
}
#ammocross i:empty { display: none; margin: 0; }
/* Bei null Munition pocht er. Das ist der einzige Moment, in dem er um Aufmerksamkeit bitten
   darf — vorher ist er ein Hinweis, hier ist er die letzte Option. */
#ammocross i.urgent { color: #ff6a52; animation: crossPanic 1.1s ease-in-out infinite; }
@keyframes crossPanic { 0%,100% { opacity: 1; } 50% { opacity: .5; } }

/* ---- Herzen beim Treffer -----------------------------------------------------------------
   Ueber der Bildmitte, gross genug zum Erfassen ohne Lesen, und nach 1.5 s wieder weg. Sie
   duerfen den Blick nicht dauerhaft belegen — es ist eine Antwort, keine Anzeige. */
/* ---- Die Herzen haben eine eigene Bahn bekommen -----------------------------------------
   Vorher: top calc(50% - 96px). Das #banner sitzt auf 40 % und ist mit --bnbig 52px hoch —
   bei 900 px Fensterhoehe spannt es etwa 315 bis 405, die Herzen lagen bei 354 bis 384. Sie
   lagen also IMMER mittendrin, wenn gerade ein Banner lief. Und die Trefferzeile
   ("Verfolger · von hinten") startet auf 50 % und steigt nach oben — sie lief ihnen entgegen.
   Drei Anzeigen, die im selben Moment erscheinen, auf demselben Streifen.

   Jetzt UNTER dem Fadenkreuz: die Meldungen steigen von 50 % aufwaerts weg, das Banner liegt
   darueber, und die Herzen haben den Bereich darunter fuer sich allein. */
#hearthit {
  position: fixed; left: 50%; top: calc(50% + 66px); transform: translateX(-50%) scale(.86);
  display: flex; gap: 10px; pointer-events: none; z-index: 41;
  opacity: 0; transition: opacity .16s ease, transform .16s ease;
}
#hearthit.show { opacity: 1; transform: translateX(-50%) scale(1); }
#hearthit .hh { font-size: 30px; line-height: 1; color: #ff4a6a; text-shadow: 0 2px 8px rgba(0,0,0,.9); }
#hearthit .hh.off { color: #5a4550; opacity: .55; }
/* forwards, sonst springt das Herz nach der Animation in seinen Grundzustand zurueck — also
   wieder auf ein volles rotes Herz. Genau das war der Fehler: es blinkte korrekt und stand
   danach wieder da, als waere nichts passiert. Der Endzustand ist jetzt das leere Herz. */
#hearthit .hh.gone { color: #ff2a2a; animation: heartGone .55s ease-out 2 forwards; }
@keyframes heartGone {
  0%   { transform: scale(1.55); opacity: 1;   color: #ff2a2a; }
  60%  { transform: scale(.92);  opacity: .85; color: #ff5a5a; }
  100% { transform: scale(1);    opacity: .5;  color: #5a4550; }
}

/* ---- Hecksensor -------------------------------------------------------------------------
   Zwei weiche Glimmflaechen unten links und rechts. Kein Pfeil, keine Position, nichts durch
   Waende — die Aussage ist "da hinten kommt was", nicht "dort steht einer". Sobald du dich
   umdrehst, faellt es weg, weil der Gegner dann vor dir ist. */
#rearwarn { position: fixed; inset: 0; pointer-events: none; z-index: 38; }
/* Warum das umgebaut wurde: der Verlauf hatte sein Maximum bei "ellipse at 0% 100%", also in
   der BILDECKE unten — der Stelle, die im Kampf am weitesten von den Augen weg ist. Die Aussage
   war da, sie kam nur nicht an. Jetzt volle Bildhoehe am Rand, das Maximum liegt damit auf
   Augenhoehe. Schmaler (15 % statt 30 %), damit es das Spielfeld nicht zudeckt, dafuer
   kraeftiger (.85 statt .55).
   KEIN transition mehr: Glaettung UND Pulsieren macht jetzt applyRear() in JS. Zwei Besitzer
   derselben Deckkraft hiessen, dass der 0.16s-Uebergang den Puls wegglaettet. */
#rearwarn .rl, #rearwarn .rr {
  position: absolute; top: 0; bottom: 0; width: 15%; opacity: 0;
}
#rearwarn .rl { left: 0;  background: linear-gradient(90deg,  rgba(255,52,52,.85), rgba(255,52,52,0)); }
#rearwarn .rr { right: 0; background: linear-gradient(270deg, rgba(255,52,52,.85), rgba(255,52,52,0)); }
/* Die dritte Richtung: direkt hinten. Vorher landete ein Gegner genau im Rueckenzentrum
   willkuerlich links oder rechts — die Anzeige log dann ueber die Richtung. */
#rearwarn .rb {
  position: absolute; left: 12%; right: 12%; bottom: 0; height: 17%; opacity: 0;
  background: linear-gradient(0deg, rgba(255,52,52,.85), rgba(255,52,52,0));
}

/* ---- Jagdfieber: dunkler und unruhiger ---------------------------------------------------
   Der Rand geht von 72 % auf 88 % Deckkraft und die Mitte wird ebenfalls dunkler. Es soll sich
   anfuehlen, als ginge das Licht aus und nur die Beute bliebe uebrig — bei 72 % war die Welt
   noch zu gut zu sehen, und damit fiel das Leuchten der Gegner kaum auf. */
#huntfx {
  background:
    radial-gradient(circle at 50% 50%, rgba(90,0,44,.26) 0%, rgba(255,47,160,.11) 38%, rgba(20,0,10,.46) 100%) !important;
  animation: huntBreath 1.6s ease-in-out infinite;
}
@keyframes huntBreath { 0%,100% { filter: brightness(1); } 50% { filter: brightness(1.22); } }

/* ---- Laufende Effekte: Zeitriss, Goldener Blick, Magnetsturm ------------------------------
   Klein und oben in der Mittelspalte. Sie sollen sagen "es laeuft noch etwas und wie lange",
   nicht um Aufmerksamkeit kaempfen — dafuer gibt es Banner. Monospace fuer die Zahl, damit die
   Leiste beim Herunterzaehlen nicht zappelt. */
#fxrow { display: none; gap: 6px; pointer-events: none; }
#fxrow .fx {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 8px; border: 1px solid; border-radius: 999px;
  background: rgba(6,9,18,.55); font-size: 12px; line-height: 1.5;
  text-shadow: 0 1px 4px rgba(0,0,0,.9);
}
#fxrow .fx b {
  font-family: 'BG Mono', ui-monospace, "SFMono-Regular", Menlo, monospace;
  font-weight: 700; font-variant-numeric: tabular-nums; min-width: 2.2em; text-align: right;
}
@media (max-width: 560px) { #fxrow .fx { font-size: 11px; padding: 1px 6px; } }
/* ============================================================================================
   MOBILE-LAYOUT: DIE HOEHE IST DIE KNAPPE ACHSE
   ============================================================================================

   Von den ueber zwanzig Media-Queries in dieser Datei pruefen vier die Hoehe, und nur eine
   davon beruehrt das Spiel (die Menuekarte). Alles andere haengt an max-width.

   Ein Handy im QUERFORMAT ist aber breit und niedrig. Gemessen an einem Galaxy S8+ mit
   740 x 360:

     max-width: 900   greift    -> Tastatur-Hinweis weg          (der einzige, der hilft)
     max-width: 660   greift NICHT -> HUD-Pillen in Desktop-Groesse
     max-width: 520   greift NICHT -> Banner bleibt bei 52 px
     max-width: 460   greift NICHT

   Das Spiel haelt 740 px fuer "fast Desktop" und uebersieht, dass nur 360 px Hoehe da sind.
   Bei 52 px Schrift und zweizeiligem Umbruch ("BOSS · LEVEL 3") frisst allein das Banner
   104 px — knapp ein Drittel der Bildhoehe.

   Dieser Block spiegelt die vorhandenen Breiten-Stufen auf die HOEHE. Er steht bewusst am
   ENDE der Datei: so gewinnt er in der Kaskade gegen die Breiten-Regeln, ohne dass eine
   einzige davon angefasst werden muss.

   NICHT angefasst: die Steuerung. Buttongroessen, Stick- und Blickzone bleiben, wie sie sind.
   -------------------------------------------------------------------------------------- */

/* ---- 1. Kurzes Querformat: alles eine Stufe kleiner ---------------------------------------
   Dieselben Werte, die @media (max-width: 660px) setzt — nur eben nach Hoehe ausgeloest. */
@media (max-height: 560px) {
  .bar { padding: 5px 7px; gap: 2px; }
  .pill b { font-size: 13px; }
  .ab { width: 19px; height: 19px; font-size: 10px; }
  .ab.panic { width: auto; min-width: 22px; padding: 0 5px; }

  /* ---- NICHT ALLES PROPORTIONAL KLEINER ---------------------------------------------------
     Die Zeilen darueber stammen aus dem Breiten-Block (max-width: 660px) und wurden hier
     zuerst WORTWOERTLICH uebernommen. Das war falsch: sie haben Herzen (15 -> 13) und
     Notzuender (12 -> 11) mitverkleinert — also ausgerechnet die zwei Werte, die man im
     Notfall in einem Sekundenbruchteil ablesen muss.

     Mobil heisst nicht "alles winzig", sondern "das Unwichtige tritt zurueck". Also getrennt:
     was ueber Leben und Tod entscheidet, wird GROESSER als am Desktop; was sich alle paar
     Minuten einmal aendert, wird kleiner.

     Die Wortmarken neben den Punkten sind hier bewusst NICHT erwaehnt: body.touch blendet sie
     bereits aus (siehe unten). Der zweizeilige Umbruch der Leiste in den Screenshots kommt
     also nicht von ihnen, sondern schlicht aus zu vielen Pillen auf zu wenig Breite — dagegen
     wirken die kleineren Abstaende und Badges oben. */

  /* Entscheidet im Moment. Groesser als am Desktop, nicht kleiner. */
  #hearts .hp { font-size: 16px; }
  #s-bombs, #s-ammo { font-size: 15px; }
  .ab.panic b { font-size: 13px; }

  /* Aendert sich alle paar Minuten — darf zuruecktreten. */
  #s-range, #s-speed { font-size: 12px; opacity: .8; }
  #mmwrap .label { font-size: 8px; margin-bottom: 4px; }
  #centercol { gap: 4px; padding-top: 6px; }
  #combo { font-size: 18px; }
  #streak { font-size: 22px !important; }
  body.touch { --mmw: 96px; --mmh: 83px; }
  /* Der Untertitel der Levelansage: 13 px mit 3 px Sperrung braucht bei 740 px Breite
     schnell zwei Zeilen, und jede kostet hier 3 % des Bildes. */
  #banner .small { font-size: 11px; letter-spacing: 2px; margin-top: 6px; }
  #banner .big { --bnbig: 34px; }
  #godvoice { font-size: 17px; letter-spacing: 2px; bottom: 24%; }
}

/* ---- 2. Sehr kurz (360 px und darunter) ---------------------------------------------------
   Die Stufe, die es bisher gar nicht gab. Bei 27 px bleibt eine zweizeilige Bossansage unter
   16 % der Bildhoehe statt bei 29 %. */
@media (max-height: 420px) {
  :root { --mmpad: 5px; }
  /* 62 -> 54 px: zwischen Minimap-Unterkante (107) und Knopfblock (Hoehe-190) bleiben hier
     nur rund 63 px. 54 plus der unsichtbare 5-px-Ring sind 64 Trefferpixel — ueber der
     48-dp-Empfehlung und ohne den Block zu beruehren. */
  body.touch .tbtn.det { width: 54px; height: 54px; }
  body.touch .tbtn.det .l { font-size: 8px; }
  body.touch { --mmw: 84px; --mmh: 73px; --hudy: 12px; }
  #mmwrap .label { display: none; }
  #banner .big { --bnbig: 27px; letter-spacing: 1px; }
  #banner .small { font-size: 10px; letter-spacing: 1.5px; margin-top: 5px; }
  #godvoice { font-size: 15px; bottom: 26%; }
  #combo { font-size: 16px; }
  #streak { font-size: 20px !important; }
  #apoc { font-size: 28px; }
  /* Die untere Wertleiste ist auf einem 360-px-Bild der am wenigsten dringende Block:
     Level, Score und Schrott liest man zwischen den Kaempfen, nicht waehrend. */
  #topbar .seg { padding: 4px 7px; }
  #topbar .seg .k { font-size: 7px; }
  #topbar .seg .v { font-size: 12px; }
}

/* ---- 3. DIE BILDMITTE GEHOERT DEM FADENKREUZ ----------------------------------------------

   #centercol stapelt neun Elemente (Rival, Schwur, Weltregel, Haendler, Auge, Apokalypse,
   Fluch, Combo, Streak) und ist horizontal ZENTRIERT. #banner sitzt zusaetzlich auf
   translate(-50%,-50%), also vertikal mittig. Auf 360 px Hoehe treffen sich beide — und
   genau darauf sitzt das Fadenkreuz.

   Die Weltregel ist der schlimmste Fall: sie steht den GANZEN Lauf dort. Ein Wort wie
   KAHLSCHLAG klebt also permanent im Zielkreuz.

   Geloest mit EINER Eigenschaft: die Spalte richtet ihre Kinder links aus statt mittig. Das
   erhaelt das Flex-Stapeln (keine Luecken, wenn ein Element ausgeblendet ist) und braucht
   weder position:fixed noch eine Aenderung am HTML.

   Was mittig BLEIBT: Fadenkreuz und #ammocross (Munition + Notzuender, siehe dort). Und die
   vier Elemente, die dramatische Inszenierung sind statt Statusanzeige — sie holen sich die
   Mitte einzeln zurueck. Das Auge muss dabei sein: das Finale skaliert es per Inline-Stil auf
   das Zweieinhalbfache, und das ergibt nur in der Bildmitte einen Sinn. */
@media (max-height: 560px) {
  #centercol { align-items: flex-start; padding-left: var(--hudx); }

  /* Nach der zweiten Runde STRIKT: mittig bleiben nur Fadenkreuz und #ammocross.
     Combo und Streak sind zwar reaktiv, aber sie beeinflussen keine Entscheidung in der
     naechsten halben Sekunde — und sie legen sich auf genau die Flaeche, auf der Gegner
     erscheinen. Sie gehen mit nach links.

     EINE Ausnahme bleibt: die Apokalypse. Sie ist ein 60-Sekunden-Countdown auf den Tod, kommt
     in rund jedem fuenfhundertsten Kampagnenlevel (3,5 % auf L4+, ohne Boss, ohne Legende) und
     ist reine Inszenierung. Ein Ereignis darf die Mitte haben; eine Statusanzeige nicht. */
  #apoc { align-self: center; }

  /* ---- DAS AUGE -------------------------------------------------------------------------
     Es sah aus wie Inszenierung und ist in Wahrheit eine Statusanzeige: godEye() blendet es
     ein, sobald godFavor > 0, und laesst es dann DAUERHAFT stehen — Groesse und Deckkraft
     folgen stufenlos der Gunst. Es gehoert also zu den Pillen, nicht in die Bildmitte.
     Nur das Finale ist eine echte Ausnahme: dort setzt der Code per Inline-Stil scale(2.6)
     bzw. translateX(-50%) scale(...), und beides ergibt ausserhalb der Mitte keinen Sinn.
     body.finale wird in game.js genau fuer diese beiden Fenster gesetzt. */
  body.finale #godeye { align-self: center; }
}

/* ============================================================================================
   MOBILE: DIE KARTEN — MENUE, ERGEBNIS, SCHWUR, SHOP
   ============================================================================================

   #overlay scrollt bereits (overflow-y: auto). Das Problem ist nicht, DASS man scrollen muss,
   sondern dass nichts es sagt und dass ausgerechnet die HANDLUNG unten liegt:

     Menue      "Tippen zum Starten" sichtbar, aber Klasse, Rang, Anmelden und Optionen nicht.
                Ein Erstspieler findet die Klassenwahl nicht.
     Ergebnis   Der "Nochmal"-Knopf liegt unter der Falz.
     Schwur     Die dritte Karte ist angeschnitten — man WAEHLT, ohne alle Optionen zu sehen.
                Das ist der schlimmste der drei: eine Wahl ohne Uebersicht ist keine Wahl.

   Drei Antworten, in dieser Reihenfolge:
     1. Weniger Hoehe verbrauchen (Polster, Ueberschriften, Abstaende).
     2. Was auf einem Telefon nichts zu suchen hat, faellt weg (Entwicklerberichte).
     3. Was danach immer noch nicht passt, bleibt erreichbar: die Aktionsleiste klebt unten.

   Es wird NICHTS umgebaut und keine Schaltflaeche entfernt — nur die Hoehe neu verteilt.
   -------------------------------------------------------------------------------------- */

/* ============================================================================================
   MOBILE FEINSCHLIFF — nur Querformat auf dem Telefon, der Rechner bleibt unberuehrt.

   Alles darunter haengt an (max-height: 560px) und (orientation: landscape) UND an body.touch.
   Die Media Query allein reicht nicht: ein sehr flach gezogenes Fenster am Rechner faellt
   ebenfalls hinein. body.touch wird nur gesetzt, wenn das Geraet wirklich beruehrbar ist.
   ============================================================================================ */
@media (max-height: 560px) and (orientation: landscape) {

  /* ---- 1. DIE KARTE NIMMT DIE GANZE BREITE -------------------------------------------------
     .card steht auf max-width: 460px. Auf 740 px Breite bleiben damit links und rechts je 140 px
     leer, waehrend die vier Moduskacheln sich im Rest draengen und ihre Namen umbrechen
     ("Tages-\nChallenge"). Auf einem Telefon ist Breite das Knappste, was es gibt — sie
     ungenutzt liegen zu lassen und dafuer den Inhalt zu stauchen, ist die falsche Reihenfolge.

     Gilt fuer JEDE Karte, nicht nur das Menue: Klasse, Werkstatt, Direktstart, Pause, Ergebnis
     haben dasselbe Problem aus derselben Zeile. */
  body.touch .card { max-width: 100% !important; }

  /* ---- 2. Die Kacheln koennen jetzt atmen ------------------------------------------------
     Mit der vollen Breite passt jeder Name in eine Zeile. Die Symbole duerfen deshalb groesser
     werden — sie sind das, was man aus zwei Metern Entfernung unterscheidet, nicht die Schrift.
     Und "Tages-Challenge" ist das laengste Wort der vier: eine Spur kleiner, dann steht die
     Reihe gleichmaessig, statt dass eine Kachel die anderen sprengt. */
  body.touch #moderow .mode .mi { font-size: 30px; }
  body.touch #moderow .mode .mt b { font-size: 15px; }
  body.touch #moderow .mode[data-mode="daily"] .mt b { font-size: 13.5px; }
  body.touch #moderow .mode .mt small { font-size: 10.5px; line-height: 1.25; }

  /* ---- 3. Munition und Notzuender verschwinden aus der Daumenspalte ----------------------
     Sie stehen seit dem Umbau am Fadenkreuz — dort, wo man beim Schiessen ohnehin hinsieht.
     Ein zweites Mal am rechten Rand ist keine Redundanz, sondern Konkurrenz: drei gestapelte
     Zahlen ueber den Knoepfen lesen sich als Block, und dann liest man keine davon.

     Uebrig bleibt die BOMBENZAHL. Sie hat kein Zuhause am Fadenkreuz, sie aendert sich selten,
     und sie ist die einzige der drei, deren Wert man VOR dem Handeln kennen muss. */
  /* (Munition, Notzuender und Bombenzahl stehen weiter unten — siehe dort, es geht um die
     Reihenfolge gegenueber dem bestehenden Daumenzeilen-Block.) */

  /* ---- 4. Minimap und Pausenknopf tiefer -------------------------------------------------
     Beide sassen buendig an der Oberkante, waehrend rechts --hudx = 18 px Luft stand. Ungleiche
     Abstaende an einer Ecke fallen sofort auf, auch wenn man nicht sagen kann, woran es liegt.

     --hudy geht auf denselben Wert wie --hudx. Der Pausenknopf haengt ueber seine
     calc()-Formel automatisch mit dran, die Minimap ueber das padding von #topzone. */
  body.touch { --hudy: 18px; }
  /* Der Pausenknopf haengt an calc(var(--hudy) - var(--badge)/2) und landet damit bei glatt 0,
     also buendig an beiden Kanten. Ein eigener Wert gibt ihm denselben Abstand wie allem
     anderen — er ist ein Knopf, kein Rahmenelement. */
  body.touch #tpause { top: 10px; right: 10px; }
}

/* Die Kraefte-Zeile in der Pause. Nur dort, deshalb steht sie hier unten und nicht bei den
   allgemeinen Kartenregeln. */
.paukr { margin: 10px 0 2px; text-align: left; }
.paukr > .k {
  font-size: 10px; letter-spacing: 2px; color: #7f8db0;
  font-family: 'BG Mono', ui-monospace, monospace; margin-bottom: 5px;
}
.paukr .kr {
  display: inline-flex; align-items: center; gap: 6px; margin: 0 8px 6px 0;
  padding: 5px 10px; border-radius: 9px; font-size: 12px; color: #cfd9ef;
  background: rgba(30,40,70,.55); border: 1px solid rgba(120,140,190,.20);
}
.paukr .kr .ki { font-size: 14px; }
.paukr .kr b { color: #8fe6ff; font-family: 'BG Mono', ui-monospace, monospace; }

@media (max-height: 560px) {
  /* ---- 1. Die Karte selbst wird flacher --------------------------------------------------
     Der vorhandene Block bei max-height: 520px macht das bereits, aber NUR fuer die Menuekarte
     (.menucard). Ergebnis, Schwur, Shop und Konto gingen leer aus — dabei sind das genau die
     Bildschirme, auf denen man etwas entscheiden muss. */
  #overlay { padding: 10px; }
  .card { padding: 16px 20px 14px !important; border-radius: 14px; }
  .card h1 { font-size: 26px !important; margin: 2px 0 6px !important; }
  .card .eyebrow { margin-bottom: 2px; letter-spacing: 2px; }
  .card .sub { font-size: 12px; margin-bottom: 8px; }
  .subrow { margin: 8px 0 2px; gap: 6px; }

  /* ---- 2. Entwicklerberichte gehoeren nicht auf ein Telefon ------------------------------
     Wurfprotokoll, Levelbilanz, Plattenbericht und die Arena-Messung sind Werkzeuge fuer den
     Playtest. Auf 360 px Hoehe sind sie der Grund, warum der wichtigste Knopf des Bildschirms
     unter der Falz liegt. Sie haengen ohnehin an USE_REPORT und verschwinden vor dem Release —
     hier verschwinden sie schon vorher, und zwar genau dort, wo sie am meisten kosten.
     Am Desktop bleiben sie unveraendert sichtbar. */
  .devrep { display: none !important; }

  /* ---- 3. ZURUECKGENOMMEN: die klebende Aktionsleiste -------------------------------------
     Hier stand eine `.btnrow { position: sticky; bottom: -14px; background: <Verlauf> }`, damit
     der Bestaetigen-Knopf immer erreichbar bleibt. Sie war der Fehler, den die Screenshots
     zeigen — und zwar aus einem Grund, den ich vorher haette pruefen muessen:
     Sticky richtet sich am scrollenden VORFAHREN aus (#overlay), wird aber vom eigenen
     Elternelement begrenzt (.card). Ist die Karte hoeher als das Bild, parkt die Leiste also
     NICHT am unteren Bildrand, sondern mitten in der Karte — und malt dort ihren dunklen
     Verlauf quer ueber den Text darueber.
     Das ist der "Strich" auf allen drei Screenshots. Und weil er Textzeilen kreuzt, sah es
     zusaetzlich nach ueberlappenden Zeilen aus (am deutlichsten bei DER UNANTASTBARE, dessen
     Zitat als einziges zweizeilig umbricht) — ein zweiter Fehler, den es nie gab.
     Dasselbe galt fuer die Verlaufskante an #overlay::after: ein zweiter dunkler Streifen,
     der ueber allem lag.
     Erreichbarkeit loest man hier nicht mit CSS-Tricks in einem Container, den man nicht
     kontrolliert. Der richtige Weg ist, die Karte KUERZER zu machen — das tun die Punkte 1
     und 2 oben — und alles Weitere am Geraet zu pruefen statt blind nachzulegen. */
}

@media (max-height: 420px) {
  /* Die letzte Stufe. Hier zaehlt nur noch: alle Optionen sehen, handeln koennen. */
  .card { padding: 12px 16px 10px !important; }
  .card h1 { font-size: 22px !important; }
  .card .eyebrow { font-size: 8px; }
  .gostats { font-size: 12px; }
  .gostats span, .gostats b { line-height: 1.5; }
  .subrow.iconrow { gap: 6px; }
}

/* ============================================================================================
   MOBILE-HUD: WENIGER IM KAMPF, GROESSER WO ES ZAEHLT
   ============================================================================================

   Bis hierher war der mobile Umbau "Desktop kleiner gemacht". Das macht das Spiel benutzbar,
   nicht angenehm. Dieser Block ist der erste Schritt zu einem HUD, das FUER Daumen gebaut ist.

   Der Bestand: eine Pillenreihe oben links mit acht bis zwoelf Werten, dazu vier Badges. Am
   Desktop eine gut sortierte Instrumententafel. Auf 360 px Hoehe konkurriert jeder Wert mit
   dem Spielfeld — und der Blick muss fuer jeden Wert in die Ecke wandern.

   Die Frage ist nicht "ist es lesbar", sondern "braucht man es WAEHREND der Bewegung":

     Leben        ja, staendig            -> oben links, gross (dort sucht man es)
     Bomben       ja, vor jedem Zug       -> an den Daumen
     Munition     ja, vor jedem Schuss    -> an den Daumen
     Notzuender   ja, im Notfall          -> an den Daumen
     Schild       ja, es laeuft ab        -> bleibt sichtbar
     Reichweite   aendert sich alle paar Minuten   -> raus, steht in der Pause
     Tempo        dito                             -> raus, steht in der Pause
     Fernzuender / Durchschlag   einmal erworben, dann dauerhaft   -> raus

   Bomben, Munition und Notzuender wandern an die rechte untere Ecke, direkt UEBER die
   Aktionsknoepfe. Begruendung: dorthin schaut man ohnehin, wenn man legt oder schiesst. Der
   Blick muss nicht mehr quer ueber den Bildschirm.

   NICHTS wird neu gezeichnet: es sind dieselben Pillen mit ihren eigenen Hintergruenden, nur
   an einem anderen Platz. Die Steuerung bleibt unberuehrt.
   -------------------------------------------------------------------------------------- */

body.touch {
  /* Die Zeile leitet sich aus #tbtns ab (right:16 bottom:16, Hoehe 176) plus acht Pixel Luft.
     Als Variable, damit sich der Streifen mitverschiebt, falls die Knoepfe je wandern. */
  --thumbrow-bottom: 200px;
  --thumbrow-right: 16px;
}

/* ---- 1. Was im Kampf nichts zu suchen hat ------------------------------------------------- */
body.touch .pill.fire,
body.touch .pill.speed,
body.touch #ab-remote,
body.touch #ab-pierce { display: none; }

/* ---- 2. Leben: dort, wo man sie sucht, und gross ------------------------------------------
   Nicht "etwas groesser" — sie sind der Wert, nach dem man im Treffermoment sucht. */
body.touch #hearts .hp { font-size: 19px; }
body.touch #bar1 > .pill:first-child { padding: 6px 10px; }

/* ---- 3. Bomben, Munition, Notzuender an den Daumen ---------------------------------------
   position: fixed nimmt sie aus der Leiste heraus. Sie behalten ihr eigenes Aussehen; es
   aendert sich nur, wo sie stehen. Rechtsbuendig gestapelt, damit unterschiedlich breite
   Zahlen (1 gegen 24) die Reihe nicht verschieben. */
body.touch #bar1 > .pill.bombcount,
body.touch #bar1 > .pill.blaster,
body.touch #bar1 .abs {
  position: fixed; right: var(--thumbrow-right); z-index: 6;
}
/* Die Bombenzahl steht jetzt auf dem LEGEN-Knopf (.tbtn .cnt). Ein HUD-Element weniger,
   und die Zahl liegt dort, wo beim Legen ohnehin Blick und Daumen sind. */
body.touch #bar1 > .pill.bombcount { display: none; }
body.touch #bar1 > .pill.blaster   { bottom: calc(var(--thumbrow-bottom) + 34px); }
body.touch #bar1 .abs              { bottom: var(--thumbrow-bottom); padding-left: 0; }

/* ---- 3b. NUR NOCH DIE BOMBENZAHL, UND DIE SITZT UNTER DER MINIMAP ------------------------
   Munition und Notzuender stehen am Fadenkreuz, die drei Kraft-Abzeichen in der Pause. Uebrig
   bleibt die Bombenzahl.

   Diese Regeln MUESSEN hier unten stehen und nicht oben im Feinschliff-Block: die drei Zeilen
   darueber haben dieselbe Spezifitaet, und bei Gleichstand gewinnt die spaetere. Ein erster
   Versuch weiter oben wurde stillschweigend ueberschrieben — die Pille blieb, wo sie war, und
   lag danach mitten in der Minimap, weil die durch --hudy: 18px sechs Pixel nach unten
   gerueckt war.

   Verankert wird jetzt von OBEN und aus denselben Variablen, aus denen die Minimap ihre Groesse
   nimmt. Damit wandert die Pille automatisch mit, wenn --hudy, --mmh oder --mmpad sich aendern:

     18 (--hudy) + 73 (--mmh) + 12 (2x --mmpad) + 12 (Rahmen 2 + Luft 10) = 115 px

   right: var(--hudx) statt --thumbrow-right, damit die rechte Kante mit der Minimap fluchtet
   statt zwei Pixel daneben zu stehen. */
@media (max-height: 560px) and (orientation: landscape) {
  body.touch #bar1 > .pill.blaster,
  body.touch #bar1 .abs { display: none; }
  /* Frueher sass hier die Bombenpille. Den Platz hat jetzt der Notzuender (.tbtn.det),
     verankert an genau denselben Variablen — die Rechnung oben gilt unveraendert. */
  body.touch #bar1 > .pill.bombcount { display: none; }
}

/* Gross genug, um sie im Augenwinkel zu lesen, statt sie anzusehen. */
body.touch #s-bombs,
body.touch #s-ammo { font-size: 19px; }
body.touch .pill.bombcount,
body.touch .pill.blaster { padding: 6px 11px; }
body.touch .ab.panic { min-width: 30px; height: 26px; padding: 0 8px; }
body.touch .ab.panic b { font-size: 16px; }
body.touch .ab.shield { width: 26px; height: 26px; font-size: 13px; }

/* ---- 4. Die zweite Reihe kann jetzt nicht mehr umbrechen ---------------------------------
   Der Umbruch in den Screenshots entstand aus zu vielen Pillen auf zu wenig Breite. Nach 1
   und 3 stehen oben links nur noch Leben — Platz ist damit kein Thema mehr. */
body.touch #bar1 { flex-wrap: nowrap; }

/* ============================================================================================
   MOBILE-ENTSCHEIDUNGSBILDSCHIRME: SCHWUR, SHOP, EREIGNIS, TRESOR
   ============================================================================================

   Alle vier bauen auf .shopgrid und eventCard auf — ein Muster, vier Bildschirme.

   Der Schwur ist der schlimmste Fall und deshalb der Massstab: Man WAEHLT dort, ohne alle
   Angebote gesehen zu haben. Eine Wahl ohne Uebersicht ist keine Wahl.

   ---- WARUM NICHT EINSPALTIG ---------------------------------------------------------------

   Das Standardmuster fuer Mobile waere "eine Karte pro Zeile". Das gilt fuer HOCHFORMAT.
   Dieses Spiel ist Querformat-only (siehe #rotate), und dort ist die Lage umgekehrt:

     Breite   740 px   reichlich
     Hoehe    360 px   knapp

   Einspaltig wuerde also die reichliche Achse verschenken und die knappe verbrauchen — aus
   drei Zeilen wuerden sechs. Genau falsch herum.

   Richtig ist das Gegenteil: MEHR Spalten. Fuenf Angebote plus Weltregel passen bei drei
   Spalten in zwei Zeilen statt in drei. Und die Tippflaeche leidet nicht: bei 740 px Breite
   ist eine Karte immer noch rund 225 px breit — das Fuenffache dessen, was ein Daumen braucht.
   Die Beschraenkung auf Mobile ist nie die Breite einer Karte, sondern die Zahl der Zeilen.

   Gerechnet fuer den Schwur bei 360 px Hoehe:

     Kartenraster   3 Zeilen a 95 px + Abstaende   =  305 px
     bei 3 Spalten  2 Zeilen                       =  200 px     -105
     Knopfleiste    zwei uebereinander              =   92 px
     nebeneinander                                  =   48 px     -44
     Weltregel-Karte kompakter                                    -18

   Zusammen rund 165 px zurueckgewonnen — genug, damit die Entscheidung auf einen Blick passt.
   -------------------------------------------------------------------------------------- */

@media (max-height: 560px) and (orientation: landscape) {
  /* ---- 1. Drei Spalten statt zwei -------------------------------------------------------- */
  .shopgrid { grid-template-columns: 1fr 1fr 1fr; gap: 8px; margin: 10px 0 12px; }

  /* ---- 2. Die Karten selbst flacher -----------------------------------------------------
     Der Inhalt bleibt vollstaendig — es wird nichts weggelassen, nur enger gesetzt. Ein
     Angebot, das man nicht ganz liest, ist so schlecht wie eines, das man nicht sieht. */
  .shopitem { padding: 9px 10px; border-radius: 10px; }
  .shopitem .nm { font-size: 12px; gap: 6px; }
  .shopitem .de { font-size: 10px; margin: 2px 0 5px; line-height: 1.28; }
  .shopitem .ct { font-size: 11px; }

  /* ---- 3. Die Weltregel-Karte kompakter -------------------------------------------------- */
  .wrcard { margin: 0 0 9px; padding: 8px 12px; border-radius: 10px; }

  /* ---- 4. Die Knoepfe nebeneinander statt uebereinander ----------------------------------
     .oathfoot stapelt "Ohne Schwur spielen" und "Hauptmenue" senkrecht — das kostet doppelt
     so viel Hoehe wie noetig, obwohl daneben 500 px Breite frei sind. */
  .oathfoot { flex-direction: row; flex-wrap: wrap; justify-content: center; gap: 10px; margin-top: 10px; }
  .oathfoot .oathgo { min-width: 190px; padding: 10px 20px; font-size: 14px; }
  .oathline { font-size: 10px; }
}

@media (max-height: 420px) and (orientation: landscape) {
  /* Letzte Stufe. Vier Spalten waeren bei 740 px noch rund 165 px je Karte — immer noch weit
     ueber jeder Daumenbreite, und es druckt fuenf Angebote in EINE Zeile. */
  .shopgrid { grid-template-columns: repeat(4, 1fr); gap: 7px; margin: 8px 0 10px; }
  .shopitem { padding: 8px 9px; }
  .shopitem .de { font-size: 9.5px; line-height: 1.25; }
  .wrcard { margin: 0 0 7px; padding: 6px 10px; }
  .oathfoot { margin-top: 8px; gap: 8px; }
  .oathfoot .oathgo { min-width: 170px; padding: 9px 18px; }
}

/* ---- DIE LAST-STAND-UHR ---------------------------------------------------------------------
   Frueher inline in game.js gesetzt und in #centercol gehaengt — siehe die Begruendung dort.
   Sie sitzt jetzt UNTER der Ansage statt auf ihr.

   Zentriert ueber left/right + text-align und NICHT ueber transform: translateX(-50%). Das ist
   Absicht — lastStandClock() schreibt jedes Bild ein transform: scale(...) fuer den Puls, und
   das wuerde eine Zentrierung per transform ueberschreiben. Derselbe Fehler waere beim Gottesauge
   moeglich gewesen. So skaliert der Puls um die Bildmitte und die Zahl bleibt, wo sie ist. */
#lsclock {
  position: fixed; left: 0; right: 0; top: 62%;
  display: none; text-align: center; pointer-events: none; z-index: 6;
  font-weight: 900; font-size: 46px; letter-spacing: 2px; line-height: 1;
  text-shadow: 0 0 22px rgba(255,40,40,.8), 0 2px 8px rgba(0,0,0,.92);
}
/* Kurzes Bild: die Ansage steht bei 27 px und braucht mit Untertitel rund 50 px um die Mitte.
   64 % statt 62 % legt die Uhr sicher darunter, ohne der Gottesstimme (bottom: 22 %) ins Gehege
   zu kommen. */
@media (max-height: 560px) { #lsclock { font-size: 34px; top: 64%; } }
@media (max-height: 420px) { #lsclock { font-size: 29px; top: 65%; } }

/* ============================================================================================
   MOBILE: MENUE UND ERGEBNISBILDSCHIRM
   ============================================================================================
   Dieselbe Rechnung wie beim Schwur, deshalb dieselbe Antwort: im QUERFORMAT ist die Breite
   reichlich und die Hoehe knapp. Also breiter setzen, nicht schmaler.

     Menue     vier Kacheln 2x2  -> eine Reihe zu viert       spart eine Kachelhoehe
     Ergebnis  Statistik untereinander -> zwei Spalten        spart die halbe Liste

   Beide Bildschirme verlieren dadurch KEINEN Inhalt. Es wird nur die Achse getauscht, auf der
   er sich ausbreitet.
   -------------------------------------------------------------------------------------- */

@media (max-height: 560px) and (orientation: landscape) {

  /* ---- MENUE ------------------------------------------------------------------------------
     Vier Kacheln in einer Reihe. Bei 740 px Breite bleiben rund 165 px je Kachel — reichlich
     fuer einen Daumen (Mindestmass 44-48 px) und genug fuer Symbol, Name und Zeile darunter.
     Das Innenleben von .mode ist ein Raster "52px 1fr auto" (Symbol links, Text rechts). Bei
     165 px Breite waere das gequetscht, also wird gestapelt: Symbol ueber Text, mittig. */
  /* .moderow ist im Bestand ein FLEX-COLUMN, kein Raster — ein blosses
     grid-template-columns haette hier gar nichts getan. Also erst auf Raster umstellen. */
  .moderow { display: grid !important; grid-template-columns: repeat(4, 1fr); gap: 8px; }
  .mode {
    grid-template-columns: 1fr; justify-items: center; text-align: center;
    gap: 5px; padding: 10px 8px; border-radius: 12px;
  }
  .mode .mi { font-size: 22px; }
  .mode .mt { align-items: center; gap: 1px; }
  .mode .mt b { font-size: 13px; }
  .mode .mt small { font-size: 9.5px; line-height: 1.25; }
  .optblock { margin-bottom: 8px; }
  .optlbl { margin-bottom: 4px; }

  /* Die Sekundaerreihe (Klasse, Rang, Anmelden, Optionen) wird flacher, aber nicht kleiner:
     ihre Tippflaechen bleiben ueber dem Mindestmass. Nur die Kappe darunter wird knapper. */
  .subbtn.ico { width: 56px; height: 50px; gap: 2px; }
  .subbtn.ico .ic { font-size: 19px; }
  .subrow.iconrow { gap: 6px; }

  /* ---- ERGEBNISBILDSCHIRM -----------------------------------------------------------------
     .gostats ist ein Raster "1fr auto" — jede Zeile eine Zeile. Fuenf Werte werden damit fuenf
     Zeilen hoch. Zwei Paare nebeneinander halbieren das, ohne dass eine Zahl verschwindet. */
  .gostats {
    grid-template-columns: 1fr auto 1fr auto;
    gap: 3px 14px; font-size: 12px; margin-bottom: 6px;
    /* max-width: none ist noetig, nicht kosmetisch: @media (max-width: 620px) deckelt die
       Liste auf 280 px. Auf dem Testgeraet (740 px breit) feuert das nicht, auf schmaleren
       Querformat-Geraeten schon — und dort waeren vier Spalten auf 280 px unbrauchbar. */
    max-width: none;
  }
  .scoreline { gap: 20px; margin: 4px 0 10px; }
  .scoreline .v { font-size: 22px; }
  .gocores { font-size: 11px; margin-bottom: 6px; }
  .sharebtn, .gridbtn, .rivalbtn { margin-bottom: 5px; padding: 8px 14px; font-size: 13px; }
}

@media (max-height: 420px) and (orientation: landscape) {
  .mode { padding: 8px 6px; gap: 4px; }
  .mode .mi { font-size: 19px; }
  .mode .mt b { font-size: 12px; }
  .mode .mt small { font-size: 9px; }
  .subbtn.ico { width: 52px; height: 46px; }
  .subbtn.ico .ic { font-size: 17px; }
  .scoreline .v { font-size: 19px; }
  .gostats { font-size: 11px; }
}

/* ============================================================================================
   MOBILE-FEINSCHLIFF: WERTLEISTE, JAGDZAEHLER, TOENUNGEN, GEDULD
   ============================================================================================ */

@media (max-height: 560px) and (orientation: landscape) {

  /* ---- 1. DIE UNTERE WERTLEISTE: NUR NOCH GEGNER ------------------------------------------
     Sie sitzt auf Touch fixiert unten in der BILDMITTE (body.touch #topbar) — genau dort, wo
     man hinsieht, wenn man vorwaerts laeuft. Fuenf Werte standen da permanent: Level, Score,
     Schrott, Gegner, Zeit. Vier davon braucht man waehrend der Bewegung nie; sie stehen nach
     jedem Level ohnehin auf der Bilanzkarte.
     "Gegner" bleibt, weil es die einzige Zahl der fuenf ist, die eine Entscheidung im Moment
     traegt: raeume ich noch oder gehe ich zum Ausgang.
     :not(.enemy) statt einer Liste, weil das Score-Segment als einziges keine eigene Klasse
     hat — eine Aufzaehlung haette es uebersehen. */
  #topbar .seg:not(.enemy) { display: none; }
  /* ---- ...AUSSER DER WELLE IN DER CHAOS-ARENA ---------------------------------------------
     Die Regel darueber traf auch das Level-Segment. In der KAMPAGNE ist das richtig: die
     Levelnummer aendert sich einmal pro Level und steht im Banner, in der Bilanz und im
     Pausenmenue.
     In der ARENA traegt dasselbe Segment die WELLE, und die ist etwas voellig anderes — sie
     ist der Punktestand des Modus, sie laeuft weiter, waehrend man spielt, und die Leiste
     darunter zeigt, wie lange die naechste noch braucht. Wer mobil spielte, hatte als einzige
     Rueckmeldung ueber seinen Fortschritt das Wellenbanner alle 65 Sekunden.
     Die Unterscheidung kostet nichts, weil das Segment die Klasse .welle ohnehin genau dann
     traegt, wenn der Modus 'endless' ist (siehe hudZeichnen). Zwei Regeln statt einer
     zusammengesetzten :not(), damit auch aeltere WebViews mitkommen. */
  #topbar .seg.level.welle { display: block; }
  #topbar .seg.level.welle .k { font-size: 8px; }
  #topbar .seg.level.welle .v { font-size: 15px; }
  /* Der Balken sitzt 3px ueber der Unterkante und braucht dort Luft, sonst klebt er auf dem
     Rand der Leiste. */
  #topbar .seg.level.welle { padding-bottom: 9px; }
  /* Trennstrich nur ZWISCHEN den beiden — nicht am aeusseren Rand. */
  #topbar .seg.enemy { border-right: 0; }
  #topbar .seg.level.welle { border-right: 1px solid rgba(120,140,190,.18); }
  body.touch #topbar { max-width: none; }
  #topbar .seg.enemy .k { font-size: 8px; }
  #topbar .seg.enemy .v { font-size: 15px; }

  /* ---- 2. DER JAGDZAEHLER ------------------------------------------------------------------
     62 px auf 360 px Hoehe sind 17 % des Bildes fuer eine einzige Ziffer. Es gab bereits eine
     Verkleinerung auf 44 px — aber unter @media (max-width: 720px), und das Testgeraet ist
     740 px breit. Dieselbe Falle wie beim Banner: die Regel war da, sie hat nie gefeuert.
     Die Hoehenstufe holt sie nach. */
  #huntcount { font-size: 38px; top: 23%; letter-spacing: 3px; }

  /* ---- 3. DIE VOLLFLAECHIGEN TOENUNGEN -----------------------------------------------------
     Fuenf Elemente legen sich als position:fixed;inset:0 ueber das ganze Bild: Schild (blau),
     Fluch (violett), Schaden (rot), Hecksensor (rot) und Jagdfieber (pink), dazu die Vignette.
     In der Arena koennen drei davon gleichzeitig laufen.
     Der eigentliche Grund, warum es mobil schlimmer ist, steht in den Werten selbst: die
     Weichzeichnung ist in ABSOLUTEN Pixeln angegeben. "inset 0 0 130px 30px" deckt auf einem
     1080 px hohen Bildschirm rund 12 % der Hoehe ab — auf 360 px sind es 36 %. Dieselbe Regel
     frisst auf dem Handy das Dreifache des Bildes.
     Also werden Weichzeichnung und Streuung anteilig zurueckgenommen, nicht die Farbe. Die
     Aussage bleibt (blau = Schild, rot = Schaden), sie belegt nur wieder den Rand statt der
     halben Flaeche. */
  #shieldring { box-shadow: inset 0 0 46px 8px rgba(90,180,255,.34), inset 0 0 16px 2px rgba(150,210,255,.26); }
  #cursetint  { box-shadow: inset 0 0 50px 10px rgba(190,50,255,.36); }
  #cursetint.on { animation-duration: 1.6s; }
  /* Die zwei Ganzflaechen-Verlaeufe duerfen zusaetzlich etwas durchsichtiger werden: sie sind
     Stimmung, keine Auskunft. */
  #huntfx { opacity: .72; }
  #vignette { opacity: .8; }

  /* ---- 4. DIE GEDULD DES GOTTES ------------------------------------------------------------
     Ein 4 px hoher Balken und 10 px Schrift sind auf einem Handy an der Wahrnehmungsgrenze —
     und ab zwei Dritteln ist das Ding keine Auskunft mehr, sondern eine Warnung (.warn/.hot).
     Eine Warnung, die man uebersieht, ist keine.
     Groesser und kraeftiger, aber im selben Ton: es bleibt eine Pille am Rand, kein Banner. */
  #stall { font-size: 11px; gap: 8px; padding: 4px 11px 5px; }
  #stall .se { font-size: 14px; }
  #stall .sb { width: 84px; height: 7px; border-radius: 4px; }
  #stall.hot { border-width: 2px; }
}

@media (max-height: 420px) and (orientation: landscape) {
  #huntcount { font-size: 32px; top: 24%; }
  #topbar .seg.enemy .v { font-size: 14px; }
  #stall .sb { width: 76px; }
}

/* ============================================================================================
   TOUCH-ERGONOMIE: DIE KNOEPFE STANZEN EIN LOCH IN DIE BLICKZONE
   ============================================================================================

   Der Bestand, nachgemessen statt vermutet:

     #touchui   z-index 7, Vollbild
       #stickzone   linke Haelfte           laufen
       #lookzone    rechte 55 %, volle Hoehe   Kamera drehen  (Zuhoerer haengen direkt daran)
       #tbtns       210x176 unten rechts, z-index 2, pointer-events: none
         .tbtn      pointer-events: auto

   Weil #tbtns bei z-index 2 ueber #lookzone (auto) liegt, bekommen die KREISE ihre Tipps
   korrekt. Was durchfaellt, sind die LUECKEN dazwischen — und die Ecken der Kreise, denn
   border-radius beschneidet auch die Trefferflaeche: bei einem 94-px-Knopf sind rund 21 % des
   umschliessenden Quadrats toter Rand.

   Damit ist der Fehlerfall nicht "Knopf reagiert nicht", sondern schlimmer: ein knapp
   verfehlter Bombenknopf wird zu einem KAMERASCHWENK. Man legt nicht, und man verliert
   gleichzeitig die Orientierung — in einem Spiel, in dem beides ueber Leben und Tod
   entscheidet, ein zusammengesetzter Fehler.

   Die Loesung ist eine Zeile: der Block faengt Beruehrungen selbst ab. Ein Fehlgriff macht dann
   NICHTS, statt die Kamera zu reissen.

   ---- WAS DAS KOSTET, ehrlich gerechnet ---------------------------------------------------
   Die Blickzone misst auf 740x360 rund 407x360 = 146.500 px². Der Block nimmt 210x176 = 36.960,
   also 25 % davon. Das klingt viel und ist es nicht: Beruehrungsereignisse bleiben bei dem
   Element, auf dem sie BEGONNEN haben — eine Drehung, die in der Bildmitte anfaengt, laeuft
   ungestoert weiter, auch wenn der Daumen ueber die Knoepfe wandert. Verloren geht nur die
   Moeglichkeit, eine Drehung ausgerechnet dort zu BEGINNEN, wo ohnehin die Knoepfe liegen.

   NICHT geaendert: Groessen und Positionen. Die Bombe ist mit 94 px bereits der groesste Knopf
   (Feuer 76, Wurf 66, Zuenden 62) — der zweite Teil des Vorschlags war also schon gebaut.
   -------------------------------------------------------------------------------------- */
/* ---- NACHTRAG: DER BLOCK IST EIN RECHTECK, DIE KNOEPFE SIND ES NICHT --------------------------
   Die Zeile oben (pointer-events: auto auf dem ganzen Block) war richtig gedacht und hat einen
   Nebenschaden, der erst auffiel, als der Notzuender unter die Minimap gewandert ist: seine alte
   Ecke — oben links im Block, x 96-180 / y 84-174 — ist seitdem LEER, schluckt aber weiter jede
   Beruehrung. Und genau dort setzt der rechte Daumen an, um die Kamera nach links und rechts zu
   schwenken. Gemeldet als "da kann ich mich nicht drehen".

   Der Block faengt jetzt nichts mehr selbst ab. Stattdessen liegen zwei unsichtbare Fangflaechen
   genau um die Knoepfe, die noch drin sind:

     ::before   180x94 unten          umschliesst LEGEN (94) und FEUER (76)
     ::after     80x80 bei bottom 99  umschliesst WURF (70) mit 5 px Rand

   Damit bleibt die Schutzwirkung, um die es ging, vollstaendig erhalten: ein knapp verfehlter
   Bombenknopf macht weiter NICHTS statt die Kamera zu reissen. Frei wird nur die Flaeche, in der
   ueberhaupt kein Knopf mehr liegt — rund 8.000 px², also 23.320 statt 31.320 tote Pixel.

   Die 5 px am WURF sind dieselbe Zahl wie beim unsichtbaren Ring darunter, aus demselben Grund.

   z-index 1 auf den Knoepfen ist noetig und nicht kosmetisch: ::after wird NACH den Kindern
   gezeichnet und laege sonst ueber dem WURF-Knopf — der wuerde dann nicht mehr reagieren.
   -------------------------------------------------------------------------------------------- */
body.touch #tbtns { pointer-events: none; touch-action: none; }
body.touch #tbtns > .tbtn { z-index: 1; }
body.touch #tbtns::before,
body.touch #tbtns::after { content: ''; position: absolute; pointer-events: auto; touch-action: none; }
body.touch #tbtns::before { right: 0; bottom: 0; width: 180px; height: 94px; }
body.touch #tbtns::after  { right: 5px; bottom: 99px; width: 80px; height: 80px; }

/* ---- UND DIE KREISE BEKOMMEN ETWAS ZUGABE -------------------------------------------------
   Zweiter Teil, unabhaengig vom Loch oben und ohne jedes Risiko: border-radius beschneidet die
   Trefferflaeche auf den Kreis, ein Tipp in die Ecke des Quadrats faellt also durch. Ein
   unsichtbarer Ring holt ihn zurueck.

   5 px, und die Zahl ist nicht gegriffen: der kleinste Abstand zwischen zwei KnopfKREISEN
   betraegt 10 px (Bombe<->Feuer und Bombe<->Wurf). Mehr als die Haelfte davon liesse die
   Trefferflaechen ueberlappen — und dann feuert im Zweifel der FALSCHE Knopf. Das waere
   schlimmer als ein Fehlgriff, der nichts tut.

   Aus demselben Grund waere "Abstaende verkleinern" die falsche Richtung: enger heisst mehr
   Verwechslungen zwischen benachbarten Knoepfen, nicht weniger Fehlgriffe. */
body.touch .tbtn::after {
  content: ''; position: absolute; inset: -5px; border-radius: 50%;
}
/* ---- Zweite Zeile unter dem Namen auf der Endkarte -----------------------------------------
   Traegt die ehrliche Auskunft, wenn eine Einreichung nicht durchkam: nachgereicht, abgelehnt,
   oder ohne Verbindung gestartet. Sie sitzt bewusst IN #whoami statt in #rankline — dort steht
   in den Challenge-Modi der persoenliche Rekord samt Serie, und der darf nicht verdraengt
   werden. Gedaempft, weil es eine Fussnote ist und keine Meldung. */
.whoami .wsub {
  margin-top: 6px; padding-top: 6px; font-size: 11px; line-height: 1.5; color: #7d8598;
  border-top: 1px solid rgba(120,150,200,.14);
}

/* ---- Rivalen-Radar und Perzentil -----------------------------------------------------------
   Der Kasten steht UEBER der Liste, weil er der Grund ist, warum jemand die Bestenliste
   oeffnet: erst wo du stehst, dann wie weit es bis zum Naechsten ist. Die Liste darunter ist
   der Beleg, nicht die Botschaft.

   Farbgebung bewusst wie .streakbox (violetter Rand, dunkler Grund) — es ist derselbe
   Kastentyp im selben Schirm, nur mit lebendigem Inhalt. Der Abstandssatz bekommt die
   Warnfarbe des Spiels, damit er sich vom reinen Zahlenwerk abhebt. */
.radar {
  margin: 0 0 12px; padding: 11px 13px; border-radius: 12px; text-align: center;
  background: rgba(16,20,31,.55); border: 1px solid rgba(155,110,255,.28);
}
.radar .rpos {
  font-family: 'BG Mono', ui-monospace, monospace; font-size: 13px; color: #cfe0f7; line-height: 1.4;
}
.radar .rpos .pz { color: #ffd24a; font-weight: 900; }
.radar .rgap, .rankline .rgap {
  margin-top: 6px; font-size: 12.5px; line-height: 1.45; color: #ff9a5a;
}
.radar .rgap b, .rankline .rgap b { color: #ffd0a8; font-weight: 800; }
/* Auf der Endkarte sitzt der Satz unter der Platzzeile, dort traegt .rankline schon den Rahmen. */
.rankline .rgap { color: #ff9a5a; }
/* Auf der Bestenliste steht das Perzentil in Gold auf kuehlem Grau und sticht heraus. Die
   .rankline ist aber SELBST schon golden (#ffce54) — dieselbe Farbe waere dort kein Akzent,
   sondern unsichtbar. Also andersherum betont: heller als die Umgebung, im selben Farbklima. */
.rankline .pz { color: #fff8e0; font-weight: 900; text-shadow: 0 0 12px rgba(255,206,84,.55); }

/* ---- DER ERSTE PLATZ LEBT -------------------------------------------------------------------
   Die Medaillenfarben weiter oben gab es schon; was fehlte, war Bewegung. Ein Podium, das
   genauso still dasteht wie Platz 874, ist kein Podium.

   Zwei Schichten, beide gedaempft: ein Atmen im Schein um die Zeile und ein warmer Streifen,
   der selten darueber wandert. "Dezent" ist hier messbar — der Glanz bleibt unter der
   Helligkeit des Textes, sonst liest man den Namen auf Platz eins schlechter als auf Platz
   zwei, und das waere die Umkehrung der Absicht.

   Der Streifen laeuft 62 % seiner Runde gar nicht: er soll auffallen, wenn er kommt, und nicht
   die ganze Zeit da sein. Ein Dauerglitzern liest sich als kaputt, nicht als wertvoll. */
.boardlist li[data-rank="1"] {
  position: relative; overflow: hidden;
  animation: goldAtem 3.4s ease-in-out infinite;
}
.boardlist li[data-rank="1"]::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(105deg, transparent 34%, rgba(255,226,150,.15) 50%, transparent 66%);
  transform: translateX(-130%);
  animation: goldStreif 5.2s ease-in-out infinite;
}
@keyframes goldAtem {
  0%, 100% { box-shadow: 0 0 10px rgba(255,206,84,.10), inset 0 0 16px rgba(255,206,84,.04); }
  50%      { box-shadow: 0 0 20px rgba(255,206,84,.26), inset 0 0 22px rgba(255,206,84,.09); }
}
@keyframes goldStreif {
  0%, 62%   { transform: translateX(-130%); }
  94%, 100% { transform: translateX(130%); }
}
/* Ist der Spieler SELBST Erster, gewaenne sonst .me weiter oben und das Gold waere weg —
   ausgerechnet in dem einen Moment, fuer den es gebaut wurde. Rahmen bleibt golden, der Name
   behaelt sein Blau, damit man sich in der Liste trotzdem sofort findet. */
.boardlist li[data-rank="1"].me { border-color: rgba(255,206,84,.7); background: rgba(48,40,14,.55); }

/* ---- DER AUFSCHLAG EINER HOCHGELAUFENEN ZAHL ------------------------------------------------
   zaehlHoch() setzt .zfertig, sobald der Zielwert steht. Kurz, hell, vorbei: es markiert das
   ENDE der Bewegung, damit die Zahl ankommt statt einfach aufzuhoeren. */
@keyframes zahlLandet {
  0%   { text-shadow: 0 0 18px rgba(106,209,255,.85); transform: scale(1.07); }
  100% { text-shadow: none; transform: none; }
}
@keyframes zahlLandetGold {
  0%   { text-shadow: 0 0 22px rgba(255,210,74,.95); transform: scale(1.09); }
  100% { text-shadow: none; transform: none; }
}
.scoreline .v.zfertig       { animation: zahlLandet .5s ease-out; }
/* Der Bestwert laeuft nur mit, wenn er GERADE erspielt wurde (siehe endkarteBeleben) — dass er
   sich ueberhaupt bewegt, ist also schon die Aussage. In seiner eigenen Farbe. */
.scoreline .v.hs.zfertig    { animation: zahlLandetGold .9s ease-out; }
.boardlist li.me .sc.zfertig { animation: zahlLandet .5s ease-out; }

/* Wer weniger Bewegung will, bekommt die Farben ohne das Leben. zaehlHoch() prueft dasselbe
   in game.js und setzt die Zahl dann sofort — hier stehen nur die rein gestalterischen
   Schichten, die kein JavaScript anfasst. */
@media (prefers-reduced-motion: reduce) {
  .boardlist li[data-rank="1"] { animation: none; box-shadow: 0 0 14px rgba(255,206,84,.16); }
  .boardlist li[data-rank="1"]::after { animation: none; opacity: 0; }
  .scoreline .v.zfertig, .scoreline .v.hs.zfertig, .boardlist li.me .sc.zfertig { animation: none; }
}

/* ---- DIE TAGESSERIE AUF DER ENDKARTE --------------------------------------------------------
   Sie stand als Beiwerk hinter dem Tagesrekord, in derselben Zeile, in derselben Farbe — und
   erst ab zwei Tagen. Damit war ausgerechnet Tag EINS unsichtbar, also der Moment, in dem
   jemand erfaehrt, dass er gerade etwas begonnen hat, das er fortsetzen kann.

   Eigene Zeile, oben, in der Signalfarbe. Der Ausblick ("Morgen: 7") ist gedaempft: er ist
   eine Einladung, keine Forderung. */
.rankline .serie {
  display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 10px;
  margin-bottom: 8px; font-size: 17px; font-weight: 900; letter-spacing: .05em; color: #ff9a5a;
}
.rankline .serie .best {
  font-size: 12px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  color: #ffd24a; opacity: .85;
}
.rankline .serie .next {
  font-size: 12px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  color: #7d8598; padding-left: 10px; border-left: 1px solid rgba(120,150,200,.22);
}
/* Der Tagesrekord rueckt eine Stufe zurueck — er ist die Bilanz, die Serie ist der Termin. */
.rankline .rekord { font-size: 13px; font-weight: 700; color: #cfe0f7; opacity: .9; }

/* ---- DIE TAGESSTUFE ALS ANSAGE --------------------------------------------------------------
   Kein Waehler mit einer freien Karte, sondern eine Feststellung. Deshalb EIN Block statt eines
   Rasters: es gibt nichts zu vergleichen und nichts anzuklicken. Die Warnfarbe des Spiels, weil
   es eine Bedrohung ankuendigt — aber ohne Rahmenblinken, denn es ist keine Gefahr im Moment,
   sondern die Bedingung des Tages. */
.tagesstufe {
  margin: 6px 0 14px; padding: 18px 20px; border-radius: 14px; text-align: center;
  background: rgba(40,16,20,.45); border: 1px solid rgba(255,58,74,.34);
}
.tagesstufe .tsname {
  font-size: 21px; font-weight: 900; letter-spacing: .06em; color: #ff8a9a; margin-bottom: 8px;
}
.tagesstufe .tsdesc { font-size: 14px; line-height: 1.55; color: #cfe0f7; }
.tagesstufe .tsgilt {
  margin-top: 10px; padding-top: 9px; font-size: 11.5px; letter-spacing: .14em;
  text-transform: uppercase; color: #7d8598; border-top: 1px solid rgba(120,150,200,.16);
}

/* ---- DER REVANCHE-KNOPF ---------------------------------------------------------------------
   Er ist die HANDLUNG der Endkarte, nicht eine Option daneben — deshalb breiter, waermer und
   ueber den Teilen-Knoepfen. Drei Zeilen statt einer Beschriftung, weil drei Dinge zusammen die
   Entscheidung tragen: was passiert, gegen WEN, und wie weit es noch ist.

   Die Zahl unten ist der eigentliche Haken. "7.390 zurück" ist eine Strecke, die man sich
   vorstellen kann; "Revanche" allein ist nur ein Wort. */
.revenge {
  display: none; flex-direction: column; align-items: center; gap: 3px;
  width: 100%; margin: 0 0 10px; padding: 13px 18px; border-radius: 13px; cursor: pointer;
  background: linear-gradient(180deg, rgba(255,90,60,.20), rgba(255,60,90,.13));
  border: 1px solid rgba(255,120,90,.52);
  box-shadow: 0 0 22px rgba(255,100,70,.13);
  transition: transform .12s ease, box-shadow .2s ease, border-color .2s ease;
}
.revenge:hover { transform: translateY(-1px); border-color: rgba(255,150,120,.8); box-shadow: 0 0 30px rgba(255,100,70,.24); }
.revenge:active { transform: translateY(1px); }
.revenge .rvtop {
  font-size: 12px; font-weight: 900; letter-spacing: .22em; text-transform: uppercase; color: #ff9a5a;
}
.revenge .rvname {
  font-size: 19px; font-weight: 900; letter-spacing: .02em; color: #fff1e6;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.revenge .rvgap {
  font-family: 'BG Mono', ui-monospace, monospace; font-size: 12.5px; font-weight: 700;
  letter-spacing: .1em; color: #ffd0a8;
}

/* ---- EINE REIHE STATT ZWEIER KNOEPFE --------------------------------------------------------
   Der Ergebnistext ist der Knopf, den man in der Herausforderung wirklich drueckt: er geht in
   jeden Chat und verlinkt dieselbe Welt. Das Bild ist die Zweitverwertung fuer Instagram und
   TikTok, wo Links nichts nuetzen — es behaelt seinen eigenen Ausgang, aber nicht mehr sein
   eigenes Gewicht. Vier gleich grosse Knoepfe untereinander sind keine Auswahl, sondern eine
   Entscheidung, die niemand treffen will. */
.sharerow { display: flex; gap: 8px; align-items: stretch; margin: 0 0 8px; }
.sharerow .gridbtn { flex: 1 1 auto; margin: 0; }
.sharerow .sharebtn.nurbild {
  flex: 0 0 auto; width: 58px; margin: 0; padding-left: 0; padding-right: 0;
  justify-content: center; gap: 0;
}
.sharerow .sharebtn.nurbild .ic { margin: 0; }