/**
 * LunatiX — style.css
 * Umsetzung der verbindlichen Vorlage full_mockup.jpg (Archiv) im
 * frischen, modularen Aufbau. Reines CSS3, keine Frameworks.
 *
 * Farben/Typo/Motion: ausschließlich die 2026-08-05 von Sven
 * freigegebenen Design-Tokens ("Mondschatten", siehe Archiv tokens.css).
 * Grafiken: ausschließlich die gelieferten PNG/JPG-Assets.
 */

/* ---- Design-Tokens (freigegeben 2026-08-05) ---- */
:root {
  --void: #080B14;
  --panel: #140F1F;
  --panel-grad-a: rgba(24, 14, 40, 0.55);
  --panel-grad-b: rgba(10, 6, 18, 0.75);

  --violet: #6D3DFF;
  --violet-glow: #B18CFF;
  --violet-deep: #1E1630;

  --gold: #C8AA4E;
  --gold-bright: #f0d38a;
  /* Eigener, zurückhaltender Ton zwischen Gold und Weiß (2026-08-08,
     Sven: "leichtes Mondgelb, nicht dominant") — bewusst NICHT --gold/
     --gold-bright wiederverwendet, die sind laut Art Bible fest für
     Rang/Auszeichnung/CTA reserviert. Erster Einsatzort: aktive Nav-
     Rubrik, siehe #mainnav a.active weiter unten. */
  --mondgelb: #E9DDA8;

  --text: #d9d2e8;
  --text-dim: #9088a8;
  --text-faint: #6a6280;

  --online: #7fe89a;

  --font-logo: "cinzel-decorative", serif;
  --font-display: "cinzel", serif;
  /* Marcellus (2026-08-11, Sven-Entscheidung nach Vergleich mehrerer
     Kandidaten) — löst Source Serif Pro als Fließtext-Schrift ab, wird
     über Google Fonts geladen (siehe header.php). Nur Schriftschnitt 400
     verfügbar, "serif" als Fallback. */
  --font-body: "Marcellus", serif;

  --radius-card: 6px;

  /* ---- Popup-Breiten, drei Stufen statt Einzelwerten (2026-08-09,
     Sven: "wäre es nicht besser alles einheitlich zu gestalten?") ----
     Vorher hatte jedes Popup seine eigene, unabhängig gewählte Zahl
     (420/640/1200px). Jetzt EIN gemeinsames System mit drei benannten
     Stufen, jedes Popup wählt die zu seinem Inhalt passende Stufe,
     statt sich eine neue Zahl auszudenken:
     - schmal:  reine Login-/Formularfelder, eine Spalte (.login-dialog)
     - mittel:  Fließtext/Teaser (.neuigkeit-popup)
     - breit:   tabellarischer/mehrspaltiger Inhalt (.verwaltung-rollen-popup)
     Ausdrücklich NICHT alle Popups auf dieselbe Breite gezwungen (Sven-
     Entscheidung, 2026-08-09) — eine 5-spaltige Tabelle bei 420px wäre
     nur unlesbar zusammengequetscht oder bräuchte ständiges seitliches
     Scrollen. Künftige neue Popups bitte eine dieser drei Variablen
     verwenden statt wieder eine eigene Zahl zu erfinden. */
  --popup-w-schmal: 420px;
  --popup-w-mittel: 640px;
  --popup-w-breit: 1200px;

  --glow-violet-soft: 0 0 16px rgba(109, 61, 255, 0.35);
  --glow-violet-strong: 0 0 24px rgba(177, 140, 255, 0.55);
  --glow-gold-soft: 0 0 16px rgba(200, 170, 78, 0.3);
  --glow-online: 0 0 6px 2px rgba(110, 231, 168, 0.8);
  /* ---- Vereinheitlichung Buttons (2026-08-10, Sven: "bitte alles auf
     16px anheben, ebenfalls mit den gleichen Effekten") ---- Gleiche
     Formel wie --glow-violet-strong (Radius 24px, Alpha 0.55), nur mit
     der jeweiligen Semantik-Farbe der Verwaltungs-Buttons, damit
     Freischalten/Annehmen (grün), Ablehnen/Löschen (rot) und Speichern
     (gold) beim Hover denselben kräftigen Glow-Auftritt wie
     .konto-absenden bekommen, ohne ihre bestehende Farbcodierung zu
     verlieren. */
  --glow-online-strong: 0 0 24px rgba(127, 232, 154, 0.55);
  --glow-rot-strong: 0 0 24px rgba(224, 135, 154, 0.55);
  --glow-gold-strong: 0 0 24px rgba(240, 211, 138, 0.55);

  --duration-standard: 300ms;
  --duration-atmospheric: 600ms;
  --ease-soft: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-breathe: cubic-bezier(0.45, 0, 0.55, 1);

  --z-background: 1;
  --z-scene-fx: 2;   /* Licht-/Feuer-Ebenen: über dem Bild, unter dem Inhalt */
  --z-main: 10;
  --z-menu: 20;
  --z-pill: 30;

  /**
   * ---- Gemeinsamer Skalierungsfaktor --s ----
   * Der Hintergrund wird per object-fit:cover + object-position:left
   * gezeichnet. --s ist EXAKT der Faktor, mit dem der Browser die
   * Hintergrund-Grafik skaliert (cover = max aus Breiten-/Höhen-Faktor).
   * --oy ist der vertikale Versatz der Bild-Oberkante (cover zentriert
   * vertikal). Jedes UI-Element über dem Ranken-Rahmen wird mit
   * calc(var(--s) * Bildkoordinate) positioniert und sitzt damit bei
   * JEDER Fenstergröße pixelgenau auf dem Rahmen.
   * Bildraum: 3440×1440 (background.jpg).
   *
   * NUR EINE Formel, KEINE Media Query (2026-08-07): Vorher gab es zwei
   * Hintergrunddateien und --s wurde per @media umgeschaltet — während
   * <picture> unabhängig davon entschied, welche Datei es lädt. Beide
   * liefen auseinander, Ergebnis waren versetzte Effekt-Ebenen (bei
   * 1900×1000 gemessene 22px). Mit einer Datei und einer Formel kann
   * das nicht mehr passieren. Details im Kopf von header.php.
   */
  --s: max(calc(100vw / 3440), calc(100vh / 1440));
  --oy: calc((100vh - var(--s) * 1440) / 2);

  /* ---- Footer-Höhe, EIN Mal zentral (2026-08-09) ----
     Sven: Footer bekam eine zweite Zeile (Impressum/Datenschutz/
     Rechtliche Hinweise, siehe .footer-rechtliches), 65 reichte für
     eine Zeile. WICHTIG, per hartem Bug gelernt: jede scrollbare Box
     (.roster/.konto-formular-seite) muss ihre eigene untere Kante exakt
     an dieser Höhe enden lassen (bottom: calc(var(--s) * --footer-h)),
     sonst läuft Scroll-Inhalt mitten im Scrollen durch die Footer-
     Zone. Eine einzige Variable statt der Zahl 65 an vier Stellen
     verstreut, damit sowas nicht wieder auseinanderlaufen kann. */
  --footer-h: 90;
}

/* ---- Basis ---- */
html, body {
  margin: 0;
  padding: 0;
  background: var(--void);
  overflow-x: hidden;
}
body {
  font-family: var(--font-body);
  color: var(--text);
}
/* BUGFIX (2026-08-12, Sven per Screenshot: "Registrieren" und "Mit
   Battle.net registrieren" nicht auf gleicher Höhe, nur in Firefox) —
   Firefox packt auf <button> serienmäßig einen unsichtbaren Innenrand
   für den Fokus-Ring (Chrome nicht). Bei zwei Elementen mit exakt
   demselben CSS-Padding (hier .konto-absenden als <button> neben
   .bnet-button als <a>) ergibt das in Firefox trotzdem unterschiedliche
   Höhen. Globaler Reset statt Einzelfall-Fix, schützt automatisch auch
   jedes künftige Button-neben-Link-Paar im Projekt. */
button::-moz-focus-inner {
  border: 0;
  padding: 0;
}
/* NACHTRAG (2026-08-12, Sven: "nein mit battlenet ist zu hoch" — auch
   NACH obigem Fokus-Rand-Fix noch ein Rest-Unterschied) — Firefox legt
   auf <button> zusätzlich noch natives Betriebssystem-Aussehen
   (-moz-appearance) mit eigener reservierter Innenhöhe, die
   ::-moz-focus-inner allein nicht wegnimmt. appearance:none schaltet
   das komplett ab, das Element folgt danach nur noch dem eigenen CSS
   (Padding/Border/Radius), exakt wie ein <a>. */
button {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
}

/* ---- Hintergrund: komponierte Master-Grafik mit Ranken-Menü ----
   Bugfix (2026-08-09, Sven per Firefox-Live-Messung mit der
   Debug-Anzeige aus header.php bestätigt): Vorher `inset:0; width:100vw;
   height:100vh; object-fit:cover; object-position:left center` — der
   Browser berechnete den Cover-Zuschnitt dabei SELBST, intern, aus den
   rohen vw/vh-Werten. Das ist eine ZWEITE, unabhängige Berechnung
   derselben Skalierung, die --s/--oy weiter oben für alle Overlay-
   Ebenen (Feuer/Fenster/Wasser/Menü) ohnehin schon liefert — in Firefox
   liefen beide Berechnungen bei bestimmten Fenstergrößen leicht
   auseinander (per `getBoundingClientRect()` nachgemessen: Fehler wuchs
   linear mit dem Abstand von der linken Bildkante, exakt das Muster
   einer echten Skalierungs-Abweichung, nicht nur Rundung). In Chromium/
   Edge trat das nicht auf, war dort also unsichtbar.
   Fix: Bild-Box wird jetzt selbst per --s/--oy positioniert und
   skaliert (dieselbe Formel wie #menu/#scene-fx), object-fit dadurch
   überflüssig (Boxgröße entspricht bereits exakt dem Seitenverhältnis
   des Bildes) — es gibt jetzt nur noch EINE Berechnung, die für Bild
   UND Overlays gleichzeitig gilt, kann dadurch nicht mehr auseinander-
   laufen. */
#site-background {
  position: fixed;
  left: 0;
  top: var(--oy);
  width: calc(var(--s) * 3440);
  height: calc(var(--s) * 1440);
  z-index: var(--z-background);
  display: block;
}

/* =====================================================================
   Belebte Szene: funkelnde Burgfenster + flackerndes Feuer
   ---------------------------------------------------------------------
   Ausgeschnittene Lichter des Artworks (siehe header.php), exakt über
   dem Hintergrund. Positionierung in BILDKOORDINATEN des 2560×1440-
   Originals, umgerechnet über --s (Skalierung) und --oy (vertikaler
   Versatz von object-fit:cover) — dieselbe Mechanik wie beim Menü.

   Bewusst zurückhaltend gehalten: Der Design Guide sagt "nicht überall
   gleichzeitig glühen lassen" und "Licht soll kostbar wirken". Die
   Deckkraft schwankt daher nur zwischen etwa 0,2 und 0,9 — die Lichter
   werden heller und dunkler, verschwinden aber nie ganz. Sie liegen
   additiv über den fest eingebackenen Lichtern, gehen also ohnehin nie
   auf Null.
   ===================================================================== */
#scene-fx img {
  position: fixed;
  pointer-events: none;
  z-index: var(--z-scene-fx);
  will-change: opacity;
}

/* WICHTIG für alle Ebenen hier: Der Ausschnitt muss RINGSUM Luft um das
   Motiv haben und die Ränder müssen auf Alpha 0 auslaufen. Beim ersten
   Versuch war der Feuer-Ausschnitt zu klein — er schnitt mitten durch
   die Flamme (rechts fehlten 59px). Der hart abgeschnittene, aufgehellte
   Flammenrest lag dann auf der vollen Flamme und sah aus wie eine zweite
   Flamme daneben. Alle Ebenen haben jetzt nachweislich Randalpha 0. */

/* ---- Burg: Grundschimmer + drei funkelnde Gruppen ----
   Der Grundschimmer (alle Fenster zusammen) atmet sehr langsam und mit
   geringem Ausschlag — das lässt die ganze Burg leben, ohne zu blinken.
   Darüber funkeln die drei Gruppen einzeln. */
.fx-burg-alle,
.fx-burg {
  left:   calc(var(--s) * 1470);
  top:    calc(var(--oy) + var(--s) * 370);
  width:  calc(var(--s) * 990);
  height: calc(var(--s) * 825);
}
.fx-burg-alle {
  position: fixed;
  pointer-events: none;
  z-index: var(--z-scene-fx);
  filter: brightness(1.7) saturate(1.15);
  animation: burg-schimmern 11s ease-in-out infinite;
}
/* Ausschlag/Tempo verstärkt (2026-08-09, Sven: "bei der Burg können
   die Lichter mehr schimmern/flackern") — tiefere Talwerte, höhere
   Spitzen, etwas kürzere Zyklen als vorher, bewusst noch unterhalb
   eines wirklichen Blinkens (das würde wieder gegen "Licht soll
   kostbar wirken" laufen). */
@keyframes burg-schimmern {
  0%, 100% { opacity: 0.22; }
  35%      { opacity: 0.8;  }
  70%      { opacity: 0.38; }
}
/* Helligkeitsfilter (2026-08-07, Sven: "bei der burg keine funkeln zu
   sehen"): Die ausgeschnittenen Fensterlichter sind genauso hell wie
   die fest eingebackenen darunter — ihr Ein- und Ausblenden erzeugte
   dadurch nur einen winzigen Unterschied. Angehoben liegt spürbar
   mehr Licht obendrauf, das Funkeln wird sichtbar. */
.fx-burg {
  filter: brightness(2.3) saturate(1.25);
}
.fx-burg-0 { animation: funkeln-a 4.8s ease-in-out infinite; }
.fx-burg-1 { animation: funkeln-b 6.2s ease-in-out infinite; animation-delay: -2.4s; }
.fx-burg-2 { animation: funkeln-c 7.4s ease-in-out infinite; animation-delay: -5.1s; }

@keyframes funkeln-a {
  0%, 100% { opacity: 0.15; }
  28%      { opacity: 1;    }
  52%      { opacity: 0.3;  }
  76%      { opacity: 0.85; }
}
@keyframes funkeln-b {
  0%, 100% { opacity: 0.35; }
  19%      { opacity: 0.1;  }
  44%      { opacity: 1;    }
  68%      { opacity: 0.35; }
  88%      { opacity: 0.65; }
}
@keyframes funkeln-c {
  0%, 100% { opacity: 0.55; }
  22%      { opacity: 0.15; }
  41%      { opacity: 0.95; }
  63%      { opacity: 0.15; }
  84%      { opacity: 1;    }
}

/* ---- Feuerschalen an den Standarten ----
   Deutlich schneller und unregelmäßiger als die Fenster — echtes Feuer
   flackert hektisch und nie im Takt. Beide Schalen haben ungleiche
   Laufzeiten (2,4s / 2,9s), damit sie sich nie synchronisieren.
   Nur Deckkraft und ein Hauch Höhenversatz, KEINE Skalierung. */
.fx-feuer-links {
  left:   calc(var(--s) * 480);
  top:    calc(var(--oy) + var(--s) * 1070);
  width:  calc(var(--s) * 200);
  height: calc(var(--s) * 275);
  animation: feuer-flackern 2.4s linear infinite;
}
.fx-feuer-rechts {
  left:   calc(var(--s) * 2040);
  top:    calc(var(--oy) + var(--s) * 1045);
  width:  calc(var(--s) * 160);
  height: calc(var(--s) * 327);
  animation: feuer-flackern 2.9s linear infinite;
  animation-delay: -1.3s;
}
@keyframes feuer-flackern {
  0%   { opacity: 0.35; transform: translateY(0); }
  7%   { opacity: 0.85; }
  14%  { opacity: 0.45; transform: translateY(calc(var(--s) * -1)); }
  23%  { opacity: 1;    }
  31%  { opacity: 0.55; }
  42%  { opacity: 0.9;  transform: translateY(calc(var(--s) * 1)); }
  50%  { opacity: 0.4;  }
  61%  { opacity: 0.95; }
  70%  { opacity: 0.5;  transform: translateY(calc(var(--s) * -1)); }
  79%  { opacity: 0.8;  }
  88%  { opacity: 0.45; }
  100% { opacity: 0.35; transform: translateY(0); }
}

/* ---- Wasserfall: fließendes Schimmern ----
   Zwei identische Ebenen wandern langsam nach unten und blenden dabei
   ein und aus, versetzt zueinander (animation-delay). Während die eine
   verschwindet, ist die nächste schon unterwegs — das ergibt einen
   durchgehenden Fluss nach unten statt eines Pulsierens.
   Der Weg ist mit 30 Einheiten bewusst kurz: Das Wasser soll schimmern,
   nicht als Ganzes verrutschen. Die Ebene selbst hat weiche Ränder
   (Alpha-Summe am Rand exakt 0), deshalb entsteht keine Kante.
   timing linear — fließendes Wasser beschleunigt nicht. */
.fx-wasser {
  left:   calc(var(--s) * 1630);
  top:    calc(var(--oy) + var(--s) * 760);
  width:  calc(var(--s) * 400);
  height: calc(var(--s) * 360);
  /* Deutlich mehr Leuchtkraft (2026-08-09, Sven: erste Anhebung auf
     1.55 war "leider auch zu schwach" — jetzt 2.3, zusätzlich
     saturate für mehr Farbintensität statt nur Helligkeit. */
  filter: brightness(2.3) saturate(1.3);
  animation: wasser-fliessen 4.5s linear infinite;
}
.fx-wasser-1 { animation-delay: -2.25s; }
@keyframes wasser-fliessen {
  0%   { opacity: 0;    transform: translateY(calc(var(--s) * -14)); }
  25%  { opacity: 0.95; }
  70%  { opacity: 0.75; }
  100% { opacity: 0;    transform: translateY(calc(var(--s) * 16)); }
}

/* ---- See: Schimmern + aufblitzende Lichtpunkte ----
   Die Spiegelungen des Sees sind in drei über die Fläche VERSTREUTE
   Gruppen geschnitten (feines 22er-Raster modulo 3). Dadurch liegen die
   Lichtpunkte jeder Gruppe verteilt über den ganzen See — blendet eine
   Gruppe kurz auf, blitzt es an vielen Stellen gleichzeitig kurz auf,
   statt dass eine Fläche blinkt. Das ist die Antwort auf "als würden
   Fische springen": Es wird kein Fisch erfunden (dafür gibt es keine
   Grafik und er würde neben dem gemalten Artwork billig aussehen),
   sondern das Wasser selbst bewegt sich.
   Gruppe 0 und 1 schimmern ruhig, Gruppe 2 setzt kurze Blitzer. */
.fx-see {
  left:   calc(var(--s) * 700);
  top:    calc(var(--oy) + var(--s) * 1050);
  width:  calc(var(--s) * 1370);
  height: calc(var(--s) * 386);
  /* Etwas mehr Leuchtkraft (2026-08-09, Sven: "über dem Wasser mehr
     schimmern") — 1.4 -> 1.6. */
  filter: brightness(1.6);
}
.fx-see-0 { animation: see-schimmern 8.5s ease-in-out infinite; }
.fx-see-1 { animation: see-schimmern 11.7s ease-in-out infinite; animation-delay: -4.2s; }
.fx-see-2 { animation: see-blitzen 9.1s linear infinite; }
@keyframes see-schimmern {
  0%, 100% { opacity: 0.22; }
  30%      { opacity: 0.8;  }
  62%      { opacity: 0.4;  }
}
@keyframes see-blitzen {
  0%,  9%   { opacity: 0.08; }
  12%       { opacity: 0.75; }
  16%, 37%  { opacity: 0.08; }
  40%       { opacity: 0.65; }
  44%, 68%  { opacity: 0.08; }
  72%       { opacity: 0.8;  }
  76%, 100% { opacity: 0.08; }
}

/* Mondlicht-Glanz auf dem See (2026-08-09) wieder entfernt — Sven:
   "sieht nicht schön aus". War ein länglicher, warm-weißer
   Schimmerstreif per radial-gradients (.fx-mondglanz), der über die
   Seefläche wanderte. Bei Bedarf später mit anderem Ansatz neu
   versuchen (siehe ROADMAP.md), nicht einfach 1:1 wiederholen. */

/* ---- Aufsteigende Lichtpunkte über dem Wasser (2026-08-09) ----
   Wie .stern (Sternenfunkeln), aber tiefer positioniert, warm gefärbt
   und mit echtem Aufsteigen statt nur Blinken — wirkt wie Funken/
   Glühwürmchen über der Seefläche. Feste, aber unregelmäßig wirkende
   Positionen innerhalb des See-Bereichs, siehe .fx-see oben. */
.fx-funke {
  position: fixed;
  width:  calc(var(--s) * 4);
  height: calc(var(--s) * 4);
  border-radius: 50%;
  background: #ffdca8;
  box-shadow: 0 0 calc(var(--s) * 8) calc(var(--s) * 2) rgba(255, 200, 130, 0.85);
  pointer-events: none;
  z-index: var(--z-scene-fx);
  opacity: 0;
  animation: funke-aufsteigen var(--dauer) ease-in infinite;
  animation-delay: var(--verzoegerung);
}
@keyframes funke-aufsteigen {
  0%   { opacity: 0; transform: translateY(0); }
  15%  { opacity: 0.9; }
  75%  { opacity: 0.5; }
  100% { opacity: 0; transform: translateY(calc(var(--s) * -220)); }
}

/* ---- Gischt-Glühen am Fuß des Wasserfalls (2026-08-09) ----
   Weicher, heller Puls genau dort, wo .fx-wasser auf den See trifft
   (siehe Koordinaten dort: Ende bei x≈1630-2030, y≈oy+1120) — simuliert
   kurz aufleuchtende Gischt, ohne eine neue Spritzer-Grafik zu
   erfinden. */
.fx-gischt {
  position: fixed;
  left:   calc(var(--s) * 1680);
  top:    calc(var(--oy) + var(--s) * 1080);
  /* Deutlich kleiner + schwächer (2026-08-09, Sven: "ist etwas zu
     stark am Wasser unten" — 260x90 -> 160x55, Alpha im Verlauf
     0.9 -> 0.55, mehr Weichzeichnung 4 -> 7 gegen den harten
     Blob-Eindruck, Spitzenopazität 0.75 -> 0.4). */
  width:  calc(var(--s) * 160);
  height: calc(var(--s) * 55);
  pointer-events: none;
  z-index: var(--z-scene-fx);
  background: radial-gradient(ellipse 60% 70% at 50% 50%, rgba(255, 255, 255, 0.55), transparent 75%);
  filter: blur(calc(var(--s) * 7));
  animation: gischt-pulsieren 3.6s ease-in-out infinite;
}
@keyframes gischt-pulsieren {
  0%, 100% { opacity: 0.15; }
  50%      { opacity: 0.4;  }
}
/* ---- Rauchschwaden aus den Burg-Kaminen (2026-08-09) ----
   Weicher grauer Blob, steigt langsam auf und löst sich dabei auf
   (Skalierung + Verblassen) — reine CSS-Ergänzung für die Burg, keine
   neue Grafik. */
.fx-rauch {
  position: fixed;
  /* Kräftiger (2026-08-09, Sven sah zunächst gar nichts — Größe/
     Deckkraft/Alpha angehoben, falls das der Grund war und nicht nur
     ein fehlender Upload). */
  width:  calc(var(--s) * 55);
  height: calc(var(--s) * 95);
  border-radius: 50%;
  background: radial-gradient(ellipse 60% 80% at 50% 100%, rgba(205, 208, 218, 0.75), transparent 75%);
  filter: blur(calc(var(--s) * 3));
  pointer-events: none;
  z-index: var(--z-scene-fx);
  opacity: 0;
  animation: rauch-aufsteigen var(--dauer) ease-out infinite;
  animation-delay: var(--verzoegerung);
}
@keyframes rauch-aufsteigen {
  0%   { opacity: 0;    transform: translateY(0) scale(0.6); }
  20%  { opacity: 0.65; }
  100% { opacity: 0;    transform: translateY(calc(var(--s) * -160)) scale(1.7); }
}
/* ---- Fernes Blinken im Wald/Bergen (2026-08-09) ----
   Kleiner + schwächer als .stern, damit es "weiter weg" wirkt statt
   wie ein normaler Stern am Himmel — kürzeres, selteneres Aufblitzen
   statt sanftem Auf-/Abschwellen. */
.fx-fern {
  position: fixed;
  width:  calc(var(--s) * 2.5);
  height: calc(var(--s) * 2.5);
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 calc(var(--s) * 4) calc(var(--s) * 0.8) rgba(255, 255, 255, 0.6);
  pointer-events: none;
  z-index: var(--z-scene-fx);
  opacity: 0;
  animation: fern-blinken var(--dauer) ease-in-out infinite;
  animation-delay: var(--verzoegerung);
}
@keyframes fern-blinken {
  0%, 85%, 100% { opacity: 0;    }
  90%           { opacity: 0.4;  }
}
@media (prefers-reduced-motion: reduce) {
  .fx-wolke { animation: none; opacity: 0; }
  .fx-funke { animation: none; opacity: 0; }
  .fx-gischt { animation: none; opacity: 0.4; }
  .fx-rauch { animation: none; opacity: 0; }
  .fx-fern { animation: none; opacity: 0; }
}

/* Der frühere Gesamt-Schimmer (ein seitlich ziehender Lichtschleier)
   wurde 2026-08-07 wieder entfernt — Sven: "man kann nicht
   identifizieren, was da leicht von links nach rechts verläuft". Ein
   Effekt, den man bemerkt aber nicht deuten kann, stört mehr als er
   nützt. Die Aufgabe übernimmt jetzt die Magie-Ebene (magie.js). */

/* ---- Wolke am Mond, reines CSS (2026-08-09) ----
   Sven: "am Mond ab und zu eine Wolke die vorbeizieht", ausdrücklich
   OHNE echtes Bild-Ausschnitt (noch keiner vorhanden). Bewusst anders
   als der oben verworfene Lichtschleier: dort war unklar, WAS man da
   sieht — hier ist die Form/Position eindeutig als Wolke lesbar
   (elliptischer, weich verwaschener Grauschleier genau über dem
   Mondbereich, keine seitenweite Bewegung). Drei überlappende
   radial-gradients statt einer einzigen Ellipse, damit die Silhouette
   nicht wie ein simpler Kreis wirkt. "Ab und zu" statt dauerhaft: der
   allergrößte Teil des 70s-Zyklus ist Opazität 0, nur ein kurzes
   Zeitfenster in der Mitte zieht sichtbar durch.
   Position ist eine GROBE Schätzung aus vorherigen Screenshots (kein
   exakt vermessener Ausschnitt wie bei den übrigen Ebenen) — bei
   Bedarf per Rückmeldung von Sven nachjustieren. */
.fx-wolke {
  position: fixed;
  /* Position nachjustiert (2026-08-09, anhand eines echten Screenshots
     von Sven nachgemessen: Mondmitte lag bei ca. Bildkoordinate
     1871,190 statt der ursprünglich nur geschätzten 1960,170). */
  left:   calc(var(--s) * 1610);
  top:    calc(var(--oy) + var(--s) * 110);
  width:  calc(var(--s) * 520);
  height: calc(var(--s) * 160);
  pointer-events: none;
  z-index: var(--z-scene-fx);
  /* Etwas weniger Weichzeichnung (2026-08-09, Sven: "dürfte stärker
     sein" — 8 -> 6), damit die Silhouette nicht zu sehr im Nichts
     verschwimmt. */
  filter: blur(calc(var(--s) * 6));
  opacity: 0;
  animation: wolke-vorbeiziehen 40s ease-in-out infinite;
}
/* Drei Farbtöne (2026-08-09, Sven: "mehrere Wolken (weiß/schwarz/grau)
   groß/klein") — Grundform (drei überlappende radial-gradients)
   bleibt gleich, nur die Farbwerte wandern in eigene Modifier-Klassen,
   damit Farbe UND Größe unabhängig voneinander kombinierbar sind. */
.fx-wolke-weiss {
  background:
    radial-gradient(ellipse 45% 60% at 30% 55%, rgba(225, 228, 236, 0.95), transparent 70%),
    radial-gradient(ellipse 40% 50% at 60% 40%, rgba(235, 238, 245, 0.9), transparent 70%),
    radial-gradient(ellipse 35% 55% at 82% 60%, rgba(215, 219, 230, 0.85), transparent 70%);
}
.fx-wolke-grau {
  background:
    radial-gradient(ellipse 45% 60% at 30% 55%, rgba(75, 82, 100, 0.95), transparent 70%),
    radial-gradient(ellipse 40% 50% at 60% 40%, rgba(85, 92, 112, 0.9), transparent 70%),
    radial-gradient(ellipse 35% 55% at 82% 60%, rgba(70, 76, 94, 0.85), transparent 70%);
}
.fx-wolke-schwarz {
  background:
    radial-gradient(ellipse 45% 60% at 30% 55%, rgba(20, 21, 28, 0.95), transparent 70%),
    radial-gradient(ellipse 40% 50% at 60% 40%, rgba(28, 30, 38, 0.9), transparent 70%),
    radial-gradient(ellipse 35% 55% at 82% 60%, rgba(16, 17, 24, 0.85), transparent 70%);
}
.fx-wolke-gross { width: calc(var(--s) * 700); height: calc(var(--s) * 210); }
.fx-wolke-klein { width: calc(var(--s) * 320); height: calc(var(--s) * 100); }
/* Gemeinsame Pause-Gruppe (.fx-wolken-gruppe) EINGEBAUT, DANN NOCH AM
   SELBEN ABEND WIEDER ENTFERNT (2026-08-09) — Sven meldete danach "die
   sind nun wie unsichtbar, wenn ich mehrmals F5 drücke sieht man sie",
   spät abends keine Zeit mehr für eine Live-Fehlersuche. Markup in
   header.php ist zurückgebaut (keine Wrapper-Gruppe mehr um die fünf
   Wolken), diese Regel bewusst nicht mit gelöscht, falls beim nächsten
   Anlauf noch der Ansatz an sich (nicht nur die Umsetzung) geprüft
   werden soll. NICHT einfach wieder genauso einbauen, siehe ROADMAP.md. */
/* Echtes Ziehen statt Ein-/Ausblenden an Ort und Stelle (2026-08-09,
   Sven: "kann man die auch ziehen lassen? anstatt ein und ausblenden")
   — transform steht jetzt NUR an 0%/100%, der Browser interpoliert die
   Bewegung dadurch DURCHGEHEND über die volle Zyklusdauer (kein
   Zwischenhalt wie vorher bei 50%/75%). Opacity fügt nur noch am
   Anfang/Ende ein kurzes Ein-/Ausblenden hinzu, während die Wolke die
   ganze Zeit über schon in Bewegung ist. */
/* Startpunkt noch weiter nach links (2026-08-09, Sven zweimal:
   "können gerne von weiter links anfangen", zuletzt "ab dem Wald" —
   -300 -> -550 -> -950), rechtes Ende weiterhin bewusst unverändert. */
@keyframes wolke-vorbeiziehen {
  0%   { opacity: 0; transform: translateX(calc(var(--s) * -950)); }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  100% { opacity: 0; transform: translateX(calc(var(--s) * 300)); }
}

/* Reduzierte Bewegung: Lichter stehen still auf mittlerer Helligkeit. */
@media (prefers-reduced-motion: reduce) {
  .fx-burg, .fx-burg-alle { animation: none; opacity: 0.5; }
  .fx-feuer  { animation: none; opacity: 0.6; }
  .fx-wasser { animation: none; opacity: 0.3; }
  .fx-see    { animation: none; opacity: 0.25; }
  .fx-wasser-1, .fx-see-2 { display: none; }
}

/* ---- Sternenfunkeln über der Hauptseite (2026-08-09) ----
   Eigene Ebene NUR über dem Content-Bereich, die Sidebar bleibt frei
   (left-Wert wie .roster, siehe dort). z-index 15 liegt bewusst
   ÜBER dem eigentlichen Inhalt (--z-main:10), aber deutlich UNTER
   Popups (z-index 40) und dem Menü (--z-menu:20) — pointer-events:none
   sorgt dafür, dass darunter trotzdem alles normal klickbar bleibt. */
#content-sterne {
  position: fixed;
  left: calc(var(--s) * 640);
  right: 0;
  top: 0;
  bottom: 0;
  z-index: 15;
  overflow: hidden;
  pointer-events: none;
}
.stern {
  position: absolute;
  left: var(--x);
  top: var(--y);
  /* Etwas kräftiger (2026-08-09, Sven: "im Hintergrund des
     Hauptfensters kann das Funkeln etwas mehr sein") — 3->4px,
     stärkerer Schein, höhere Spitzenhelligkeit weiter unten. */
  width: calc(var(--s) * 4);
  height: calc(var(--s) * 4);
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 calc(var(--s) * 9) calc(var(--s) * 1.5) rgba(255, 255, 255, 0.85);
  opacity: 0;
  animation: stern-funkeln var(--dauer) ease-in-out infinite;
  animation-delay: var(--verzoegerung);
}
@keyframes stern-funkeln {
  0%, 100% { opacity: 0; }
  50%      { opacity: 0.9; }
}
@media (prefers-reduced-motion: reduce) {
  .stern { animation: none; opacity: 0.25; }
}

/* =====================================================================
   Ranken-Menü (Sidebar) — liegt pixelgenau auf dem eingebauten Rahmen
   Vermessene Panel-Bereiche (Bildraum 2560×1440):
     oberes Panel  x 40–440, y 60–580
     unteres Panel x 30–430, y 680–1370
   ===================================================================== */
#menu {
  position: fixed;
  left: 0;
  top: var(--oy);
  width: calc(var(--s) * 500);
  height: calc(var(--s) * 1440);
  z-index: var(--z-menu);
  pointer-events: none; /* nur echte Links/Buttons/Formularelemente darin sind klickbar */
}
#menu a,
#menu button,
#menu input {
  /* input ergänzt (2026-08-11, Bugfix: Helligkeits-Regler war komplett
     unbedienbar, Sven: "ich kann selbst nichts einstellen") — #menu
     setzt pointer-events:none pauschal und gab es bisher nur für a/
     button gezielt wieder frei, der neue <input type="range"> wurde
     dabei schlicht vergessen und war dadurch nicht klickbar/ziehbar. */
  pointer-events: auto;
}

/* ---- Marke (oberes Panel) ----
   Logo + Maskottchen bilden EINE Gruppe im oberen Rahmen-Fenster
   (Sven, 2026-08-07): Der Panel-Innenraum endet im Bildraum bei y≈620,
   dort beginnt der Quer-Balken; der violette Diamant sitzt bei y 628–660.
   Die Gruppe endet deshalb OBERHALB von 620 — Katze bis y 590, weichster
   Glow-Ausläufer bis y 607 — damit der Diamant frei sichtbar bleibt.
   Beide Werte gehören zusammen: Wird einer geändert, muss der andere um
   denselben Betrag mitwandern. */
.brand {
  position: absolute;
  /* Größen-Test wieder zurückgenommen (2026-08-08, Sven: "Logo bitte
     direkt wieder verkleinern") — zurück auf die alte 360er Breite/
     Position. Runenkreis bleibt trotzdem deaktiviert (das kam beim
     größeren Logo als "besser" zurück, siehe menu.php-Kommentar). */
  left: calc(var(--s) * 60);
  top: calc(var(--s) * 95);
  width: calc(var(--s) * 360);
  text-align: center;
}
/* Logo-Bild (2026-08-08) — freigestellt aus logo_lunatix_ohne_rune.png
   (Original war zu 100% deckend/schwarzer Hintergrund trotz Alpha-
   Kanal, per Helligkeits-Schwellwert transparent gemacht, siehe
   assets/images/logo_lunatix.png). 1774×887 (≈2:1) auf volle
   Spaltenbreite (360) → 180 hoch. Bei top:95 bleiben bis zur Katze
   (emblem-top 294) 19 Einheiten Luft — geprüft per DOM-Messung.
   .brand-logo-wrap ist nötig, damit der Runenkreis (siehe unten)
   absolut relativ zum Logo positioniert werden kann, nicht relativ
   zur ganzen .brand-Spalte. */
.brand-logo-wrap {
  position: relative;
}
.brand-logo {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  height: auto;
  margin: 0 auto;
}
/* ---- Runenkreis hinter dem Logo (2026-08-08, 5. Anlauf: Drehung raus) ----
   Eigene Ebene, NICHT ins Logo eingebacken — unabhängig
   positionierbar.

   Sven hat die "Spitze-auf-A-Spitze"-Vorgabe zurückgezogen ("da er
   sich ja eh dreht ist die Spitze nicht wirklich relevant, war wohl
   mein Fehler") — dadurch freie Wahl bei der Positionierung, nur noch
   "mittig hinter dem Schriftzug, groß genug, kein Überlapp mit der
   Katze" als Vorgaben.

   Die Rotation selbst wurde komplett entfernt (2026-08-08, Sven: "der
   Runenkreis verzerrt weiterhin"): Der 4. Anlauf hatte das Bild bereits
   quadratisch aufgefüllt (1536×1536, siehe Bild-Historie), um die
   Box-Verzerrung bei 90°/270° zu beheben — der Ring verzog sich
   trotzdem weiter sichtbar beim Drehen. Statt einen 6. Anlauf an der
   Animation zu versuchen: Kreis steht jetzt fest, andere Logo-Effekte
   (Blitze etc., siehe Roadmap) sind der nächste Fokus.

   Position/Größe: mittig hinter dem Wortlaut zentriert (Logo-Mitte
   180/90 von 360×180), 340 Einheiten Durchmesser — geprüft, dass der
   sichtbare Ring (nicht der transparente Rand) noch ca. 12 Einheiten
   Luft zur Katze lässt.

   FEHLGRIFF korrigiert (2026-08-08): Der vorherige Versuch, "etwas
   verbreitert" als reine Breiten-Änderung umzusetzen (width 380,
   height weiter 340), machte aus dem runden Ring ein sichtbares Oval
   — bei den vier gleichmäßigen Zacken an den Himmelsrichtungen fällt
   jede Abweichung von der Kreisform sofort auf ("sieht sehr komisch
   aus", Sven). Rückfrage ergab: gleichmäßig größer (380×380, bleibt
   rund) UND vertikal wieder exakt auf die Logo-Mitte zentriert wie
   ursprünglich.

   Feinjustierung (2026-08-08, Sven, nach Live-Blick): "die obere
   Spitze müsste passend zur Spitze vom A sein, aber der Kreis muss
   dennoch weiter nach unten" — die Logo-Mitte-Zentrierung oben hatte
   die Ring-Spitze rechnerisch bei y≈-23 platziert, deutlich ÜBER dem
   ganzen Schriftzug (der bei y=0 beginnt), das erklärt das freie
   Schweben der Spitze im Screenshot.
   Beide Bilder pixelgenau vermessen (Python/PIL, keine Schätzung):
   A-Spitze in logo_lunatix.png bei Pixel (889, 179) von 1774×887 →
   im 360×180-Logo-Maßstab lokal x≈180,4 / y≈36,3 (liegt fast exakt
   auf der horizontalen Logo-Mitte — die Links-Ausrichtung war also
   schon richtig).
   Ring-Spitze in logo_runenkreis.png bei Pixel (772, 311) von
   1536×1536 → bei 380er Boxgröße lokal x≈191,0 / y≈77,0 relativ zur
   Box-Ecke.
   Damit die Ring-Spitze exakt auf die A-Spitze trifft: left = 180,4 −
   191,0 ≈ -10 (praktisch unverändert), top = 36,3 − 77,0 ≈ -41 (statt
   -100, also 59 Einheiten tiefer — das ist das geforderte "weiter
   nach unten").
   ACHTUNG Kollisionsrisiko: Der untere Zacken des Rings sitzt dadurch
   rechnerisch bei y≈275 (lokal) bzw. absolut bei ≈370 im Menü-
   Koordinatenraum — das liegt spürbar UNTERHALB der Emblem-Oberkante
   (294, siehe .emblem unten) und damit im oberen Drittel der Katzen-
   Box (Ohren/Kopf-Bereich). Die Katze steht im DOM nach dem Logo und
   hat kein eigenes z-index, gewinnt die normale Stapelreihenfolge
   also automatisch vor dem Ring (z-index 0) — kein hartes Glitchen zu
   erwarten, der Ring würde einfach hinter der Katze verschwinden,
   aber das könnte trotzdem unschön aussehen.

   Korrektur (2026-08-08, Sven: "der Ring muss mind. 10px nach oben",
   nachdem die Spitze-auf-Spitze-Ausrichtung insgesamt zu tief saß —
   der Großteil des Rings hing unterhalb des Schriftzugs statt ihn zu
   umrahmen): top von -41 um 10 auf -51.
   Weitere Korrektur (2026-08-08, Sven: "weitere 15px nach oben"): top
   von -51 um 15 auf -66.
   Weitere Korrektur (2026-08-08, Sven: "weitere 10px"): top von -66
   um 10 auf -76.
   Größen-Test (2026-08-08, Sven: "ist der Kreis eventuell einfach zu
   groß, was wäre bei 360x360"): von 380×380 auf 360×360 verkleinert,
   Mittelpunkt der vorherigen Box beibehalten (cx=180, cy=114 aus
   left -10/top -76 + halbe Breite 190) → bei 360er Größe (halbe
   Breite 180) ergibt sich left=0, top=-66.
   Weiterer Größen-Test (2026-08-08, Sven: "340x340 bitte"): gleicher
   Mittelpunkt (cx=180, cy=114) beibehalten, bei 340er Größe (halbe
   Breite 170) ergibt sich left=10, top=-56.
   Weitere Korrektur (2026-08-08, Sven: "5px nach oben"): top von -56
   um 5 auf -61. */
.brand-runenkreis {
  position: absolute;
  z-index: 0;
  left: calc(var(--s) * 10);
  top: calc(var(--s) * -61);
  width: calc(var(--s) * 340);
  height: calc(var(--s) * 340);
  pointer-events: none;
}

/* ---- Logo-Animationsebenen (2026-08-08) ----
   Aus .ideensammlung/logos/beschreibung_logo_lunatix_animiert.md
   (Ideensammlung eines früheren Chats, der wegen Kontextgröße beendet
   wurde — Katalog per Transkript-Suche rekonstruiert). Kristall bewusst
   NICHT umgesetzt (Sven: "wollten wir eh nicht mehr beachten").

   Alles hier kommt OHNE neue freigestellte Assets aus: Kristall und
   Wappen sind zwar weiterhin fest im flachen logo_lunatix.png
   eingebacken (keine eigene Bewegung möglich), aber Blitz/Lichtreflex
   nutzen genau dieses Bild als CSS-Maske (mask-image) — die Wirkung
   bleibt dadurch exakt auf die Metall-Buchstaben begrenzt, ganz ohne
   Freistellung. Horde/Allianz-Boxen sind per Pixel-Scan (Python/PIL,
   Alpha>80 + Rot- bzw. Blauanteil dominant) exakt vermessen:
   Horde  (768,618)–(856,742) von 1774×887 → lokal (360er Maßstab)
          left 153 / top 122 / 24×31.
   Allianz (876,620)–(1016,748) von 1774×887 → lokal
          left 175 / top 123 / 34×31. */

/* Arkane Energie/Blitz (MD: "Energie baut sich links auf. Ein
   violetter Blitz schießt durch alle Buchstaben. Das Metall
   reflektiert das Licht. Danach verblasst alles wieder.") — alle 17s,
   kurzer Flash statt Dauerschleife. */
/* Technik-Wechsel (2026-08-08, Sven: "Lichtreflex flackert/springt
   unsauber statt gleichmäßig zu laufen"): Beide Effekte animierten
   bisher background-position auf einer maskierten + geblendeten
   Ebene — background-position ist eine Paint-Eigenschaft, der Browser
   muss sie JEDEN Frame neu zeichnen (kein GPU-Compositing), zusammen
   mit mask-image + mix-blend-mode kann das genau das beschriebene
   Ruckeln/Springen verursachen. Jetzt bewegt sich stattdessen ein
   ::after-Kindelement per transform:translateX() — transform läuft
   auf dem Compositor, unabhängig vom Rest neu berechnet, spürbar
   ruckelfrei. Der äußere Container bleibt der Masken-/Blend-Rahmen
   (unverändert exakt auf die Metall-Buchstaben begrenzt), overflow:
   hidden schneidet das breitere Kindelement sauber ab. */
.brand-logo-blitz,
.brand-logo-lichtreflex {
  position: absolute;
  inset: 0;
  z-index: 2;
  overflow: hidden;
  pointer-events: none;
  -webkit-mask-image: url('../images/logo_lunatix.png');
          mask-image: url('../images/logo_lunatix.png');
  -webkit-mask-size: 100% 100%;
          mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  mix-blend-mode: screen;
}
.brand-logo-blitz::after,
.brand-logo-lichtreflex::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 60%;
  will-change: transform;
}
.brand-logo-blitz {
  opacity: 0;
  animation: logo-blitz-opazitaet 17s ease-in infinite;
}
.brand-logo-blitz::after {
  background: linear-gradient(100deg,
    transparent 0%,
    rgba(177, 140, 255, .9) 42%,
    rgba(232, 214, 255, 1) 50%,
    rgba(177, 140, 255, .9) 58%,
    transparent 100%);
  animation: logo-blitz-lauf 17s ease-in infinite;
}
/* Lauftempo (2026-08-08, Sven: "das von rechts nach links verlaufende
   Element ist einmal normal und einmal rasend schnell, sieht unsauber
   aus"): Der Blitz legte seine komplette Strecke ursprünglich in nur
   15% von 17s (≈2,55s) zurück — mehr als doppelt so schnell wie der
   Lichtreflex (damals 300% in 6s). Jetzt läuft er über 40% des
   Zyklus (≈6,8s), Tempo damit fast identisch zum Lichtreflex, nur der
   stille Rest davor bleibt lang (Aufbau-Pause). Zwei Animationen statt
   einer, weil opacity (Sichtbarkeit) und transform (Laufweg) getrennte
   Eigenschaften sind. */
@keyframes logo-blitz-opazitaet {
  0%, 60% { opacity: 0; }
  68%     { opacity: .85; }
  85%     { opacity: 1; }
  100%    { opacity: 0; }
}
@keyframes logo-blitz-lauf {
  0%, 60% { transform: translateX(-160%); }
  68%     { transform: translateX(-40%); }
  85%     { transform: translateX(120%); }
  100%    { transform: translateX(240%); }
}

/* Lichtreflex (MD: "Alle paar Sekunden läuft ein Licht über das
   Metall. Nicht als weißer Balken. Sondern wie echtes Mondlicht.") —
   deutlich häufiger und dezenter als der Blitz, dauerhaft in
   Schleife statt als seltener Flash. */
.brand-logo-lichtreflex::after {
  background: linear-gradient(115deg,
    transparent 0%,
    transparent 38%,
    rgba(226, 224, 255, .55) 50%,
    transparent 62%,
    transparent 100%);
  animation: logo-lichtreflex-lauf 6s linear infinite;
}
@keyframes logo-lichtreflex-lauf {
  0%   { transform: translateX(-160%); }
  100% { transform: translateX(240%); }
}

/* Horde/Allianz — NUR NOCH kräftiger dargestellt, kein Glow mehr
   (Sven, 2026-08-08: "sind nicht schön übereinander gelegt, wir
   lassen es einfach damit dass sie kräftiger dargestellt werden").
   Das pulsierende box-shadow-Glimmen ist komplett raus.
   Zusätzlich sauberer als vorher: Statt einer rechteckigen/ovalen Box
   (die auf dem transparenten Logo-Hintergrund ringsum sichtbar
   "mitgesättigt" hat — vermutlich der Hauptgrund fürs unschöne
   Übereinanderliegen) nutzt backdrop-filter jetzt dieselbe
   mask-image-Technik wie Blitz/Lichtreflex: Die Aufsättigung wirkt
   dadurch NUR auf die tatsächlichen Wappen-Pixel, nicht auf den
   transparenten Rand drumherum. mask-size ist die volle Logogröße,
   mask-position verschiebt die Maske um genau denselben Betrag wie
   left/top der Box negativ — Sprite-Masking-Standardtechnik.

   Sprite-Zoom wieder verworfen (2026-08-08, Sven: "sieht sehr komisch
   aus"): Die Wappen sind im Quellbild nur ~30px groß — jede
   Vergrößerung (auch nur 1,5-fach) vergrößert zwangsläufig auch die
   vorhandene Unschärfe, wirkt dadurch wie ein verwaschener Fleck statt
   ein schärferes Wappen. Zurück auf die saubere Maskentechnik (nur
   Aufsättigung, keine Größenänderung) — daf sitzt exakt auf den
   echten Pixeln, keine künstliche Unschärfe. Dafür jetzt spürbar
   stärker aufgesättigt als beim ersten Versuch (Sven wollte "wieder
   kräftiger"). Eine ECHTE Vergrößerung ohne Unschärfe bräuchte die
   Wappen als eigene, hochauflösend freigestellte Assets — derselbe
   Blocker wie beim Kristall. */
.brand-emblem-glow {
  position: absolute;
  z-index: 2;
  pointer-events: none;
  -webkit-mask-image: url('../images/logo_lunatix.png');
          mask-image: url('../images/logo_lunatix.png');
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-size: calc(var(--s) * 360) calc(var(--s) * 180);
          mask-size: calc(var(--s) * 360) calc(var(--s) * 180);
  -webkit-backdrop-filter: saturate(2.6) contrast(1.35) brightness(1.2);
          backdrop-filter: saturate(2.6) contrast(1.35) brightness(1.2);
}
.brand-emblem-horde {
  left: calc(var(--s) * 153);
  top: calc(var(--s) * 122);
  width: calc(var(--s) * 24);
  height: calc(var(--s) * 31);
  -webkit-mask-position: calc(var(--s) * -153) calc(var(--s) * -122);
          mask-position: calc(var(--s) * -153) calc(var(--s) * -122);
}
.brand-emblem-allianz {
  left: calc(var(--s) * 175);
  top: calc(var(--s) * 123);
  width: calc(var(--s) * 34);
  height: calc(var(--s) * 31);
  -webkit-mask-position: calc(var(--s) * -175) calc(var(--s) * -123);
          mask-position: calc(var(--s) * -175) calc(var(--s) * -123);
}

/* Versteckt den Text visuell, bleibt aber für Screenreader/Browsertab
   vorhanden — kein „nur Bild ohne Textalternative". Standard-Technik
   (nicht display:none, das würde auch Screenreadern den Text nehmen). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ---- Maskottchen (Asset-Pack 05_MASCOT_CAT) ----
   Zwei Ebenen: cat_glow (Flammen) hinten, cat_full (Katze) vorn.
   Die Katze steht FEST — sie hat bewusst KEINE eigene Bewegung, sonst
   löst sie sich optisch vom Diamanten, auf dem sie steht (Sven,
   2026-08-07). Animiert wird ausschließlich das Licht. */
.emblem {
  position: absolute;
  /* z-index (2026-08-08, Bugfix: "Rauch steigt nicht einmal über die
     Katze hinaus"): .brand-logo & Co haben explizite z-index-Werte
     (1/2 für Blitz/Lichtreflex-Ebenen) — per CSS-Stacking-Regeln
     werden Kinder mit explizitem z-index aus ihrer eigentlichen
     Position "herausgehoben" und immer ÜBER JEDEM Element ohne
     eigenen z-index gemalt, egal wo sie im DOM stehen. .emblem (Katze
     + Rauch) hatte bisher gar keinen z-index, landete dadurch IMMER
     hinter dem Logo, sobald der Rauch dort hochstieg — unabhängig von
     Transform-Position. Fix: eigener, höherer z-index, damit Rauch
     (und die Katze) bei Überlappung sichtbar vor dem Logo liegen. */
  z-index: 3;
  left: calc(var(--s) * 70);
  /* 294 + 340 = 634. Gemessen: Der FESTE Körper der Katze endet bei
     97,8% der Bildhöhe, die untersten 7,6 Einheiten sind nur noch feine
     Ranken. Die sichtbaren Pfoten sitzen damit auf 626,4 — exakt der
     Oberkante des Diamanten (626). Vorher (284) schwebten sie real 10
     Einheiten darüber, obwohl die Box rechnerisch bündig war. */
  top: calc(var(--s) * 294);
  width: calc(var(--s) * 340);
  height: calc(var(--s) * 340);
}
.emblem img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Flammen — MÜSSEN deutlich größer sein als die Katze, sonst sind sie
   unsichtbar: Der sichtbare Inhalt von cat_glow.png füllt nur 79% der
   Boxbreite und 80% der Boxhöhe (gemessen), bei gleicher Boxgröße lägen
   die Flammen also vollständig hinter der 331px breiten Katze.
   Box 500×500 an Position (-80/-150) relativ zur Katze, dadurch exakt
   mittig zu ihr (Katzen-Mitte x 240) → sichtbare Flammen x 35–433
   gegenüber der Katze bei 74–406 und y 222–624 (enden an den Pfoten).
   Bewusst NICHT größer: Die Flammen würden im Atem-Höhepunkt sonst bis
   über den LunatiX-Schriftzug wachsen. Für die Sichtbarkeit sorgt der
   filter weiter unten, nicht die Größe.
   filter (2026-08-07, Sven: "immer noch kein Glow-Effekt"): Der Grund
   war NICHT die Größe. Die Flammen in cat_glow.png sind dunkelviolett —
   und liegen auf einem ohnehin violett gemusterten Panel. Sie
   verschmolzen dadurch mit der Hintergrundtextur. brightness/saturate
   hebt ausschließlich die im Asset vorhandene Farbe an (kein neuer
   Farbwert), bis sie sich vom Panel abhebt. Nicht höher drehen: ab etwa
   brightness(2) kippt der Rauch sichtbar ins Rosa und verlässt damit
   die Violett/Gold-Palette.

   KEIN scale() mehr (Sven, 2026-08-07: "soll nicht groß und klein
   werden, soll sich einfach wie Rauch verhalten"). Vorher skalierte
   diese Ebene 0.96 → 1.07 und pumpte dadurch sichtbar — das war es,
   was im Hintergrund atmete, nicht der Rauch. Feuer und Rauch ändern
   ihre Größe nicht, sie flackern in der HELLIGKEIT und wabern leicht.
   Deshalb jetzt: unregelmäßige Deckkraft-Stufen mit ungleichen
   Abständen (echtes Feuer flackert nie im Takt) plus ein Hauch
   seitliches Wandern — keine einzige Größenänderung. */
.emblem .glow {
  inset: auto;
  left: calc(var(--s) * -80);
  top: calc(var(--s) * -150);
  width: calc(var(--s) * 500);
  height: calc(var(--s) * 500);
  filter: brightness(1.5) saturate(1.35);
  animation: glow-flackern 7.3s linear infinite;
}
@keyframes glow-flackern {
  0%   { opacity: 0.72; transform: translateX(0); }
  9%   { opacity: 0.95; }
  17%  { opacity: 0.66; transform: translateX(calc(var(--s) * 2)); }
  28%  { opacity: 1;    }
  36%  { opacity: 0.78; }
  47%  { opacity: 0.62; transform: translateX(calc(var(--s) * -2)); }
  55%  { opacity: 0.9;  }
  63%  { opacity: 0.7;  }
  74%  { opacity: 1;    transform: translateX(calc(var(--s) * 1)); }
  82%  { opacity: 0.68; }
  91%  { opacity: 0.88; }
  100% { opacity: 0.72; transform: translateX(0); }
}

/* Fraktions-Wechsel auf der Brust (Sven, 2026-08-07):
   Horde ~10s, langsam über 3s auf Allianz, dort ~10s, langsam zurück.
   Die Horde-Fassung liegt fest darunter, nur die Allianz-Fassung wird
   ein- und ausgeblendet — dadurch gibt es keinen Moment, in dem beide
   halb durchsichtig sind und der Hintergrund durchscheint.
   Zyklus 26s: 0–10s Horde (0–38,46%), Überblendung bis 13s (50%),
   Allianz bis 23s (88,46%), Rückblende bis 26s. */
.emblem .cat-allianz {
  animation: fraktion-wechsel 26s linear infinite;
}
@keyframes fraktion-wechsel {
  0%,     38.46% { opacity: 0; }
  50%,    88.46% { opacity: 1; }
  100%           { opacity: 0; }
}

/* Rauch (cat_shadow_clean.png) — AUFSTEIGEND, nicht pulsierend.
   (Sven, 2026-08-07: "der rauch wird groß und klein, kann es nicht
   einfach flüssig wie bei rauch dargestellt werden?")
   Vorher wurde die Grafik per scale() gepumpt — das liest sich als
   pulsierender Klecks, nie als Rauch. Jetzt steigt sie stattdessen
   gleichmäßig auf und blendet dabei aus, so wie echter Rauch verweht.

   Zwei Ebenen mit halb versetztem Start (animation-delay negativ, damit
   sofort beim Laden schon Rauch da ist) ergeben einen DURCHGEHENDEN
   Strom: Während die eine ausblendet, ist die nächste schon unterwegs —
   es gibt keinen Moment ohne Rauch und kein sichtbares "Atmen".
   timing-function linear: Rauch beschleunigt nicht, jede andere Kurve
   würde wieder als Pumpen wahrgenommen.
   Unterschiedliche Laufzeiten (16s / 19s) verhindern, dass sich beide
   Ebenen je synchronisieren.

   smoke-a liegt HINTER der Katze, smoke-b davor — dadurch wirkt der
   Rauch räumlich um sie herum statt als Fläche dahinter. Der filter
   hebt (wie beim Glow) nur die im Asset vorhandene Farbe an. */
.emblem .smoke-a,
.emblem .smoke-b,
.emblem .smoke-c,
.emblem .smoke-feet {
  inset: auto;
  pointer-events: none;
}

/* Vordere Schwaden: aufgehellt, damit sie sich vom Panel abheben. */
.emblem .smoke-b,
.emblem .smoke-c {
  filter: brightness(1.6) saturate(1.15);
}

/* HINTEN bewusst ANDERS als vorne (Sven, 2026-08-07: "hinter der Katze
   anders dargestellt"): abgedunkelt und weichgezeichnet statt hell.
   Grund: Hinter einer deckenden Katze sieht man ohnehin nur die Ränder —
   helle Schwaden wirkten dort wie abgerissene Fetzen ("keine Ahnung was
   das darstellen soll"). Dunkel und unscharf liest sich derselbe Rauch
   als Tiefe und Schatten hinter ihr, nicht als Objekt. Der Weichzeichner
   nimmt der Grafik zusätzlich ihre erkennbare Form. */
.emblem .smoke-a {
  /* ACHTUNG: --s ist bereits eine LÄNGE (100vw geteilt durch eine Zahl).
     blur(calc(var(--s) * 6px)) wäre Länge mal Länge und damit ungültig —
     dann fällt die GANZE filter-Angabe still aus. Korrekt ist der reine
     Faktor ohne Einheit. */
  filter: brightness(0.55) saturate(0.85) blur(calc(var(--s) * 6));
}

/* Aufsteigende Rauchwolke hinter der Katze (Sven, 2026-08-07: "es soll
   richtig aufsteigen, als wäre es eine Wolke").
   EINE Ebene kann nie nach Wolke aussehen — sie bleibt ein einzelner
   Fleck, der hochwandert. Deshalb DREI Schwaden, die
     · jeweils an den Pfoten starten und weit nach oben ziehen (330),
     · zu je einem Drittel des Zyklus versetzt loslaufen,
     · unterschiedlich breit sind und in verschiedene Richtungen driften.
   Dadurch überlappen sich immer zwei bis drei Schwaden in verschiedenen
   Höhen — das liest sich als quellende Wolke statt als wanderndes
   Objekt. Keine Skalierung; die Fülle entsteht aus der Überlagerung. */
/* WARUM die hinteren Schwaden vorher nicht als Wolke lasen (Sven,
   2026-08-07: "keine Ahnung was das darstellen soll"): Sie waren mit
   390 Einheiten kaum breiter als die Katze (340) — und weil sie HINTER
   ihr liegen und sie deckend ist, blieben nur schmale Streifen an den
   Rändern sichtbar. Ein paar Fetzen links und rechts ergeben kein Bild.
   Jetzt 520–600 breit: Der Rauch quillt seitlich deutlich heraus und
   steigt über ihren Kopf hinaus — dort ist er frei sichtbar und liest
   sich endlich als Wolke. */
.emblem .smoke-a {
  width: calc(var(--s) * 520);
  height: calc(var(--s) * 380);
  animation: rauch-steigen 15s linear infinite;
}
.emblem .smoke-a1 {
  left: calc(var(--s) * -90);
  top:  calc(var(--s) * -40);    /* Unterkante 634 = Pfotenhöhe */
}
.emblem .smoke-a2 {
  left: calc(var(--s) * -40);
  top:  calc(var(--s) * -20);
  width: calc(var(--s) * 420);
  animation-delay: -5s;
  animation-name: rauch-steigen-2;
}
.emblem .smoke-a3 {
  left: calc(var(--s) * -130);
  top:  calc(var(--s) * -60);
  width: calc(var(--s) * 600);
  animation-delay: -10s;
  animation-name: rauch-steigen-3;
}
/* Ziel geändert (2026-08-08, Sven: "der Rauch der aufsteigt soll ins
   Logo aufsteigen und dann noch 1-2 Sekunden ums Logo schwirren und
   verschwindet dann"): Vorher stieg die Wolke geradlinig bis -330 (weit
   über die Sidebar hinaus) und verblasste dabei schon lange vorher fast
   unsichtbar. Jetzt drei Phasen statt einer geraden Linie:
     1. Aufstieg (0–58%) bis auf Höhe des Logos.
     2. Schwirren (58–70%, bei 15s Zyklusdauer ≈1,8s): kurzes Hin-und-Her
        in X bei nahezu gleichbleibendem Y — wirkt wie Rauch, der um die
        Buchstaben herumtreibt statt stur weiterzusteigen.
     3. Auflösen (70–100%): Deckkraft auf 0, dabei minimal weiter nach
        oben/außen — Rauch verweht, statt hart abzuschneiden.
   Die drei Ebenen zielen auf leicht unterschiedliche Höhen/Richtungen
   (wie schon vorher bei den Startpositionen), damit es nach einer
   Wolke aussieht statt nach drei synchronen Kopien. */
@keyframes rauch-steigen {
  0%   { opacity: 0;    transform: translate(0, 0); }
  14%  { opacity: 0.9;  transform: translate(calc(var(--s) * 4),  calc(var(--s) * -14)); }
  40%  { opacity: 0.85; transform: translate(calc(var(--s) * 9),  calc(var(--s) * -36)); }
  58%  { opacity: 0.8;  transform: translate(calc(var(--s) * 14), calc(var(--s) * -54)); }
  64%  { opacity: 0.75; transform: translate(calc(var(--s) * 26), calc(var(--s) * -58)); }
  70%  { opacity: 0.6;  transform: translate(calc(var(--s) * 4),  calc(var(--s) * -60)); }
  100% { opacity: 0;    transform: translate(calc(var(--s) * 14), calc(var(--s) * -90)); }
}
@keyframes rauch-steigen-2 {
  0%   { opacity: 0;    transform: translate(0, 0); }
  16%  { opacity: 0.85; transform: translate(calc(var(--s) * -6),  calc(var(--s) * -18)); }
  42%  { opacity: 0.8;  transform: translate(calc(var(--s) * -16), calc(var(--s) * -48)); }
  58%  { opacity: 0.75; transform: translate(calc(var(--s) * -24), calc(var(--s) * -74)); }
  64%  { opacity: 0.7;  transform: translate(calc(var(--s) * -8),  calc(var(--s) * -78)); }
  70%  { opacity: 0.55; transform: translate(calc(var(--s) * -30), calc(var(--s) * -80)); }
  100% { opacity: 0;    transform: translate(calc(var(--s) * -20), calc(var(--s) * -110)); }
}
@keyframes rauch-steigen-3 {
  0%   { opacity: 0;    transform: translate(0, 0); }
  12%  { opacity: 0.85; transform: translate(calc(var(--s) * 3),  calc(var(--s) * -10)); }
  38%  { opacity: 0.8;  transform: translate(calc(var(--s) * 7),  calc(var(--s) * -22)); }
  58%  { opacity: 0.75; transform: translate(calc(var(--s) * 8),  calc(var(--s) * -34)); }
  64%  { opacity: 0.7;  transform: translate(calc(var(--s) * 20), calc(var(--s) * -38)); }
  70%  { opacity: 0.55; transform: translate(calc(var(--s) * -4), calc(var(--s) * -40)); }
  100% { opacity: 0;    transform: translate(calc(var(--s) * 10), calc(var(--s) * -62)); }
}

/* Rauch VOR der Brust — deutlich kräftiger als zuvor (Sven, 2026-08-07:
   "der rauch vor der brust darf viel stärker erscheinen"): Fläche von
   260×200 auf 330×260 vergrößert und die Spitzendeckkraft von 0,38 auf
   0,85 angehoben. Er zieht dadurch sichtbar vor der Brustplatte vorbei,
   ohne das Fraktionswappen dauerhaft zu verdecken — die Deckkraft fällt
   im zweiten Drittel wieder ab, das Wappen ist also immer wieder frei. */
/* ---- Vordere Schwaden: AN DEN FRAKTIONSWECHSEL GEKOPPELT ----
   (Sven, 2026-08-07: "zeitlich so einrichten, dass er immer stärker
   wird, bevor sich das Logo Horde/Allianz wechselt")

   Der Fraktionswechsel läuft im 26s-Zyklus: Horde 0–10s, Überblendung
   10–13s, Allianz 13–23s, Rückblende 23–26s.
   Die Schwaden bekommen deshalb exakt 13s — sie passen damit GENAU
   zweimal in einen Wechselzyklus. Ihr Höhepunkt liegt bei 69% (≈9,0s),
   also unmittelbar bevor das Wappen bei 10s zu kippen beginnt, und
   ebenso vor dem Rückwechsel bei 23s. Die Deckkraft steigt davor
   stufenweise an (0 → 0,25 → 0,5 → 0,9) und fällt während des Wechsels
   wieder ab. Das ist keine ungefähre Abstimmung, sondern feste
   Kopplung: 13 × 2 = 26.
   ACHTUNG: Wird eine der beiden Dauern geändert, muss die andere
   mitgeändert werden, sonst laufen sie auseinander. */
.emblem .smoke-b {
  left: calc(var(--s) * 10);
  top: calc(var(--s) * 110);
  width: calc(var(--s) * 330);
  height: calc(var(--s) * 260);
  animation: rauch-steigen-vorn 13s linear infinite;
}
@keyframes rauch-steigen-vorn {
  0%   { opacity: 0;    transform: translate(0, 0); }
  25%  { opacity: 0.25; }
  50%  { opacity: 0.5;  }
  69%  { opacity: 0.9;  }   /* ≈9,0s — Höhepunkt kurz vor dem Wechsel */
  82%  { opacity: 0.5;  }
  100% { opacity: 0;    transform: translate(calc(var(--s) * -12), calc(var(--s) * -150)); }
}

/* Zweite Wolke, leicht vorlaufend (Start 1s früher) und breiter —
   dadurch quillt der Rauch vor dem Wechsel spürbar an, ohne dass beide
   Schwaden deckungsgleich übereinanderliegen. */
.emblem .smoke-c {
  left: calc(var(--s) * -60);
  top:  calc(var(--s) * 70);
  width: calc(var(--s) * 440);
  height: calc(var(--s) * 300);
  animation: rauch-steigen-vorn-2 13s linear infinite;
  animation-delay: -1s;
}
@keyframes rauch-steigen-vorn-2 {
  0%   { opacity: 0;    transform: translate(0, 0); }
  30%  { opacity: 0.2;  }
  55%  { opacity: 0.4;  }
  72%  { opacity: 0.62; }
  85%  { opacity: 0.35; }
  100% { opacity: 0;    transform: translate(calc(var(--s) * 20), calc(var(--s) * -190)); }
}

/* Dunkle Nebelwolke an den Pfoten (Sven, 2026-08-07). Bewusst ANDERS
   als der helle Rauch darüber: abgedunkelt statt aufgehellt (filter
   brightness 0.5), dadurch wirkt sie wie Bodennebel im Schatten und
   konkurriert nicht mit den Flammen.
   Sie steigt NICHT auf — Bodennebel zieht seitwärts. Deshalb nur ein
   langsames Wandern und ein sanftes An- und Abschwellen der Deckkraft,
   mit 23s bewusst der langsamste Takt aller Ebenen. Keine Skalierung. */
/* ---- Rauch links/rechts, parallel ab den Pfoten (2026-08-08, Sven) ----
   Korrigiert: Der erste Versuch positionierte die Ebenen bei
   emblem-relativ -150 bzw. +230 (Breite 260) — das reichte bis zu
   80 Einheiten über die rechte Menükante (500) bzw. weit links aus
   dem Menü heraus (negativer Seitenbereich). Jetzt auf denselben
   bereits etablierten "Panel-Innenraum" begrenzt wie smoke-feet
   (Seiten-Kommentar dort: "x 40–440" in Bildkoordinaten = emblem-
   relativ -30 bis 370, da .emblem selbst bei left:70 sitzt).
   Zwei gestaffelte Ebenen pro Seite (Sven: "darf wesentlich öfters
   aufsteigen") statt nur einer — dieselbe Mehr-Ebenen-Idee wie bei
   .smoke-a hinter der Katze, nur mit kürzerer Zyklusdauer (8s statt
   17–19s) und halbem Versatz, damit nie eine Lücke ohne Rauch entsteht. */
.emblem .smoke-seite {
  inset: auto;
  pointer-events: none;
  width: calc(var(--s) * 150);
  height: calc(var(--s) * 240);
  filter: brightness(1.5) saturate(1.1);
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
.emblem .smoke-links {
  left: calc(var(--s) * -30);
  animation-name: rauch-steigen-links;
  animation-duration: 8s;
}
.emblem .smoke-links1 { top: calc(var(--s) * 140); }
.emblem .smoke-links2 { top: calc(var(--s) * 160); animation-delay: -4s; }

.emblem .smoke-rechts {
  left: calc(var(--s) * 220);
  animation-name: rauch-steigen-rechts;
  animation-duration: 8s;
}
.emblem .smoke-rechts1 { top: calc(var(--s) * 140); animation-delay: -2s; }
.emblem .smoke-rechts2 { top: calc(var(--s) * 160); animation-delay: -6s; }

@keyframes rauch-steigen-links {
  0%   { opacity: 0;    transform: translate(0, 0); }
  14%  { opacity: 0.7;  }
  60%  { opacity: 0.5;  }
  100% { opacity: 0;    transform: translate(calc(var(--s) * -14), calc(var(--s) * -220)); }
}
@keyframes rauch-steigen-rechts {
  0%   { opacity: 0;    transform: translate(0, 0); }
  14%  { opacity: 0.7;  }
  60%  { opacity: 0.5;  }
  100% { opacity: 0;    transform: translate(calc(var(--s) * 14), calc(var(--s) * -220)); }
}

.emblem .smoke-feet {
  /* Unterkante 622 — endet knapp ÜBER dem Diamanten (ab 626), sonst
     würde die dunkle Wolke ihn abdunkeln. Breite auf den Panel-
     Innenraum begrenzt (x 40–440), sonst läuft sie über den Rahmen. */
  left: calc(var(--s) * -30);
  top: calc(var(--s) * 153);
  width: calc(var(--s) * 400);
  height: calc(var(--s) * 175);
  filter: brightness(0.5) saturate(0.9);
  animation: bodennebel 23s linear infinite;
}
@keyframes bodennebel {
  0%   { opacity: 0.35; transform: translateX(calc(var(--s) * -14)); }
  25%  { opacity: 0.75; }
  50%  { opacity: 0.5;  transform: translateX(calc(var(--s) * 14)); }
  75%  { opacity: 0.8;  }
  100% { opacity: 0.35; transform: translateX(calc(var(--s) * -14)); }
}

/* Reduzierte Bewegung: Licht und Rauch stehen still, bleiben sichtbar. */
@media (prefers-reduced-motion: reduce) {
  .emblem .glow       { animation: none; opacity: 0.85; }
  .emblem .smoke-a    { animation: none; opacity: 0.4; }
  .emblem .smoke-a2, .emblem .smoke-a3 { display: none; }
  .emblem .smoke-b    { animation: none; opacity: 0.45; }
  .emblem .smoke-c    { animation: none; opacity: 0.3; }
  .emblem .smoke-feet { animation: none; opacity: 0.55; }
  .emblem .smoke-seite { animation: none; opacity: 0.4; }
  .brand-logo-blitz       { animation: none; opacity: 0; }
  .brand-logo-lichtreflex { animation: none; }
  .brand-logo-blitz::after, .brand-logo-lichtreflex::after { animation: none; transform: translateX(-160%); }
}

/* ---- Navigation + Konto/Social/Musik als EIN fließender Block
   (2026-08-09, echter Struktur-Bugfix, siehe ausführlichen Kommentar
   in menu.php) ---- #menu-unten trägt jetzt die einzige feste
   Bild-Koordinate (top:705, vorher direkt auf #mainnav), #mainnav
   selbst ist kein position:absolute mehr — seine tatsächliche Höhe
   ergibt sich automatisch aus der echten Anzahl Nav-Punkte (3-6 je
   Rolle). #sidebar-aktionen (siehe weiter unten) folgt direkt im
   normalen Fluss mit einem festen margin-top statt einer eigenen,
   erratenen Gesamt-Koordinate — kollidiert dadurch bei JEDER
   Nav-Länge/jedem Bildschirm-Seitenverhältnis nicht mehr. */
#menu-unten {
  position: absolute;
  left: calc(var(--s) * 70);
  top: calc(var(--s) * 705);
  width: calc(var(--s) * 330);
}
/* Feste Mindesthöhe für die maximale Nav-Punkte-Zahl (2026-08-10, Sven
   per 3 Screenshots Gast/Mitglied/Admin: "Anmelden/Registrieren" bzw.
   "Caiphas | Abmelden" sollen immer auf der Höhe sitzen, die aktuell
   nur beim vollsten Zustand erreicht wird) — "bewerbung" ist NUR für
   Gäste, "mitglieder"/"raidplan"/"loot"/"mein-profil" NUR für
   Eingeloggte, "verwaltung" kommt nur bei Leadership/Admin oben drauf —
   beide Gruppen (Gast vs. Eingeloggt) schließen sich gegenseitig aus,
   das Maximum ist damit IMMER die Punktezahl der eingeloggten
   Leadership/Admin-Ansicht, keine geschätzte Zahl. #mainnav reserviert
   dadurch bei JEDER Rolle denselben Platz, der Rest bleibt bei weniger
   Punkten einfach leer/transparent, #sidebar-aktionen folgt weiterhin
   direkt danach (siehe margin-top weiter unten) und landet dadurch
   immer exakt an derselben Stelle wie beim vollsten Zustand.
   **2026-08-11 auf 7 aktualisiert** (Sven: neuer Nav-Punkt "Loot"
   zwischen Raidplan und Mein Profil, siehe "Loot"-Kommentar in
   menu.php) — vorher 6, siehe History für den alten Wert. Die
   Zeilenhöhe selbst wurde im GLEICHEN Zug von 79 auf 68 verkleinert
   (siehe #mainnav a weiter oben), sonst wäre der gesamte Block höher
   geworden und hätte Konto-/Musik-/Social-Icon-Block nach unten aus dem
   sichtbaren Bereich gedrückt — die 68 hier MUSS mit der 68 dort
   übereinstimmen. WICHTIG bei künftigen Nav-Änderungen: kommt ein
   weiterer gleichzeitig möglicher Punkt dazu, muss die Zahl hier
   (aktuell 7) wieder mit angepasst werden.
   "+ 6px" (aus der ursprünglichen "+5px"-Live-Messung bei 6 Punkten
   hochgerechnet): die border-bottom-Linien ZWISCHEN den Punkten
   (#mainnav a:last-child hat keine, bei n Punkten also n-1 Linien) sind
   echte "1px solid", nicht mit --s skaliert — ohne diesen festen
   Zuschlag wäre der volle Zustand selbst um diese Pixelzahl höher als
   die eigene Reserve. */
#mainnav {
  min-height: calc(var(--s) * 68 * 7 + 6px);
}
/* Mobile Kopfleiste (2026-08-11) — nur im Hochformat sichtbar, siehe
   @media (orientation: portrait) weiter unten im Dokument. Basis-Regel
   MUSS als eigene (nicht-Media-Query-)Regel stehen, sonst wäre ein
   <div> hier ohne jede Regel standardmäßig display:block und würde auch
   im Querformat/Desktop sichtbar sein. */
#mobil-leiste {
  display: none;
}
#mainnav a {
  position: relative; /* Bezugspunkt für den Leuchtbalken bei .active */
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 22);
  /* 79 -> 68 (2026-08-11, direkte Folge des 7. Nav-Punkts "Loot") — bei
     unveränderter Zeilenhöhe wäre der ganze Block 7x79 statt 6x79 hoch
     geworden und hätte den darunterliegenden Konto-/Musik-/Social-Icon-
     Block bei normalen Fensterhöhen aus dem sichtbaren Sidebar-Bereich
     gedrückt (Sven: "die 3 Icons sind komplett nach unten gerutscht,
     egal mit welcher Rolle man eingeloggt ist"). 68 statt 79 hält den
     GESAMTEN Nav-Block bei 7 Zeilen fast exakt so hoch wie vorher bei 6
     Zeilen (7*68 ≈ 6*79), Sven hatte genau diesen Weg schon am 09.08.
     als Idee vorgeschlagen ("Nav-Punkte etwas enger zusammenrücken"),
     jetzt umgesetzt statt nur die Reserve zu vergrößern. */
  height: calc(var(--s) * 68);
  border-bottom: 1px solid rgba(177, 140, 255, 0.14);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 25);
  letter-spacing: 0.08em;
  text-transform: none;
  color: var(--text);
  text-decoration: none;
  transition: color var(--duration-standard) var(--ease-soft),
              text-shadow var(--duration-standard) var(--ease-soft);
}
#mainnav a:last-child {
  border-bottom: none;
}
#mainnav .nav-icon {
  width: calc(var(--s) * 42);
  height: calc(var(--s) * 42);
  object-fit: contain;
  opacity: 0.85;
  transition: opacity var(--duration-standard) var(--ease-soft),
              filter var(--duration-standard) var(--ease-soft);
}
#mainnav a:hover {
  color: var(--violet-glow);
  text-shadow: var(--glow-violet-soft);
}
/* Deutlich von :hover unterscheidbar (2026-08-08, Sven: "man sieht
   wirklich sehr schwer, welcher Button gerade aktiv ist"). Mehrere
   Zwischenstände verworfen (Leuchtbalken, gefüllte Icon-Raute,
   Mondgelb-Glühen+Schweben, violetter Blitz, Rauch von links nach
   rechts) — am Ende die einfachste Lösung gewollt (Sven: "der Button
   soll einfach gelblich dargestellt werden", gleicher Ton wie die
   übrigen Gold-Buttons auf der Seite). Bewusst OHNE jede Animation,
   OHNE Fläche/Balken — nur ein ruhiger Farbwechsel auf Text UND
   Icon. */
#mainnav a.active {
  color: var(--gold-bright);
}
#mainnav a.active .nav-icon {
  opacity: 1;
  filter: sepia(1) saturate(3) brightness(1.05) hue-rotate(-10deg);
}

/* ---- Social-Icons (2026-08-09, jetzt Flex-Kind in #sidebar-aktionen,
   siehe Kommentar dort und in menu.php) ----
   War vorher ein eigenes, absolut positioniertes Element ÜBER der
   Konto-Zeile (Sven: "Buttons Twitch und co an die Stelle von Caiphas
   versetzen") — durch die Verschiebung ins gemeinsame Flex-Markup
   VISUELL vertauscht, keine eigene position/top/left mehr nötig. */
.social {
  width: 100%;
  display: flex;
  justify-content: center;
  gap: calc(var(--s) * 44);
}
.social img {
  width: calc(var(--s) * 50);
  height: calc(var(--s) * 50);
  object-fit: contain;
  opacity: 0.85;
  transition: opacity var(--duration-standard) var(--ease-soft),
              filter var(--duration-standard) var(--ease-soft);
}
.social a:hover img {
  opacity: 1;
  filter: drop-shadow(0 0 8px rgba(177, 140, 255, 0.55));
}

/* ---- Musik-Schalter (Footer, 4. Anlauf 2026-08-08) ----
   Text jetzt so groß wie Online-Anzeige/Copyright ("dezent & gleich
   groß"), das Icon bleibt bewusst deutlich größer als sein eigener
   Text (Sven: "Symbol viel zu klein zur Schriftgröße" — vorher war das
   Verhältnis Icon:Text nur ~1,2:1, jetzt ~1,85:1).
   text-shadow statt Hintergrundfläche für Lesbarkeit (siehe #main-footer
   oben) — dieselbe Schatten-Formel wie bei .footer-online/.footer-copy,
   damit alle drei Elemente gleich "schweben". */
.music-toggle {
  display: inline-flex;
  align-items: center;
  padding: 0;
  background: none;
  border: none;
  cursor: pointer;
  font-family: var(--font-display);
  font-size: calc(var(--s) * 14);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9), 0 2px 10px rgba(0, 0, 0, 0.75);
  transition: color var(--duration-standard) var(--ease-soft);
}
/* Sound-Symbol als CSS-Maske statt als <img>: Nur die Silhouette aus
   icon_sound_clean.png wird verwendet, die Farbe kommt aus den Tokens.
   Dadurch lässt sich exakt umfärben — Gold für "aus", Online-Grün für
   "an" (Sven, 2026-08-07). Das Original icon_sound.png ist eine
   Rechteck-Kachel mit dunklem Hintergrund und 90% deckenden Pixeln,
   als Symbol also nicht direkt verwendbar.
   Basis-Schlagschatten (dunkel) IMMER an, egal ob Musik läuft oder
   nicht — sonst verschwindet das Icon je nach Hintergrundbild. */
.music-toggle .music-mitte {
  position: relative;
  display: inline-block;
}
.music-toggle .music-icon {
  position: absolute;
  top: 50%;
  right: 100%;                      /* links neben dem Text, außerhalb */
  margin-right: calc(var(--s) * 14);
  transform: translateY(-50%);
  width: calc(var(--s) * 32);
  height: calc(var(--s) * 32);
  background-color: var(--gold);
  -webkit-mask: url('../images/icon_sound_clean.png') center / contain no-repeat;
          mask: url('../images/icon_sound_clean.png') center / contain no-repeat;
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.9));
  transition: background-color var(--duration-standard) var(--ease-soft),
              filter var(--duration-standard) var(--ease-soft);
}
.music-toggle:hover {
  color: var(--gold-bright);
}
.music-toggle:hover .music-icon {
  background-color: var(--gold-bright);
}

/* Zustand "Musik läuft": Symbol und Schrift in Online-Grün (dieselbe
   Farbe wie die Online-Anzeige, kein neuer Grünton). Das Icon
   pulsiert sanft, solange Musik läuft (Sven, 2026-08-08: "soll eben
   animiert werden wenn es abspielt") — reine Deckkraft/Schein-Animation,
   KEIN scale(): dieselbe Motion-Regel wie beim Maskottchen-Feuer
   weiter oben ("Licht atmet, nichts pumpt"). */
.music-toggle.an,
.music-toggle.an:hover {
  color: var(--online);
}
.music-toggle.an .music-icon,
.music-toggle.an:hover .music-icon {
  background-color: var(--online);
  animation: musik-icon-pulsieren 2.2s ease-in-out infinite;
}
@keyframes musik-icon-pulsieren {
  0%, 100% { filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.9)) drop-shadow(0 0 calc(var(--s) * 4) rgba(127, 232, 154, 0.55)); }
  50%      { filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.9)) drop-shadow(0 0 calc(var(--s) * 12) rgba(127, 232, 154, 0.95)); }
}

/* =====================================================================
   Content — rechts neben dem Ranken-Menü
   ===================================================================== */
#content {
  position: relative;
  z-index: var(--z-main);
  margin-left: calc(var(--s) * 640);
  padding-right: 4vw;
  /* Reserviert Platz für den fixierten Footer (2026-08-08, Sven: der
     Footer soll transparent im Vordergrund stehen, der Hauptseiten-
     Inhalt darf nie darüberliegen). Ohne dieses Polster würde die
     letzte Kachel-/Kartenreihe ganz unten hinter #main-footer
     verschwinden, sobald bis zum Seitenende gescrollt wird. */
  padding-bottom: calc(var(--s) * var(--footer-h));
}

/* ---- Sanftes Einblenden beim Ansichtswechsel (2026-08-08) ----
   Sven: "ich klicke auf Mitglieder und die Seite kommt zwar direkt,
   aber mit dem Gefühl sie erscheint leicht" — der Inhalt soll weiter
   SOFORT da sein (kein künstliches Verzögern des Ladens selbst), nur
   beim Erscheinen nicht hart "snappen", sondern kurz sanft auffaden.
   script.js setzt bei jedem innerHTML-Wechsel diese Klasse neu (mit
   erzwungenem Reflow dazwischen, sonst würde der Browser eine bereits
   vorhandene Klasse ignorieren und die Animation nicht neu starten).

   BUGFIX (2026-08-08, Sven: "bei Mitglieder bleibt die Seite leer"):
   Die erste Fassung animierte zusätzlich transform:translateY(...).
   Ein transform ≠ none auf einem Vorfahren macht diesen per CSS-Spec
   zum neuen Bezugsrahmen für position:fixed-Nachfahren — und genau
   das ist .roster (volle Fensterhöhe, position:fixed, siehe dort).
   Dadurch bezog sich .roster beim Mitglieder-Klick plötzlich auf
   #content statt auf den Viewport und landete unsichtbar irgendwo
   außerhalb. Die Daten kamen korrekt an (API separat geprüft), nur
   die Darstellung brach. "Neuigkeiten" nutzt intern kein
   position:fixed, deshalb fiel es dort nicht auf. Fix: nur noch
   reines Opacity-Fade, kein transform mehr auf #content. */
#content.einblenden {
  animation: content-einblenden .45s ease both;
}
@keyframes content-einblenden {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  #content.einblenden { animation: none; }
}

/* ---- Footer, 7. Anlauf (2026-08-09) ----
   Musik-Schalter ist ausgezogen (siehe #sidebar-musik weiter unten,
   jetzt Teil von #menu) — Sven: "es geht komplett unter an dieser
   Stelle, weil der Hintergrund dominiert".
   #main-footer enthält jetzt nur noch Online (rechtsbündig,
   Sven: "unterhalb mit der 9 vom Rufer abschließen" — padding-right
   4vw ist exakt derselbe Wert wie .roster's eigener rechter
   Innenabstand, dadurch schließt Online exakt mit der Wert-Spalte des
   Diagramms ab) und Copyright (weiterhin eigenständig zentriert).

   Weiterhin KEIN Hintergrund-Kasten (siehe vorherige Anläufe: eine
   Fläche sah immer wie ein verwaschener Fleck aus) — Schlagschatten
   direkt auf Text macht die Schrift robust lesbar, ohne dass ein
   sichtbares Rechteck entsteht.

   65 -> --footer-h/90 (2026-08-09, Sven: "Impressum · Datenschutz ·
   Rechtliche Hinweise" als zweite Zeile unter dem Copyright, siehe
   .footer-rechtliches). Höhe kommt jetzt aus der zentralen Variable
   --footer-h (siehe :root), NICHT mehr als eigene Zahl hier — jede
   scrollbare Box im Rest der Seite muss exakt an dieser Höhe enden
   (siehe .roster/.konto-formular-seite), das darf nie wieder
   auseinanderlaufen. */
#main-footer {
  position: fixed;
  left: calc(var(--s) * 500);
  right: 0;
  bottom: 0;
  height: calc(var(--s) * var(--footer-h));
  z-index: var(--z-pill);
  display: flex;
  align-items: center;
  justify-content: flex-end;
  /* 2026-08-12, Bugfix: "Online"-Button und "Musik an" (neu von der
     Sidebar in den Footer verschoben) klebten ohne jeden Abstand
     aneinander ("8/22 ONLINEMUSIK AN") — #main-footer hatte bisher nur
     EIN "echtes" Flex-Kind (Online-Button, .footer-copy ist
     position:absolute und damit ohnehin aus dem Fluss genommen), ein
     gap war deshalb nie nötig gewesen. Jetzt mit Musik-Schalter +
     Helligkeits-Regler als weitere direkte Kinder explizit ergänzt.
     Reihenfolge laut Sven: Musik an -> Regler -> Online, von links
     nach rechts (die drei stehen als direkte Geschwister in genau
     dieser Markup-Reihenfolge, siehe includes/menu.php). */
  gap: calc(var(--s) * 32);
  padding-right: 4vw;
  pointer-events: none;
}
#main-footer > * {
  pointer-events: auto;
}

/* ---- Musik-Schalter, eigene Zone über der Sidebar (2026-08-08) ----
   Eigenständiges fixiertes Element statt Teil von #menu: #menu selbst
   ist über --oy an die Bildoberkante gekoppelt, an der Viewport-
   UNTERKANTE ausgerichtet zu bleiben (Sven: "gleiche Höhe wie jetzt")
   braucht ein eigenes position:fixed;bottom:0 — dieselbe Höhe wie
   #main-footer, aber auf Sidebar-Breite und darin mittig (Sven:
   "sollte aber bitte mittig positioniert werden"). */
/* ---- Musik-Schalter, wieder Teil des Sidebar-Rahmens (2026-08-08) ----
   War kurz position:fixed;bottom:0 (an der Viewport-Unterkante) — Sven:
   "der Musik Button muss leider nach oben", saß bei Vollbild UND bei
   kleinerem Fenster zu weit unten (dicht an Taskleiste/Fensterrand).
   Jetzt wieder wie ganz zu Anfang: position:absolute INNERHALB von
   #menu, an die Bildoberkante (--oy) gekoppelt statt an die Viewport-
   Unterkante — dieselben bereits eingemessenen Koordinaten wie
   .social (left 70, width 330), nur direkt darunter (Social-Icons
   enden bei top 1205 + Icon-Höhe 50 ≈ 1255, 45 Einheiten Luft bis
   1300 für den Schalter). */
/* ---- Musik-Schalter + Konto-Link, EINE gemeinsame Zeile (2026-08-08) ----
   Sven: "Musik an und Registrieren nebeneinander setzen, Musik bei
   Registrieren positionieren" — vorher zwei einzeln positionierte
   Elemente untereinander (#sidebar-musik + .konto-link), jetzt ein
   Flex-Container an der bisherigen Musik-Position. Trennzeichen
   (senkrechter Strich) macht die Gruppierung optisch klar, ohne dass
   beide wie ein einziges klickbares Element wirken. */
/* Zwei Zeilen (2026-08-08, Sven: "dann wieder aufteilen: Anmelden |
   Registrieren, darunter Musik An/Aus") statt einer gemeinsamen Zeile —
   #sidebar-aktionen ist jetzt nur noch der stapelnde Rahmen, die
   eigentliche Zentrierung/Trennstriche passieren pro Zeile. */
/* ECHTER STRUKTUR-BUGFIX (2026-08-09, Sven auf einem 24"-Monitor:
   "Caiphas | Abmelden" lag über "Verwaltung") — die lange Geschichte
   fester Pixel-Offsets hier drüber (1300->1295->-10px->-190px->-90px
   ->-70px) hatte alle denselben Grundfehler: #sidebar-aktionen zielte
   auf eine ERRATENE GESAMT-Bildkoordinate, die nur bei Svens exakter
   Auflösung UND exakt 6 Nav-Punkten wirklich aufging. Jetzt kein
   position:absolute mehr — #sidebar-aktionen folgt #mainnav im
   normalen Textfluss (beide sind jetzt Kinder von #menu-unten, siehe
   dort), margin-top ersetzt den erratenen Gesamt-Wert durch einen
   simplen FESTEN ABSTAND zum tatsächlichen (variablen) Nav-Ende —
   dadurch bei jeder Nav-Länge/jedem Bildschirm-Seitenverhältnis
   automatisch korrekt, keine weitere Pixel-Jagd mehr nötig. */
#sidebar-aktionen {
  margin-top: calc(var(--s) * 26);
  width: calc(var(--s) * 330);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--s) * 10);
}
.konto-zeile,
.musik-zeile {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(var(--s) * 18);
}
/* Feine Einzel-Verschiebung (2026-08-09, Sven: "Caiphas | Abmelden 20px
   nach oben, Musik an nur 15px nach oben") — bewusst per transform statt
   negativem margin/eigener top-Position: wirkt rein visuell, verschiebt
   NICHT den Flex-Fluss der jeweils anderen Zeilen (sonst müsste bei
   jeder Verschiebung hier die Position von .social/der jeweils anderen
   Zeile neu nachgerechnet werden). */
.konto-zeile {
  transform: translateY(-20px);
}
.musik-zeile {
  /* -15px -> -5px (2026-08-09, Sven: "Musik noch minimal nach unten") */
  transform: translateY(-5px);
}
/* Helligkeits-Regler, FÜNFTER (endgültig einfachster) Anlauf
   (2026-08-12, Sven: "Helligkeitsbutton soll wieder sichtbar sein mit
   Regler" — nach mehreren gescheiterten Klapp-/Ausfahr-Varianten, siehe
   History, zurück zur simpelsten denkbaren Lösung) — Icon + Regler
   stehen IMMER beide sichtbar nebeneinander, kein Verstecken, kein
   Hover, keine Animation, keine eigene Zustandsverwaltung mehr in
   script.js nötig. Icon in derselben Größe wie .music-icon (32px).
   Kontrast-Regler (2026-08-14) übernimmt exakt dieselbe Optik/Größe —
   gruppierte Selektoren statt eigenem CSS-Block, nur der Wrapper-Name
   unterscheidet sich (siehe menu.php). */
.helligkeit-wrap,
.kontrast-wrap {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--s) * 10);
}
.helligkeit-icon,
.kontrast-icon {
  flex-shrink: 0;
  width: calc(var(--s) * 32);
  height: calc(var(--s) * 32);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: calc(var(--s) * 18);
  color: var(--gold);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9), 0 2px 10px rgba(0, 0, 0, 0.75);
}
.helligkeit-schieber,
.kontrast-schieber {
  width: calc(var(--s) * 110);
  accent-color: var(--gold);
  cursor: pointer;
}
/* SECHSTER Anlauf: Klick-Panel statt Dauer-Anzeige im Footer
   (2026-08-12, Sven: "kleines Menü einbauen, welches bei Klick wie ein
   Popup-Fenster aussieht ... soll sich unten in der Fenster-Ecke öffnen
   und wieder schließbar sein") — .footer-einstellungen-btn sitzt normal
   im Footer-Fluss, .footer-einstellungen-panel ist dagegen
   position:fixed und dadurch bewusst NICHT an den Footer gebunden,
   sondern fest an der Fenster-Ecke selbst verankert (bleibt daher auch
   an Ort und Stelle, falls der Footer sich mal in der Höhe ändert).
   Gleiche Panel-Optik wie Popups/Karten im Projekt (--panel-grad-a/b +
   Gold-Innenumriss), kein neues Motiv erfunden. */
/* Icon+Text statt reinem Icon-Quadrat (2026-08-12, Sven: "'Zahnrad
   Symbol' könnte auch 'Optionen' heißen") — gleiche Textoptik wie
   .music-toggle daneben (Display-Schrift, Versalien, Gold), damit
   beide Footer-Buttons als zusammengehöriges Paar wirken. */
.footer-einstellungen-btn {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--s) * 10);
  padding: 0;
  background: none;
  border: none;
  font-family: var(--font-display);
  font-size: calc(var(--s) * 14);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9), 0 2px 10px rgba(0, 0, 0, 0.75);
  cursor: pointer;
  transition: color var(--duration-standard) var(--ease-soft);
}
.footer-einstellungen-icon {
  font-size: calc(var(--s) * 18);
}
.footer-einstellungen-btn:hover,
.footer-einstellungen-btn[aria-expanded="true"] {
  color: var(--gold-bright);
}
/* Sanftes Ein-/Ausblenden (2026-08-12, Sven: "beim Öffnen soll es
   langsam erscheinen, nach 10 Sekunden automatisch langsam
   verschwinden") — ZWEITER Anlauf: der ERSTE Versuch schaltete das
   [hidden]-Attribut hart um, was zusätzlich noch einen echten
   Spezifitäts-Bug hatte (display:flex hier überschrieb die [hidden]-
   Regel, das Panel blieb dadurch praktisch immer sichtbar). Jetzt
   dasselbe bewährte Muster wie .hinweis-toast: das Panel bleibt
   technisch immer im DOM (kein hidden mehr), .sichtbar steuert
   opacity+pointer-events per Übergang, script.js togglet nur noch
   diese eine Klasse. Startet unsichtbar UND unklickbar (pointer-
   events:none), damit es im geschlossenen Zustand nicht unsichtbar
   über anderen Elementen liegt und deren Klicks stiehlt. */
.footer-einstellungen-panel {
  position: fixed;
  right: calc(var(--s) * 24);
  bottom: calc(var(--s) * (var(--footer-h) + 16));
  z-index: var(--z-pill);
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 8);
  min-width: calc(var(--s) * 220);
  padding: calc(var(--s) * 26) calc(var(--s) * 24) calc(var(--s) * 22);
  /* Blickdicht statt des halbtransparenten --panel-grad-a/b (2026-08-12,
     Sven: "die 2 Fenster dürfen gerne ohne Transparenz sein" — der
     Seiteninhalt dahinter schimmerte durch, gleiches Muster wie
     .archiv-popup-kopf). */
  background: linear-gradient(rgba(24, 14, 40, 0.99), rgba(10, 6, 18, 0.99));
  border: 1px solid var(--violet-deep);
  outline: 1px solid rgba(200, 170, 78, 0.2);
  outline-offset: calc(var(--s) * -6);
  border-radius: var(--radius-card);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5);
  opacity: 0;
  pointer-events: none;
  /* Gleiche Verstärkung wie beim Online-Panel (2026-08-13) — 8px -> 28px,
     macht das "von unten nach oben fahren" wirklich sichtbar. */
  transform: translateY(calc(var(--s) * 28));
  transition: opacity var(--duration-atmospheric) var(--ease-soft),
              transform var(--duration-atmospheric) var(--ease-soft);
}
.footer-einstellungen-panel.sichtbar {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
  .footer-einstellungen-panel { transition: opacity var(--duration-standard) linear; transform: none; }
}
/* ---- Echtes CSS-Hover statt JS-Timer-Nachbau (2026-08-13, Sven:
   "das mit dem Hover gefällt mir weiterhin nicht, fühlt sich total
   verbuggt an") ---- Der JS-Nachbau (mouseenter/mouseleave + Timer)
   war grundsätzlich fehleranfällig, egal wie oft die Verzögerungen
   nachjustiert wurden — echtes ":hover" kennt solche Race Conditions
   gar nicht erst, der Browser regelt jeden Rand-/Übergangsfall selbst.
   .footer-hover-zone (siehe includes/menu.php, umschließt Button UND
   Panel) nutzt "display:contents" — nimmt die Hülle komplett aus dem
   Layout raus, Button bleibt exakt so positioniert wie vorher, das
   Panel bleibt weiter unabhängig position:fixed relativ zum Fenster,
   NICHT zu dieser Hülle (display:contents erzeugt keinen eigenen
   Containing Block). Nur auf echten Maus-Geräten aktiv (`hover:hover`
   + `pointer:fine`) — Touch-Geräte lösen weiterhin nur per Klick aus
   (siehe .sichtbar-Regeln, die bleiben unverändert parallel bestehen).
   :focus-within zusätzlich für Tastatur-Bedienung (Tab auf den
   Button). */
.footer-hover-zone {
  display: contents;
}
@media (hover: hover) and (pointer: fine) {
  .footer-hover-zone:hover .footer-einstellungen-panel,
  .footer-hover-zone:focus-within .footer-einstellungen-panel {
    opacity: 1;
    pointer-events: auto;
    transform: translateY(0);
  }
}
/* Eigene Zeile je Regler (2026-08-12, Sven: "kann man das besser
   anordnen?") — feste Mindesthöhe (32px, wie die Icons selbst) sorgt
   für gleichmäßigen Abstand, auch wenn Musik-Schalter/Helligkeits-
   Regler intern unterschiedlich aufgebaut sind (Icon-Maske per
   CSS-Trick vs. einfaches Flex-Icon). */
.footer-einstellungen-zeile {
  display: flex;
  align-items: center;
  min-height: calc(var(--s) * 32);
}
.footer-einstellungen-panel .music-toggle {
  justify-content: center;
}
/* BUGFIX (2026-08-12, Sven: "das Musik [Icon] ist außerhalb des
   Fensters") — .music-icon sitzt normalerweise per position:absolute
   AUSSERHALB der eigentlichen Button-Box (nach links versetzt, siehe
   .music-toggle .music-icon weiter oben), das war für die alte
   Sidebar-Position gedacht, wo viel Luft daneben war. Im engen Panel
   hier ragt das Icon dadurch über den linken Innenrand hinaus. Nur in
   diesem Kontext auf normale Inline-Anordnung umgestellt (Icon+Text
   nebeneinander im normalen Textfluss statt der Absolute-Positionierung),
   bleibt dadurch garantiert innerhalb des Panels. */
.footer-einstellungen-panel .music-toggle .music-mitte {
  position: static;
  display: inline-flex;
  align-items: center;
  gap: calc(var(--s) * 14);
}
.footer-einstellungen-panel .music-toggle .music-icon {
  position: static;
  flex-shrink: 0;
  margin-right: 0;
  transform: none;
}
.footer-einstellungen-panel .helligkeit-wrap,
.footer-einstellungen-panel .kontrast-wrap {
  justify-content: center;
}
/* Footer-Zeile für Musik+Helligkeit (2026-08-12, Sven: "bitte Musik
   und Helligkeit Buttons in den Footer verschieben") — #main-footer ist
   bereits flex (siehe dort), dieser Block reiht sich als weiteres
   Zeilen-Element zwischen Online-Anzeige und Copyright ein. */
/* Trennstrich nur INNERHALB der Konto-Zeile (zwischen Anmelden und
   Registrieren) — die Musik-Zeile steht in einer eigenen Reihe
   darunter, dazwischen kein senkrechter Strich mehr nötig. */
.konto-zeile > * {
  position: relative;
}
.konto-zeile > * + *::before {
  content: '';
  position: absolute;
  left: calc(var(--s) * -9);
  top: 15%;
  bottom: 15%;
  width: 1px;
  background: rgba(177, 140, 255, 0.25);
}
.konto-link {
  font-family: var(--font-display);
  font-size: calc(var(--s) * 14);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-dim);
  text-decoration: none;
  transition: color var(--duration-standard) var(--ease-soft);
}
/* Hover angeglichen an #mainnav (2026-08-08, Sven: "kann man den Hover
   anders gestalten?") — vorher reiner Farbwechsel zu Gold, jetzt
   derselbe sanfte Violett-Glow (Farbe + text-shadow) wie die
   Hauptnavigation darüber, damit sich die Konto-Links wie Teil
   desselben Systems anfühlen statt wie ein eigener Stil. */
.konto-link:hover {
  color: var(--violet-glow);
  text-shadow: var(--glow-violet-soft);
}
/* Eingeloggt-Name (2026-08-08) — kein Link, deshalb dezent in der
   Klassenfarbe statt der Hover-Farbe, damit klar ist: das ist eine
   Anzeige, kein Button. */
.konto-name {
  color: var(--violet-glow);
  cursor: default;
}

@keyframes online-pulsieren {
  0%, 100% { opacity: 0.75; box-shadow: 0 0 5px 1px rgba(110, 231, 168, 0.55); }
  50%      { opacity: 1;    box-shadow: 0 0 11px 3px rgba(110, 231, 168, 0.9); }
}

/* Website-Anwesenheit (2026-08-12, Sven: "wie weiß ich, welches
   Gildenmitglied eingeloggt ist?") — gleiche Optik wie .footer-online
   (Discord-Zähler), damit beide als zusammengehörige "Status-Zeile"
   wirken, aber bewusst eigener Klassenname/Text ("auf der Seite" statt
   nochmal "Online"), siehe Kommentar in includes/menu.php. Panel-
   Mechanik (Position/Fade) 1:1 von .footer-einstellungen-panel
   übernommen, kein zweites Muster erfunden. */
.footer-online-mitglieder-btn {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 10);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 14);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gold);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9), 0 2px 10px rgba(0, 0, 0, 0.75);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  transition: color var(--duration-standard) var(--ease-soft);
}
.footer-online-mitglieder-btn:hover,
.footer-online-mitglieder-btn[aria-expanded="true"] {
  color: var(--gold-bright);
}
.footer-online-mitglieder-btn .dot {
  width: calc(var(--s) * 12);
  height: calc(var(--s) * 12);
  border-radius: 50%;
  background: var(--online);
  animation: online-pulsieren 2.4s ease-in-out infinite;
}
/* Website-/Discord-Zahl abwechselnd, GRUNDLEGEND NEU (2026-08-13, Sven:
   "muss das umbrechen so sein? kann man X Gesamt Online auf gleiche
   Höhe wie Optionen stellen und Discord/LunatiX ohne () kleiner
   darstellen? nur Zahl+Quelle sollen sichtbar wechseln") — vorher EIN
   Textblock mit fester Breite (25ch), der ohne "white-space:nowrap"
   einfach umbrach, sobald der Text nicht mehr reinpasste (der
   eigentliche Auslöser für den gemeldeten Zeilenumbruch). Jetzt DREI
   Teile: "Gesamt Online" bleibt fest stehen (ändert sich nie, kein
   Grund es mit auszublenden), NUR die Zahl UND die kleine Quelle-
   Angabe (Discord/LunatiX) wechseln/blenden. white-space:nowrap
   erzwingt eine einzige Zeile, egal was passiert. */
#footer-online-mitglieder-text {
  display: inline-flex;
  align-items: baseline;
  gap: calc(var(--s) * 4);
  white-space: nowrap;
}
#footer-online-mitglieder-zahl {
  display: inline-block;
  min-width: 2.4ch;
  text-align: right;
  font-variant-numeric: tabular-nums;
  opacity: 1;
  filter: blur(0);
  transform: translateY(0);
  transition: opacity 250ms var(--ease-soft),
              filter 250ms var(--ease-soft),
              transform 250ms var(--ease-soft);
}
/* Klein, ohne Klammern, dezent (2026-08-13) — "Discord"/"LunatiX" sind
   beide 7 Zeichen lang, feste Breite macht auch diesen Teil unabhängig
   vom aktuellen Inhalt gleich breit (kein Sprung beim Wechsel). */
#footer-online-mitglieder-quelle {
  display: inline-block;
  width: 6.5ch;
  font-family: var(--font-body);
  /* 0.7em -> 0.85em (2026-08-14, Sven: "Discord/LunatiX etwas größer
     darstellen, Farbe beibehalten") — Farbe (--text-dim) bewusst
     unangetastet, nur die Größe wächst. */
  font-size: 0.85em;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-dim);
  opacity: 1;
  filter: blur(0);
  transform: translateY(0);
  transition: opacity 250ms var(--ease-soft),
              filter 250ms var(--ease-soft),
              transform 250ms var(--ease-soft);
}
/* Reines Weichzeichnen (2026-08-13, Sven testet jetzt Variante 2 statt
   4) — kein Schieben mehr, nur Unschärfe+Fade. Alter Wert verschwimmt
   an Ort und Stelle, neuer Wert kommt scharf zurück. */
#footer-online-mitglieder-zahl.footer-online-mitglieder-text--wechsel,
#footer-online-mitglieder-quelle.footer-online-mitglieder-text--wechsel {
  opacity: 0;
  filter: blur(4px);
}
.footer-online-mitglieder-panel {
  position: fixed;
  right: calc(var(--s) * 24);
  bottom: calc(var(--s) * (var(--footer-h) + 16));
  z-index: var(--z-pill);
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 12);
  min-width: calc(var(--s) * 220);
  max-width: calc(var(--s) * 280);
  max-height: calc(var(--s) * 320);
  overflow-y: auto;
  padding: calc(var(--s) * 26) calc(var(--s) * 24) calc(var(--s) * 22);
  /* Blickdicht statt des halbtransparenten --panel-grad-a/b (2026-08-12,
     Sven: "die 2 Fenster dürfen gerne ohne Transparenz sein" — der
     Seiteninhalt dahinter schimmerte durch, gleiches Muster wie
     .archiv-popup-kopf). */
  background: linear-gradient(rgba(24, 14, 40, 0.99), rgba(10, 6, 18, 0.99));
  border: 1px solid var(--violet-deep);
  outline: 1px solid rgba(200, 170, 78, 0.2);
  outline-offset: calc(var(--s) * -6);
  border-radius: var(--radius-card);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5);
  opacity: 0;
  pointer-events: none;
  /* Deutlicheres "von unten nach oben fahren" (2026-08-13, Sven: "soll
     aber von unten nach oben fahren") — 8px waren beim Hover-Umstieg
     kaum wahrnehmbar, 28px machen die Aufstiegsbewegung klar sichtbar,
     ohne dass es hektisch wirkt (Dauer/Timing unverändert). */
  transform: translateY(calc(var(--s) * 28));
  transition: opacity var(--duration-atmospheric) var(--ease-soft),
              transform var(--duration-atmospheric) var(--ease-soft);
  /* Violetter Scrollbalken (2026-08-12, siehe .raidplan-klasse-palette
     für den Anlass) statt Browser-Standard, falls die Namensliste mal
     länger wird als max-height erlaubt. */
  scrollbar-width: thin;
  scrollbar-color: var(--violet-deep) transparent;
}
.footer-online-mitglieder-panel.sichtbar {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
  .footer-online-mitglieder-panel { transition: opacity var(--duration-standard) linear; transform: none; }
}
/* Echtes CSS-Hover (2026-08-13) — gleiche Begründung/gleiches Muster
   wie beim Optionen-Panel, siehe dortiger ausführlicher Kommentar. */
@media (hover: hover) and (pointer: fine) {
  .footer-hover-zone:hover .footer-online-mitglieder-panel,
  .footer-hover-zone:focus-within .footer-online-mitglieder-panel {
    opacity: 1;
    pointer-events: auto;
    transform: translateY(0);
  }
}
.footer-online-mitglieder-kopf {
  font-family: var(--font-display);
  /* Gleiche Schriftgröße/-sperrung wie im Optionen-Panel (2026-08-12,
     Sven: "wie bei den Optionen die Schriftgröße nutzen") — 13/0.1em
     wirkte im Vergleich zu "MUSIK AN" (14px/0.14em) kleiner/schwächer. */
  font-size: calc(var(--s) * 14);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold-bright);
}
.footer-online-mitglieder-liste {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 10);
}
.footer-online-mitglieder-liste li {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 10);
  font-size: calc(var(--s) * 15);
  color: var(--text);
}
.footer-online-mitglieder-liste img {
  width: calc(var(--s) * 22);
  height: calc(var(--s) * 22);
  border-radius: calc(var(--s) * 4);
  flex-shrink: 0;
}
/* Deutlich sichtbarer statt kaum lesbar (2026-08-12) — var(--text-dim)
   + kursiv ging auf dem dunklen Panel-Hintergrund fast unter, gerade
   wenn hier der einzige Inhalt zwischen Zähler und Discord-Zeile ist. */
.footer-online-mitglieder-leer {
  font-size: calc(var(--s) * 14);
  color: var(--text);
}
/* BattleTag klickbar/kopierbar (2026-08-13, siehe ROADMAP.md "BattleTag
   klickbar/kopierbar machen") — Basis-Look für BEIDE Einsatzorte
   (Verwaltung-Tabelle UND dieses Popup hier), Popup-Variante bekommt
   zusätzlich die kompakte "Bnet"-Pillen-Optik. */
.battletag-kopieren {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-decoration: underline dotted;
  text-underline-offset: 2px;
}
.battletag-kopieren:hover {
  color: var(--gold-bright);
}
.footer-online-mitglieder-bnet {
  flex-shrink: 0;
  margin-left: auto;
  padding: calc(var(--s) * 2) calc(var(--s) * 8);
  border: 1px solid rgba(200, 170, 78, 0.35);
  border-radius: calc(var(--s) * 10);
  font-size: calc(var(--s) * 11);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--text-dim);
}
.footer-online-mitglieder-bnet:hover {
  border-color: var(--gold-bright);
  color: var(--gold-bright);
}

/* Mitglieder-Zähler-Zeile wieder entfernt (2026-08-12, Sven: erst "was
   soll das für ein Mist?", dann "wieso steht das eigentlich da?") —
   .footer-online-mitglieder-zaehler dadurch ungenutzt, Regel entfernt
   statt toter CSS-Leiche. */
/* Trennstrich zwischen der Seiten-Liste und dem Discord-Wert (2026-08-12,
   Sven: "Discord kann gerne ins geöffnete Fenster, nur unten
   angesiedelt und mit einem Trennstrich getrennt"). */
.footer-online-mitglieder-trenner {
  height: 1px;
  background: rgba(200, 170, 78, 0.2);
}
/* Discord-Zeile jetzt IM Popup statt im Footer (2026-08-12) — gedimmt
   wie .footer-online-mitglieder-leer, aber klickbar (leert weiterhin
   die Ansicht, siehe script.js #footer-online-leeren), daher eigener
   Hover-Zustand statt font-style:italic. Zahl rechtsbündig (Sven:
   "unten bei Discord kann die Zahl der Online-User nach rechts
   wandern") — dafür jetzt volle Breite + space-between zwischen
   Label-Span und Zahlen-Span statt reinem Fließtext, siehe
   includes/menu.php. Schriftgröße wie im Optionen-Panel (Sven: "wie
   bei den Optionen die Schriftgröße nutzen"). */
.footer-online-mitglieder-discord {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  font-family: var(--font-display);
  font-size: calc(var(--s) * 14);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  transition: color var(--duration-standard) var(--ease-soft);
}
.footer-online-mitglieder-discord:hover {
  color: var(--gold);
}

/* Wieder eigenständig zentriert (Sven, 2026-08-08) — #main-footer ist
   bereits position:fixed und damit automatisch Bezugsrahmen für
   position:absolute-Kinder, keine zusätzliche Regel nötig.
   top:50%+translate(-50%,-50%) statt nur translateX (2026-08-09, Sven:
   "Impressum · Datenschutz · Rechtliche Hinweise" als zweite Zeile
   dazu, siehe .footer-rechtliches) — vertikal zentriert jetzt den
   ganzen Block (beide Zeilen zusammen) in der höheren Footer-Leiste,
   unabhängig davon wie hoch der Block insgesamt ist. */
.footer-copy {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--s) * 6);
  font-family: var(--font-body);
  font-size: calc(var(--s) * 14);
  color: var(--text-dim);
  white-space: nowrap;
  text-align: center;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9), 0 2px 10px rgba(0, 0, 0, 0.6);
}

/* ---- Rechtliche Pflichtseiten im Footer (2026-08-09) ----
   Sven: "Impressum · Datenschutz · Rechtliche Hinweise", jeweils als
   eigene, echte Seite verlinkt (siehe impressum.php etc. + menu.php).
   Kleiner/dezenter als die Copyright-Zeile darüber (12 statt 14px,
   gedämpfte statt normale Textfarbe) — informativ, aber nicht
   konkurrierend zur Online-Anzeige/Copyright. */
.footer-rechtliches {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 8);
  font-size: calc(var(--s) * 12);
  letter-spacing: 0.02em;
}
.footer-rechtliches a {
  color: var(--text-faint);
  text-decoration: none;
  transition: color var(--duration-standard) var(--ease-soft);
}
.footer-rechtliches a:hover {
  color: var(--gold-bright);
}
.footer-rechtliches span {
  color: var(--text-faint);
}

/* ---- Hero (erste Bildschirmseite) ---- */
/* Fest verankert von oben statt vertikal zentriert (2026-08-08, Sven
   per Screenshot: "das Willkommen ist viel zu tief" — bei zentriertem
   Layout hing der Textblock je nach Viewport-Höhe unterschiedlich weit
   unten, kein fester Ankerpunkt). Gleiche Formel wie bei den anderen
   Seiten (.roster/.konto-formular-seite), nur kleinerer Wert, weil die
   Hero-Schrift selbst schon sehr groß ist. */
.hero {
  position: relative;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  box-sizing: border-box;
  /* 140→240 (2026-08-08, Sven per Screenshot: "Willkommen steht nun zu
     hoch, wäre es nicht besser auch die gleiche Position wie bei
     Bewerbung etc. zu nehmen?") — auf denselben Wert wie
     .roster/.konto-formular-seite vereinheitlicht statt eines eigenen
     kleineren Hero-Werts, konsistente Startposition über alle
     Ansichten hinweg. */
  padding-top: max(0px, calc(var(--oy) + var(--s) * 240));
  padding-bottom: calc(var(--s) * 60);
}
.hero .eyebrow {
  font-family: var(--font-display);
  /* 25→30 (2026-08-08, Sven: "willkommen bei die gleiche Größe wie
     Bewerbung?") — gleiche Schriftgröße wie .konto-formular-kopf h3,
     beide sind jetzt gleichwertige Seiten-Label auf gleicher Höhe. */
  font-size: calc(var(--s) * 30);
  letter-spacing: 0.38em;
  text-transform: uppercase;
  color: var(--violet-glow);
}
.hero h2 {
  margin: calc(var(--s) * 18) 0 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: calc(var(--s) * 145);
  letter-spacing: 0.05em;
  line-height: 1;
  text-transform: uppercase;
  background: linear-gradient(180deg, #FFFFFF 0%, var(--text) 45%, var(--text-dim) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 0 24px rgba(177, 140, 255, 0.25));
}
.hero .divider {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 16);
  width: calc(var(--s) * 620);
  margin-top: calc(var(--s) * 20);
}
.hero .divider .line {
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(177, 140, 255, 0.55), transparent);
}
.hero .divider .diamond {
  width: calc(var(--s) * 42);
  height: calc(var(--s) * 42);
  object-fit: contain;
}
.hero .tagline {
  margin-top: calc(var(--s) * 26);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 33);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--violet-glow);
}
.hero .desc {
  margin: calc(var(--s) * 22) 0 0;
  max-width: calc(var(--s) * 720);
  font-size: calc(var(--s) * 27);
  line-height: 1.65;
  color: var(--text);
}
.hero .cta {
  display: inline-block;
  align-self: flex-start;
  margin-top: calc(var(--s) * 48);
  padding: calc(var(--s) * 24) calc(var(--s) * 64);
  background: linear-gradient(var(--panel-grad-a), var(--panel-grad-b));
  border: 1px solid var(--violet-deep);
  outline: 1px solid rgba(200, 170, 78, 0.35);
  outline-offset: calc(var(--s) * -6);
  border-radius: var(--radius-card);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 25);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--gold-bright);
  text-decoration: none;
  box-shadow: var(--glow-violet-soft);
  transition: box-shadow var(--duration-standard) var(--ease-soft),
              border-color var(--duration-standard) var(--ease-soft),
              transform var(--duration-standard) var(--ease-soft);
}
.hero .cta:hover {
  border-color: var(--violet);
  box-shadow: var(--glow-violet-strong);
  transform: translateY(calc(var(--s) * -3));
}

/* ---- Scroll-Hinweis (unten in der Hero-Ansicht) ---- */
.scroll-cue {
  position: absolute;
  bottom: calc(var(--s) * 44);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--s) * 10);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 18);
  letter-spacing: 0.42em;
  text-transform: uppercase;
  color: var(--text-dim);
  text-decoration: none;
  animation: cue-atmen 3s var(--ease-breathe) infinite alternate;
}
.scroll-cue .chevron {
  width: calc(var(--s) * 16);
  height: calc(var(--s) * 16);
  border-right: 2px solid var(--violet-glow);
  border-bottom: 2px solid var(--violet-glow);
  transform: rotate(45deg);
}
@keyframes cue-atmen {
  from { opacity: 0.55; transform: translateX(-50%) translateY(0); }
  to   { opacity: 1;    transform: translateX(-50%) translateY(calc(var(--s) * 8)); }
}

/* ---- Neuigkeiten: Kopf bleibt stehen, nur dieser Bereich scrollt
   (2026-08-09, Sven: "die Überschrift der Rubrik soll fest verankert
   sein, nicht mitscrollen") — #neuigkeiten ist der einzige Nicht-
   .roster-header-Nachfolger in `.roster.neuigkeiten-startseite`
   (siehe index.php), bekommt deshalb selbst flex:1+min-height:0+
   overflow-y:auto, exakt das etablierte Muster von .roster-grid-wrap/
   .roster-grid. */
/* Scrollbalken bis an den ECHTEN Seitenrand gezogen (2026-08-10, Sven:
   "immer ganz außen wie bei Gildeninfo") — .gildeninfo IST selbst
   .roster (overflow-y direkt auf der äußeren Box, siehe dort), sitzt
   dadurch automatisch am echten right:0-Rand. #neuigkeiten (wie alle
   übrigen Ansichten mit fest stehendem Kopf) ist dagegen nur ein KIND
   von .roster, das dessen padding-right:4vw erbt — der Balken landete
   dadurch 4vw vor dem echten Rand. Bleed-Trick (gleiches Muster wie
   beim Archiv-Popup-Kopf): negativer margin-right zieht die Box bis an
   die echte Kante, padding-right gleicht das wieder aus (4vw für die
   ursprüngliche optische Distanz + die üblichen 14px Balken-Abstand),
   der Scrollbalken selbst sitzt danach exakt auf dem echten Rand.
   overflow-x:hidden auf .roster fängt das Hinausragen sicher ab. */
#neuigkeiten {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  margin-right: -4vw;
  padding-right: calc(4vw + var(--s) * 14);
  scrollbar-gutter: stable;
  scrollbar-width: thin;
  scrollbar-color: var(--violet-deep) transparent;
}

/* ---- News-Sektion ---- */
.news-section {
  padding: calc(var(--s) * 40) 0 calc(var(--s) * 100);
}
.news-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid rgba(177, 140, 255, 0.18);
  padding-bottom: calc(var(--s) * 16);
  margin-bottom: calc(var(--s) * 36);
}
.news-header h3 {
  margin: 0;
  font-family: var(--font-display);
  font-size: calc(var(--s) * 30);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--text);
}
.news-nav {
  display: flex;
  gap: calc(var(--s) * 12);
}
/* ---- Gemeinsames "Raider.IO"-Dach über Raidfortschritt + Mythisch+
   (2026-08-09, Sven: "soll Raidfortschritt wie Mythisch+ eigenständig
   stehen. Raider.IO ist hier nur die Überschrift.") ---- Eine Ebene
   höher/dezenter als die einzelnen .news-header h3-Zeilentitel
   darunter, damit die Hierarchie klar bleibt: Raider.IO (Dach) >
   Raidfortschritt/Mythisch+ (gleichrangige Unterzeilen). */
.neuigkeiten-raiderio-gruppe {
  margin-bottom: calc(var(--s) * 56);
}
.neuigkeiten-gruppe-titel {
  margin: 0 0 calc(var(--s) * 28);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 22);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.neuigkeiten-gruppe-titel span {
  color: var(--violet-glow);
}
/* Kein eigener :last-child-Nachtrag nötig — die bereits bestehende
   globale Regel (.neuigkeiten-kategorie:last-child weiter unten im
   Stylesheet) greift genauso innerhalb dieser Gruppe, "letztes Kind"
   gilt relativ zum jeweils eigenen Elternelement. */
.news-arrow {
  width: calc(var(--s) * 46);
  height: calc(var(--s) * 46);
  background: linear-gradient(var(--panel-grad-a), var(--panel-grad-b));
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
  color: var(--text-dim);
  font-size: calc(var(--s) * 26);
  line-height: 1;
  cursor: pointer;
  transition: color var(--duration-standard) var(--ease-soft),
              border-color var(--duration-standard) var(--ease-soft);
}
.news-arrow:hover {
  color: var(--violet-glow);
  border-color: var(--violet);
}
/* ---- "Archiv"-Link (2026-08-09, Sven: "ab 10 Einträgen soll ein
   Archiv entstehen ... wenn man dann auf Archiv klickt kann gerne ein
   Popup Fenster aufgehen") — bewusst als Text-Button statt Icon (wie
   .news-arrow), damit klar ist: eigene Aktion, kein weiterer
   Karussell-Pfeil. Nur bei Raider.IO/M+ vorhanden (siehe
   neuigkeitenKarussellHtmlBauen()). */
.news-archiv-link {
  align-self: center;
  padding: calc(var(--s) * 8) calc(var(--s) * 16);
  margin-right: calc(var(--s) * 4);
  background: none;
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 13);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
  cursor: pointer;
  transition: color var(--duration-standard) var(--ease-soft),
              border-color var(--duration-standard) var(--ease-soft);
}
.news-archiv-link:hover {
  color: var(--gold-bright);
  border-color: var(--gold);
}
.news-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: calc(var(--s) * 36);
}
/* Gleicher doppelter Rahmen wie die Popup-Fenster und
   "Bewerbung absenden" (2026-08-08, Sven: "diesen Glow-Effekt könnt
   man doch auf jede Kachel anpassen") — dünner Außenrahmen +
   nach innen versetzter, dezenter Gold-Umriss (outline-offset negativ).
   Bewusst nur der RUHENDE Rahmen, keine dauerhafte Glut/Animation
   (siehe "sonst wirds zu wild mit lauter Effekten") — das eigentliche
   Glühen bleibt weiterhin dem Hover vorbehalten. */
.news-card {
  display: flex;
  gap: calc(var(--s) * 24);
  padding: calc(var(--s) * 24);
  background: linear-gradient(var(--panel-grad-a), var(--panel-grad-b));
  border: 1px solid var(--violet-deep);
  outline: 1px solid rgba(200, 170, 78, 0.2);
  outline-offset: -6px;
  border-radius: var(--radius-card);
  transition: border-color var(--duration-standard) var(--ease-soft),
              box-shadow var(--duration-standard) var(--ease-soft);
}
.news-card:hover {
  border-color: var(--violet);
  box-shadow: var(--glow-violet-soft);
}
.card-thumb {
  width: calc(var(--s) * 170);
  height: calc(var(--s) * 170);
  object-fit: cover;
  border-radius: var(--radius-card);
  flex-shrink: 0;
}
.card-body {
  min-width: 0;
}
.news-titel {
  font-family: var(--font-display);
  font-size: calc(var(--s) * 23);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gold);
}
/* Raid-Name-Zeile unter dem Bossnamen (2026-08-09, Sven: "kann man den
   Namen nicht unter den Boss setzen, dann das Datum und dann den
   Rest? und etwas größer! und farblich darstellen (normal/hero/
   mythisch)") — nur auf den Raidfortschritt-Karten genutzt (siehe
   raiderioKartenHtmlBauen()), direkt unter dem Titel (minimaler
   Abstand), 15 -> 19px, Farbe je Schwierigkeitsstufe über Modifier-
   Klasse statt einer einzigen festen Farbe. */
.news-raidname {
  margin-top: calc(var(--s) * 2);
  font-size: calc(var(--s) * 19);
  font-weight: 600;
}
.news-raidname--normal {
  color: var(--text-dim);
}
.news-raidname--heroic {
  color: var(--gold-bright);
}
.news-raidname--mythic {
  color: #E2708F;
}
/* Schwierigkeit auch im Popup farblich (2026-08-11, Sven: "kann man
   die Schwierigkeit normal/heroisch/mythisch farblich zum Vorschein
   bringen?") — dieselben drei Farben wie .news-raidname--* oben, hier
   auf der Popup-Datumszeile (färbt Raidname+Datum zusammen ein, siehe
   Verdrahtung in script.js), betrifft NUR Raid-Einträge, da nur die
   ein data-popup-schwierigkeit-Attribut mitschicken. */
.neuigkeit-popup-datum--normal {
  color: var(--text-dim);
}
.neuigkeit-popup-datum--heroic {
  color: var(--gold-bright);
}
.neuigkeit-popup-datum--mythic {
  color: #E2708F;
}
.news-datum {
  margin-top: calc(var(--s) * 6);
  font-size: calc(var(--s) * 18);
  color: var(--text-dim);
}
.card-body p {
  margin: calc(var(--s) * 12) 0 0;
  font-size: calc(var(--s) * 21);
  line-height: 1.55;
  color: var(--text);
}
/* ---- Karussell-Kacheln: feste Zeilenzahl statt wachsender Höhe
   (2026-08-09, Sven per Screenshot) ----
   Zwei zusammenhängende Beobachtungen, eine Ursache: Titel UND
   Beschreibung wuchsen bisher mit ihrer natürlichen Textlänge — ein
   1-zeiliger Titel ("Rotmire") ergab eine kürzere Kachel als ein
   2-zeiliger ("Mitternacht bricht herein"), dadurch wirkte die
   Raider.IO-Reihe "ungleich". Bei Wowhead fiel zusätzlich auf, dass
   lange Überschriften/Teaser die Kachel unschön in die Höhe zogen.
   Lösung für beides zugleich: Titel UND Beschreibungstext bekommen
   eine FESTE reservierte Höhe (min-height = 2 Zeilen, unabhängig vom
   tatsächlichen Inhalt) UND `-webkit-line-clamp:2`, das bei zu langem
   Text nach der 2. Zeile mit "…" abschneidet, statt eine 3. Zeile
   anzuhängen. Kurze Texte (z.B. "Rotmire") bekommen dadurch etwas
   Luft unter sich, lange werden nie länger als 2 Zeilen — alle Karten
   in einer Reihe landen dadurch auf derselben Höhe, unabhängig von
   Kategorie oder Textlänge. Betrifft automatisch alle drei Kategorien
   (Gildenintern/Raider.IO/Wowhead), da alle dieselbe .news-titel/
   .card-body-p-Struktur teilen. */
.news-card--karussell .news-titel {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: 1.3;
  min-height: calc(var(--s) * 23 * 1.3 * 2);
}
.news-card--karussell .card-body p {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.news-link {
  display: inline-block;
  margin-top: calc(var(--s) * 14);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 18);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold);
  text-decoration: none;
  transition: color var(--duration-standard) var(--ease-soft);
}
.news-link:hover {
  color: var(--gold-bright);
}

/* ---- Neuigkeiten: drei Karussell-Kategorien (2026-08-08, komplett
   neu aufgebaut) ----
   Sven: Gildenintern/Raider.IO/Wowhead, "durch den Pfeil nach rechts
   und links spielt es ja eigentlich keine Rolle wieviel". Horizontales
   Scrollen statt Seiten zu zählen — .news-arrow (bereits oben
   definiert) scrollt um eine Kartenbreite, scroll-snap rastet sauber
   ein. Eigener Scrollbalken ausgeblendet (Pfeile sind die eigentliche
   Bedienung), gleiches -webkit-scrollbar-Muster wie .roster-grid. */
.neuigkeiten-kategorie {
  margin-bottom: calc(var(--s) * 56);
}
.neuigkeiten-kategorie:last-child {
  margin-bottom: 0;
}
.neuigkeiten-lade-hinweis {
  padding: calc(var(--s) * 60) 0;
  text-align: center;
  font-size: calc(var(--s) * 20);
  color: var(--text-dim);
}
.news-karussell {
  display: flex;
  gap: calc(var(--s) * 24);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-bottom: calc(var(--s) * 8);
  scrollbar-width: none;
}
.news-karussell::-webkit-scrollbar {
  display: none;
}
.news-card--karussell {
  flex: 0 0 calc(var(--s) * 380);
  scroll-snap-align: start;
  display: block; /* .news-card ist sonst flex mit .card-thumb, hier kein Bild */
}
/* Bild-Kachel testweise (2026-08-21, Sven per Screenshot: "kann man
   sowas eigentlich umsetzen? also das bestehende so umgestalten") —
   volles Bild oben auf der Kachel statt nur im "Mehr lesen"-Popup.
   Die Bilddaten selbst existierten schon vorher (bild_pfad/raid_bild/
   dungeon_bild kamen fertig vom Server), wurden bisher nur nicht auf
   der Kachel selbst gezeigt. .card-hero wird nur gerendert, wenn ein
   Eintrag ein Bild hinterlegt hat (siehe newsHeroHtml() in script.js) —
   Einträge ohne Bild bleiben unverändert reine Text-Kacheln.
   Negative Margins ziehen das Bild bis an den Kachelrand (die Karte
   selbst hat ringsum 24px Innenabstand), oben abgerundet passend zum
   Kachel-Radius, unten eckig, da direkt .card-body folgt. */
.news-card--karussell .card-hero {
  margin: calc(var(--s) * -24) calc(var(--s) * -24) calc(var(--s) * 18);
  border-radius: var(--radius-card) var(--radius-card) 0 0;
  overflow: hidden;
}
.news-card--karussell .card-hero-img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}
.neuigkeiten-leer {
  padding: calc(var(--s) * 30) 0;
  color: var(--text-faint);
  font-size: calc(var(--s) * 16);
}
/* Raider.IO-Karten (2026-08-08) — gleiche Kachel-Optik wie Gildenintern
   (Sven: "gleiche Kachel wie bei Gildenintern nutzen"), Titel in Gold
   statt Text-Farbe, damit Bosse auf den ersten Blick als eigene
   Kategorie erkennbar bleiben, ohne ein zweites Karten-Idiom zu bauen. */
.news-card--raiderio .news-titel {
  color: var(--gold-bright);
  /* KORREKTUR (2026-08-09, Sven: "wenn der Bossname 2 Zeilen hat und
     ein anderer nur 1 Zeile, dann muss für die Bossnamen IMMER 2
     Zeilen reserviert sein/bleiben") — die vorherige Aufhebung der
     2-Zeilen-Reserve (siehe History) war falsch: dadurch standen
     Raidname/Datum/Text bei einzeiligen Titeln höher als bei
     zweizeiligen, die Kacheln nebeneinander waren nicht mehr auf
     gleicher Höhe ausgerichtet. Die geerbte 2-Zeilen-Reservierung aus
     .news-card--karussell .news-titel (siehe dort) bleibt deshalb für
     Raid-Karten jetzt bewusst bestehen — ein kurzer Bossname lässt
     dann zwar etwas Leerraum unter sich, dafür fluchten alle Karten
     einer Reihe garantiert gleich, unabhängig von der Titellänge. */
}

/* ---- Live-Karte (2026-08-08) ---- Bindet den offiziellen
   Twitch-Player per iFrame ein (siehe liveKartenHtmlBauen in
   script.js) — braucht mehr Breite als die reinen Text-Karten, damit
   das Video nicht zu klein wird, und ist deshalb breiter als die
   sonst einheitliche Kartenbreite; wird bewusst als eigene Kategorie
   ganz vorne im Karussell-Block geführt. */
.news-card--live {
  flex-basis: calc(var(--s) * 480);
  display: flex;
  flex-direction: column;
  padding: calc(var(--s) * 16);
  gap: calc(var(--s) * 16);
}
.live-player-wrap {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-card);
  overflow: hidden;
  background: #000;
}
.live-player-wrap iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
.news-card--live .news-titel {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 10);
  text-transform: none;
  letter-spacing: 0.02em;
  /* min-height der neuen 2-Zeilen-Reservierung (siehe .news-card--
     karussell .news-titel weiter oben) wieder aufheben — Live-Titel
     ist eine einzeilige Flex-Zeile (Punkt + Name), die 2-Zeilen-
     Problematik der anderen Kategorien betrifft sie nicht. */
  min-height: 0;
}
.live-punkt {
  width: calc(var(--s) * 12);
  height: calc(var(--s) * 12);
  border-radius: 50%;
  background: #eb0400;
  box-shadow: 0 0 8px rgba(235, 4, 0, 0.8);
  flex-shrink: 0;
  animation: live-punkt-pulsieren 1.8s ease-in-out infinite;
}
@keyframes live-punkt-pulsieren {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

/* ---- Live-Dummy-Kachel (2026-08-08) ---- Ersetzt die Live-Kachel,
   wenn niemand streamt (siehe liveDummyKarteHtmlBauen in script.js) —
   bindet bewusst KEINEN Twitch-iFrame ein, damit Twitchs eigenes
   weißes "Kanal ist offline"-Overlay gar nicht erst zum Zug kommt
   (Sven, 2026-08-08: "wieso ist das weisse im bild.. kann man das
   ausschalten?" — Twitch bietet dafür keine Theming-Option). */
.live-player-wrap--offline {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-card);
  overflow: hidden;
  background:
    radial-gradient(ellipse at 30% 120%, rgba(109, 61, 255, 0.16), transparent 60%),
    var(--void);
  display: flex;
  align-items: center;
  justify-content: center;
}
.live-dummy-logo {
  /* 2026-08-08: live_offline_logo liegt jetzt passgenau im 16:9-
     Format (1920x1080) vor — füllt die Fläche 1:1, keine Skalier-/
     Zuschneide-Kompromisse mehr nötig. 2026-08-14: PNG -> JPG
     (Ladezeiten-Thema), Format/Maße sonst unverändert. */
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
  opacity: 0.95;
  filter: drop-shadow(0 0 18px rgba(177, 140, 255, 0.35));
}
.live-punkt--offline {
  background: var(--text-faint);
  box-shadow: none;
  animation: none;
}
.zuletzt-live {
  margin-left: auto;
  font-family: var(--font-body);
  font-size: calc(var(--s) * 14);
  font-weight: normal;
  letter-spacing: normal;
  text-transform: none;
  color: var(--text-faint);
}
.live-dummy-aufruf {
  margin-top: calc(var(--s) * 14);
  padding-top: calc(var(--s) * 14);
  border-top: 1px solid var(--violet-deep);
  /* 15->16, siehe Begründung bei .roster-meta weiter oben. */
  font-size: calc(var(--s) * 16);
  color: var(--text-dim);
}
.live-dummy-aufruf b {
  color: var(--violet-glow);
}

/* ---- Neuigkeit-Popup (2026-08-08) ----
   "Mehr lesen" öffnet dieses EIGENE Popup statt eine externe Seite zu
   öffnen (Sven: "der RSS-Feed soll eben in unserem Popup-Fenster
   ausgegeben werden") — gleiches Overlay-Idiom wie .login-overlay,
   [hidden] + :not([hidden]) bewusst genauso (siehe Bugfix-Kommentar
   dort: eine eigene display-Regel direkt auf dem Element hätte
   dieselbe Spezifität wie die Browser-[hidden]-Regel). */
.neuigkeit-popup-overlay {
  position: fixed;
  inset: 0;
  z-index: 40;
  align-items: center;
  justify-content: center;
  padding: 4vw;
  /* Kein backdrop-filter mehr (2026-08-09, Sven bestätigte das
     "Geister-Popup": nach dem Schließen per X blitzt kurz eine fast
     leere, transluzente Box mit demselben Rahmen auf). War schon in
     der letzten Session als Verdacht notiert (siehe Gedächtnis-
     Speicher) — backdrop-filter erzeugt eine eigene GPU-Kompositions-
     ebene, beim abrupten [hidden]-Toggle rendert der Browser davon
     manchmal noch einen letzten, leeren Frame, bevor display:none
     greift. Etwas dunklerer, blurloser Hintergrund umgeht das
     Kompositions-Artefakt komplett statt nur das Symptom zu kaschieren. */
  background: rgba(6, 4, 12, 0.85);
}
.neuigkeit-popup-overlay:not([hidden]) {
  display: flex;
}
.neuigkeit-popup {
  position: relative;
  width: 100%;
  max-width: var(--popup-w-mittel);
  max-height: 80vh;
  overflow-y: auto;
  /* Violetter Scrollbalken (2026-08-12) statt Browser-Standard, siehe
     .raidplan-klasse-palette für den Anlass. */
  scrollbar-width: thin;
  scrollbar-color: var(--violet-deep) transparent;
  padding: calc(var(--s) * 36);
  background: linear-gradient(var(--panel-grad-a), var(--panel-grad-b));
  border: 1px solid var(--violet-deep);
  outline: 1px solid rgba(200, 170, 78, 0.2);
  outline-offset: -6px;
  border-radius: var(--radius-card);
  /* Kleinerer Blur-Radius (2026-08-09, dritter Anlauf beim "Geister-
     Popup" — backdrop-filter-Entfernung und hartes display:none haben
     den Bug NICHT behoben, Sven meldete ihn weiterhin). Große weiche
     box-shadow-Weichzeichnung (vorher 60px) auf einem position:fixed-
     Element mit eigenem Scroll-Kontext (overflow-y:auto) ist ein
     bekannter Auslöser für genau dieses Chromium-Artefakt: die
     GPU-Kompositionsebene für den Schatten kann beim abrupten
     Entfernen einen Frame lang nachhängen. Deutlich kleinerer,
     dichterer Schatten statt eines großen weichen Blooms.*/
  box-shadow: var(--glow-violet-soft), 0 8px 24px rgba(0, 0, 0, 0.5);
}
.neuigkeit-popup h3 {
  margin: 0 calc(var(--s) * 30) calc(var(--s) * 8) 0;
  font-family: var(--font-display);
  font-size: calc(var(--s) * 26);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--violet-glow);
}
/* 14 -> 17 (2026-08-09, Sven zum M+-Popup: "das Datum oben darf auch
   größer dargestellt werden") — gilt für alle Popups, die dieselbe
   Klasse nutzen (Neuigkeit/M+/Archiv), damit es einheitlich bleibt. */
.neuigkeit-popup-datum {
  font-size: calc(var(--s) * 17);
  color: var(--text-dim);
}
/* ---- Archiv-Popup: fest verankerter Kopf (2026-08-09, Sven per
   Screenshot: "kann man hier bitte das 'Mythisch+: Archiv' fest
   verankern und darunter alle rund auflisten?") ----
   .neuigkeit-popup selbst ist der Scroll-Container (overflow-y:auto),
   der Kopf bekommt position:sticky + negative Ränder, die exakt das
   Padding des Popups aufheben (bleedet dadurch bis an den Rand),
   plus denselben Innenabstand wieder als eigenes Padding — dasselbe
   Muster wie sticky Tabellenköpfe (.verwaltung-tabelle th) an anderer
   Stelle im Projekt. Titel UND Schließen-Button stecken jetzt darin,
   bleiben dadurch BEIDE sichtbar, auch bei einer langen Liste wie im
   Screenshot (23+ Einträge). */
/* BUGFIX #3, diesmal live per JavaScript nachgemessen statt geraten
   (2026-08-09) — der vorherige Fix (top:0) war selbst der Fehler:
   position:sticky mit top:0 rastet an der PADDING-Kante des Scroll-
   Containers ein, NICHT an dessen Rand-/Border-Kante. Der Kopf bleedet
   per negativem margin (-36px) bewusst bis an die echte Rand-Kante
   (36px VOR der Padding-Kante) — mit top:0 blieb dadurch eine leere
   Lücke zwischen Dialogrand und Kopf sichtbar (bestätigt per
   Live-Messung: dialog-top 152px, kopf-top mit top:0 bei 171px statt
   den korrekten ~153px). Zurück auf -36px, damit sticky exakt an der
   Stelle einrastet, an die der Kopf durch den negativen margin bereits
   bewegt wurde. Betrifft automatisch auch die Raid-Archiv-Ansicht,
   dasselbe Popup wird für beide Kategorien wiederverwendet.
   Blickdichter Verlauf (statt der halbtransparenten --panel-grad-a/b,
   die für Karten AUF dem bereits blickdichten Popup-Hintergrund gedacht
   sind) bleibt bestehen, war ein zweiter, unabhängig richtiger Fix. */
.archiv-popup-kopf {
  position: sticky;
  top: calc(var(--s) * -36);
  z-index: 2;
  margin: calc(var(--s) * -36) calc(var(--s) * -36) calc(var(--s) * 20);
  padding: calc(var(--s) * 36) calc(var(--s) * 60) calc(var(--s) * 16) calc(var(--s) * 36);
  background: linear-gradient(rgba(24, 14, 40, 0.99), rgba(10, 6, 18, 0.99));
  border-bottom: 1px solid rgba(177, 140, 255, 0.18);
}
.archiv-popup-kopf #archiv-popup-titel {
  margin: 0;
}
/* .login-schliessen ist position:absolute — durch position:sticky auf
   .archiv-popup-kopf wird DIESER Kopf jetzt sein Bezugsrahmen (statt
   .neuigkeit-popup), der Button bleibt dadurch automatisch im
   sichtbaren Kopf-Bereich stehen, kein eigener Positionswert nötig. */

/* Artikelbild (2026-08-09, Sven: "kann man dieses bitte in unser Popup
   mit einbetten?") — kommt direkt aus Wowheads eigenem RSS-Feed.
   KEIN "display:block" direkt hier (Bugfix-Muster von früher, siehe
   .gildeninfo-emoji-palette-Kommentar): hätte dieselbe Spezifität wie
   das Browser-Standard-[hidden]{display:none} und würde es unabhängig
   von der Reihenfolge überstimmen — display steht deshalb NUR unten in
   :not([hidden]).
   #mplus-popup-bild (2026-08-10, Sven: "das ganze hätte ich gerne für
   die Kacheln in mythisch+") teilt sich dieselbe Optik/Regel — beide
   IDs zusammen statt einer Kopie, damit eine spätere Anpassung nicht an
   zwei Stellen gepflegt werden muss. */
#neuigkeit-popup-bild,
#mplus-popup-bild {
  width: 100%;
  max-height: calc(var(--s) * 320);
  object-fit: cover;
  border-radius: var(--radius-card);
  margin-top: calc(var(--s) * 16);
}
#neuigkeit-popup-bild:not([hidden]),
#mplus-popup-bild:not([hidden]) {
  display: block;
}
/* Direktes Kind statt Nachfahren-Selektor (2026-08-27, Sven per
   Screenshot: Item-Tooltip im neuen Loot-Popup "komisch dargestellt")
   — Ursache: diese Regel (Klasse+Element, Spezifität 0,1,1) war
   spezifischer als die einzelnen .charakter-popup-tooltip-*-Klassen
   (Spezifität 0,1,0) und überschrieb deren kompakte Margins/
   Schriftgrößen, sobald der Item-Tooltip (tief verschachtelt in der
   Loot-Tabelle) innerhalb eines .neuigkeit-popup lag. Alle echten
   Popup-Textabsätze (siehe #neuigkeit-popup-body usw.) liegen als
   DIREKTES Kind von .neuigkeit-popup, der Tooltip nie — ">" statt " "
   trifft weiterhin alle bestehenden Fälle, schließt aber tief
   verschachtelte Absätze wie den Tooltip sauber aus. */
.neuigkeit-popup > p {
  /* Gleiche Größe wie .card-body p auf den Kacheln (2026-08-08, Sven:
   * "sollte dann der Text bitte die gleiche Größe haben wie außerhalb
   * der Text, den man lesen kann") — war vorher kleiner als die Karte. */
  margin: calc(var(--s) * 20) 0 0;
  font-size: calc(var(--s) * 21);
  line-height: 1.7;
  color: var(--text);
  white-space: pre-wrap;
}
.neuigkeit-popup-quelle {
  margin-top: calc(var(--s) * 24);
  padding-top: calc(var(--s) * 16);
  border-top: 1px solid rgba(177, 140, 255, 0.18);
  font-size: calc(var(--s) * 17);
  color: var(--text-faint);
}
.neuigkeit-popup-quelle a {
  color: var(--violet-glow);
  text-decoration: none;
}
.neuigkeit-popup-quelle a:hover {
  color: var(--gold-bright);
}

/* ---- M+-Detail-Popup + Archiv-Popup (2026-08-09) ----
   Sven: volle Lauf-Details (Punkte/Zeit/Affixe/Gildenmitglieder) im
   eigenen Popup statt nur Fließtext, siehe #mplus-popup in script.js.
   .mplus-popup-roster wird auch fürs Archiv-Popup wiederverwendet
   (einfache Liste Titel+Datum, gleiche Optik, kein zweites Muster
   nötig). */
.mplus-popup-affixe {
  margin: calc(var(--s) * 4) 0 calc(var(--s) * 20);
  color: var(--text-dim);
  font-size: calc(var(--s) * 16);
}
.mplus-popup-roster {
  margin: 0;
  padding: 0;
  list-style: none;
}
/* Größer/präsenter (2026-08-09, Sven: "die Spieler könnte man größer
   darstellen" + "sollte sich eher alles oberhalb abspielen und präsent
   sein", im Gegenzug wurde .mplus-popup-hinweis darunter verkleinert,
   siehe dort) — 17 -> 19px, etwas mehr Innenabstand. */
.mplus-popup-roster li {
  padding: calc(var(--s) * 13) 0;
  border-bottom: 1px solid rgba(177, 140, 255, 0.12);
  font-size: calc(var(--s) * 19);
  color: var(--text);
}
.mplus-popup-roster li:last-child {
  border-bottom: none;
}
.mplus-popup-roster li strong {
  color: var(--gold-bright);
}
/* Roster-Zeilen als feste Flex-Spalten statt Fließtext (2026-08-09,
   Sven: "Namen der Spieler größer + davor das Symbol der
   Spezialisierung" + "alles richtig untereinander steht, nicht so
   versetzt") — Icon/Name/Rolle-Info/Score sind jetzt eigene Elemente
   mit fester bzw. Mindestbreite, dadurch fluchten Name und Score bei
   jeder Zeile exakt untereinander, unabhängig davon, wie lang
   Rollen-Info/Klasse/Spec gerade ausfallen. Nur .mplus-roster-zeile
   betroffen, NICHT die Archiv-Liste (.archiv-eintrag-btn bleibt
   einfacher Fließtext, anderes Bedürfnis dort). */
.mplus-popup-roster li.mplus-roster-zeile {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 12);
}
.mplus-roster-icon {
  flex: 0 0 auto;
  width: calc(var(--s) * 30);
  height: calc(var(--s) * 30);
  border-radius: calc(var(--s) * 5);
  border: 1px solid var(--violet-deep);
  object-fit: cover;
}
.mplus-roster-icon-leer {
  width: calc(var(--s) * 30);
  height: calc(var(--s) * 30);
}
.mplus-roster-name {
  flex: 0 0 auto;
  min-width: calc(var(--s) * 130);
  font-weight: 600;
  color: var(--gold-bright);
  /* text-decoration/cursor (2026-08-10) — diese Klasse sitzt jetzt auch
     auf einem <a> (Armory-Link, siehe mplusPopupBefuellen() in
     script.js), vorher immer ein reiner <span>. */
  text-decoration: none;
  cursor: pointer;
}
.mplus-roster-name:hover {
  color: var(--gold);
}
.mplus-roster-info {
  flex: 1 1 auto;
  color: var(--text);
}
.mplus-roster-score {
  flex: 0 0 auto;
  color: var(--text-dim);
  font-size: calc(var(--s) * 16);
  white-space: nowrap;
}
/* Klickbare Archiv-Einträge (2026-08-09, Sven: "ist es eigentlich
   auch möglich die Einträge, die im Archiv sind, klickbar zu machen?")
   — .mplus-popup-roster wird auch für die Archiv-Liste wiederverwendet
   (siehe script.js), der eigentliche Klick-Bereich ist jetzt ein
   <button> statt reinem Text, deshalb Browser-Button-Standard hier
   zurückgesetzt + Hover-Zustand ergänzt. */
/* Flex-Zeile statt reinem Block (2026-08-09, Sven: "kann man hier
   nicht schon am rechten Rand die Spezialisierungen anzeigen?") —
   Text (Titel+Meta) links, Spec-Icons rechts, siehe
   .archiv-eintrag-icons weiter unten. Gilt für alle Archiv-Einträge,
   .archiv-eintrag-icons bleibt bei Raidfortschritt-Einträgen einfach
   leer/ungenutzt. */
.archiv-eintrag-btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(var(--s) * 12);
  width: 100%;
  padding: 0;
  background: none;
  border: none;
  text-align: left;
  font-family: inherit;
  font-size: inherit;
  color: inherit;
  cursor: pointer;
  transition: color var(--duration-standard) var(--ease-soft);
}
.archiv-eintrag-btn:hover {
  color: var(--gold-bright);
}
.archiv-eintrag-btn:hover strong {
  color: var(--gold-bright);
}
/* Zwei eigene Zeilen statt einem einzelnen, engen String (2026-08-09,
   Sven: "kann man das auch besser anordnen? und auch die Information
   wieviel Gildenmitglieder dabei waren?") — Titel fett/gold oben,
   Metazeile (Datum/Punkte/Mitgliederanzahl bzw. Raid+Schwierigkeit)
   kleiner/gedeckter darunter, siehe script.js für den Aufbau. */
.archiv-eintrag-titel {
  display: block;
  font-weight: 600;
  color: var(--gold-bright);
}
.archiv-eintrag-meta {
  display: block;
  margin-top: calc(var(--s) * 4);
  font-size: calc(var(--s) * 14);
  color: var(--text-dim);
}
.archiv-eintrag-btn:hover .archiv-eintrag-titel {
  color: var(--gold);
}
.archiv-eintrag-text {
  flex: 1 1 auto;
  min-width: 0;
}
/* Spec-Icons am rechten Rand (2026-08-09, Sven: "kann man hier nicht
   schon am rechten Rand die Spezialisierungen anzeigen?") — kleine
   Vorschau der beteiligten Gildenmitglieder, gleiche Icons wie im
   Detail-Popup, nur kompakter und ohne Namen (der steht als
   title-Tooltip beim Hover). Nur bei M+-Einträgen befüllt. */
.archiv-eintrag-icons {
  display: flex;
  flex: 0 0 auto;
  gap: calc(var(--s) * 4);
}
.archiv-eintrag-icon {
  width: calc(var(--s) * 22);
  height: calc(var(--s) * 22);
  border-radius: calc(var(--s) * 4);
  border: 1px solid var(--violet-deep);
  object-fit: cover;
}
/* Hinweis bei unvollständigem Roster (2026-08-09, Sven: "die anderen
   4 werden nicht mit aufgezählt? waren das randoms? wenn ja bitte
   auch vermerken") — dezent, klar als Randnotiz erkennbar.
   Verkleinert (2026-08-09, Sven: "muss ja nicht präsent erscheinen,
   sollte sich eher alles oberhalb abspielen und präsent sein") — echte
   Randnotiz statt fast gleich groß wie der Roster selbst. */
/* Nochmal kleiner (2026-08-09, Sven: "diesen Text möchte ich bitte
   kleiner dargestellt") — 11 -> 9px, jetzt so klein wie eine echte
   Fußnote. */
/* 2026-08-11, Sven: "wird nicht kleiner" — echter Bug gefunden: die
   allgemeine Regel ".neuigkeit-popup p" oben (Zeile ~2491, font-size 21px)
   hat wegen höherer CSS-Spezifität (Klasse + Element) diese einfache
   Klassen-Regel IMMER überstimmt, egal welcher font-size-Wert hier stand.
   Fix: Elternklasse mit in den Selektor genommen (Spezifität 2 Klassen >
   1 Klasse + 1 Element), damit der Wert hier auch wirklich ankommt. */
.neuigkeit-popup .mplus-popup-hinweis {
  margin-top: calc(var(--s) * 10);
  padding-top: calc(var(--s) * 8);
  border-top: 1px solid rgba(177, 140, 255, 0.18);
  font-size: calc(var(--s) * 15);
  line-height: 1.5;
  font-style: italic;
  color: var(--text-faint);
}

/* =====================================================================
   Mitglieder-Roster (2026-08-08) — lädt echte Daten von LunatiX @ Thrall
   (EU) über sites/roster_api.php nach, siehe script.js. Bewusst im
   selben Karten-Idiom wie .news-card (Panel-Verlauf, --violet-deep-
   Rahmen, Glow bei Hover) — keine neue visuelle Sprache erfunden.

   Zweistufig (aktualisiert 2026-08-08, Sven: die volle Kachel-Flut war
   "zu überladen"): Erst eine Klassen-Übersicht mit Anzahl
   (.klassen-grid), die Mitgliederkacheln (.roster-grid) bleiben
   verborgen, bis eine Klasse angeklickt wird — dann zeigt sich nur
   deren Kacheln (siehe rosterFilterVerdrahten in script.js).
   ===================================================================== */
/* ---- Mitglieder-Ansicht: eigenes fixiertes Layout (2026-08-08) ----
   Sven: Kopf ("Mitglieder" + Klassen-Kacheln) soll fest stehen bleiben,
   nur die Mitgliederkacheln der gewählten Klasse sollen scrollen, das
   Balkendiagramm soll unten fest verankert sein — bei 244 Mitgliedern
   sonst eine sehr lange Seite.

   Löst sich komplett aus dem normalen Seitenfluss von #content (das
   für "Neuigkeiten" weiterhin normal scrollt) und positioniert sich
   selbst über die volle Viewporthöhe, mit denselben Rand-Werten wie
   #content (margin-left/padding-right), damit optisch nichts springt.
   display:flex + flex-direction:column: Kopf und Diagramm behalten
   ihre natürliche Höhe (flex-shrink:0 wäre hier unnötig, sie wachsen
   ohnehin nicht), NUR .roster-grid bekommt flex:1 + eigenen Scroll —
   dafür braucht es min-height:0 (sonst verweigert Flexbox das
   Schrumpfen unter die Inhaltshöhe und es entsteht kein Scrollbalken,
   sondern ein Überlauf über den Viewport hinaus). */
/* ---- Scroll-Grenze (2026-08-09, vierter/richtiger Anlauf) ----
   Sven: "der Footer muss außerhalb des scrollbaren Content-Bereichs
   liegen", "der Scrollbereich muss exakt oberhalb des Footers enden".
   ECHTER FEHLER der ersten drei Anläufe: `bottom: 0` + `padding-bottom`
   sieht nach einer Reserve aus, ist aber KEINE — padding-bottom hängt
   nur zusätzlichen LEEREN SCROLL-WEG ans Ende des Inhalts (wirkt sich
   nur auf scrollHeight aus), es ändert NICHT die tatsächliche Höhe/
   den Clip-Bereich (clientHeight) der Box selbst. Die Box blieb also
   die ganze Zeit volle 100vh hoch — MITTEN im Scrollen (nicht erst am
   Ende) läuft dadurch jede Zeile zwangsläufig einmal durch genau den
   Pixel-Bereich, in dem der Footer liegt, und wird dort sichtbar
   (Footer hat keinen deckenden Hintergrund, nur Textschatten).
   Richtig: die Box muss selbst schon kleiner sein. `bottom: calc(var(--s)
   * 65)` statt `bottom: 0` — dieselbe Formel wie die Footer-Höhe
   (#main-footer) weiter unten — verkürzt die tatsächliche, gerenderte
   Box um genau die Footer-Höhe. Der Footer-Streifen liegt dadurch
   komplett AUSSERHALB der Box, an KEINER Scroll-Position kann dort
   noch Inhalt erscheinen, das ist jetzt echtes Clipping statt nur ein
   Extra-Scrollweg.
   Genau DAS ist .roster: die gemeinsame Basisklasse JEDER Ansicht
   (Gildeninfo/Neuigkeiten/Mitglieder/Verwaltung/Mein Profil/Raidplan/
   Datenschutz/Rechtliche Hinweise — jede view*HtmlBauen()-Funktion in
   script.js baut ihr <section> darauf auf, #content selbst hat nie
   eigenen sichtbaren Inhalt).

   FÜNFTER ANLAUF (2026-08-09, Sven: "die Überschrift der Rubrik soll
   fest verankert sein, nicht mitscrollen" — betraf Datenschutz/
   Rechtliche Hinweise/Neuigkeiten/Raidplan/Mitglieder/Mein Profil/
   Verwaltung). Der vierte Anlauf hatte overflow-y:auto auf .roster
   SELBST gesetzt, damit der komplette Inhalt (inkl. Kopf) gemeinsam
   scrollt — genau das war der Fehler: dieselbe Regel traf ALLE
   Ansichten, auch die, deren Kopf (.roster-header, flex-shrink:0)
   eigentlich stehen bleiben sollte, während nur der eigentliche Inhalt
   darunter läuft (das Muster gab es für Mitglieder/Verwaltung schon
   LANGE vor diesem Footer-Thema, siehe .roster-grid weiter unten).
   Richtig: overflow-y GEHÖRT NICHT auf .roster selbst, sondern immer
   auf den jeweiligen Inhalts-Bereich EINER Ansicht (eigenes flex:1 +
   min-height:0 + overflow-y:auto, exakt wie schon lange bei
   .roster-grid-wrap/.roster-grid) — .roster liefert nur noch die
   Bühne (fixierte Höhe, unten exakt an der Footer-Oberkante endend
   über `bottom`), OHNE selbst zu scrollen:
   - Mitglieder/Verwaltung: bereits vorhanden (.roster-grid-wrap/
     .roster-grid bzw. .verwaltung-tab-seite/.verwaltung-tabelle-wrap),
     unverändert.
   - Gildeninfo: eigene Regel weiter unten bei .gildeninfo (dort DARF
     der Kopf mitscrollen, Sven-Entscheidung vom selben Tag, siehe
     Kommentar dort — bewusst die einzige Ausnahme).
   - Neuigkeiten/Mein Profil/Datenschutz/Rechtliche Hinweise: jeweils
     eigene, kleine Regel bei #neuigkeiten/.mein-profil
     .gildeninfo-verwaltung/.rechtstext weiter unten im Stylesheet. */
.roster {
  position: fixed;
  top: 0;
  left: calc(var(--s) * 640);
  right: 0;
  /* Echte untere Grenze der Box (nicht nur Padding!) — dieselbe
     zentrale Variable wie #main-footers height, der Footer-Streifen
     liegt dadurch immer komplett außerhalb dieser Box. */
  bottom: calc(var(--s) * var(--footer-h));
  box-sizing: border-box;
  /* Sicherheitsnetz gegen seitliches Überlaufen (siehe roster-header-Fix
     weiter unten) — .roster ist fixiert und liegt außerhalb des
     normalen body-Scrollflusses, das globale html,body{overflow-x:
     hidden} schützt es deshalb NICHT automatisch mit.
     KEIN overflow-y hier (siehe "Fünfter Anlauf" oben) — jede Ansicht
     regelt ihren eigenen scrollenden Innenbereich selbst. */
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
  padding-right: 4vw;
  /* Titel auf Höhe des Sidebar-Logos, + 40px tiefer insgesamt (Sven,
     2026-08-08: 10px, 20px, 20px, dann 10px wieder zurück) — dieselbe
     Formel wie bei den fest positionierten Ebenen in header.php:
     var(--oy) fängt den (seltenen) Fall ab, dass das Hintergrundbild
     oben/unten beschnitten wird, max(0px, …) verhindert ein ungültiges
     negatives padding-top. */
  /* 160→240 (2026-08-08, Sven per Screenshot: "sollte man es nicht
     weiter nach unten versetzen?" — Inhalt startete zu dicht am
     Mond/oberen Bildrand, konkurrierte optisch mit der Szene). */
  padding-top: max(0px, calc(var(--oy) + var(--s) * 240));
  /* Nur noch reine Optik (letzte Zeile soll nicht direkt am unteren
     Rand kleben) — die eigentliche Footer-Reserve übernimmt jetzt
     `bottom` weiter oben, nicht mehr dieses Padding. */
  padding-bottom: calc(var(--s) * 24);
}

/* Fix (2026-08-08, Sven, per Screenshot: "244 Charaktere · LunatiX @
   Thrall" ragte bei schmalen Fenstern über den rechten Rand hinaus,
   nicht auf gleicher Höhe mit den Klassen-Kacheln/Karten darunter).
   Ursache: Flex-Kinder schrumpfen standardmäßig nicht unter ihre
   eigene Textbreite (min-width:auto) — bei wenig Platz drückte
   .roster-count den ganzen Kopf über den rechten Rand von .roster
   hinaus, während Kacheln/Karten (die selbst umbrechen können) brav
   im Rahmen blieben. flex-wrap:wrap lässt die Zahl bei Bedarf in eine
   zweite Zeile fallen, statt seitlich rauszulaufen — dadurch schließt
   der Kopf jetzt IMMER mit demselben rechten Rand ab wie alles
   darunter. */
.roster-header {
  flex-shrink: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: calc(var(--s) * 8) calc(var(--s) * 20);
  justify-content: space-between;
  border-bottom: 1px solid rgba(177, 140, 255, 0.18);
  padding-bottom: calc(var(--s) * 16);
  margin-bottom: calc(var(--s) * 36);
}
.roster-header h3 {
  margin: 0;
  font-family: var(--font-display);
  font-size: calc(var(--s) * 30);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--text);
}
.roster-header h3 span {
  color: var(--violet-glow);
}
.roster-count {
  font-size: calc(var(--s) * 16);
  color: var(--text-dim);
}
/* "(Alpha)"-Badge neben Überschriften (2026-08-12, Sven: "Raidplaner
   dahinter '(Alpha)'") — dezente Gold-Pille, keine neue Farbe, gleiche
   Töne wie z.B. .verwaltung-tab. */
.roster-header-badge {
  align-self: center;
  font-family: var(--font-display);
  font-size: calc(var(--s) * 13);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--gold-bright);
  border: 1px solid rgba(200, 170, 78, 0.4);
  border-radius: 999px;
  padding: calc(var(--s) * 4) calc(var(--s) * 12);
}

/* ---- Gildeninfo (2026-08-08) ----
   "Über uns"-Text (Svens echter Text, siehe includes/gildeninfo_text.php)
   + automatischer Raid-Fortschritt (Raider.IO, siehe sites/sync_raidprogress.php).
   .gildeninfo nutzt zusätzlich zur .roster-Basisklasse (Positionierung/
   Scroll-Verhalten, identisch zu Mitglieder/Verwaltung) diese eigenen Regeln. */
/* Einzige Ausnahme vom "Kopf bleibt stehen"-Muster (2026-08-09, Sven:
   "ich möchte nur dass die Seiten bleiben, nur der Scroll soll
   oberhalb vom Footer stattfinden. Bei Gildeninfo darf also die
   gesamte Seite nach oben wegscrollen") — NUR hier scrollt der
   komplette Inhalt (Hero + Kopf + Text) gemeinsam als ein Block, siehe
   "Fünfter Anlauf"-Kommentar bei .roster weiter oben. Auf allen
   anderen Ansichten bleibt .roster-header dagegen fest stehen. */
.gildeninfo {
  overflow-y: auto;
  padding-right: calc(var(--s) * 14);
  scrollbar-gutter: stable;
  scrollbar-width: thin;
  scrollbar-color: var(--violet-deep) transparent;
}

/* Hero-Block oben auf der Gildeninfo-Seite (2026-08-08, Sven per
   Screenshot: "das müsste noch irgendwie dazu") — .hero liefert schon
   alle Schriftgrößen für .eyebrow/h2/.tagline/.desc (siehe weiter oben
   im Stylesheet), hier NUR die vollbild-/zentrierungsspezifischen
   Eigenschaften für den eingebetteten Kontext überschreiben. Später im
   Stylesheet als .hero selbst -> gewinnt bei gleicher Spezifität. */
/* Einleitungstext auf die Breite der Gildeninfo-Box darunter begrenzt
   (2026-08-12, Sven: "den Spruch lassen wir doch, soll sich eben nur
   auf das Fenster der Gildeninfo anpassen von der Breite her") — exakt
   derselbe max-width-Wert wie .gildeninfo-ueber-uns (1300px, siehe
   dort), statt der schmaleren 720*--s von .hero .desc, damit der Text
   in weniger, längeren Zeilen umbricht statt in vielen kurzen. */
.gildeninfo-hero .desc {
  max-width: 1300px;
}
.gildeninfo-hero {
  /* flex-shrink:0 (2026-08-08, echter Bug per Messung gefunden): ohne
     das quetscht der scrollende .roster-Flex-Container (overflow-y:auto)
     diesen Block auf fast 0px Höhe zusammen, obwohl der Inhalt sichtbar
     blieb (überlief einfach nach unten) — exakt dasselbe Muster, wegen
     dem .roster-header schon länger flex-shrink:0 hat. */
  flex-shrink: 0;
  min-height: 0;
  padding: 0 0 calc(var(--s) * 44);
  margin-bottom: calc(var(--s) * 36);
  border-bottom: 1px solid rgba(177, 140, 255, 0.18);
}

/* Deutlich größer/luftiger (2026-08-08, Sven: "der Text ist sehr
   klein" + "größere, luftigere Typografie", verglichen mit dem
   Hero-Beschreibungstext .desc) — 16→19, line-height 1.7→1.85. */
/* Exakt dieselbe Größe/Zeilenhöhe wie .hero .desc (2026-08-08, Sven:
   "den Text unterhalb von Gildeninfo bitte die gleiche Schriftart und
   Größe wie oberhalb von Gildeninfo nehmen 'Gemeinsam stellen wir...'")
   — font-family war schon identisch (beide erben var(--font-body) von
   body), nur font-size (19 statt 27) und line-height (1.85 statt 1.65)
   wichen ab. */
/* Panel-Karte + Cormorant Garamond (2026-08-09, Sven: "ich würde mich
   gerne für Option 1 entscheiden", danach zwei Korrekturen: "die
   Breite ist mir zu schmal" (820->1000) und "das mit dem H gefällt mir
   nicht" (Initiale entfernt, siehe Kommentar-Historie in script.js).
   Gleicher doppelter Rahmen wie News-Karten/Popups (dünner Außenrahmen
   + dezenter Gold-Innenumriss), löst das eigentliche Problem
   (Lesbarkeit gegen den unruhigen Wasserfall-Hintergrund) zuverlässig. */
.gildeninfo-ueber-uns {
  /* 1000 -> 1300 (2026-08-09, Sven: "kann man das Fenster nicht
     breiter machen?" — Screenshot zeigte reichlich ungenutzten Platz
     rechts daneben). */
  max-width: 1300px;
  background: linear-gradient(var(--panel-grad-a), var(--panel-grad-b));
  border: 1px solid var(--violet-deep);
  outline: 1px solid rgba(200, 170, 78, 0.2);
  outline-offset: -6px;
  border-radius: var(--radius-card);
  padding: calc(var(--s) * 28) calc(var(--s) * 32);
  /* Cormorant Garamond entfernt (2026-08-11, Sven: "ich möchte auch
     hier die Schriftart Marcellus") — nutzt jetzt dieselbe Fließtext-
     Schrift wie der Rest des Projekts (var(--font-body) = Marcellus),
     keine Extra-Ausnahme mehr für diesen einen Textblock. Gilt
     automatisch auch für .rechtstext (Datenschutz/Rechtliche Hinweise),
     die dieselbe Panel-Klasse teilt. */
  font-family: var(--font-body);
  font-size: calc(var(--s) * 25);
  line-height: 1.75;
  color: var(--text);
}
.gildeninfo-ueber-uns p {
  margin: 0 0 calc(var(--s) * 20);
}
.gildeninfo-ueber-uns h4 {
  margin: calc(var(--s) * 34) 0 calc(var(--s) * 14);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 21);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gold-bright);
}
.gildeninfo-ueber-uns ul {
  margin: 0 0 calc(var(--s) * 20);
  padding-left: calc(var(--s) * 26);
}
.gildeninfo-ueber-uns li {
  margin-bottom: calc(var(--s) * 6);
}
.gildeninfo-ueber-uns .gildeninfo-signatur {
  margin-top: calc(var(--s) * 32);
  color: var(--text-dim);
  font-style: italic;
}

/* ---- Datenschutz/Rechtliche Hinweise (2026-08-09) ----
   Text von Sven geliefert (.ideensammlung/LunatiX_Datenschutzerklaerung.md
   + LunatiX_Rechtliche_Hinweise.md), deutlich mehr Fließtext/
   Unterüberschriften als die kurze "Über uns"-Seite. Nutzt bewusst
   dieselbe Panel-Karte wie .gildeninfo-ueber-uns (Design Guide ist
   gesperrt, keine neue Optik erfinden) — .rechtstext als zusätzlicher
   Modifikator senkt nur die Schriftgröße (25→18), reine Fließtext-
   Paragraphen in großer Zierschrift wären bei ~20 Unterabschnitten pro
   Seite kaum lesbar.
   flex/overflow (2026-08-09, Sven: "Überschrift der Rubrik soll fest
   verankert sein") — NUR über den Modifikator .rechtstext, NICHT auf
   .gildeninfo-ueber-uns selbst (das würde Gildeninfos "Über uns"
   mitverändern, dort soll laut Sven ja gerade ALLES inkl. Hero+Kopf
   gemeinsam scrollen, siehe .gildeninfo weiter oben — bewusst zwei
   unterschiedliche Verhalten für dieselbe Panel-Optik). */
.rechtstext {
  font-size: calc(var(--s) * 18);
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding-right: calc(var(--s) * 14);
  scrollbar-gutter: stable;
  scrollbar-width: thin;
  scrollbar-color: var(--violet-deep) transparent;
}
.rechtstext-stand {
  margin: 0 0 calc(var(--s) * 28) !important;
  color: var(--text-dim);
  font-style: italic;
}
/* Markiert Stellen, die Sven noch von Hand ausfüllen muss (z.B.
   Hosting-Anbieter/Serverstandort) — bewusst auffällig, damit sowas
   nicht unbemerkt live so stehen bleibt. */
.rechtstext-todo {
  color: var(--gold-bright);
  border-bottom: 1px dashed var(--gold);
}

.gildeninfo-raid-titel {
  margin: calc(var(--s) * 40) 0 calc(var(--s) * 18);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 18);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text);
  border-top: 1px solid rgba(177, 140, 255, 0.18);
  padding-top: calc(var(--s) * 28);
  max-width: 820px;
}
.gildeninfo-raid-leer {
  color: var(--text-dim);
  font-size: calc(var(--s) * 15);
}
.raid-karten-grid {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--s) * 16);
  max-width: 820px;
}
.raid-karte {
  flex: 1 1 calc(var(--s) * 220);
  min-width: calc(var(--s) * 200);
  padding: calc(var(--s) * 18) calc(var(--s) * 20);
  background: linear-gradient(rgba(24, 14, 40, 0.9), rgba(10, 6, 18, 0.96));
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
}
.raid-name {
  font-family: var(--font-display);
  font-size: calc(var(--s) * 16);
  letter-spacing: 0.04em;
  color: var(--text);
  margin-bottom: calc(var(--s) * 6);
}
.raid-zusammenfassung {
  font-family: var(--font-display);
  font-size: calc(var(--s) * 22);
  letter-spacing: 0.04em;
  color: var(--gold-bright);
  margin-bottom: calc(var(--s) * 12);
}
.raid-bosse {
  display: flex;
  gap: calc(var(--s) * 14);
  font-size: calc(var(--s) * 13);
  color: var(--text-dim);
}

/* ---- Klassen-Übersicht (2026-08-08) ----
   Ein Chip pro vorkommender Klasse, Name in der offiziellen WoW-
   Klassenfarbe (per Inline-Variable --klassenfarbe aus script.js, wie
   schon bei .roster-name), Anzahl gedimmt daneben. Klick filtert die
   Kacheln darunter; bis dahin bleibt .roster-grid verborgen. */
/* Fix (2026-08-08, Sven: "Dämonenjäger ist abgeschnitten" — betraf nach
   der ersten Korrektur nicht mehr nur Dämonenjäger, sondern JEDEN
   längeren Namen bei ausreichend vielen Klassen im Fenster). Ursache:
   CSS-Grid mit minmax() zwingt ALLE Kacheln auf dieselbe Breite,
   unabhängig vom Inhalt — reicht die gemeinsame Breite bei vielen
   Spalten nicht für die längsten Namen, greift die Ellipse, egal wie
   groß der Mindestwert gewählt wird.
   Dauerhafte Lösung: kein Grid mehr, sondern ein Flex-Layout, das
   umbricht (flex-wrap). Jede Kachel bekommt genau die Breite ihres
   eigenen Inhalts (kein flex:1, kein erzwungenes Schrumpfen) — dadurch
   kann kein Name mehr abgeschnitten werden, bei JEDER Fenstergröße. */
.klassen-grid {
  flex-shrink: 0;
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--s) * 14);
  padding-bottom: calc(var(--s) * 40);
}
/* Blickdichter, dieselbe Fassung wie .roster-card weiter unten (Sven,
   2026-08-08: "oben bei den Klassen könnte man es ebenfalls
   umsetzen") — gleicher Grund: helle Bildstellen schienen durch den
   Standard-Panel-Verlauf durch. */
.klassen-chip {
  display: flex;
  align-items: center;
  /* Etwas kompakter (2026-08-08, Sven: "da ist ja viel Platz zum
     rechten Rand der Kachel") — gap 12->9, padding 14/20->11/15. */
  gap: calc(var(--s) * 9);
  padding: calc(var(--s) * 11) calc(var(--s) * 15);
  background: linear-gradient(rgba(24, 14, 40, 0.9), rgba(10, 6, 18, 0.96));
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
  cursor: pointer;
  transition: border-color var(--duration-standard) var(--ease-soft),
              box-shadow var(--duration-standard) var(--ease-soft);
}
/* Hover/aktiv in der jeweiligen Klassenfarbe statt generischem Violett
   (2026-08-10, Sven: "kann man hier beim Hovern nicht den Effekt
   einbringen, so wie bei jedem anderen Button, mit der jeweiligen
   Farbe?") — nutzt dieselbe --klassenfarbe-Variable, die pro Chip schon
   inline gesetzt ist (siehe klassenUebersichtHtmlBauen() in script.js),
   gleiches color-mix()-Muster wie bei .diagramm-fuellung weiter unten.
   Fällt ohne --klassenfarbe (z.B. der "Alle"-Chip) auf Violett zurück. */
.klassen-chip:hover {
  border-color: var(--klassenfarbe, var(--violet));
  box-shadow: 0 0 calc(var(--s) * 16) color-mix(in srgb, var(--klassenfarbe, var(--violet)) 40%, transparent);
}
.klassen-chip.active {
  border-color: var(--klassenfarbe, var(--violet));
  box-shadow: 0 0 calc(var(--s) * 24) color-mix(in srgb, var(--klassenfarbe, var(--violet)) 60%, transparent);
}
/* Offizielles Blizzard-Icon (2026-08-08, per Rückfrage: "offiziell von
   Blizzard laden" statt selbst erfundener Grafik) — einmalig lokal
   gespeichert über sites/sync_klassen_icons.php, dieselbe Battle.net-
   API wie der Mitglieder-Roster. */
.klassen-icon {
  width: calc(var(--s) * 30);
  height: calc(var(--s) * 30);
  border-radius: var(--radius-card);
  object-fit: cover;
  flex-shrink: 0;
}
.klassen-name {
  white-space: nowrap;
  font-family: var(--font-display);
  font-size: calc(var(--s) * 18);
  letter-spacing: 0.02em;
  color: var(--klassenfarbe, var(--text));
}
.klassen-anzahl {
  white-space: nowrap;
  font-family: var(--font-body);
  font-size: calc(var(--s) * 15);
  color: var(--text-dim);
}

/* Scrollbarer Mittelteil (2026-08-08, Sven: "die Anzeige der Spieler
   darf gerne einen Scroll bekommen") — flex:1 füllt den Platz zwischen
   den fest stehenden Klassen-Kacheln und dem fest stehenden Diagramm,
   min-height:0 ist zwingend nötig, sonst verweigert Flexbox das
   Schrumpfen unter die Kartenhöhe und es gibt keinen Scrollbalken,
   sondern einen Überlauf über den Viewport hinaus. Bevor eine Klasse
   gewählt ist (display:none), nimmt die Zeile keinen Platz ein — das
   Diagramm rückt dann direkt unter die Kacheln, genau wie gewünscht.
   ---- Wrapper ergänzt (2026-08-08, Sven: "nächste Kacheln werden
   abgeschnitten angezeigt, gerne mit Scroll-Hinweis, der beim Scrollen
   verschwindet") ----
   flex/min-height/sichtbar-Steuerung sind auf .roster-grid-wrap
   gewandert, .roster-grid bleibt die eigentliche Scrollfläche
   (height:100% des Wrappers statt eigener flex-Wert). Der Wrapper ist
   position:relative, damit .roster-scroll-cue absolut darüber liegen
   kann, OHNE beim Scrollen der Kacheln mitzuwandern (anders als ein
   Kind von .roster-grid selbst, das würde mitscrollen). */
.roster-grid-wrap {
  display: none;
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
}
.roster-grid-wrap.sichtbar {
  display: block;
}
.roster-grid {
  /* Flexbox-Zeilen statt Grid (2026-08-22, Sven: "bitte immer Klasse
     in eine Reihe [...] wenn Klassen in eine 2. Reihe rutschen dann
     ist es so") — ein normales Grid füllt Lücken einfach mit der
     nächsten Karte auf, egal welcher Klasse, dadurch konnten zwei
     Klassen dieselbe Zeile teilen. script.js baut jetzt PRO Klasse
     eine eigene .roster-klassen-reihe (siehe dort), .roster-grid ist
     nur noch der vertikal stapelnde Rahmen darum (Scroll/Bleed/Maske
     unverändert). */
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 16);
  height: 100%;
  overflow-y: auto;
  /* Eigene Scrollbar-Spalte (2026-08-08, Sven: Der native Balken schnitt
     mitten durch die letzte Kartenspalte, siehe "Haesslon" im
     Screenshot) — padding-right reserviert Platz, scrollbar-gutter:stable
     hält den Platz auch dann, wenn (noch) gar nicht gescrollt werden
     muss, damit die Kartenbreite nicht springt, sobald doch gescrollt
     wird. */
  /* Bleed bis zum echten Rand (2026-08-10, Sven: "Scroll immer ganz
     außen wie bei Gildeninfo") — gleiches Prinzip wie bei #neuigkeiten,
     siehe Kommentar dort. Nebeneffekt bewusst in Kauf genommen: die
     auto-fill-Spalten bekommen dadurch etwas mehr Platz, ggf. passt
     eine Kartenspalte mehr in eine Reihe als vorher. */
  margin-right: -4vw;
  padding-right: calc(4vw + var(--s) * 14);
  padding-bottom: calc(var(--s) * 24);
  scrollbar-gutter: stable;
  scrollbar-width: thin;
  scrollbar-color: var(--violet-deep) transparent;
  /* Ausblend-Maske am unteren Rand, verstärkt (2026-08-08, Sven: "die
     darunterliegenden Kacheln erkennt man weiterhin, sieht nicht schön
     aus") — der erste Versuch (56px Ausblendzone) war deutlich kürzer
     als eine ganze Kartenreihe (~150px), dadurch blieb der Großteil
     jeder angeschnittenen Karte voll deckend und gut lesbar, nur ihr
     letzter Rand verblasste. Jetzt eine ~170px-Zone (deckt eine ganze
     angeschnittene Kartenreihe ab) mit einem Zwischenstopp für einen
     weicheren, weniger linearen Verlauf. */
  -webkit-mask-image: linear-gradient(to bottom,
    black 0%, black calc(100% - 170px),
    rgba(0, 0, 0, 0.35) calc(100% - 60px),
    transparent 100%);
          mask-image: linear-gradient(to bottom,
    black 0%, black calc(100% - 170px),
    rgba(0, 0, 0, 0.35) calc(100% - 60px),
    transparent 100%);
}
/* BUGFIX (2026-08-09, Sven auf einem 24"-Monitor: "warum hört der
   Scroll früher auf, als die Seite eigentlich ist?") — die Maske oben
   blendet die letzten ~170px IMMER aus, auch am ECHTEN Ende der Liste,
   wo gar nichts mehr folgt. Sieht dann aus wie ein abgeschnittener
   Scroll, ist aber nur die Maske, die ihren eigentlichen Zweck (eine
   angeschnittene Reihe als Scroll-Andeutung zeigen) am Ende gar nicht
   mehr erfüllen soll. .roster-grid-am-ende (siehe script.js,
   maskeAktualisieren()) hebt die Maske am echten Ende komplett auf. */
.roster-grid.roster-grid-am-ende {
  -webkit-mask-image: none;
          mask-image: none;
}
/* Eigener, dezenter Scrollbalken statt des Standard-Systembalkens
   (Chrome/Edge/Safari — Firefox nutzt scrollbar-width/-color oben).
   Dieselben Violett-Töne wie überall sonst, keine neue Farbe. */
.roster-grid::-webkit-scrollbar {
  width: calc(var(--s) * 8);
}
.roster-grid::-webkit-scrollbar-track {
  background: transparent;
}
.roster-grid::-webkit-scrollbar-thumb {
  background: var(--violet-deep);
  border-radius: calc(var(--s) * 4);
}
.roster-grid::-webkit-scrollbar-thumb:hover {
  background: var(--violet);
}

/* Scroll-Hinweis ENTFERNT (2026-08-08, Sven: "im Mitgliederbereich ist
   ein Scroll-Symbol, das soll weg") — Markup-Ausgabe in script.js
   (rosterHtmlBauen) gestrichen, dieser Style-Block damit hinfällig.
   Der interne Scroll der Mitgliederkacheln (.roster-grid) bleibt
   unverändert bestehen, nur der Hinweis-Chip fällt weg. */
/* Jetzt ein <a> statt <div> (2026-08-08, Sven: Kacheln sollen zur
   Armory-Seite des Charakters verlinken) — display:block erhält das
   bisherige Blockverhalten als Grid-Item, text-decoration/color:inherit
   verhindern die browserübliche blaue/unterstrichene Link-Optik. */
/* Blickdichter als der globale --panel-grad-a/b-Token (2026-08-08,
   Sven: "manche Kacheln kaum lesbar durch den Hintergrund" — helle
   Bildstellen wie Mond/Burglichter schienen durch, besonders bei
   hellen Klassenfarben wie Priester-Weiß). Dieselben Violett/Void-Töne,
   nur deutlich weniger durchsichtig — der globale Token bleibt für
   News-Karten/Chips unangetastet, das ist bewusst nur hier lokal
   satter. */
.roster-card {
  display: block;
  position: relative;
  padding: calc(var(--s) * 16) calc(var(--s) * 18);
  background: linear-gradient(rgba(24, 14, 40, 0.9), rgba(10, 6, 18, 0.96));
  /* 2026-08-11, Sven: "kann man hier die Kacheln auch farblich an die
     Klasse anpassen, wie oben bei den Buttons" — --klassenfarbe kommt
     jetzt genau wie bei .klassen-chip per Inline-Style aus script.js
     (rosterHtmlBauen()), gleiches color-mix()-Muster: Rand in Ruhe nur
     dezent eingefärbt, beim Hover volle Klassenfarbe + Glow. Fällt ohne
     Farbe (sollte praktisch nie vorkommen) auf Violett zurück. */
  border: 1px solid color-mix(in srgb, var(--klassenfarbe, var(--violet-deep)) 45%, var(--violet-deep));
  /* Gleicher doppelter Rahmen wie News-Karten/Popups, siehe Kommentar
     dort (2026-08-08, Sven: Glow-Effekt auf jede Kachel anpassen). */
  outline: 1px solid rgba(200, 170, 78, 0.2);
  outline-offset: -6px;
  border-radius: var(--radius-card);
  text-decoration: none;
  color: inherit;
  transition: border-color var(--duration-standard) var(--ease-soft),
              box-shadow var(--duration-standard) var(--ease-soft);
}
.roster-card:hover {
  border-color: var(--klassenfarbe, var(--violet));
  box-shadow: 0 0 calc(var(--s) * 16) color-mix(in srgb, var(--klassenfarbe, var(--violet)) 40%, transparent);
}
/* Rahmen für "Meine Charaktere (Twinks)" bei Mein Profil (2026-08-10,
   Sven: "kann man das auch in einen Rahmen einfassen?") — gleiche
   Panel-Optik wie .roster-card darüber (Konto/Charakter/Battle.net),
   aber OHNE dessen Hover-/Link-Zustand: hier steckt eine reine Liste
   drin (twinksListeHtmlBauen() in script.js baut ein <ul
   class="mplus-popup-roster"> direkt hinein), kein klickbarer Link.
   :empty ausgenommen, damit während des kurzen "Wird geladen…"-Blitzes
   nicht schon ein leerer Rahmen aufblitzt, bevor Inhalt da ist — greift
   praktisch nie, da der Platzhaltertext selbst schon Inhalt ist, aber
   sauberer für den Fall eines komplett leeren Zwischenzustands. */
#mein-profil-twinks:not(:empty) {
  padding: calc(var(--s) * 16) calc(var(--s) * 18);
  background: linear-gradient(rgba(24, 14, 40, 0.9), rgba(10, 6, 18, 0.96));
  border: 1px solid var(--violet-deep);
  outline: 1px solid rgba(200, 170, 78, 0.2);
  outline-offset: -6px;
  border-radius: var(--radius-card);
}
/* ---- Twinks-Zeile (2026-08-10) ----
   Sven: "bitte hier wie folgt darstellen: Charaktername / Server
   (Realm)-(Region) / Rasse · Klasse / Stufe / Gildenname". Gleiches
   Flex-Zeilen-Muster wie .mplus-roster-zeile (feste Spalten, fluchten
   untereinander), ohne Icon-Spalte (Twinks haben keine Spec-Icons).
   Wiederverwendet .mplus-popup-roster als Listen-Rahmen (li-Trennlinie
   usw.), nur die Zeile selbst ist neu. */
/* Einheitlich 10px statt 14 (2026-08-10, Sven: "mach mal 10px Abstand
   zwischen jedem Punkt") — feste Spalten (siehe .twink-klasse) sorgen
   schon für Ordnung, der Zeilen-Abstand selbst darf enger sein, seit
   nichts mehr per flex-grow auseinandergezogen wird. */
/* 10->30 (2026-08-10, Sven: "immer noch zu wenig Abstand") */
.twink-zeile {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: calc(var(--s) * 30);
}
/* Mehr Luft zwischen den Zeilen (2026-08-10, Sven: "immer noch zu nah
   beieinander") — .twink-zeile sitzt auf dem <li> selbst, das erbt
   sonst nur das knappe padding:13px 0 von .mplus-popup-roster li
   (geteilte Basis-Klasse mit dem M+-Popup, dort bewusst kompakter).
   Höhere Spezifität (li.twink-zeile statt nur .twink-zeile) nötig, um
   das zu überstimmen. */
.mplus-popup-roster li.twink-zeile {
  padding: calc(var(--s) * 20) 0;
}
.twink-name {
  flex: 0 0 auto;
  min-width: calc(var(--s) * 130);
  font-weight: 600;
  color: var(--gold-bright);
  text-decoration: none;
  cursor: pointer;
}
.twink-name:hover {
  color: var(--gold);
}
.twink-realm {
  flex: 0 0 auto;
  color: var(--text-dim);
}
/* flex 1->0 (2026-08-10, Sven per Screenshot: "Stufe und Gilde so weit
   weg" — .twink-klasse sog als einziges Element mit flex-grow den
   kompletten Restplatz der Zeile auf und schob Stufe/Gilde bis an den
   rechten Rand. Feste Breite statt Wachstum, Spalten rücken dadurch
   direkt hinter Rasse/Klasse zusammen. */
/* Fraktions-Abzeichen (2026-08-10, Sven: "Fraktion (Symbol)" zwischen
   Server und Rasse/Klasse) — kein passendes kleines Icon im Projekt
   vorhanden (nur die großen Maskottchen-Grafiken), deshalb ein reines
   CSS-Abzeichen statt eines Bild-Downloads: Kreis mit H/A, in den
   üblichen Fraktionsfarben (Horde rot, Allianz blau). */
/* Echtes Wappen-Bild statt CSS-Text-Abzeichen (2026-08-10, Sven
   lieferte die freigestellten Original-Wappen nach) — Quellbild
   160×250 (Hochformat, ca. 0,64:1), Höhe fest, Breite folgt proportional
   über object-fit:contain, kein Zuschneiden/Verzerren. */
.twink-fraktion {
  flex: 0 0 auto;
  height: calc(var(--s) * 26);
  width: calc(var(--s) * 18);
  object-fit: contain;
}
.twink-klasse {
  flex: 0 0 calc(var(--s) * 200);
  color: var(--text);
}
/* Spezialisierung (2026-08-10) — zwischen Rasse/Klasse und Stufe,
   dezenter als der Charaktername, aber nicht so blass wie Server/Gilde.
   Eigene Flex-Zeile für Icon+Text (2026-08-10, Sven: "vor der
   Spezialisierung bitte jeweils noch das Symbol anfügen"), gleiches
   Icon-Set wie beim M+-Popup-Roster (.mplus-roster-icon dort).
   Innerer Abstand 6->10 (2026-08-10, Sven: "immer 10px Abstand
   dazwischen"), einheitlich mit den übrigen Spalten. */
.twink-spec {
  flex: 0 0 calc(var(--s) * 160);
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 10);
  color: var(--text-dim);
}
.twink-spec-icon {
  flex: 0 0 auto;
  width: calc(var(--s) * 22);
  height: calc(var(--s) * 22);
  border-radius: calc(var(--s) * 4);
  object-fit: cover;
}
.twink-stufe {
  flex: 0 0 auto;
  white-space: nowrap;
  color: var(--text-dim);
}
.twink-gilde {
  flex: 0 0 auto;
  white-space: nowrap;
  color: var(--text-faint);
}
/* Der Gildenname ist jetzt teils ein <a> (Armory-Link, siehe
   armoryGildeUrl() in script.js) — Browser-Standard-Link-Blau +
   Unterstreichung überschrieben (2026-08-10, Sven: "warum dunkelblau?
   das passt absolut nicht" / "besonders nicht unterstreichen"), erbt
   stattdessen die gedeckte Textfarbe von .twink-gilde, leichtes
   Aufhellen beim Hover als einziges Feedback. */
.twink-gilde a {
  color: inherit;
  text-decoration: none;
}
.twink-gilde a:hover {
  color: var(--text);
}
/* Kleines Kästchen statt großem Button (2026-08-12, Sven per
   Screenshot: "der Button ist zu groß, kann man da nicht ein Kästchen
   setzen?") — leeres, klickbares Kästchen für "als Hauptcharakter
   setzen", gefülltes Sternchen-Kästchen (nicht klickbar) für den
   aktuellen Hauptcharakter. Gleiche Größe wie die Klassen-Icons
   daneben (.footer-online-mitglieder-liste img u.ä.), fällt dadurch
   nicht aus der Zeile heraus. */
.twink-haupt-kaestchen {
  flex: 0 0 auto;
  width: calc(var(--s) * 24);
  height: calc(var(--s) * 24);
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 1px solid var(--violet-deep);
  border-radius: calc(var(--s) * 4);
  font-size: calc(var(--s) * 14);
  line-height: 1;
  color: transparent;
  cursor: pointer;
  transition: border-color var(--duration-standard) var(--ease-soft),
              color var(--duration-standard) var(--ease-soft);
}
/* BUGFIX (2026-08-12, Sven per Screenshot: "warum hier 2 Sterne bei
   den anderen 1 Stern?") — :not(:disabled) trifft auf ein <span>
   (kein Formularelement, kann nie "disabled" sein) IMMER zu, die
   Hover-Vorschau griff dadurch fälschlich auch beim schon aktiven
   Kästchen (das echte "★" steht dort schon als Text drin, siehe
   script.js) — zusammen ergab das zwei Sterne beim Hover. Jetzt über
   :not(.twink-haupt-kaestchen--aktiv) sauber auf das klickbare
   <button>-Kästchen begrenzt. */
.twink-haupt-kaestchen:not(.twink-haupt-kaestchen--aktiv):hover:not(:disabled) {
  border-color: var(--gold);
  color: var(--gold-bright);
}
.twink-haupt-kaestchen:not(.twink-haupt-kaestchen--aktiv):hover:not(:disabled)::before {
  content: '\2605';
}
.twink-haupt-kaestchen:disabled {
  cursor: default;
}
.twink-haupt-kaestchen--laedt {
  opacity: 0.5;
}
.twink-haupt-kaestchen--aktiv {
  border-color: var(--gold);
  background: rgba(200, 170, 78, 0.12);
  color: var(--gold-bright);
  cursor: default;
}
/* Manuelle Reihenfolge (2026-08-12, Sven: "cool wäre wenn man auch die
   Chars 'sortieren' könnte", Pfeile UND Drag & Drop) — zwei winzige
   Pfeil-Buttons übereinander, gleiche gedimmte Optik wie die übrigen
   Zeilen-Icons, erst bei Hover/aktiv Gold. Ziehbare Zeilen bekommen
   einen Greif-Cursor, das Ablage-Ziel beim Drüberziehen einen dezenten
   Gold-Rahmen als Feedback. */
.twink-reihenfolge-pfeile {
  flex: 0 0 auto;
  margin-left: auto;
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 2);
}
.twink-pfeil-hoch,
.twink-pfeil-runter {
  background: none;
  border: none;
  padding: 0;
  font-size: calc(var(--s) * 10);
  line-height: 1;
  color: var(--text-faint);
  cursor: pointer;
  transition: color var(--duration-standard) var(--ease-soft);
}
.twink-pfeil-hoch:hover:not(:disabled),
.twink-pfeil-runter:hover:not(:disabled) {
  color: var(--gold-bright);
}
.twink-pfeil-hoch:disabled,
.twink-pfeil-runter:disabled {
  opacity: 0.3;
  cursor: default;
}
.twink-zeile[draggable="true"] {
  cursor: grab;
}
.twink-zeile--ablage-ziel {
  outline: 1px dashed var(--gold);
  outline-offset: calc(var(--s) * 4);
}
.roster-name {
  font-family: var(--font-display);
  font-size: calc(var(--s) * 20);
  letter-spacing: 0.03em;
  /* Platz fürs Klassen-Icon oben rechts reservieren (2026-08-22) —
     sonst könnten lange Namen (+ "Neu"/"Pool"-Tag) unter das
     absolut positionierte Icon laufen. */
  padding-right: calc(var(--s) * 34);
  /* Farbe kommt inline aus der offiziellen WoW-Klassenfarbe (siehe
     sites/klassen.php) — bewusst die einzige Ausnahme von der
     Markenpalette, siehe Art Bible. */
}
/* Spalte statt Zeile (2026-08-08, seit Rasse+Klasse gemeinsam angezeigt
   werden, z.B. "Lichtgeschmiedeter Draenei Priester" — mit der alten
   Zeilen-Anordnung (space-between neben "Stufe 90") wäre das dieselbe
   Falle wie vorher bei den Klassen-Chips: kein Platz, hässlicher
   Umbruch. Untereinander darf der Klassentext frei umbrechen, ohne
   dass Nachbar-Text ins Gehege kommt. */
.roster-meta {
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 2);
  margin-top: calc(var(--s) * 6);
  /* 15->16 (2026-08-22, Sven per Screenshots: "Schriftarten stellenweise
     sehr klein" — die Kacheln haben reichlich Platz, der Klasse/Rasse-
     Text darin wirkte im Verhältnis zur Kachelgröße zu klein). */
  font-size: calc(var(--s) * 16);
  color: var(--text-dim);
}
/* Fix (2026-08-08, Sven: "Stufe/Rang immer auf derselben Höhe" — bei
   "Voltoriel"/"Asteni" direkt sichtbar, weil Rasse+Klasse mal 1, mal 2
   Zeilen braucht, z.B. "Lichtgeschmiedeter Draenei Paladin"). em-Werte
   statt calc(var(--s)*…), weil sie sich automatisch auf die eigene
   Schriftgröße beziehen — line-height fest auf 1.3, min-height fest auf
   2 Zeilen davon (2.6em), unabhängig von --s. Dadurch nimmt die Zeile
   IMMER denselben Platz ein, ob 1 oder 2 Zeilen Text drinstehen, und
   Stufe (Zeile 3) + Rang (Zeile 4) rutschen bei jeder Kachel gleich weit
   nach unten. */
.roster-klasse {
  display: block;
  line-height: 1.3;
  min-height: 3.9em;
}
.roster-stufe {
  /* 13->14, siehe Begründung bei .roster-meta oben. */
  font-size: calc(var(--s) * 14);
}
/* Zeile statt einzelnem Element (2026-08-09, Sven: "kann man rechts
   neben dem Rang die M+ Wertung einpflegen?") — Rang bleibt links wie
   bisher, die M+-Wertung (falls vorhanden) steht rechts daneben in der
   gleichen Zeile, space-between hält den Abstand dazwischen flexibel
   statt fest zu verdrahten. */
.roster-rang-zeile {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-top: calc(var(--s) * 8);
}
.roster-rang {
  /* 13->14, siehe Begründung bei .roster-meta oben. */
  font-size: calc(var(--s) * 14);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gold);
}
.roster-mplus {
  font-family: var(--font-display);
  /* 15->16, siehe Begründung bei .roster-meta oben. */
  font-size: calc(var(--s) * 16);
  font-weight: 600;
  color: var(--violet-glow);
}
/* Platzhalter-Zustand, solange noch keine Wertung hinterlegt ist
   (2026-08-09) — bewusst deutlich blasser als ein echter Wert, damit
   auf den ersten Blick klar ist "hier fehlt (noch) etwas", nicht
   "hier steht eine kleine Zahl". */
.roster-mplus-leer {
  color: var(--text-dim);
  font-weight: 400;
  cursor: help;
}
.roster-laedt,
.roster-fehler {
  padding: calc(var(--s) * 80) 0;
  text-align: center;
  font-size: calc(var(--s) * 22);
  color: var(--text-dim);
}
/* "Keine Termine geplant"-Hinweis (2026-08-08 ursprünglich als reiner
   Platzhalter-Text eingeführt, 2026-08-10 zum echten Leer-Zustand der
   fertigen Raidplan-Ansicht umgewidmet, siehe .raidplan-ansicht
   weiter unten). */
.raidplan-folgt {
  padding: calc(var(--s) * 80) 0;
  text-align: center;
  font-family: var(--font-display);
  font-size: calc(var(--s) * 28);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--violet-glow);
}
/* Loot-Platzhalter-Artwork (2026-08-12, Sven: "bei 'Loot' auf die
   Seite packen, aber so dass es auch gut aussieht") — zentriert,
   begrenzte Breite (wirkt bei voller Seitenbreite verloren/verzerrt),
   sanfter violetter Glow statt eines harten Rahmens/Kastens, damit der
   ohnehin schon transparente Bildrand weich in den Seitenhintergrund
   übergeht statt in einer sichtbaren Box zu stecken. */
.loot-platzhalter-bild {
  display: block;
  margin: calc(var(--s) * 40) auto 0;
  width: 100%;
  max-width: calc(var(--s) * 900);
  height: auto;
  filter: drop-shadow(0 0 40px rgba(109, 61, 255, 0.35));
}
/* Raidplan-Ansicht (2026-08-10, siehe ROADMAP.md "Eigener Raidplaner")
   — Terminkarten im selben Idiom wie .news-card (Verlauf/Rahmen/Glow),
   nur mit den Raid-Helper-typischen Rollen-Spalten statt Fließtext. */
.raidplan-ansicht {
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 24);
}
.raidplan-termin {
  padding: calc(var(--s) * 24);
  background: linear-gradient(var(--panel-grad-a), var(--panel-grad-b));
  border: 1px solid var(--violet-deep);
  outline: 1px solid rgba(200, 170, 78, 0.2);
  outline-offset: -6px;
  border-radius: var(--radius-card);
}
/* Ein-/Ausklappen (2026-08-11, Sven: "kann man diese Fenster ein- und
   ausklappbar machen?") — Kopf jetzt eine Flex-Zeile aus Klapp-Button +
   Text-Spalte (Titel+Meta bleiben untereinander wie bisher, nur beide
   zusammen jetzt NEBEN dem Button statt den ganzen Kopf-Container
   einzunehmen). */
.raidplan-termin-kopf {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 12);
}
.raidplan-termin-kopf h4 {
  margin: 0;
  font-family: var(--font-display);
  font-size: calc(var(--s) * 24);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--violet-glow);
}
.raidplan-termin-meta {
  margin-top: calc(var(--s) * 4);
  font-size: calc(var(--s) * 15);
  color: var(--text-dim);
}
/* Deutlich sichtbarer (2026-08-11, Sven: "fällt ja gar nicht auf, dass
   man hier auf und zu klappen kann") — gleiche "wichtiger kleiner
   Icon-Button"-Sprache wie der "i"-Button bei Mitglieder-Verwaltung
   (.verwaltung-rollen-info-btn): kräftiger Gold-Rahmen + Glow statt
   des bisherigen unauffälligen Violett-Randes, dazu größer (32->38px). */
.raidplan-klapp-btn {
  flex-shrink: 0;
  width: calc(var(--s) * 38);
  height: calc(var(--s) * 38);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: linear-gradient(var(--panel-grad-a), var(--panel-grad-b));
  border: 1px solid var(--gold);
  border-radius: 50%;
  color: var(--gold-bright);
  cursor: pointer;
  box-shadow: var(--glow-gold-soft);
  transition: border-color var(--duration-standard) var(--ease-soft),
              color var(--duration-standard) var(--ease-soft),
              box-shadow var(--duration-standard) var(--ease-soft);
}
.raidplan-klapp-btn:hover {
  border-color: var(--gold-bright);
  color: #fff;
  box-shadow: var(--glow-gold-soft), 0 0 0 3px rgba(200, 170, 78, 0.18);
}
.raidplan-klapp-pfeil {
  font-size: calc(var(--s) * 18);
  line-height: 1;
  transition: transform var(--duration-standard) var(--ease-soft);
}
/* Pfeil zeigt in Ruhe nach unten ("aufgeklappt"), dreht sich beim
   Zuklappen nach links (klassische Akkordeon-Optik) — aria-expanded
   kommt vom Klick-Handler in script.js, kein zusätzliches JS für die
   reine Optik nötig. */
.raidplan-klapp-btn[aria-expanded="false"] .raidplan-klapp-pfeil {
  transform: rotate(-90deg);
}
/* Archiv-Abschnitt einklappbar (2026-08-13, Sven: "sollte einklappbar
   sein, das nur 'Archiv' da steht") — Button übernimmt bewusst die
   Typografie von .gildeninfo-raid-verwaltung-titel (gleiche Optik wie
   die übrigen Abschnittstitel), nur eben klickbar. Standardmäßig
   zugeklappt (hidden im Markup), Pfeil-Rotation gleiches Prinzip wie
   .raidplan-klapp-btn oben, nur eigene Klasse statt aria-expanded auf
   demselben Selektor (unterschiedliche Button-Optik). */
.raidplan-archiv-klapp-btn {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 6);
  margin: calc(var(--s) * 28) 0 calc(var(--s) * 12);
  padding: 0;
  background: none;
  border: none;
  font-family: var(--font-display);
  font-size: calc(var(--s) * 15);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gold-bright);
  cursor: pointer;
}
.raidplan-archiv-klapp-btn:hover {
  color: #fff;
}
.raidplan-archiv-klapp-btn .raidplan-klapp-pfeil {
  font-size: calc(var(--s) * 14);
  transform: rotate(-90deg);
}
.raidplan-archiv-klapp-btn--offen .raidplan-klapp-pfeil {
  transform: rotate(0deg);
}
/* Kompakte Zusammenfassungszeile bei eingeklapptem Termin (2026-08-11,
   Sven: erst "soll dann einfach unten nur noch stehen ... Tank (x/x) ·
   Heiler (x/x) · DD (x/x)", dann nachgebessert: "sticht hier nicht
   wirklich ins Auge" — reiner Text in Gold ging im dunklen Kartenkörper
   unter. Jetzt als eigener kleiner Badge-Streifen: eigener Hintergrund/
   Rahmen (gleiche Panel-Sprache wie der Rest der Karte), deutlich
   größere Schrift, damit auf einen Blick klar ist "hier steckt noch
   was drin", auch wenn die Karte zugeklappt ist. */
.raidplan-termin-zusammenfassung {
  margin-top: calc(var(--s) * 16);
  padding: calc(var(--s) * 10) calc(var(--s) * 16);
  background: rgba(200, 170, 78, 0.08);
  border: 1px solid rgba(200, 170, 78, 0.35);
  border-radius: var(--radius-card);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 18);
  letter-spacing: 0.04em;
  color: var(--gold-bright);
}
.raidplan-termin-notiz {
  margin: calc(var(--s) * 12) 0 0;
  font-size: calc(var(--s) * 16);
  color: var(--text);
}
/* "Abgelaufen"-Vermerk (2026-08-13, siehe ROADMAP.md "Raidplaner:
   abgelaufene Termine, Archiv + Vorlage-Funktion") — leicht rot
   getönt statt der sonstigen Violett/Gold-Akzente, damit der Zustand
   auf einen Blick eindeutig ist (Grün=frei, Gold=CTA, Rot=vorbei). */
.raidplan-abgelaufen-pille {
  display: inline-block;
  vertical-align: 2px;
  margin-left: calc(var(--s) * 8);
  padding: calc(var(--s) * 2) calc(var(--s) * 10);
  border-radius: calc(var(--s) * 10);
  background: rgba(179, 57, 76, 0.15);
  color: #e0879a;
  font-family: var(--font-body);
  font-size: calc(var(--s) * 13);
  letter-spacing: normal;
  text-transform: none;
}
.raidplan-rollen-reihe {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--s) * 16);
  margin-top: calc(var(--s) * 20);
}
.raidplan-rollen-spalte {
  /* position:relative (2026-08-11) — Bezugsrahmen für die absolut
     positionierte Spec-Nachfrage (.raidplan-spec-abfrage), siehe dort. */
  position: relative;
  flex: 1 1 calc(var(--s) * 200);
  min-width: calc(var(--s) * 180);
  padding: calc(var(--s) * 14);
  background: rgba(10, 6, 18, 0.35);
  border: 1px solid rgba(177, 140, 255, 0.18);
  border-radius: calc(var(--s) * 8);
}
.raidplan-rollen-kopf {
  font-family: var(--font-display);
  font-size: calc(var(--s) * 16);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--gold-bright);
}
.raidplan-rollen-kopf span {
  color: var(--text-dim);
  text-transform: none;
  letter-spacing: normal;
  font-family: var(--font-body);
}
.raidplan-rollen-liste {
  list-style: none;
  margin: calc(var(--s) * 10) 0;
  padding: 0;
  min-height: calc(var(--s) * 24);
}
.raidplan-rollen-liste li {
  padding: calc(var(--s) * 4) 0;
  /* 14 -> 17 (2026-08-11, Sven: "finde den Namen zu klein dargestellt") */
  font-size: calc(var(--s) * 17);
  font-weight: 500;
  color: var(--text);
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 8);
}
/* Klassen-Icon vor dem Namen (2026-08-11, Sven: "meine Klasse angeben
   wollen inkl. dem Symbol") — dieselben Icons wie auf der Mitglieder-
   Seite (assets/images/icons/klasse-<id>.jpg), 1:1-Seitenverhältnis.
   18 -> 26 + eigener Rahmen (2026-08-11, Sven: "mit den Symbolen nicht
   zufrieden" — bei 18px wirkten die feindetaillierten Blizzard-Icons
   nur wie ein unklarer bunter Fleck, kein Bug, reine Größenfrage).
   Dezenter Goldrahmen setzt das Icon zusätzlich klarer vom dunklen
   Hintergrund ab, gleiche Farbe wie die übrigen Akzent-Rahmen. */
.raidplan-klasse-icon {
  width: calc(var(--s) * 26);
  height: calc(var(--s) * 26);
  border-radius: calc(var(--s) * 5);
  border: 1px solid rgba(200, 170, 78, 0.4);
  object-fit: cover;
  flex-shrink: 0;
}
.raidplan-status-zeile .raidplan-klasse-icon {
  width: calc(var(--s) * 20);
  height: calc(var(--s) * 20);
  vertical-align: -6px;
  margin-right: calc(var(--s) * 4);
}
.raidplan-nummer {
  color: var(--text-dim);
}
/* Spezialisierung hinter dem Namen (2026-08-11, Sven: "wäre auch cool,
   wenn man dann noch die Spezialisierung dabei sehen würde") — dezent
   gedämpft statt in derselben Stärke wie der Name, gleiches Prinzip wie
   .raidplan-status-zeile (Label betont, Zusatzinfo gedämpft). */
.raidplan-spec-label {
  color: var(--text-dim);
  font-size: 0.9em;
  font-weight: 400;
}
/* Eigene Anmelde-Zeile hervorgehoben (2026-08-10) — auf einen Blick
   erkennbar, wo man selbst in der Liste steht. */
.raidplan-eigene-zeile {
  color: var(--gold-bright) !important;
  font-weight: 600;
}
.raidplan-leer {
  color: var(--text-dim);
  font-style: italic;
}
.raidplan-rollen-spalte .verwaltung-btn {
  width: 100%;
  text-align: center;
  margin-right: 0;
}
/* Spec-Nachfrage bei mehreren Möglichkeiten (2026-08-11, zweiter
   Anlauf) — erscheint DIREKT in der angeklickten Rollen-Spalte (siehe
   raidplanSpecAuswahlAnzeigen() in script.js), nicht mehr weit weg bei
   der Klasse-Auswahl unten.
   position:absolute (2026-08-11, dritter Anlauf, Sven per Screenshot:
   "kann man das Pulldown über das Fenster legen? weil es sich bei 3
   Auswahlen einfach vergrößert, das sieht komisch aus" — die Spalte
   wuchs bisher in der Höhe mit und stand dadurch höher als die beiden
   Nachbar-Spalten) — schwebt jetzt ÜBER dem restlichen Karteninhalt,
   OHNE die Spaltenhöhe/das Kartenraster zu verändern, exakt wie die
   Klasse-Auswahl-Palette (.raidplan-klasse-palette) es schon tut. */
.raidplan-spec-abfrage {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 15;
  margin-top: calc(var(--s) * 6);
  padding: calc(var(--s) * 10);
  background: linear-gradient(rgba(24, 14, 40, 0.98), rgba(10, 6, 18, 0.99));
  border: 1px solid var(--gold-bright);
  border-radius: calc(var(--s) * 6);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5);
}
.raidplan-spec-abfrage-titel {
  margin-bottom: calc(var(--s) * 6);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 13);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--gold-bright);
}
.raidplan-spec-abfrage .raidplan-spec-option {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 8);
  width: 100%;
  background: none;
  border: 1px solid transparent;
  border-radius: calc(var(--s) * 4);
  padding: calc(var(--s) * 6);
  margin-bottom: calc(var(--s) * 2);
  cursor: pointer;
  color: var(--text);
  font-size: calc(var(--s) * 13);
  text-align: left;
  transition: background var(--duration-standard) var(--ease-soft),
              border-color var(--duration-standard) var(--ease-soft);
}
.raidplan-spec-abfrage .raidplan-spec-option:hover {
  background: rgba(109, 61, 255, 0.15);
  border-color: var(--violet-deep);
}
.raidplan-spec-abfrage .raidplan-spec-option img {
  width: calc(var(--s) * 22);
  height: calc(var(--s) * 22);
  border-radius: calc(var(--s) * 3);
  object-fit: cover;
  flex-shrink: 0;
}
.raidplan-status-zeile {
  margin-top: calc(var(--s) * 10);
  font-size: calc(var(--s) * 14);
  color: var(--text-dim);
}
.raidplan-status-zeile strong {
  color: var(--text);
}
/* Vorschlagsliste "Waren beim letzten Mal dabei" (2026-08-13, siehe
   ROADMAP.md "Raidplaner ... Vorlage-Funktion") — eigener, gedämpfter
   Kasten VOR den echten Rollen-Spalten, bewusst optisch zurückhaltender
   (kein Gold-Rahmen wie bei einer aktiven Auswahl), da hier nichts
   anklickbar ist, nur reine Information. */
.raidplan-vorschlaege {
  margin-top: calc(var(--s) * 16);
  padding: calc(var(--s) * 12) calc(var(--s) * 14);
  background: rgba(10, 6, 18, 0.35);
  border: 1px dashed rgba(177, 140, 255, 0.3);
  border-radius: calc(var(--s) * 8);
}
.raidplan-vorschlaege-kopf {
  font-family: var(--font-display);
  font-size: calc(var(--s) * 13);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.raidplan-vorschlaege-liste {
  list-style: none;
  margin: calc(var(--s) * 8) 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--s) * 6) calc(var(--s) * 16);
}
.raidplan-vorschlaege-liste li {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 6);
  font-size: calc(var(--s) * 15);
  color: var(--text-dim);
}
/* Name+Klasse-Mini-Formular (2026-08-11, siehe raidplanTerminHtmlBauen()
   in script.js für die Begründung) — bewusst schlicht gehalten, gleiche
   Optik wie die übrigen Formularfelder im Projekt (.verwaltung-rolle-
   auswahl-ähnlich), nur eigene Klasse statt der zu wiederverwenden, weil
   hier zwei unterschiedlich breite Felder nebeneinander sitzen sollen. */
.raidplan-charakter-wahl {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--s) * 10);
  margin-top: calc(var(--s) * 18);
}
.raidplan-name-feld {
  padding: calc(var(--s) * 8) calc(var(--s) * 12);
  background: rgba(10, 6, 18, 0.5);
  border: 1px solid var(--violet-deep);
  border-radius: calc(var(--s) * 4);
  color: var(--text);
  font-family: var(--font-body);
  font-size: calc(var(--s) * 14);
  /* flex:1 (praktisch die halbe Kartenbreite) -> feste, deutlich
     kompaktere Breite (2026-08-11, Sven: "das Eingabefeld für den
     Charakternamen zu lang") — wächst nicht mehr mit der Karte mit,
     ein Charaktername braucht diese Breite nie. */
  flex: 0 0 calc(var(--s) * 260);
  max-width: 100%;
}
.raidplan-name-feld::placeholder {
  color: var(--text-dim);
}
/* Abgelaufene Termine sind inaktiv (2026-08-13) — passendes Ausgrauen
   fürs Eingabefeld, die Buttons haben schon eine :disabled-Regel
   (.verwaltung-btn:disabled). */
.raidplan-name-feld:disabled {
  opacity: 0.4;
  cursor: default;
}
/* Klasse-Auswahl als eigenes Knopf+Klapp-Panel statt nativem <select>
   (2026-08-11, Sven per Screenshot: "farblich voll daneben gegriffen,
   passt gar nicht zum Style" — das aufgeklappte Options-Menü eines
   <select> zeichnet zwingend das Betriebssystem, CSS kann da nicht
   ran). Gleiches Muster wie .neuigkeit-bild-palette bei den
   Neuigkeiten, nur mit Klassen-Icons statt Motiv-Bildern. */
.raidplan-klasse-wrap {
  position: relative;
}
.raidplan-klasse-btn {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 8);
  margin-right: 0;
}
.raidplan-klasse-btn img {
  width: calc(var(--s) * 20);
  height: calc(var(--s) * 20);
  border-radius: calc(var(--s) * 3);
  object-fit: cover;
}
.raidplan-klasse-palette {
  position: absolute;
  top: calc(100% + var(--s) * 8);
  right: 0;
  z-index: 10;
  grid-template-columns: repeat(3, 1fr);
  gap: calc(var(--s) * 6);
  width: calc(var(--s) * 300);
  max-height: calc(var(--s) * 260);
  overflow-y: auto;
  padding: calc(var(--s) * 10);
  background: linear-gradient(rgba(24, 14, 40, 0.98), rgba(10, 6, 18, 0.99));
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5);
  /* Eigener violetter Scrollbalken statt Browser-Standard (2026-08-12,
     Sven per Screenshot: "diesen Scroll bitte der Style-Farbe von
     unserer Seite anpassen") — gleiches Muster wie überall sonst im
     Projekt (z.B. #neuigkeiten, .gildeninfo-klasse-palette). */
  scrollbar-width: thin;
  scrollbar-color: var(--violet-deep) transparent;
}
.raidplan-klasse-palette:not([hidden]) {
  display: grid;
}
.raidplan-klasse-option {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--s) * 4);
  background: none;
  border: 1px solid transparent;
  border-radius: calc(var(--s) * 6);
  padding: calc(var(--s) * 6);
  cursor: pointer;
  color: var(--text-dim);
  font-size: calc(var(--s) * 12);
  transition: background var(--duration-standard) var(--ease-soft),
              border-color var(--duration-standard) var(--ease-soft);
}
.raidplan-klasse-option:hover {
  background: rgba(109, 61, 255, 0.15);
  border-color: var(--violet-deep);
}
.raidplan-klasse-option img {
  width: calc(var(--s) * 32);
  height: calc(var(--s) * 32);
  border-radius: calc(var(--s) * 4);
  object-fit: cover;
}
/* Zweite Stufe: Spezialisierungen der gewählten Klasse (2026-08-11) —
   eigene Klasse statt .raidplan-klasse-option wiederzuverwenden, weil
   hier eine ROLLE dazukommt (Text neben dem Namen) und die Liste nur
   2-4 statt 13 Einträge hat, deshalb EINSPALTIG statt Raster. */
.raidplan-spec-option {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 8);
  width: 100%;
  background: none;
  border: 1px solid transparent;
  border-radius: calc(var(--s) * 6);
  padding: calc(var(--s) * 8);
  cursor: pointer;
  color: var(--text);
  font-size: calc(var(--s) * 14);
  text-align: left;
  transition: background var(--duration-standard) var(--ease-soft),
              border-color var(--duration-standard) var(--ease-soft);
}
.raidplan-spec-option:hover {
  background: rgba(109, 61, 255, 0.15);
  border-color: var(--violet-deep);
}
.raidplan-spec-option img {
  width: calc(var(--s) * 28);
  height: calc(var(--s) * 28);
  border-radius: calc(var(--s) * 4);
  object-fit: cover;
  flex-shrink: 0;
}
.raidplan-eigener-status {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: calc(var(--s) * 10);
  margin-top: calc(var(--s) * 18);
  padding-top: calc(var(--s) * 14);
  border-top: 1px solid rgba(177, 140, 255, 0.18);
  font-size: calc(var(--s) * 15);
  color: var(--text-dim);
}
.raidplan-status-btns {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--s) * 4);
}
.roster-warnung {
  margin: 0 0 calc(var(--s) * 20);
  padding: calc(var(--s) * 12) calc(var(--s) * 18);
  background: rgba(179, 57, 76, 0.12);
  border: 1px solid rgba(179, 57, 76, 0.4);
  border-radius: var(--radius-card);
  font-size: calc(var(--s) * 15);
  color: var(--text);
}

/* ---- Verwaltung (2026-08-08) ----
   Nutzt .roster als Grundgerüst (fixiertes Panel, .roster-header-
   Kopfzeile) — nur die Tabelle selbst ist neu. Erste Tabelle im
   Projekt, deshalb konsequent aus den bestehenden Tokens abgeleitet
   statt neu erfunden (Panel-Verlauf/--violet-deep-Rahmen wie
   .news-card/.roster-card, Status-Farben nur wo wirklich Status
   gemeint ist). */
/* Eigener Panel-Hintergrund (2026-08-08, Sven: "durch das Transparente
   nicht sehr gut lesbar") — vorher hatte nur die th-Kopfzeile eine
   eigene Hintergrundfarbe, der restliche Tabellenkörper war komplett
   durchsichtig und damit über unruhigen Bildstellen schwer lesbar.
   Derselbe Panel-Verlauf wie .news-card/.roster-card statt neu
   erfunden. */
/* ---- Untermenü mit drei Reitern (2026-08-08, Sven: "wie ein Menü
   einbauen für die einzelnen Rubriken, Verwaltung/Bewerbungen/
   Gildeninfo, und jede eben innerhalb der Verwaltung eine eigene
   Seite") ----
   [hidden] statt einer eigenen display-Regel auf .verwaltung-tab-seite
   (WICHTIG, siehe Kommentar in script.js bei verwaltungTabsVerdrahten:
   eine eigene "display: ..."-Regel auf demselben Element hätte
   dieselbe Spezifität wie [hidden]{display:none} und würde je nach
   Ladereihenfolge zufällig gewinnen — echter Bug aus einer früheren
   Session). Deshalb hier bewusst KEIN display auf .verwaltung-tab-seite
   selbst, nur Größe/Abstand. */
/* RÜCKBAU (2026-08-14, Sven: "würde dann gerne wieder auf die
   Reihenfolge der Buttons zurück") — die zweistufige Gruppen-
   Navigation (Obergruppen-Reiter + Unterreiter, siehe History oben im
   Datum-Kommentar davor) wurde komplett zurückgebaut. Zurück auf EINE
   einzige Reiter-Reihe (.verwaltung-untermenue/.verwaltung-tab), genau
   das Kachel-Maß, das sich ohnehin konsequent durchs ganze Projekt
   zieht. */
.verwaltung-untermenue {
  flex-shrink: 0;
  display: flex;
  gap: calc(var(--s) * 10);
  margin-top: calc(var(--s) * 24);
  margin-bottom: calc(var(--s) * 24);
}
.verwaltung-tab {
  padding: calc(var(--s) * 14) calc(var(--s) * 40);
  background: linear-gradient(rgba(24, 14, 40, 0.9), rgba(10, 6, 18, 0.96));
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 16);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  cursor: pointer;
  transition: border-color var(--duration-standard) var(--ease-soft),
              color var(--duration-standard) var(--ease-soft),
              box-shadow var(--duration-standard) var(--ease-soft);
}
.verwaltung-tab:hover {
  border-color: var(--violet);
  color: var(--text);
}
/* Glow verstärkt (2026-08-10, Sven: "kann man hier auch diesen Glow
   einfügen?" — soft war offenbar kaum sichtbar) — gleiche Stärke wie
   bei den übrigen CTA-Buttons. */
.verwaltung-tab.active {
  border-color: var(--gold);
  box-shadow: var(--glow-gold-strong);
  color: var(--gold-bright);
}
/* Leichter violetter Glow für "Bewerbungen" (2026-08-22, Sven: "leicher
   glow lila damit es etwas raussticht") — soll auch im NICHT-aktiven
   Zustand auffallen (neue Bewerbungen warten ja unabhängig davon,
   welcher Reiter gerade offen ist), deshalb eigene Regel statt nur
   ein .active-Zusatz. Weicht dem Gold-Glow, sobald der Reiter selbst
   aktiv ist (Spezifität von .active reicht dafür schon aus). */
.verwaltung-tab--glow {
  border-color: var(--violet);
  box-shadow: var(--glow-violet-soft);
}
.verwaltung-tab-seite:not([hidden]) {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  /* Sicherheitsnetz (2026-08-09) — die Reiter "Accounts"/"Bewerbungen"
     scrollen längst sauber über .verwaltung-tabelle-wrap (siehe
     weiter unten), aber die Reiter "Gildeninfo"/"Neuigkeiten" betten
     .gildeninfo-verwaltung/.neuigkeiten-verwaltung-liste ohne eigenen
     Scroll-Bereich ein. overflow-y hier fängt das ab, falls deren
     Inhalt (z.B. ein langer "Über uns"-Text) mal höher wird als der
     verfügbare Platz — sonst exakt derselbe Fehler wie beim Footer-
     Bug, nur eine Ebene tiefer versteckt. */
  overflow-y: auto;
  /* Vereinheitlichung (2026-08-10) — hatte bisher als einziger
     scrollbarer Bereich im Projekt gar keine eigene Scrollbalken-Optik,
     lief mit dem nackten Browser-Standardbalken. Jetzt dasselbe Muster
     wie überall sonst (dünner violetter Balken, Abstand zum Rand). */
  /* Bleed bis zum echten Rand (2026-08-10) — betrifft hier nur die
     Reiter Gildeninfo/Neuigkeiten (deren eigentlicher Scrollbereich,
     siehe Kommentar oben), Mitglieder/Bewerbungen scrollen weiterhin
     über .verwaltung-tabelle-wrap (eigene Panel-Box, bewusst NICHT bis
     zum Rand gezogen, siehe dort). */
  margin-right: -4vw;
  padding-right: calc(4vw + var(--s) * 14);
  scrollbar-gutter: stable;
  scrollbar-width: thin;
  scrollbar-color: var(--violet-deep) transparent;
}
/* min-width:0 (2026-08-09, Sven: "Tabellen sollen bei kleinerer
   Browsergröße kleiner werden und man sieht dennoch die Buttons") —
   ohne das verweigert ein Flex-Kind per Browser-Standard das
   Schrumpfen unter seine Inhaltsbreite (dasselbe Prinzip wie
   min-height:0 bei .roster-grid weiter oben, nur für die Breite statt
   Höhe), overflow:auto konnte dadurch nie wirklich greifen — die
   Tabelle wuchs bei zu wenig Platz einfach über den sichtbaren
   Bereich hinaus, statt einen eigenen Scrollbalken zu zeigen. */
.verwaltung-tabelle-wrap {
  flex: 1 1 auto;
  min-height: 0;
  min-width: 0;
  overflow: auto;
  /* 20->32 (2026-08-10, Sven: alle vier Reiter sollen denselben Abstand
     zu den Tabs haben wie Gildeninfo, siehe .gildeninfo-editor-label). */
  margin-top: calc(var(--s) * 32);
  background: linear-gradient(var(--panel-grad-a), var(--panel-grad-b));
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
  scrollbar-gutter: stable;
  scrollbar-width: thin;
  scrollbar-color: var(--violet-deep) transparent;
  /* KEIN padding-right hier (2026-08-10, Korrektur eigener Vorumsetzung):
     anders als bei den reinen Textflächen (#neuigkeiten/.gildeninfo/...)
     hat diese Tabelle eine sticky Kopfzeile mit EIGENEM, deckendem
     Zell-Hintergrund (.verwaltung-tabelle th, siehe dort) — ein
     zusätzliches padding-right hätte die Tabelle schmäler als ihren
     Rahmen gemacht, dadurch hörte die dunkle Kopfzeilen-Füllung sichtbar
     vor dem rechten Rand auf (Sven per Screenshot: "hier schließt der
     Hintergrund nicht völlig ab"). scrollbar-gutter:stable reicht hier
     allein, reserviert den Balken-Platz ohne die Tabelle selbst zu
     verschmälern. */
}
/* table-layout:fixed (2026-08-08, Sven: "die Tabellen bitte besser
   aufbauen") — ohne das ignoriert der Browser die <colgroup>-Breiten
   aus script.js und richtet sich stattdessen nach dem längsten
   Zelleninhalt, genau das sorgte für die krumme, "nicht identische"
   Spaltenaufteilung. Mit fixed gelten die <col style="width:...">-
   Werte verbindlich, Zellinhalte brechen dafür sauber um statt die
   Spalte zu sprengen (siehe word-wrap unten). */
/* 14→17 (2026-08-08, Sven: "die Darstellung der Charaktere usw. größer
   darstellen, die Schrift ist leider zu klein") — zieht Name/Sub-Zeile/
   Motivation etc. automatisch mit, die darunter definierten Regeln
   erben die Basisgröße von hier. */
.verwaltung-tabelle {
  width: 100%;
  table-layout: fixed;
  border-collapse: collapse;
  font-size: calc(var(--s) * 17);
}
.verwaltung-tabelle td {
  overflow-wrap: break-word;
}
/* min-height 2026-08-10 (Sven: Mitglieder-Kopfzeile höher als
   Bewerbungen-Kopfzeile, "die Größe kommt wohl wegen dem 'i' bei
   Mitglieder") — ERSTER Versuch, hat laut Sven NICHT gegriffen (siehe
   2026-08-11-Fix bei .verwaltung-aktionen-kopf/.verwaltung-rollen-
   info-btn: der Button steht dort jetzt position:absolute, nimmt sich
   also gar nicht mehr aus der Zellenhöhe). min-height bleibt trotzdem
   als harmloses Sicherheitsnetz stehen (z.B. falls hier später mal ein
   Icon OHNE position:absolute ergänzt wird), ist aber NICHT mehr die
   eigentliche Lösung des Höhen-Problems. */
.verwaltung-tabelle th {
  position: sticky;
  top: 0;
  min-height: calc(var(--s) * 28);
  padding: calc(var(--s) * 12) calc(var(--s) * 16);
  background: rgba(24, 14, 40, 0.96);
  border-bottom: 1px solid var(--violet-deep);
  text-align: left;
  font-family: var(--font-display);
  font-size: calc(var(--s) * 12);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.verwaltung-tabelle td {
  /* 12->7 senkrecht (2026-08-14, Sven per Screenshot: "die Höhe der
     Charaktere ist viel zu hoch" + "bitte an Bewerbung orientieren" —
     beide Tabellen teilen sich diese eine Regel, Mitglieder wirkte nur
     höher, weil einzelne Zellen dort mehr Textzeilen haben (Name+Realm,
     Online-Status+Login-Zähler usw.) — weniger senkrechtes Innenpolster
     macht beide Tabellen gleichermaßen kompakter, ohne die Schrift
     selbst zu verkleinern. */
  padding: calc(var(--s) * 7) calc(var(--s) * 16);
  border-bottom: 1px solid rgba(177, 140, 255, 0.12);
  color: var(--text);
  vertical-align: middle;
}
.verwaltung-zeile:hover {
  background: rgba(109, 61, 255, 0.06);
}

/* ---- "i"-Button + Rollen-&-Rechte-Popup (2026-08-09) ----
   Sven: "kann man hier auch neben aktionen rechts ans ende ein 'i'
   einbringen ... zeigt an welche Rolle was sehen kann?".
   ZWEITER Anlauf bei der Zeilenhöhe (2026-08-11, Sven: "hier sind bei
   Mitglieder und bei Bewerbungen die Größen nicht identisch! hatte ich
   schon mal gebeten, das anzupassen" — der erste Versuch war eine
   min-height auf .verwaltung-tabelle th, siehe Kommentar dort, hat
   laut Screenshot NICHT gegriffen, vermutlich weil position:sticky-
   Zellen die eigentliche Höhenberechnung in manchen Browsern anders
   behandeln). ECHTER Struktur-Fix statt eines weiteren Höhenwerts: der
   Button steht jetzt per position:absolute komplett AUSSERHALB des
   normalen Höhenflusses der Zelle — die Kopfzeile wird dadurch NUR
   noch von der Textzeile "Aktionen" bestimmt, exakt wie bei jeder
   anderen Kopfzelle auch (auch in der Bewerbungen-Tabelle, die dieses
   Icon gar nicht hat). Beide Tabellen sind dadurch automatisch gleich
   hoch, weil kein zweites Element mehr in die Höhenberechnung
   einfließt — kein Pixel-Abgleich mehr nötig. */
.verwaltung-aktionen-kopf {
  position: relative;
  /* Platz rechts freihalten, damit der Text "Aktionen" nicht unter dem
     absolut positionierten Button verschwindet. */
  padding-right: calc(var(--s) * 36) !important;
}
/* Deutlich sichtbarer (2026-08-09, Sven per Screenshot: "das 'i' ist
   viel zu klein und geht farblich total unter") — vorher unauffällig
   transparent mit dünnem Rahmen, ging vor dem dunklen Hintergrund
   komplett unter. Jetzt: größer (20->28px), echter Panel-Hintergrund
   statt "none" (gleicher Verlauf wie .verwaltung-btn/.news-card, kein
   neues Motiv erfunden), kräftigere Gold-Farbe statt gedämpftem
   --text-dim, serifenlose Display-Schrift + fett statt dünner
   Body-Kursive — als "i" auf einen Blick erkennbar, nicht nur bei
   genauem Hinsehen. */
.verwaltung-rollen-info-btn {
  /* position:absolute (2026-08-11, siehe Kommentar oben) — nimmt den
     Button aus dem Höhenfluss der Zelle, mittig zur Zeile verankert.
     ZWEITER Feinschliff (2026-08-11, Sven: "das 'i' passt hier nicht
     mehr richtig, kann man das weiter nach links verschieben? und
     auch besser darstellen, verliert sich extrem") — right:0 saß exakt
     an der rechten Innenkante der Zelle, dadurch wirkte der Kreis am
     Tabellenrand angeschnitten. Jetzt mit Luft zum Rand (statt 0), und
     blickdichter Hintergrund (gleiche "satte" Variante wie beim Toast/
     bei .klassen-chip) statt des halbtransparenten Panel-Verlaufs, der
     vor der ähnlich dunklen Kopfzeilen-Füllung kaum noch auffiel. */
  position: absolute;
  top: 50%;
  /* 4 -> 20 (2026-08-11, Sven: "immer noch nicht", nochmal weiter nach
     links). */
  right: calc(var(--s) * 20);
  transform: translateY(-50%);
  width: calc(var(--s) * 30);
  height: calc(var(--s) * 30);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: linear-gradient(rgba(24, 14, 40, 0.96), rgba(10, 6, 18, 0.99));
  border: 2px solid var(--gold);
  border-radius: 50%;
  font-family: var(--font-display);
  font-weight: 700;
  font-style: normal;
  font-size: calc(var(--s) * 16);
  line-height: 1;
  color: var(--gold-bright);
  cursor: pointer;
  box-shadow: var(--glow-gold-soft);
  transition: border-color var(--duration-standard) var(--ease-soft),
              color var(--duration-standard) var(--ease-soft),
              box-shadow var(--duration-standard) var(--ease-soft);
}
.verwaltung-rollen-info-btn:hover {
  border-color: var(--gold-bright);
  color: #fff;
  box-shadow: var(--glow-gold-soft), 0 0 0 3px rgba(200, 170, 78, 0.18);
}
/* Deutlich größer (2026-08-09, Sven per Screenshot: "kann man das
   bitte größer darstellen? und hier bitte eine Schriftart nehmen die
   wir eh schon nutzen") — max-width 900->1200, alle Schriftgrößen
   angehoben. Schriftart war technisch schon korrekt (erbt --font-body
   von body, nirgends wurde etwas Neues eingebunden), jetzt zusätzlich
   überall explizit --font-body/--font-display statt vererbt, damit es
   auf den ersten Blick eindeutig ist. KEINE neue Schriftart.
   1200px -> var(--popup-w-breit) (2026-08-09, Sven: "wäre es nicht
   besser alles einheitlich zu gestalten?", siehe die drei Popup-
   Breiten-Stufen im :root weiter oben) — Wert bleibt identisch,
   kommt jetzt aber aus dem gemeinsamen System statt einer eigenen
   Zahl. */
.verwaltung-rollen-popup {
  max-width: var(--popup-w-breit);
}
.verwaltung-rollen-popup h3 {
  margin: 0 0 calc(var(--s) * 10);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 32);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text);
}
.verwaltung-rollen-hinweis {
  margin: 0 0 calc(var(--s) * 26);
  font-family: var(--font-body);
  color: var(--text-dim);
  font-size: calc(var(--s) * 18);
}
.verwaltung-rollen-tabelle-wrap {
  overflow-x: auto;
}
.verwaltung-rollen-tabelle {
  width: 100%;
  min-width: 640px;
  border-collapse: collapse;
  font-family: var(--font-body);
  font-size: calc(var(--s) * 18);
}
.verwaltung-rollen-tabelle th {
  padding: calc(var(--s) * 14) calc(var(--s) * 16);
  border-bottom: 1px solid var(--violet-deep);
  text-align: left;
  font-family: var(--font-display);
  font-size: calc(var(--s) * 13);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.verwaltung-rollen-tabelle th:not(:first-child) {
  text-align: center;
}
.verwaltung-rollen-tabelle td {
  padding: calc(var(--s) * 14) calc(var(--s) * 16);
  border-bottom: 1px solid rgba(177, 140, 255, 0.12);
  font-family: var(--font-body);
  color: var(--text);
}
.verwaltung-rollen-tabelle td:not(:first-child) {
  text-align: center;
}
.verwaltung-rollen-ja {
  color: var(--online);
  font-weight: 700;
}
.verwaltung-rollen-nein {
  color: var(--text-faint);
}
.verwaltung-name {
  font-family: var(--font-display);
  letter-spacing: 0.03em;
  color: var(--text);
}
.verwaltung-sub {
  margin-top: calc(var(--s) * 2);
  font-size: calc(var(--s) * 14);
  color: var(--text-faint);
}
/* Eigene Spalte statt Anhängsel am Charakternamen (2026-08-09, Sven:
   "sehen würde, wenn ein Charakter aus der Gilde gegangen ist") — kein
   margin-left mehr nötig, steht jetzt allein in der eigenen Zelle.
   Zwei Zustände: grün (noch im Roster) / rot (kein Treffer mehr, siehe
   verwaltungZeileHtmlBauen() für den genauen Titel-Hinweistext). */
.verwaltung-treffer {
  display: inline-block;
  padding: calc(var(--s) * 3) calc(var(--s) * 8);
  border-radius: calc(var(--s) * 10);
  font-size: calc(var(--s) * 12);
  white-space: nowrap;
}
.verwaltung-treffer-ja {
  background: rgba(127, 232, 154, 0.14);
  color: var(--online);
}
.verwaltung-treffer-nein {
  background: rgba(179, 57, 76, 0.16);
  color: #E28FA0;
}
/* "Online"-Spalte (2026-08-12, Sven: "für den Admin wäre es cool, in
   der Verwaltung sehen zu können, welches Mitglied wann und wie oft
   online war") — gleicher grüner Punkt+Puls wie beim Discord-Online-
   Button im Footer (.footer-online .dot), gleiche Bedeutung: "gerade
   aktiv". Wer gerade NICHT online ist, zeigt stattdessen nur den
   relativen Zeitpunkt in gedämpfter Farbe. */
.verwaltung-online-status {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 8);
  color: var(--text-dim);
}
.verwaltung-online-jetzt {
  color: var(--online);
  font-weight: 600;
}
.verwaltung-online-status .dot {
  flex-shrink: 0;
  width: calc(var(--s) * 10);
  height: calc(var(--s) * 10);
  border-radius: 50%;
  background: var(--online);
  animation: online-pulsieren 2.4s ease-in-out infinite;
}
/* Kleines Armory-Link-Symbol neben dem Charakternamen (2026-08-09,
   Sven: "link zwar entfernen aber daneben ein Symbol für Armory
   einpflegen mit der Verlinkung" — der Name selbst soll NICHT mehr
   verlinkt sein). Inline-SVG statt Bilddatei, currentColor übernimmt
   automatisch die Textfarbe der Umgebung. */
/* Echtes WoW-Icon statt Inline-SVG-Pfeil (2026-08-09) — opacity-
   Übergang statt color, weil ein <img> keine currentColor-Färbung
   annimmt. */
/* Name/Server untereinander (2026-08-10, Sven: "immer alles
   untereinander platziert, Server hier auch ohne ()") — vorher liefen
   Name, Armory-Icon und "(Realm)" alle in einer Fließtext-Zeile,
   siehe verwaltungZeileHtmlBauen() in script.js. */
.verwaltung-charakter-name {
  font-weight: 600;
}
/* Icon + Schrift größer (2026-08-10, Sven: "das WoW-Logo und
   Servername dürfen gerne etwas größer sein") — 13->16px Schrift,
   Icon hatte bisher feste HTML-Attribute width/height="16" (nicht mit
   --s skaliert, siehe script.js), jetzt per CSS gesteuert wie überall
   sonst im Projekt. */
.verwaltung-charakter-realm {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 7);
  margin-top: calc(var(--s) * 4);
  color: var(--text-dim);
  font-size: calc(var(--s) * 16);
}
.verwaltung-armory-link {
  display: inline-flex;
  vertical-align: middle;
  opacity: 0.75;
  transition: opacity var(--duration-standard) var(--ease-soft);
}
.verwaltung-armory-link img {
  display: block;
  width: calc(var(--s) * 20);
  height: calc(var(--s) * 20);
}
.verwaltung-armory-link:hover {
  opacity: 1;
}
/* Status-Badges — bewusst NICHT die Klassenfarben-Palette, das sind
   hier echte Zustände (siehe Design-System-Konvention: Status-Farben
   sind eigenständig, keine wiederverwendeten Kategorie-Farben). */
.verwaltung-status {
  display: inline-block;
  padding: calc(var(--s) * 3) calc(var(--s) * 10);
  border-radius: calc(var(--s) * 10);
  font-size: calc(var(--s) * 12);
  white-space: nowrap;
}
.verwaltung-status.status-pending {
  background: rgba(200, 170, 78, 0.15);
  color: var(--gold-bright);
}
.verwaltung-status.status-freigegeben {
  background: rgba(127, 232, 154, 0.15);
  color: var(--online);
}
.verwaltung-status.status-abgelehnt {
  background: rgba(179, 57, 76, 0.15);
  color: #e0879a;
}
/* "deaktiviert" (2026-08-19) — bewusst NEUTRAL statt Rot wie
   "abgelehnt": keine Ablehnung, sondern ein automatisch erkannter
   Gildenaustritt, jederzeit über "Reaktivieren" umkehrbar. */
.verwaltung-status.status-deaktiviert {
  background: rgba(144, 136, 168, 0.18);
  color: var(--text-dim);
}
/* .verwaltung-sichtbar-auswahl mit angehängt (2026-08-08, Sven per
   Screenshot: "kann man das Pulldown von Sichtbare Rolle nicht wie
   Rolle anpassen?") — echter Bug, nicht nur Geschmackssache: für
   "Sichtbare Rolle" gab es bisher GAR KEINE eigene CSS-Regel, das
   Dropdown lief die ganze Zeit mit der nackten Browser-Standardoptik,
   deshalb der sichtbare Unterschied zu "Rolle" daneben. */
/* Größe an .verwaltung-btn angeglichen (2026-08-08, Sven: "die
   Pulldown-Menüs dürfen der Größe wie die Buttons in der Zeile
   angepasst werden") — vorher deutlich kleiner (6/10 + 13px) als die
   Aktions-Buttons (8/14 + 14px) in derselben Tabellenzeile, dadurch
   wirkte die Zeile uneinheitlich hoch. */
.verwaltung-rolle-auswahl,
.verwaltung-sichtbar-auswahl {
  padding: calc(var(--s) * 8) calc(var(--s) * 14);
  background: var(--void);
  border: 1px solid var(--violet-deep);
  border-radius: calc(var(--s) * 4);
  font-family: var(--font-body);
  font-size: calc(var(--s) * 14);
  color: var(--text);
}
.verwaltung-rolle-auswahl:disabled,
.verwaltung-sichtbar-auswahl:disabled {
  opacity: 0.5;
}
/* Fokus-Ring (2026-08-09, Sven per Screenshot: "die Eingabefelder
   wirken abgeschnitten, wenn man aktiv darin ist, außerdem haben sie
   einen anderen Glow als gewünscht") — für diese beiden Klassen gab
   es bisher GAR KEINE eigene :focus-Regel, der Browser zeichnete
   deshalb seinen eigenen Standard-Fokusring (meist blau, je nach
   Browser/OS unterschiedlich dick/eckig, dadurch wirkte es "anders"
   und an den abgerundeten Ecken "abgeschnitten"). Gleiches Muster wie
   überall sonst im Projekt (siehe #registrieren-formular input:focus/
   #gildeninfo-ueber-uns-editor:focus): eigener violetter Rahmen +
   weicher Schein statt des Browser-Standards. Betrifft automatisch
   ALLE Felder mit dieser Klasse — Neuigkeit-Formular (Überschrift/
   Datum/Verfasser/Kurztext) UND die Rolle-/Sichtbare-Rolle-Dropdowns
   in der Mitglieder-Tabelle, bisher überall gleich fehlend. */
.verwaltung-rolle-auswahl:focus,
.verwaltung-sichtbar-auswahl:focus {
  outline: none;
  border-color: var(--violet);
  box-shadow: var(--glow-violet-soft);
}
/* Langtext-Feld hatte bisher noch nicht mal eine gemeinsame Klasse
   (siehe #neuigkeit-langtext-feld in script.js) — gleicher Fokus-Ring
   trotzdem, direkt über die ID. */
#neuigkeit-langtext-feld:focus {
  outline: none;
  border-color: var(--violet);
  box-shadow: var(--glow-violet-soft);
}
/* BUGFIX (2026-08-09, Sven: "wenn ich das Browserfenster verkleinere,
   verschwinden die Buttons" — der Scroll-Ansatz von eben griff nicht
   zuverlässig genug). white-space:nowrap zwang die drei Buttons
   (Freischalten/Ablehnen/Löschen) IMMER in eine einzige Zeile, bei zu
   wenig Platz liefen sie dadurch optisch aus der Zelle/Tabelle/Seite
   heraus, statt zu reagieren. Jetzt dürfen sie stattdessen in eine
   zweite Zeile umbrechen (flex-wrap) — die Buttons bleiben dadurch
   IMMER sichtbar, die Zeile wird bei wenig Platz einfach höher statt
   breiter. */
.verwaltung-aktionen {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--s) * 6);
}
.verwaltung-aktionen .verwaltung-btn {
  margin-right: 0; /* .verwaltung-btn hat noch margin-right, ersetzt durch gap oben */
}
/* 11→14 + Padding leicht erhöht (2026-08-08, Sven) — seit die
   Tabellenschrift 14→17 gewachsen ist (siehe .verwaltung-tabelle),
   wirkten ALLE .verwaltung-btn-Buttons (Tabellen-Aktionen UND die
   Gildeninfo-Buttons "Speichern"/"Jetzt aktualisieren", gleiche
   Klasse) dagegen zu klein — betraf technisch alle gleich, war kein
   Speichern-spezifischer Bug. */
/* 2026-08-10, Sven per Live-Screenshot: die CTA-Größe (14×40, 16px,
   Gold-Outline, Glow) passte NICHT zu den Tabellen-Aktionen
   (Freischalten/Ablehnen/Löschen) und den Neuigkeiten-Verwaltung-
   Buttons (Bearbeiten/Löschen) — dort wieder komplett auf den
   ursprünglichen, kompakten Stand zurückgesetzt. Der "Speichern"-Button
   bei Mein Profil (Twitch-Kanal) sollte dagegen groß bleiben — genau
   das war der Interessenkonflikt (gleiche Klasse für beides), deshalb
   jetzt entkoppelt: .mein-profil-speichern (eigene Klasse, siehe weiter
   unten) trägt die CTA-Optik, .verwaltung-btn bleibt wieder die
   kompakte Verwaltungs-Optik für alle Tabellen-/Formular-Aktionen. */
.verwaltung-btn {
  /* Vereinheitlicht auf exakt die .verwaltung-tab-Maße (2026-08-24,
     Sven: "ich möchte das die buttons alle die gleiche größe haben")
     — bewusst dieselbe Größe wie überall sonst, auch in Tabellenzeilen;
     margin-right sorgt weiter für Abstand zwischen mehreren Buttons in
     derselben Zelle. */
  padding: calc(var(--s) * 14) calc(var(--s) * 40);
  margin-right: calc(var(--s) * 6);
  background: linear-gradient(rgba(24, 14, 40, 0.9), rgba(10, 6, 18, 0.96));
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 16);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  cursor: pointer;
  transition: border-color var(--duration-standard) var(--ease-soft),
              color var(--duration-standard) var(--ease-soft);
}
.verwaltung-btn:hover:not(:disabled) {
  border-color: var(--violet);
}
/* Glow beim Hover (2026-08-10, Sven: "kann man hier auch diesen Glow
   einfügen in der jeweiligen Farbe?") — in der jeweiligen Aktionsfarbe
   (nutzt dieselben Tokens wie die großen CTA-Buttons). */
.verwaltung-btn-ok:hover:not(:disabled) {
  color: var(--online);
  border-color: var(--online);
  box-shadow: var(--glow-online-strong);
}
.verwaltung-btn-ablehnen:hover:not(:disabled) {
  color: #e0879a;
  border-color: rgba(179, 57, 76, 0.6);
  box-shadow: var(--glow-rot-strong);
}
/* Löschen (2026-08-08) — dauerhaft (nicht erst bei :hover) in Rot-Tönen,
   damit die unwiderrufliche Aktion sich optisch klar von den anderen
   Buttons absetzt, nicht erst beim Draufzeigen auffällt. */
.verwaltung-btn-loeschen {
  color: #e0879a;
  border-color: rgba(179, 57, 76, 0.5);
}
.verwaltung-btn-loeschen:hover:not(:disabled) {
  color: #fff;
  background: rgba(179, 57, 76, 0.35);
  border-color: #e0879a;
  box-shadow: var(--glow-rot-strong);
}
/* Speichern-Buttons in Gold statt Grau (2026-08-08, Sven: "den
   Speichern-Button bitte auch im Gelb-Ton") — eigene Modifier-Klasse
   statt .verwaltung-btn-ok umzufärben, weil die auch für "Annehmen"/
   "Freischalten" verwendet wird, wo Grün als Bestätigungs-Farbe
   inhaltlich weiter Sinn ergibt. Wird u.a. von "Speichern"/"Jetzt von
   Raider.IO aktualisieren" in der Gildeninfo-Verwaltung genutzt (nicht
   mehr von Mein Profil, siehe .mein-profil-speichern weiter unten). */
.verwaltung-btn-gold {
  color: var(--gold-bright);
  border-color: rgba(200, 170, 78, 0.5);
}
.verwaltung-btn-gold:hover:not(:disabled) {
  color: var(--gold-bright);
  border-color: var(--gold-bright);
  box-shadow: var(--glow-gold-soft);
}
.verwaltung-btn:disabled {
  opacity: 0.4;
  cursor: default;
}
/* Bewerbungen-Block (2026-08-08) — zweite Tabelle unter den Accounts,
   nutzt dieselben .verwaltung-tabelle*-Regeln, nur Überschrift und
   Motivations-Spalte sind neu. */
.verwaltung-untertitel {
  margin: calc(var(--s) * 32) 0 0;
  font-family: var(--font-display);
  font-size: calc(var(--s) * 18);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text);
}
/* white-space:normal statt pre-wrap (2026-08-08, Sven per Screenshot:
   Bewerbungstext mit vielen einzelnen Zeilenumbrüchen zeigte jedes
   Wort in einer eigenen Zeile) — pre-wrap übernahm JEDEN Zeilenumbruch
   aus dem freien Bewerbungs-Textfeld 1:1, auch viele kurze
   Absichts-/Versehens-Umbrüche hintereinander. normal fasst das wieder
   zu normalem Fließtext zusammen und bricht nur an der Spaltenbreite
   um — echte Absätze (Leerzeile) blieben zuvor ohnehin nicht als
   solche erkennbar, keine inhaltliche Einbuße. */
.verwaltung-motivation {
  white-space: normal;
  line-height: 1.5;
  color: var(--text-dim);
}
.verwaltung-leer {
  padding: calc(var(--s) * 24);
  text-align: center;
  color: var(--text-faint);
}
/* Beworben-am-Spalte (2026-08-13) — eigene Spalte statt Sub-Zeile am
   Charakternamen, gedämpfte Farbe wie die restlichen Nebeninfos.
   Schriftgröße bewusst NICHT eigens gesetzt (2026-08-13, Sven fiel der
   Größenunterschied zu "Nachricht" auf) — erbt jetzt dieselbe Größe wie
   .verwaltung-motivation von .verwaltung-tabelle (17px), nur die Farbe
   bleibt gedämpfter. */
.verwaltung-beworben-am {
  color: var(--text-faint);
  white-space: nowrap;
}

/* ---- Mitgliederverlauf (2026-08-13) ----
   Gleiche Pillen-Sprache wie .verwaltung-status (Grün/Rot statt
   generischer Farbe), damit "Beigetreten"/"Verlassen" auf einen Blick
   auseinanderzuhalten sind. */
.mitgliederverlauf-verwaltung {
  padding: calc(var(--s) * 4) 0;
}
.mitgliederverlauf-ereignis {
  display: inline-block;
  padding: calc(var(--s) * 3) calc(var(--s) * 10);
  border-radius: calc(var(--s) * 10);
  font-size: calc(var(--s) * 12);
  white-space: nowrap;
}
.mitgliederverlauf-ereignis--beigetreten {
  background: rgba(127, 232, 154, 0.15);
  color: var(--online);
}
.mitgliederverlauf-ereignis--verlassen {
  background: rgba(179, 57, 76, 0.15);
  color: #e0879a;
}
/* Klassen-Icon + Klassenfarbe (2026-08-13, Sven: "kann man vor die
   Klasse noch das jeweilige Klassensymbol platzieren und natürlich
   auch die Klassenfarbe?") — gleiche Icon-Optik/Größe wie das
   Raidplaner-Klassensymbol (.raidplan-klasse-icon). */
.mitgliederverlauf-klasse-zelle {
  white-space: nowrap;
}
.mitgliederverlauf-klasse-icon {
  width: calc(var(--s) * 22);
  height: calc(var(--s) * 22);
  border-radius: calc(var(--s) * 4);
  border: 1px solid rgba(200, 170, 78, 0.4);
  object-fit: cover;
  vertical-align: -6px;
  margin-right: calc(var(--s) * 6);
}

/* ---- Gildeninfo-Verwaltung (2026-08-08) ----
   Sven: "Möglichkeit geben, den Text selbst zu ändern oder eben die
   Option über Raider.IO zu aktualisieren". Textarea statt WYSIWYG
   (bewusst, siehe Kommentar in script.js), gleiche Panel-/Button-
   Sprache wie der Rest der Verwaltung. */
/* Breiter (2026-08-08, Sven: "das Eingabefeld darf gerne etwas
   breiter werden") — 820→1200px. */
.gildeninfo-verwaltung {
  max-width: 1200px;
  /* 2026-08-11, Sven: "der Hintergrund ist zu präsent" — die Form-
     Sektion selbst hatte bisher KEIN eigenes Panel, nur die einzelnen
     Felder waren abgedunkelt, dazwischen/drumherum schien das bewegte
     Hintergrundbild voll durch. ERSTER Versuch (komplettes Popup-Panel
     mit eigenem Rand/Outline) sah laut Sven wie "Kasten im Kasten" aus,
     weil die Felder selbst schon einen eigenen Rand haben (siehe
     .verwaltung-rolle-auswahl usw.) — deshalb jetzt NUR die
     Hintergrundfläche zum Abdunkeln, bewusst OHNE eigenen Rand/Outline,
     die Feld-Rahmen bleiben die einzige sichtbare Kontur.
     ZWEITER Fix (Sven: "der Hintergrund ist zu weit oben an den
     Buttons, das sieht nach nichts aus") — .gildeninfo-verwaltung sitzt
     direkt in .verwaltung-tab-seite, OHNE jeden Abstand zu den Reitern
     (Mitglieder/Bewerbungen/…) direkt darüber, das Panel stieß dadurch
     nahtlos an die Buttons. Ursprünglich hier per margin-top gelöst —
     DRITTER Fix (Sven: "wenn ich scrollt ist der Abstand weg, bitte
     überall fixen"): margin-top hier lag INNERHALB des scrollenden
     Bereichs (.verwaltung-tab-seite hat overflow-y:auto) und
     verschwand beim Scrollen mit. Jetzt stattdessen als margin-bottom
     auf .verwaltung-untermenue gelöst (liegt außerhalb des Scroll-
     Containers, bleibt immer sichtbar) — hier deshalb ersatzlos
     entfernt, sonst doppelter Abstand.
     VIERTER Fix (Sven: "hier muss es wieder angepasst werden, damit
     die Buttons wieder erscheinen ohne Scroll") — die Polsterung oben/
     unten (28px) plus der neue margin-bottom an der Reiter-Leiste
     (24px) haben zusammen mehr Höhe gefressen, als die feste
     Textfeld-Höhe von #gildeninfo-ueber-uns-editor (30vh + 245px) noch
     Puffer ließ, dadurch rutschten "Speichern"/"Abbrechen" unter den
     sichtbaren Rand. Vertikale Polsterung hier verkleinert (28 -> 16),
     seitliche unverändert. */
  padding: calc(var(--s) * 16) calc(var(--s) * 32);
  background: linear-gradient(var(--panel-grad-a), var(--panel-grad-b));
  border-radius: var(--radius-card);
}

/* Mein Profil: Kopf bleibt stehen, nur der Karten-Bereich scrollt
   (2026-08-09, Sven: "Überschrift der Rubrik soll fest verankert
   sein"). .gildeninfo-verwaltung ist hier direktes Kind von
   .roster-header (siehe meinProfilHtmlBauen() in script.js) — bewusst
   NUR für Mein Profil gescoped (.mein-profil davor), NICHT auf
   .gildeninfo-verwaltung selbst, das würde sonst auch den
   Verwaltung-Reiter "Gildeninfo" mitverändern, der schon sein eigenes
   Scroll-Muster über .verwaltung-tab-seite hat. */
.mein-profil .gildeninfo-verwaltung {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding-right: calc(var(--s) * 14);
  scrollbar-gutter: stable;
  scrollbar-width: thin;
  scrollbar-color: var(--violet-deep) transparent;
}
/* Etwas Abstand nach oben + gleiche Typografie wie
   .gildeninfo-raid-verwaltung-titel (2026-08-08, Sven: "'Über uns'
   etwas nach unten und die Schriftgröße/-art wie 'Raid-Fortschritt
   (Raider.IO)' anpassen") — vorher direkt am oberen Rand, klein/dünn
   (13px/0.08em), jetzt gleiche Größe/Sperrung wie die Raid-Überschrift
   darunter (15px/0.1em), damit beide als gleichwertige Abschnitts-
   titel wirken. Kein flex-column mehr nötig (Textarea steht jetzt als
   eigenständiges Element nach der Toolbar, nicht mehr im Label). */
.gildeninfo-editor-label {
  display: block;
  /* 28->32 (2026-08-10, Sven: "noch 3-4px weiter nach unten"), dient
     jetzt auch als Referenzwert für die anderen drei Reiter (Mitglieder/
     Bewerbungen/Neuigkeiten), siehe dort.
     Abstand NACH UNTEN (zum Eingabefeld darunter) 14->20 (2026-08-11,
     Sven: "Titel zum Eingabefeld bisschen Abstand nach unten, auch bei
     den anderen Eingabefeldern") — betrifft alle Formular-Labels, die
     diese Klasse nutzen. */
  margin: calc(var(--s) * 32) 0 calc(var(--s) * 20);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 15);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  /* 2026-08-11, Sven: gleiches Problem wie bei
     .gildeninfo-raid-verwaltung-titel ("geht unter") — aus
     Konsistenzgründen dieselbe Lösung (var(--text-dim) -> var(--gold-
     bright)) übernommen, betrifft ALLE Formular-Feldlabel im
     Verwaltungsbereich (Titel/Datum/Uhrzeit/Notiz usw., nicht nur beim
     Raidplan). */
  color: var(--gold-bright);
}
/* Formatierungs-Toolbar (2026-08-08, Sven: "die Möglichkeit geben,
   den Text als kursiv/bold/unterstrichen und farblich nutzen zu
   können") — umschließt die Textauswahl im Feld darunter mit dem
   passenden HTML-Tag (siehe textareaAuswahlUmschliessen() in
   script.js). */
.gildeninfo-editor-toolbar {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 8);
  margin-bottom: calc(var(--s) * 10);
}
.gildeninfo-format-btn {
  width: calc(var(--s) * 34);
  height: calc(var(--s) * 34);
  padding: 0;
  background: linear-gradient(var(--panel-grad-a), var(--panel-grad-b));
  border: 1px solid var(--violet-deep);
  border-radius: calc(var(--s) * 4);
  font-family: var(--font-body);
  font-size: calc(var(--s) * 14);
  color: var(--text-dim);
  cursor: pointer;
  transition: border-color var(--duration-standard) var(--ease-soft),
              color var(--duration-standard) var(--ease-soft);
}
.gildeninfo-format-btn:hover {
  border-color: var(--violet);
  color: var(--text);
}
.gildeninfo-format-farbe {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 6);
  padding: 0 calc(var(--s) * 10);
  height: calc(var(--s) * 34);
  background: linear-gradient(var(--panel-grad-a), var(--panel-grad-b));
  border: 1px solid var(--violet-deep);
  border-radius: calc(var(--s) * 4);
  font-family: var(--font-body);
  font-size: calc(var(--s) * 13);
  color: var(--text-dim);
  cursor: pointer;
}
.gildeninfo-format-farbe input[type="color"] {
  width: calc(var(--s) * 20);
  height: calc(var(--s) * 20);
  padding: 0;
  border: none;
  border-radius: calc(var(--s) * 3);
  background: none;
  cursor: pointer;
}
/* Emoji-Palette (2026-08-08, Sven: "eine Palette mit Emojis
   einbinden") — position:relative auf dem Wrapper, Palette klappt
   direkt darunter auf. Gleiches [hidden]/:not([hidden])-Muster wie
   .neuigkeit-popup-overlay (siehe Bugfix-Kommentar dort). */
.gildeninfo-emoji-wrap {
  position: relative;
}
.gildeninfo-emoji-palette {
  /* KEIN "display: grid" hier direkt (Bugfix 2026-08-08, Sven: Palette
   * war beim Öffnen der Verwaltung fälschlich schon offen) — hätte
   * dieselbe Spezifität wie das Browser-Standard-[hidden]{display:none}
   * und würde es unabhängig von der Reihenfolge überstimmen, siehe
   * Kommentar oben und das gleiche Muster bei .neuigkeit-popup-overlay.
   * display:grid steht deshalb NUR unten in :not([hidden]). */
  position: absolute;
  top: calc(100% + var(--s) * 6);
  left: 0;
  z-index: 10;
  grid-template-columns: repeat(7, 1fr);
  gap: calc(var(--s) * 4);
  width: calc(var(--s) * 260);
  padding: calc(var(--s) * 10);
  background: linear-gradient(rgba(24, 14, 40, 0.98), rgba(10, 6, 18, 0.99));
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5);
}
.gildeninfo-emoji-palette:not([hidden]) {
  display: grid;
}
.gildeninfo-emoji-option {
  width: calc(var(--s) * 30);
  height: calc(var(--s) * 30);
  background: none;
  border: 1px solid transparent;
  border-radius: calc(var(--s) * 4);
  font-size: calc(var(--s) * 16);
  cursor: pointer;
  transition: background var(--duration-standard) var(--ease-soft),
              border-color var(--duration-standard) var(--ease-soft);
}
.gildeninfo-emoji-option:hover {
  background: rgba(109, 61, 255, 0.15);
  border-color: var(--violet-deep);
}
/* Klassen/Spec-Palette (2026-08-11, Sven: "kann man bei Gildeninfo noch
   ein Pulldown neben die Emojies einfügen mit den Klassen und Specs zur
   Auswahl? natürlich an die Größe anpassen") — gleiches Wrapper-/
   [hidden]-Muster wie .gildeninfo-emoji-palette direkt oberhalb, aber
   eine Liste statt eines Emoji-Rasters (13 Klassen x bis zu 4 Specs
   wären als Raster viel zu unübersichtlich), deshalb höher UND mit
   eigenem Scroll-Bereich statt in die Breite zu wachsen. */
.gildeninfo-klasse-palette {
  position: absolute;
  top: calc(100% + var(--s) * 6);
  left: 0;
  z-index: 10;
  flex-direction: column;
  gap: calc(var(--s) * 4);
  width: calc(var(--s) * 300);
  max-height: calc(var(--s) * 320);
  overflow-y: auto;
  padding: calc(var(--s) * 10);
  background: linear-gradient(rgba(24, 14, 40, 0.98), rgba(10, 6, 18, 0.99));
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5);
  scrollbar-width: thin;
  scrollbar-color: var(--violet-deep) transparent;
}
.gildeninfo-klasse-palette:not([hidden]) {
  display: flex;
}
.gildeninfo-klasse-gruppe + .gildeninfo-klasse-gruppe {
  margin-top: calc(var(--s) * 4);
  padding-top: calc(var(--s) * 4);
  border-top: 1px solid rgba(177, 140, 255, 0.12);
}
.gildeninfo-klasse-option {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 8);
  width: 100%;
  padding: calc(var(--s) * 6) calc(var(--s) * 8);
  background: none;
  border: 1px solid transparent;
  border-radius: calc(var(--s) * 4);
  font-family: var(--font-body);
  font-size: calc(var(--s) * 14);
  font-weight: 600;
  color: var(--klassenfarbe, var(--text));
  text-align: left;
  cursor: pointer;
  transition: background var(--duration-standard) var(--ease-soft),
              border-color var(--duration-standard) var(--ease-soft);
}
.gildeninfo-klasse-option img {
  width: calc(var(--s) * 20);
  height: calc(var(--s) * 20);
  border-radius: calc(var(--s) * 3);
}
.gildeninfo-klasse-option:hover {
  background: color-mix(in srgb, var(--klassenfarbe, var(--violet)) 15%, transparent);
  border-color: color-mix(in srgb, var(--klassenfarbe, var(--violet)) 40%, transparent);
}
.gildeninfo-spec-liste {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--s) * 4);
  padding: calc(var(--s) * 2) 0 calc(var(--s) * 4) calc(var(--s) * 28);
}
.gildeninfo-spec-option {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 5);
  padding: calc(var(--s) * 4) calc(var(--s) * 8);
  background: none;
  border: 1px solid var(--violet-deep);
  border-radius: calc(var(--s) * 4);
  font-family: var(--font-body);
  font-size: calc(var(--s) * 12);
  color: var(--text-dim);
  cursor: pointer;
  transition: border-color var(--duration-standard) var(--ease-soft),
              color var(--duration-standard) var(--ease-soft);
}
.gildeninfo-spec-option img {
  width: calc(var(--s) * 15);
  height: calc(var(--s) * 15);
  border-radius: calc(var(--s) * 2);
}
.gildeninfo-spec-option:hover {
  border-color: var(--klassenfarbe, var(--violet));
  color: var(--klassenfarbe, var(--text));
}
/* Eingefügtes Icon IM Fließtext des "Über uns"-Bereichs (nicht in der
   Palette selbst, siehe oben) — bündig in der Zeilenhöhe des Textes
   statt in Originalgröße (typisch 56x56px Blizzard-Icon), "natürlich
   an die Größe anpassen". vertical-align:middle statt baseline, sonst
   hängt der Text spürbar tiefer als das Icon. GILT SOWOHL im Editor
   (.gildeninfo-editor-wysiwyg) ALS AUCH in der echten öffentlichen
   Anzeige (.gildeninfo-ueber-uns) — der gespeicherte Text landet 1:1
   in beiden, ohne diese zweite Stelle wäre das Icon dort riesig. */
.gildeninfo-editor-wysiwyg .gildeninfo-inhalt-icon,
.gildeninfo-ueber-uns .gildeninfo-inhalt-icon {
  width: 1.2em;
  height: 1.2em;
  vertical-align: middle;
  border-radius: calc(var(--s) * 3);
}
/* Schriftgröße-Palette (2026-08-11, Sven: "auch die Möglichkeit auf
   die Schriftgröße geben") — gleiches Auf/Zu-Muster wie Emoji/Klasse,
   hier eine simple untereinander stehende Liste statt Raster (nur 4
   Optionen), jede Option in ihrer eigenen Zielgröße dargestellt, damit
   man den Effekt schon in der Liste sieht statt raten zu müssen. */
.gildeninfo-groesse-palette {
  position: absolute;
  top: calc(100% + var(--s) * 6);
  left: 0;
  z-index: 10;
  flex-direction: column;
  gap: calc(var(--s) * 2);
  width: calc(var(--s) * 140);
  padding: calc(var(--s) * 8);
  background: linear-gradient(rgba(24, 14, 40, 0.98), rgba(10, 6, 18, 0.99));
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5);
}
.gildeninfo-groesse-palette:not([hidden]) {
  display: flex;
}
.gildeninfo-groesse-option {
  padding: calc(var(--s) * 6) calc(var(--s) * 10);
  background: none;
  border: 1px solid transparent;
  border-radius: calc(var(--s) * 4);
  font-family: var(--font-body);
  color: var(--text);
  text-align: left;
  cursor: pointer;
  transition: background var(--duration-standard) var(--ease-soft),
              border-color var(--duration-standard) var(--ease-soft);
}
.gildeninfo-groesse-option:hover {
  background: rgba(109, 61, 255, 0.15);
  border-color: var(--violet-deep);
}
/* Bild-Auswahl für gildeninterne Neuigkeiten (2026-08-10, Sven: "Feld
   mit einem Pulldown-Menü ... Bild aussuchen" + "wenn man das Pulldown
   öffnet darf gerne eine kleine Vorschau erscheinen") — gleiches
   Knopf+Klapp-Raster-Muster wie die Emoji-Palette direkt oberhalb,
   nur mit Bild-Kacheln statt Emoji-Buttons und einer eigenen kleinen
   Vorschau neben dem Knopf, die den AKTUELL gewählten Stand zeigt. */
.neuigkeit-bild-wrap {
  position: relative;
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 10);
}
.neuigkeit-bild-vorschau-bild {
  width: calc(var(--s) * 48);
  height: calc(var(--s) * 27);
  object-fit: cover;
  border-radius: calc(var(--s) * 4);
  border: 1px solid var(--violet-deep);
}
.neuigkeit-bild-vorschau {
  font-size: calc(var(--s) * 14);
  color: var(--text-dim);
}
.neuigkeit-bild-palette {
  /* KEIN "display: grid" hier direkt, gleicher Bugfix-Grund wie bei
     .gildeninfo-emoji-palette (siehe Kommentar dort). */
  position: absolute;
  top: calc(100% + var(--s) * 8);
  left: 0;
  z-index: 10;
  grid-template-columns: repeat(4, 1fr);
  gap: calc(var(--s) * 8);
  width: calc(var(--s) * 440);
  max-height: calc(var(--s) * 320);
  overflow-y: auto;
  /* Violetter Scrollbalken (2026-08-12) statt Browser-Standard, siehe
     .raidplan-klasse-palette für den Anlass. */
  scrollbar-width: thin;
  scrollbar-color: var(--violet-deep) transparent;
  padding: calc(var(--s) * 12);
  background: linear-gradient(rgba(24, 14, 40, 0.98), rgba(10, 6, 18, 0.99));
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5);
}
.neuigkeit-bild-palette:not([hidden]) {
  display: grid;
}
.neuigkeit-bild-option {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--s) * 4);
  background: none;
  border: 1px solid transparent;
  border-radius: calc(var(--s) * 6);
  padding: calc(var(--s) * 6);
  cursor: pointer;
  transition: background var(--duration-standard) var(--ease-soft),
              border-color var(--duration-standard) var(--ease-soft);
}
.neuigkeit-bild-option:hover {
  background: rgba(109, 61, 255, 0.15);
  border-color: var(--violet-deep);
}
.neuigkeit-bild-option img {
  width: 100%;
  height: calc(var(--s) * 55);
  object-fit: cover;
  border-radius: calc(var(--s) * 4);
}
.neuigkeit-bild-option span {
  font-size: calc(var(--s) * 12);
  color: var(--text-dim);
  text-align: center;
}
/* Lesbare Fließtext-Schrift statt Monospace (2026-08-08, Sven: "die
   hier angezeigte ist viel zu klein und unleserlich") — Courier New
   sollte ursprünglich rohes HTML besser erkennbar machen, wirkte in
   der Praxis aber vor allem winzig und schwer lesbar. Gleiche Schrift
   wie der Rest der Formularfelder (--font-body), zusätzlich größer. */
#gildeninfo-ueber-uns-editor,
#neuigkeit-formular textarea {
  display: block;
  width: 100%;
  box-sizing: border-box;
  padding: calc(var(--s) * 15) calc(var(--s) * 18);
  background: var(--void);
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
  font-family: var(--font-body);
  /* 17 -> 20 (2026-08-09, Sven: "auch etwas größer darstellen von der
     Schriftgröße") — jetzt sogar etwas größer als die Bewerbung/
     Registrierung-Formularfelder (18px), auf Svens ausdrücklichen
     Wunsch für genau dieses Eingabefenster. */
  font-size: calc(var(--s) * 20);
  line-height: 1.6;
  color: var(--text);
  resize: vertical;
  /* overflow nötig, damit (a) "resize" überhaupt einen Anfasser zeigt
     (Standardverhalten außerhalb von <textarea>) und (b) längerer Text
     im WYSIWYG-Editor (2026-08-11-Umbau, jetzt ein contenteditable div
     statt textarea) INNERHALB des Feldes scrollt statt es zu sprengen. */
  overflow-y: auto;
  /* Violetter Scrollbalken (2026-08-12) statt Browser-Standard, siehe
     .raidplan-klasse-palette für den Anlass. */
  scrollbar-width: thin;
  scrollbar-color: var(--violet-deep) transparent;
}
/* Innentypografie des WYSIWYG-Editors (2026-08-11-Umbau) — vorher
   waren <p>/<h4>/<ul> als sichtbarer HTML-Klartext im Textfeld zu
   sehen, jetzt werden sie ECHT gerendert und brauchen deshalb eigene
   Regeln, sonst käme nur der nackte Browser-Standard (keine
   Absatzabstände, unformatierte h4 usw.). Bewusst nah an der
   öffentlichen Darstellung von "Über uns" gehalten, damit der Editor
   schon in etwa zeigt, wie es später aussieht. */
#gildeninfo-ueber-uns-editor p {
  margin: 0 0 calc(var(--s) * 14);
}
#gildeninfo-ueber-uns-editor p:last-child {
  margin-bottom: 0;
}
#gildeninfo-ueber-uns-editor h4 {
  font-family: var(--font-display);
  font-size: calc(var(--s) * 17);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--gold-bright);
  margin: calc(var(--s) * 18) 0 calc(var(--s) * 8);
}
#gildeninfo-ueber-uns-editor ul {
  margin: 0 0 calc(var(--s) * 14);
  padding-left: 1.4em;
}
#gildeninfo-ueber-uns-editor li {
  margin-bottom: calc(var(--s) * 4);
}
/* Platzhalter (2026-08-11) — contenteditable zeigt von Haus aus keinen
   Platzhaltertext wie ein <input>/<textarea>, hier über :empty +
   data-placeholder nachgebaut, greift praktisch nur, falls der "Über
   uns"-Text jemals komplett geleert wird. */
#gildeninfo-ueber-uns-editor:empty::before {
  content: attr(data-placeholder);
  color: var(--text-faint);
}
/* Feste, deutlich größere Mindesthöhe NUR für den "Über uns"-Editor
   (2026-08-09, Sven zeigte einen Screenshot mit einer viel größeren
   Box als tatsächlich vorhanden — hatte beim vorherigen Mal nur die
   Schriftgröße vergrößert, nicht die Boxhöhe selbst mitgezogen).
   min-height statt nur des rows-Attributs, damit die Größe auch bei
   kurzem Text stabil bleibt (rows allein bestimmt nur die Höhe pro
   Zeichenzeile, nicht die Mindesthöhe). resize:vertical bleibt aktiv,
   Sven kann bei Bedarf trotzdem noch weiter vergrößern. */
/* min-height -> height, 2. Anlauf mit vh statt --s (2026-08-10, Sven:
   erste Korrektur auf 480*--s reichte bei seiner Fensterhöhe immer noch
   nicht, Buttons blieben unterhalb des sichtbaren Bereichs). --s hängt
   an der HINTERGRUNDBILD-Skalierung (max(vw/3440, vh/1440)), nicht am
   tatsächlich verfügbaren Platz für DIESES eine Feld — bei bestimmten
   Fensterformen reichte das nicht zuverlässig. Jetzt direkt in vh
   (echte Fensterhöhe): 35vh lässt bei jeder Fenstergröße verlässlich
   Platz für Titel/Tabs/Label/Toolbar oberhalb UND Speichern/Abbrechen
   unterhalb übrig, ohne von der Bild-Skalierung abhängig zu sein.
   Langer Text scrollt weiterhin INNERHALB des Feldes (Browser-
   Standardverhalten), resize:vertical bleibt aktiv für Handanpassung. */
#gildeninfo-ueber-uns-editor {
  /* 2026-08-11, Sven: "das Textfeld muss kleiner werden, damit die
     Buttons wieder nach oben kommen" — mit dem neuen Panel-Hintergrund
     drumherum (siehe .gildeninfo-verwaltung) plus dem WYSIWYG-Umbau
     reichte die bisherige Höhe (30vh + 245px) nicht mehr, um
     Speichern/Abbrechen ohne Scrollen zu zeigen. Deutlich verkleinert
     (30vh+245px -> 22vh+160px), resize:vertical bleibt aktiv, falls
     Sven es sich bei Bedarf selbst wieder größer zieht. */
  height: calc(22vh + 160px);
}
/* Formular-Grundraster für die Neuigkeiten-Verwaltung (2026-08-08) —
   gleiche Label-Optik wie .gildeninfo-editor-label, hier mehrfach
   untereinander statt nur einmal. */
#neuigkeit-formular {
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 16);
  margin-bottom: calc(var(--s) * 30);
}
#neuigkeit-formular .gildeninfo-editor-label {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 6);
}
/* Abstand zu den Tabs (2026-08-10, Sven: alle vier Reiter sollen
   denselben Abstand haben wie Gildeninfo) — die generelle margin:0 oben
   gilt für ALLE Labels im Formular (Zeilenabstand kommt vom flex-gap
   des Formulars selbst), nur das ERSTE braucht zusätzlich den
   Tabs-Abstand obendrauf. :first-of-type statt :first-child, da davor
   noch ein unsichtbares <input type="hidden"> im Markup steht (siehe
   script.js). */
#neuigkeit-formular .gildeninfo-editor-label:first-of-type {
  margin-top: calc(var(--s) * 32);
}
#neuigkeit-formular input[type="text"],
#neuigkeit-formular input[type="date"] {
  padding: calc(var(--s) * 12) calc(var(--s) * 16);
  font-size: calc(var(--s) * 15);
}
/* 2026-08-11, Sven: "bitte hier den Abstand genauso wie bei
   Neuigkeiten zwischen 'Überschrift' und 'Eingabefeld'" — Raidplan
   hatte bisher KEINE eigene Regel wie #neuigkeit-formular direkt
   oberhalb, verließ sich nur auf die knappe Standard-Zeilenhöhe
   zwischen Label-Text und dem direkt darunter umbrechenden Eingabefeld.
   Exakt dasselbe Muster übernommen: eigener Zeilenabstand fürs
   Formular selbst (statt Label-margin) + gap:6px NUR zwischen
   Label-Text und Feld. ">"-Kindselektor beim :first-of-type, damit NUR
   "Titel" (direktes Kind des Formulars) den Extra-Abstand nach oben
   bekommt, NICHT das jeweils erste Label INNERHALB einer
   .raidtermin-feld-reihe (Datum/Tank-Plätze) — sonst stünden die
   Label einer Reihe nicht mehr auf gleicher Höhe. */
#raidtermin-formular {
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 16);
  margin-bottom: calc(var(--s) * 30);
}
#raidtermin-formular .gildeninfo-editor-label {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 6);
}
#raidtermin-formular > .gildeninfo-editor-label:first-of-type {
  margin-top: calc(var(--s) * 32);
}
#gildeninfo-ueber-uns-editor:focus {
  outline: none;
  border-color: var(--violet);
  box-shadow: var(--glow-violet-soft);
}
.gildeninfo-editor-buttons {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 14);
  margin-top: calc(var(--s) * 14);
  margin-bottom: calc(var(--s) * 8);
}
/* Mehr Luft zum "Über uns"-Textfeld (2026-08-10, Sven: "bisschen
   Abstand zu den Buttons") — nur DIESES eine Buttons-Div betroffen
   (direkt nach dem Editor-Feld), nicht die anderen Stellen, die
   .gildeninfo-editor-buttons ebenfalls nutzen (Erstellen/Sync-Buttons
   usw.), deshalb über den Nachbar-Selektor statt der Klasse direkt. */
#gildeninfo-ueber-uns-editor + .gildeninfo-editor-buttons {
  margin-top: calc(var(--s) * 28);
}
.gildeninfo-status {
  font-size: calc(var(--s) * 13);
  color: var(--text-dim);
}
.gildeninfo-raid-verwaltung-titel {
  margin: calc(var(--s) * 28) 0 calc(var(--s) * 12);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 15);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  /* 2026-08-11, Sven: "fällt irgendwie nicht ins Gewicht und geht
     irgendwie unter" (Beispiel "Konto"-Titel vor dem Wolkenhintergrund)
     — war var(--text-dim), blasses Violett ohne genug Kontrast zum
     hellen/unruhigen Hintergrundbild. Betrifft alle Abschnittstitel, die
     diese Klasse teilen (Konto, Mitglieder, Neuigkeiten-Verwaltung,
     Raid-Fortschritt usw.), nicht nur "Konto". */
  color: var(--gold-bright);
}
/* .neuigkeiten-verwaltung-liste teilt sich dieselben Regeln (2026-08-08,
   Neuigkeiten-Verwaltung kam als vierter Reiter dazu, identisches
   Listen-Idiom) — Selektor einfach mit angehängt statt zu duplizieren. */
.gildeninfo-raid-verwaltung-liste,
.neuigkeiten-verwaltung-liste {
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 8);
  margin: 0 0 calc(var(--s) * 20);
  padding: 0;
  list-style: none;
}
.gildeninfo-raid-verwaltung-liste li,
.neuigkeiten-verwaltung-zeile {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: calc(var(--s) * 16);
  padding: calc(var(--s) * 10) calc(var(--s) * 14);
  background: linear-gradient(rgba(24, 14, 40, 0.9), rgba(10, 6, 18, 0.96));
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
  font-size: calc(var(--s) * 14);
}
.gildeninfo-raid-verwaltung-leer {
  padding: calc(var(--s) * 14);
  color: var(--text-faint);
}

/* ---- Raidplan-Verwaltung: Feld-Reihen + bessere Sichtbarkeit
   bestehender Termine (2026-08-11, Sven per Screenshot: "Eingabefelder,
   Buttons etc alles gleich anpassen, wie die gesamte Seite auch
   aufgebaut ist" + genaues Wunsch-Layout Datum/Uhrzeit-von/-bis in
   einer Reihe, Tank-/Heiler-/DD-Plätze in der nächsten + "Bestehende
   Termine könnten besser sichtbar sein") ----
   Eigene, ZUSÄTZLICHE Klassen statt die geteilten .neuigkeiten-
   verwaltung-*-Klassen selbst zu verändern — die Neuigkeiten-Verwaltung
   nutzt dieselben Klassen und war nicht Teil der Rückmeldung, ein
   direktes Umstyling dort hätte sie ungefragt mitverändert. */
.raidtermin-feld-reihe {
  display: flex;
  flex-wrap: wrap;
  /* 16->36 (2026-08-11, Sven: "bei Datum/Uhrzeit usw müsste der
     Abstand nach rechts getätigt werden, das sieht zu gepresst aus")
     — betrifft Datum/Uhrzeit von/Uhrzeit bis UND Tank-/Heiler-/DD-
     Plätze, alle nutzen dieselbe Zeilen-Klasse. */
  gap: calc(var(--s) * 36);
}
.raidtermin-feld-reihe .gildeninfo-editor-label {
  flex: 1 1 calc(var(--s) * 160);
}
/* Gleiche Karten-Optik wie .news-card (Gold-Konturlinie zusätzlich zum
   Violett-Rahmen) — hebt die Zeile deutlicher vom dunklen Hintergrund
   ab als der reine Violett-Rahmen der Basis-Klasse. */
.raidplan-verwaltung-zeile {
  outline: 1px solid rgba(200, 170, 78, 0.2);
  outline-offset: -6px;
  padding: calc(var(--s) * 14) calc(var(--s) * 18);
  transition: border-color var(--duration-standard) var(--ease-soft);
}
.raidplan-verwaltung-zeile:hover {
  border-color: var(--violet);
}
.raidplan-verwaltung-titel {
  font-family: var(--font-display);
  font-size: calc(var(--s) * 18);
  letter-spacing: 0.04em;
  color: var(--gold-bright);
}

/* ---- Registrierung/Login-Formulare (2026-08-08) ----
   Eigene Ansicht im Hauptbereich, dasselbe Aufbau-Idiom wie .roster:
   fixiert rechts der Sidebar, Kopfzeile mit demselben Zweifarben-
   Titel-Trick wie "Mit<span>glieder</span>" (.roster-header h3 span).
   Eingabefelder als eigene Karten im .news-card-Idiom (Panel-Verlauf,
   --violet-deep-Rahmen) — erste Formularfelder im Projekt, es gab
   dafür noch keine Vorlage, deshalb konsequent aus den bestehenden
   Design-Tokens abgeleitet statt neu erfunden.
   `bottom: 0` -> `bottom: calc(var(--s)*var(--footer-h))` (2026-08-09,
   derselbe Fix wie bei .roster, siehe dort für die ausführliche
   Erklärung): overflow-y:auto sitzt hier direkt auf der Box, mit
   `bottom:0` bliebe die Box trotzdem volle 100vh hoch und Inhalt
   liefe beim Scrollen mitten durch die Footer-Zone. Die Box muss
   selbst schon an der Footer-Oberkante enden. */
.konto-formular-seite {
  position: fixed;
  top: 0;
  left: calc(var(--s) * 640);
  right: 0;
  bottom: calc(var(--s) * var(--footer-h));
  overflow-y: auto;
  /* Violetter Scrollbalken (2026-08-12) statt Browser-Standard, siehe
     .raidplan-klasse-palette für den Anlass. */
  scrollbar-width: thin;
  scrollbar-color: var(--violet-deep) transparent;
  box-sizing: border-box;
  padding-right: 4vw;
  /* 160→240 (2026-08-08, Sven per Screenshot: "sollte man es nicht
     weiter nach unten versetzen?" — Inhalt startete zu dicht am
     Mond/oberen Bildrand, konkurrierte optisch mit der Szene). */
  padding-top: max(0px, calc(var(--oy) + var(--s) * 240));
  /* Nur noch reine Optik, keine Footer-Reserve mehr nötig (übernimmt
     jetzt `bottom` weiter oben, siehe Kommentar dort). */
  padding-bottom: calc(var(--s) * 24);
}
.konto-formular-kopf {
  margin-bottom: calc(var(--s) * 30);
  padding-bottom: calc(var(--s) * 16);
  border-bottom: 1px solid rgba(177, 140, 255, 0.18);
}
.konto-formular-kopf h3 {
  margin: 0 0 calc(var(--s) * 8);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 30);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--text);
}
.konto-formular-kopf h3 span {
  color: var(--violet-glow);
}
.konto-formular-hinweis {
  /* Größer + mehr Abstand zur Überschrift (2026-08-12, Sven per
     Screenshot: "kann 1. größer dargestellt werden und etwas Abstand
     zur Überschrift") — 15->18px, zusätzlicher margin-top statt nur
     dem knappen 8px-Abstand, den bisher allein .konto-formular-kopf
     h3's margin-bottom lieferte. */
  margin: calc(var(--s) * 10) 0 0;
  max-width: 640px;
  font-size: calc(var(--s) * 18);
  line-height: 1.5;
  color: var(--text-dim);
}
.konto-formular-wechsel {
  max-width: 640px;
  margin-top: calc(var(--s) * 16);
  font-size: calc(var(--s) * 14);
  color: var(--text-dim);
  /* Mittig (2026-08-12, Sven) — steht jetzt im Formular selbst (siehe
     Bugfix davor), zentriert wirkt dort stimmiger als linksbündig wie
     die Formularfelder darüber. Schriftart: Marcellus, kommt vom
     globalen var(--font-body), keine eigene Regel hier nötig. */
  text-align: center;
}
.konto-formular-wechsel a {
  color: var(--violet-glow);
  text-decoration: none;
  transition: color var(--duration-standard) var(--ease-soft);
}
.konto-formular-wechsel a:hover {
  color: var(--gold-bright);
}
/* Größe (2026-08-08, Sven: "könnte man sicherlich größer darstellen"):
   max-width 480→640, mehr Innenabstand, größere Eingabefeld-Schrift
   (16→18) — insgesamt spürbar präsenter statt eines schmalen Kastens. */
/* #bewerbung-formular überall mit ergänzt (2026-08-08) — identisches
   Formular-Aussehen, kein zweites Regel-Set nötig. Nur die
   Bewerbung braucht zusätzlich ein <textarea>, siehe eigene Regel
   unten. */
#registrieren-formular,
#bewerbung-formular {
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 22);
  max-width: 640px;
  padding: calc(var(--s) * 40);
  /* 2026-08-12, Sven: "du sollst es nicht dunkler machen, bitte wieder
     an den kompletten Style halten wie alles andere auch" — zurück auf
     den normalen, projektweiten Panel-Verlauf, keine Sonderbehandlung
     hier. Das "abgeschnitten wirkende" Problem war vermutlich eher die
     Größe/Proportion (siehe .bewerbung-spalten weiter unten), nicht die
     Deckkraft. */
  background: linear-gradient(var(--panel-grad-a), var(--panel-grad-b));
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
}
/* Bewerbung kompakter (2026-08-12, Sven: "das Fenster natürlich wieder
   anpassen, dass es ohne Scroll dargestellt wird") — seit den neuen
   E-Mail/Passwort-Feldern deutlich länger als Registrieren (teilt sich
   sonst dieselbe Basis-Klasse). Weniger Abstand zwischen den Feldern
   UND weniger Innenabstand oben/unten auf der Seite selbst, NUR für
   die Bewerbung (Registrieren bleibt unverändert, braucht den Platz
   nicht zu sparen). */
.konto-formular-seite--bewerbung {
  padding-top: max(0px, calc(var(--oy) + var(--s) * 140));
}
#bewerbung-formular {
  gap: calc(var(--s) * 14);
  padding: calc(var(--s) * 28) calc(var(--s) * 40);
}
#registrieren-formular .formular-zeile,
#bewerbung-formular .formular-zeile {
  display: flex;
  gap: calc(var(--s) * 18);
  /* BUGFIX (2026-08-12, Sven per Screenshot: "müsste soweit nach
     links, damit es bündig mit E-Mail steht") — width:100% statt dem
     impliziten Flex-Stretch-Verhalten erzwungen, damit die Zeile
     garantiert exakt dieselbe Breite wie die einspaltigen Felder
     darüber bekommt, unabhängig von Browser-Eigenheiten beim
     automatischen Ausfüllen von Flex-Kindern. */
  width: 100%;
  box-sizing: border-box;
}
/* ZWEITER, strukturell sauberer Anlauf (2026-08-12, Sven: "Charakter-
   name, Klasse, Discord-Name sollen alle gleich groß sein und
   UNTEREINANDER stehen" + "Realm, Spezialisierung, Battle.net-Name
   sollen ebenfalls untereinander stehen") — der ERSTE Versuch
   (align-items:flex-start auf der gemeinsamen Zeile) behob nur das
   Stretch-Symptom, änderte aber nichts an der eigentlich falschen
   Zeilen-Paar-Struktur (Charaktername+Realm/Klasse+Spezialisierung/
   Discord+Battle.net als je EINE Zeile nebeneinander). Jetzt zwei
   komplett unabhängige, echte Spalten (siehe bewerbungHtmlBauen() in
   script.js): links Charaktername→Klasse→Discord-Name, rechts
   Realm→Spezialisierung→Battle.net-Name, jede Spalte einfach von oben
   nach unten gestapelt — dadurch kann gar kein Stretch-Konflikt
   zwischen unterschiedlich hohen Nachbar-Feldern mehr entstehen. */
.bewerbung-spalten {
  display: flex;
  gap: calc(var(--s) * 40);
}
.bewerbung-spalte {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 22);
}
#registrieren-formular .formular-zeile label,
#bewerbung-formular .formular-zeile label {
  flex: 1;
  /* BUGFIX (2026-08-12, Sven: "die Verzerrung des Eingabefelds", nur in
     Firefox sichtbar, Chrome zeigte es korrekt) — Flex-Kinder haben
     standardmäßig min-width:auto, das lässt sie nie schrumpfen unter
     die eigene Inhalts-Mindestbreite. Firefox setzt diese eingebaute
     Mindestbreite bei <input>-Feldern höher an als Chrome, wodurch die
     rechte Spalte (Realm) trotz flex:1 breiter als die linke wurde,
     statt beide gleich breit zu halten. min-width:0 hebt die Bremse
     auf, flex:1 kann die Spalten dadurch wieder wirklich gleich groß
     rechnen, browserunabhängig. */
  min-width: 0;
}
#registrieren-formular label,
#bewerbung-formular label {
  display: flex;
  flex-direction: column;
  /* 8 -> 20 (2026-08-12, Sven per Screenshot: Label/Feld-Abstand wirkte
     zu knapp, gleicher Wert wie beim Raidplan-/Neuigkeiten-Formular in
     der Verwaltung, siehe #raidtermin-formular/#neuigkeit-formular
     .gildeninfo-editor-label, für Konsistenz übernommen). */
  gap: calc(var(--s) * 20);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 13);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  /* var(--text-dim) -> var(--gold-bright) (2026-08-12, Sven per
     Screenshot: "bei Bewerbungen die Überschriften ebenfalls farblich
     anpassen wie auf dem Bild") — gleiche Lösung wie schon bei
     .gildeninfo-editor-label ("geht unter"), jetzt auch hier
     angeglichen statt der dunkleren Alt-Farbe. */
  color: var(--gold-bright);
}
/* Ausnahme für ganze Fragen statt kurzer Substantive (2026-08-08, Sven:
   "nicht nur groß geschrieben" bei "Warum möchtest du zu uns?") —
   GROSSGESCHRIEBEN passt bei "CHARAKTERNAME"/"KLASSE" usw., eine
   komplette Frage liest sich in Versalien dagegen schwerer.
   SELEKTOR bewusst "#bewerbung-formular label.formular-frage-label"
   statt nur ".formular-frage-label" (echter Bug, gleiches Muster wie
   schon einmal bei .rollen-option: die Regel "#bewerbung-formular
   label" oben hat dieselbe/höhere Spezifität und hätte sonst
   gewonnen, egal an welcher Stelle im Stylesheet diese Regel steht). */
#bewerbung-formular label.formular-frage-label {
  /* BUGFIX (2026-08-12, Sven per Screenshot: "was ist denn das für
     eine Schriftart? Sieht ja schrecklich aus") — var(--font-display)
     (Cinzel) ist als Versalienschrift gebaut, in Kleinschreibung (hier
     bewusst ohne text-transform:uppercase, ganze Frage statt kurzem
     Substantiv) wirken Cinzels Kleinbuchstaben-Formen ungewohnt/
     unruhig. var(--font-body) (Marcellus) ist für genau diesen Fall
     gedacht: normale, in Groß-/Kleinschreibung geschriebene Sätze. */
  font-family: var(--font-body);
  text-transform: none;
  letter-spacing: 0.01em;
  font-size: calc(var(--s) * 15);
}
#registrieren-formular input,
#bewerbung-formular input,
#bewerbung-formular select,
#bewerbung-formular textarea {
  padding: calc(var(--s) * 15) calc(var(--s) * 18);
  background: var(--void);
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
  font-family: var(--font-body);
  font-size: calc(var(--s) * 18);
  color: var(--text);
  transition: border-color var(--duration-standard) var(--ease-soft),
              box-shadow var(--duration-standard) var(--ease-soft);
}
/* resize:none + overflow:hidden (2026-08-08, Sven: "wenn der Text mehr
   ist als das Fenster, soll es sich automatisch nach unten
   vergrößern") — die Höhe kommt jetzt vollständig aus
   textareaAutoWachsen() in script.js (misst scrollHeight bei jeder
   Eingabe neu), ein manueller Ziehgriff würde dem nur in die Quere
   kommen. min-height bleibt als Startgröße/Untergrenze. */
#bewerbung-formular textarea {
  resize: none;
  overflow: hidden;
  min-height: calc(var(--s) * 100);
  font-family: inherit;
}
/* Eigener Pfeil statt Browser-Standard (2026-08-08) — passt sonst nicht
   zum dunklen Panel. Gold als fester Hex-Wert im Data-URI, weil url()
   keine CSS-Variablen versteht. */
#bewerbung-formular select {
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 9l6 6 6-6' fill='none' stroke='%23C8AA4E' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right calc(var(--s) * 16) center;
  background-size: calc(var(--s) * 16);
  padding-right: calc(var(--s) * 44);
}
#bewerbung-formular select option {
  background: var(--panel);
  color: var(--text);
}
#registrieren-formular input:focus,
#bewerbung-formular input:focus,
#bewerbung-formular select:focus,
#bewerbung-formular textarea:focus {
  outline: none;
  border-color: var(--violet);
  box-shadow: var(--glow-violet-soft);
}

/* ---- Spezialisierungs-Auswahl im Bewerbungsformular (2026-08-08,
   zweite Runde) ----
   Sven: "wir müssen die Bewerbung ändern, nicht nach nur
   Tank/Heal/DD" — eine Checkbox PRO ECHTER SPEZIALISIERUNG statt nur
   pro Rolle (z.B. Jäger zeigt jetzt 3 Chips für alle 3 Schaden-Specs,
   nicht mehr nur 1). Gleiche Chip-Sprache wie .klassen-chip
   (Mitglieder-Klassenfilter). Serverseitig wird dieselbe Klasse-zu-
   Spec-Zuordnung nochmal echt geprüft (siehe
   sites/klassen.php::lunatix_klassen_spezialisierungen()). */
/* ---- Klasse + Spezialisierung(en) in einer Zeile (2026-08-08, Sven:
   erst "Klasse-Feld schmaler, Symbole daneben, nur Symbol", dann
   korrigiert: "das Pulldown bitte wie das Feld darüber und darunter
   gleich anpassen, die Spezialisierungen sollten dann ebenfalls von
   dem Feld darüber und darunter links nach rechts beginnen" —
   Screenshots zeigten Klasse breiter als Realm/Battle.net-Name und die
   Spec-Box schmaler, weil sie vorher an ihrem Inhalt (3 vs. 4 Chips)
   ausgerichtet war. Jetzt exakt dieselbe 50/50-Aufteilung wie jede
   andere .formular-zeile (beide Spalten flex:1) — die Spec-Box hat
   dadurch bei JEDER Klasse dieselbe Breite, unabhängig ob 3 oder 4
   Specs (bei 3 bleibt der vierte Platz einfach frei statt die Box zu
   schrumpfen, exakt wie von Sven gewünscht), und die Chips beginnen
   darin links, genau unter dem linken Rand des Feldes darüber/darunter. */
.formular-klasse-feld {
  min-width: 0;
}
.formular-rollen-block {
  display: flex;
  flex-direction: column;
  /* 10 -> 20 (2026-08-12, Sven: "die Symbole passen nicht in die Höhe")
     — .formular-rollen-block ist KEIN echtes <label> (das hat per
     eigener Regel gap:20px zwischen Text und Feld), sondern ein
     eigenständiges <div>, hatte deshalb bisher einen ABWEICHENDEN,
     kleineren Abstand (10px) zwischen "Spezialisierung(en)" und den
     Icons als "Klasse" zu seinem Dropdown — dadurch standen beide
     Reihen nicht auf gleicher Höhe. Jetzt exakt derselbe Wert. */
  gap: calc(var(--s) * 20);
  flex: 1;
  min-width: 0;
}
.formular-rollen-label {
  font-family: var(--font-display);
  font-size: calc(var(--s) * 13);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  white-space: nowrap;
}
/* flex-wrap + min-height statt fixer height (2026-08-08, Sven per
   Screenshot: "warum sind die Symbole nun verzerrt?") — Ursache war
   NICHT die Icon-Größe selbst, sondern Flexbox: bei schmaler Chip-Box
   (z.B. auf kleineren Fenstern) hat der Browser die Chips in der
   Breite zusammengequetscht (flex-shrink), ihre feste Höhe aber
   unangetastet gelassen — Ergebnis waren schmale, hohe statt
   quadratische Kacheln. flex-wrap lässt Chips bei Platzmangel in eine
   zweite Zeile umbrechen statt sich zu verformen, min-height (statt
   height) erlaubt das Wachsen bei mehreren Zeilen, bleibt bei einer
   Zeile aber bündig mit dem Klasse-Dropdown. Die eigentliche Fixgröße
   der Chips kommt jetzt von .rollen-option (flex-shrink:0, siehe
   dort) — das Quetschen kann dadurch strukturell nicht mehr passieren. */
.rollen-auswahl {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--s) * 10);
  min-height: calc(var(--s) * 52);
}
/* Icon-only Chips (2026-08-08) — quadratisch statt breiter Pille, da
   kein Textlabel mehr danebensteht (Spec-Name steckt im title-Tooltip
   + .sr-only für Tastatur/Screenreader). overflow:hidden, damit das
   randfüllende Icon (siehe .rollen-icon img) nicht über die abgerundete
   Chip-Ecke hinaussteht. `flex: 0 0 auto` (2026-08-08) hält die Chips
   IMMER quadratisch — ohne das durfte Flexbox sie bei knappem Platz in
   der Breite stauchen (siehe Kommentar bei .rollen-auswahl).
   SELEKTOR bewusst "#bewerbung-formular label.rollen-option" statt nur
   ".rollen-option" (2026-08-08, echter Bug per Sven-Screenshot
   gefunden): .rollen-option IST selbst ein <label>, die weiter oben
   stehende Regel "#bewerbung-formular .formular-zeile label { flex:1 }"
   hat exakt dieselbe Spezifität wie ein einfaches ".rollen-option"
   und stand zuerst im Dokument — mein flex-shrink:0 wurde dadurch
   überschrieben. Erst mit gleicher Spezifität (ID + Klasse + Typ) UND
   späterer Position im Stylesheet gewinnt diese Regel zuverlässig. */
#bewerbung-formular label.rollen-option {
  flex: 0 0 auto;
  position: relative;
  display: flex;
  width: calc(var(--s) * 52);
  height: calc(var(--s) * 52);
  overflow: hidden;
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
  cursor: pointer;
  transition: border-color var(--duration-standard) var(--ease-soft),
              box-shadow var(--duration-standard) var(--ease-soft);
}
/* Eigenes Häkchen übernimmt der Chip-Umriss selbst (.ausgewaehlt),
   die native Checkbox bleibt funktional, aber unsichtbar. */
.rollen-option input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.rollen-option:hover {
  border-color: var(--violet);
}
/* Deutlich sichtbarer Auswahl-Zustand (2026-08-08, Sven: "sieht man
   kaum, was man ausgewählt hat") — der bunte Klassen-Spec-Icon-Hintergrund
   schluckt einen dünnen 1px-Goldrahmen fast komplett. Jetzt drei
   Signale gleichzeitig statt nur eines: (1) deutlich dickerer,
   hellerer Rahmen inkl. Innen-Ring, (2) kräftigerer Glow als
   --glow-gold-soft, (3) ein eigenes Häkchen-Badge oben rechts, das
   unabhängig vom Icon-Motiv IMMER erkennbar bleibt. Nicht ausgewählte
   Chips werden zusätzlich leicht abgedunkelt, damit die ausgewählten
   im Vergleich sichtbar "vorne" liegen. */
.rollen-option.ausgewaehlt {
  border: 2px solid var(--gold-bright);
  box-shadow: 0 0 0 2px rgba(240, 211, 138, 0.4),
              0 0 calc(var(--s) * 16) rgba(200, 170, 78, 0.9);
}
.rollen-option.ausgewaehlt::after {
  content: '✓';
  position: absolute;
  top: calc(var(--s) * 2);
  right: calc(var(--s) * 2);
  width: calc(var(--s) * 16);
  height: calc(var(--s) * 16);
  border-radius: 50%;
  background: var(--gold-bright);
  color: #241a06;
  font-size: calc(var(--s) * 11);
  font-weight: 700;
  line-height: calc(var(--s) * 16);
  text-align: center;
  box-shadow: 0 0 calc(var(--s) * 6) rgba(0, 0, 0, 0.6);
}
/* Icon füllt die gesamte Chip-Fläche randlos aus (2026-08-08, Sven:
   "die Symbole auch voll ausfüllen in dem Kästchen, wo sie hinterlegt
   sind") — object-fit:cover statt eines kleineren, zentrierten Icons
   mit Innenabstand. */
.rollen-icon {
  display: block;
  width: 100%;
  height: 100%;
}
.rollen-icon img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  filter: brightness(0.6) saturate(0.7);
  transition: filter var(--duration-standard) var(--ease-soft);
}
.rollen-option.ausgewaehlt .rollen-icon img {
  filter: brightness(1.05) saturate(1.2);
}
.konto-absenden {
  /* Feste Höhe statt Padding-basierter Berechnung (2026-08-12, Sven:
     "setz doch einfach mal die Buttons auf gleiche Höhe!!!!" — die
     bisherigen Fixes (-moz-focus-inner, appearance:none) reduzierten
     den Unterschied, trafen ihn aber offenbar nicht exakt). box-sizing:
     border-box + explizite height + Flex-Zentrierung macht die Höhe
     komplett unabhängig davon, wie <button> vs. <a> Padding/Border in
     der jeweiligen Browser-Engine intern verrechnen — beide Elemente
     bekommen exakt denselben Wert, kein Wackelspielraum mehr. */
  box-sizing: border-box;
  height: calc(var(--s) * 54);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  align-self: center;
  margin-top: calc(var(--s) * 8);
  padding: 0 calc(var(--s) * 40);
  background: linear-gradient(var(--panel-grad-a), var(--panel-grad-b));
  border: 1px solid var(--violet-deep);
  outline: 1px solid rgba(200, 170, 78, 0.35);
  outline-offset: calc(var(--s) * -5);
  border-radius: var(--radius-card);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 16);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold-bright);
  cursor: pointer;
  transition: box-shadow var(--duration-standard) var(--ease-soft),
              border-color var(--duration-standard) var(--ease-soft);
}
.konto-absenden:hover:not(:disabled) {
  border-color: var(--violet);
  box-shadow: var(--glow-violet-strong);
}
.konto-absenden:disabled {
  opacity: 0.6;
  cursor: default;
}

/* ---- "Speichern" bei Mein Profil (2026-08-10) ----
   Bewusst EIGENE Klasse statt .verwaltung-btn-gold, nachdem sich zwei
   gegensätzliche Wünsche an derselben geteilten Klasse gezeigt haben:
   Sven wollte diesen Button CTA-groß wie .konto-absenden, aber die
   Tabellen-Aktionen/Neuigkeiten-Verwaltung (auch .verwaltung-btn-gold)
   sollten klein bleiben. Optik hier 1:1 wie .konto-absenden (Gold-
   Rahmen, gleicher Glow), nur ohne den `align-self:center`/margin-top,
   da der Button hier in einer eigenen Flex-Zeile mit dem Status-Text
   sitzt (siehe .gildeninfo-editor-buttons), nicht zentriert im Formular. */
.mein-profil-speichern {
  padding: calc(var(--s) * 14) calc(var(--s) * 40);
  background: linear-gradient(var(--panel-grad-a), var(--panel-grad-b));
  border: 1px solid var(--violet-deep);
  outline: 1px solid rgba(200, 170, 78, 0.35);
  outline-offset: calc(var(--s) * -5);
  border-radius: var(--radius-card);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 16);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold-bright);
  cursor: pointer;
  transition: box-shadow var(--duration-standard) var(--ease-soft),
              border-color var(--duration-standard) var(--ease-soft);
}
.mein-profil-speichern:hover:not(:disabled) {
  border-color: var(--violet);
  box-shadow: var(--glow-violet-strong);
}
.mein-profil-speichern:disabled {
  opacity: 0.6;
  cursor: default;
}

/* ---- "Große" Verwaltungs-Aktionen (2026-08-10) ----
   Sven, nach zwei Rückfragen präzisiert: die MEHREREN Buttons NEBEN-
   EINANDER in einer Tabellenzeile (Freischalten/Ablehnen/Löschen bei
   Mitgliedern+Bewerbungen, Bearbeiten/Löschen bei Neuigkeiten) bleiben
   bei der kompakten .verwaltung-btn-Optik — ALLE ÜBRIGEN, eigenständig
   stehenden Verwaltungs-Aktionen (Speichern/Abbrechen im Gildeninfo-
   "Über uns"-Editor, Erstellen/Abbrechen im Neuigkeit-Formular, "Jetzt
   von Raider.IO aktualisieren", "Jetzt M+ aktualisieren", "Bestehende
   M+-Einträge nachübersetzen") werden auf CTA-Größe/-Effekt wie
   .konto-absenden gehoben. EIGENE Klassen statt .verwaltung-btn
   wiederzuverwenden, weil genau dieselbe Modifier-Kombination
   (.verwaltung-btn.verwaltung-btn-ok z.B.) sonst gleichzeitig klein
   UND groß sein müsste, je nachdem wo sie steht — das ist mit reinem
   CSS ohne Kontext-Selektor nicht abbildbar, zwei getrennte
   Klassen-Familien sind die robuste Lösung. */
.verwaltung-btn-gross {
  padding: calc(var(--s) * 14) calc(var(--s) * 40);
  background: linear-gradient(var(--panel-grad-a), var(--panel-grad-b));
  border: 1px solid var(--violet-deep);
  outline: 1px solid rgba(200, 170, 78, 0.35);
  outline-offset: calc(var(--s) * -5);
  border-radius: var(--radius-card);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 16);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-dim);
  cursor: pointer;
  transition: box-shadow var(--duration-standard) var(--ease-soft),
              border-color var(--duration-standard) var(--ease-soft),
              color var(--duration-standard) var(--ease-soft);
}
.verwaltung-btn-gross:hover:not(:disabled) {
  border-color: var(--violet);
  box-shadow: var(--glow-violet-strong);
}
.verwaltung-btn-gross:disabled {
  opacity: 0.4;
  cursor: default;
}
.verwaltung-btn-gross-gold {
  color: var(--gold-bright);
  border-color: rgba(200, 170, 78, 0.5);
}
.verwaltung-btn-gross-gold:hover:not(:disabled) {
  color: var(--gold-bright);
  border-color: var(--gold-bright);
  box-shadow: var(--glow-gold-strong);
}
.verwaltung-btn-gross-ok:hover:not(:disabled) {
  color: var(--online);
  border-color: var(--online);
  box-shadow: var(--glow-online-strong);
}

/* ---- Kanalname-Feld bei Mein Profil (2026-08-10) ----
   Sven: "das Eingabefeld könnte man auch anpassen wie allgemein auch"
   — bisher lief es mit .verwaltung-rolle-auswahl (kompakte Optik für
   die kleinen Dropdowns in der Verwaltungs-Tabelle, siehe dort),
   dieselbe Verwechslung wie beim "Speichern"-Button daneben: geteilte
   Klasse für zwei unterschiedliche Bedürfnisse. Jetzt eigene Klasse,
   1:1 der "allgemeinen", prominenteren Feld-Optik nachgebaut, die
   Registrieren/Bewerbung schon nutzen (padding 15×18, 18px). */
.mein-profil-eingabe {
  padding: calc(var(--s) * 15) calc(var(--s) * 18);
  background: var(--void);
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
  font-family: var(--font-body);
  font-size: calc(var(--s) * 18);
  color: var(--text);
  transition: border-color var(--duration-standard) var(--ease-soft),
              box-shadow var(--duration-standard) var(--ease-soft);
}
.mein-profil-eingabe:focus {
  outline: none;
  border-color: var(--violet);
  box-shadow: var(--glow-violet-soft);
}

/* ---- Battle.net-Login-Button + Trenner (2026-08-08) ----
   Bewusst innerhalb der bestehenden Violett/Gold-Palette gehalten statt
   Battle.net-Markenblau zu übernehmen — konsistent mit dem Rest der
   Seite, keine fremde Akzentfarbe. Falls das offizielle Battle.net-Blau
   gewünscht ist, einfach Bescheid geben. */
.bnet-button {
  /* flex statt block (2026-08-12) — bei fester height (siehe unten)
     zentriert text-align:center allein den Text nur horizontal, nicht
     vertikal. display:flex übernimmt beides über align-items/
     justify-content, exakt wie bei .konto-absenden. */
  display: flex;
  align-items: center;
  justify-content: center;
  /* Auf der Registrierungsseite (breiter Container) dieselbe Breite wie
     #registrieren-formular (max-width:640px) — im schmalen Login-Popup
     (.login-dialog max-width:420px) begrenzt ohnehin schon der Dialog
     selbst, hier also folgenlos. */
  max-width: 640px;
  text-align: center;
  /* Auf CTA-Größe/-Effekt von .konto-absenden angehoben (2026-08-10,
     Sven: "alles auf 16px, gleiche Effekte") — steht direkt neben
     .konto-absenden-Buttons (Registrieren/Anmelden), sollte nie kleiner
     wirken. */
  /* Exakt dieselbe feste Höhe wie .konto-absenden (2026-08-12, Sven:
     "setz doch einfach mal die Buttons auf gleiche Höhe!!!!") — siehe
     Begründung dort. box-sizing:border-box hier zusätzlich nötig, sonst
     würde padding+border zur eigentlichen height noch dazu addiert. */
  box-sizing: border-box;
  height: calc(var(--s) * 54);
  padding: 0 calc(var(--s) * 40);
  background: linear-gradient(var(--panel-grad-a), var(--panel-grad-b));
  border: 1px solid var(--violet-deep);
  outline: 1px solid rgba(200, 170, 78, 0.35);
  outline-offset: calc(var(--s) * -5);
  border-radius: var(--radius-card);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 16);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--violet-glow);
  text-decoration: none;
  transition: border-color var(--duration-standard) var(--ease-soft),
              box-shadow var(--duration-standard) var(--ease-soft),
              color var(--duration-standard) var(--ease-soft);
}
.bnet-button:hover {
  border-color: var(--violet);
  box-shadow: var(--glow-violet-strong);
  color: var(--gold-bright);
}
/* ---- Registrieren + Battle.net nebeneinander (2026-08-08) ----
   Sven: "kann man den Button oben nicht neben den Button von
   Registrieren setzen?" — .bnet-button ist sonst (Login-Popup) ein
   volle-Breite-Block; hier innerhalb der Button-Zeile stattdessen auf
   Inhaltsbreite begrenzt, damit beide Buttons nebeneinander passen. */
.konto-formular-buttons {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: calc(var(--s) * 16);
  margin-top: calc(var(--s) * 8);
}
.konto-formular-buttons .bnet-button {
  display: inline-flex;
  width: auto;
  max-width: none;
}
/* BUGFIX (2026-08-12, per Live-Messung mit getBoundingClientRect im
   Browser gefunden) — Höhe/Padding/Border/Outline waren zwischen
   .konto-absenden und .bnet-button längst identisch (27px beide), der
   sichtbare 2px-Versatz kam von .konto-absendens eigenem margin-top
   (für den Normalfall gedacht, wo der Button allein unter Feldern
   steht) — hier in der gemeinsamen Button-Zeile schob das NUR
   "Registrieren" nach unten, "Mit Battle.net registrieren" hat dieses
   margin-top gar nicht. In diesem Kontext auf 0 zurückgesetzt. */
.konto-formular-buttons .konto-absenden {
  margin-top: 0;
}
.konto-trenner {
  display: flex;
  align-items: center;
  max-width: 640px;
  gap: calc(var(--s) * 12);
  margin: calc(var(--s) * 16) 0;
  font-family: var(--font-display);
  font-size: calc(var(--s) * 11);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.konto-trenner::before,
.konto-trenner::after {
  content: '';
  flex: 1;
  height: 1px;
  background: rgba(177, 140, 255, 0.18);
}
.formular-fehler {
  padding: calc(var(--s) * 12) calc(var(--s) * 18);
  background: rgba(179, 57, 76, 0.12);
  border: 1px solid rgba(179, 57, 76, 0.4);
  border-radius: var(--radius-card);
  font-size: calc(var(--s) * 14);
  color: var(--text);
}
/* Erfolgs-Hinweis in Formularen (2026-08-13, "Passwort vergessen") —
   selbes Kasten-Idiom wie .formular-fehler, nur in Grün statt Rot,
   nutzt dieselbe Online-Farbe wie die restlichen Erfolgs-/Online-
   Zustände im Projekt (--online) statt eine neue Grün-Variante
   einzuführen. */
.formular-hinweis-erfolg {
  padding: calc(var(--s) * 12) calc(var(--s) * 18);
  background: rgba(127, 232, 154, 0.12);
  border: 1px solid rgba(127, 232, 154, 0.4);
  border-radius: var(--radius-card);
  font-size: calc(var(--s) * 14);
  color: var(--text);
}

/* ---- Login-Popup (2026-08-08) ----
   Erstes Overlay/Modal im Projekt — es gab dafür noch keine Vorlage,
   deshalb aus den bestehenden Design-Tokens abgeleitet (dasselbe
   Panel-/Rahmen-Idiom wie .news-card/#registrieren-formular), nicht
   neu erfunden. z-index bewusst über --z-menu (20), damit das Popup
   auch über der Sidebar liegt. position:fixed hier UNGEFÄHRLICH (siehe
   PHP-Kommentar bei #login-overlay in menu.php) — kein Vorfahre mit
   transform dazwischen. */
.login-overlay {
  position: fixed;
  inset: 0;
  z-index: 40;
  align-items: center;
  justify-content: center;
  padding: 4vw;
  /* Kein backdrop-filter mehr (2026-08-09, Sven bestätigte das
     "Geister-Popup": nach dem Schließen per X blitzt kurz eine fast
     leere, transluzente Box mit demselben Rahmen auf). War schon in
     der letzten Session als Verdacht notiert (siehe Gedächtnis-
     Speicher) — backdrop-filter erzeugt eine eigene GPU-Kompositions-
     ebene, beim abrupten [hidden]-Toggle rendert der Browser davon
     manchmal noch einen letzten, leeren Frame, bevor display:none
     greift. Etwas dunklerer, blurloser Hintergrund umgeht das
     Kompositions-Artefakt komplett statt nur das Symptom zu kaschieren. */
  background: rgba(6, 4, 12, 0.85);
}
/* BUGFIX (2026-08-08, Sven: "sobald ich Hard-Refresh mache, kommt IMMER
   das Anmelde-Popup"): display:flex direkt auf .login-overlay hatte
   exakt dieselbe Spezifität wie die Browser-Standardregel für
   [hidden] (display:none) — weil Autoren-CSS nach dem UA-Stylesheet
   geladen wird, gewann unsere Regel, das hidden-Attribut wurde
   dadurch komplett wirkungslos. Erst :not([hidden]) macht die
   Sichtbarkeit wieder wirklich vom Attribut abhängig. */
.login-overlay:not([hidden]) {
  display: flex;
}
.login-dialog {
  position: relative;
  width: 100%;
  /* 420px -> var(--popup-w-schmal) (2026-08-09, Sven: "wäre es nicht
     besser alles einheitlich zu gestalten?") — Wert bleibt identisch,
     kommt jetzt aus dem gemeinsamen 3-Stufen-System im :root. */
  max-width: var(--popup-w-schmal);
  padding: calc(var(--s) * 36);
  background: linear-gradient(var(--panel-grad-a), var(--panel-grad-b));
  border: 1px solid var(--violet-deep);
  outline: 1px solid rgba(200, 170, 78, 0.2);
  outline-offset: -6px;
  border-radius: var(--radius-card);
  /* Kleinerer Blur-Radius (2026-08-09, dritter Anlauf beim "Geister-
     Popup" — backdrop-filter-Entfernung und hartes display:none haben
     den Bug NICHT behoben, Sven meldete ihn weiterhin). Große weiche
     box-shadow-Weichzeichnung (vorher 60px) auf einem position:fixed-
     Element mit eigenem Scroll-Kontext (overflow-y:auto) ist ein
     bekannter Auslöser für genau dieses Chromium-Artefakt: die
     GPU-Kompositionsebene für den Schatten kann beim abrupten
     Entfernen einen Frame lang nachhängen. Deutlich kleinerer,
     dichterer Schatten statt eines großen weichen Blooms.*/
  box-shadow: var(--glow-violet-soft), 0 8px 24px rgba(0, 0, 0, 0.5);
}
.login-dialog h3 {
  margin: 0 0 calc(var(--s) * 20);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 24);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--violet-glow);
}
#login-formular,
#passwort-reset-formular {
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 16);
}
#login-formular label,
#passwort-reset-formular label {
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 8);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 13);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
}
#login-formular input,
#passwort-reset-formular input {
  padding: calc(var(--s) * 12) calc(var(--s) * 14);
  background: var(--void);
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
  font-family: var(--font-body);
  font-size: calc(var(--s) * 16);
  color: var(--text);
  transition: border-color var(--duration-standard) var(--ease-soft),
              box-shadow var(--duration-standard) var(--ease-soft);
}
#login-formular input:focus,
#passwort-reset-formular input:focus {
  outline: none;
  border-color: var(--violet);
  box-shadow: var(--glow-violet-soft);
}

/* ---- Passwort-Sichtbarkeit (2026-08-08, Sven: "Auge-Symbol
   einbringen") ----
   .passwort-feld ersetzt das <input> als direktes Flex-Kind von
   <label> — das <input> selbst braucht deshalb explizit width:100%,
   sonst stretcht es (anders als vorher) nicht mehr automatisch über
   die Flex-Spalte (das übernahm bisher align-items:stretch auf
   <label>, wirkt aber nur auf direkte Flex-Kinder). box-sizing:
   border-box hält die Breite trotz eigenem Padding exakt gleich mit
   den übrigen Feldern. padding-right reserviert Platz für den Button,
   damit eingegebener Text nicht unter das Icon läuft. */
#registrieren-formular .passwort-feld input,
#login-formular .passwort-feld input,
#passwort-reset-formular .passwort-feld input,
#bewerbung-formular .passwort-feld input {
  width: 100%;
  box-sizing: border-box;
  padding-right: calc(var(--s) * 46);
}
.passwort-feld {
  position: relative;
}
.passwort-toggle {
  position: absolute;
  top: 50%;
  right: calc(var(--s) * 8);
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: calc(var(--s) * 30);
  height: calc(var(--s) * 30);
  padding: 0;
  background: none;
  border: none;
  border-radius: 50%;
  color: var(--text-dim);
  cursor: pointer;
  transition: color var(--duration-standard) var(--ease-soft),
              background-color var(--duration-standard) var(--ease-soft);
}
.passwort-toggle svg {
  width: calc(var(--s) * 18);
  height: calc(var(--s) * 18);
  fill: currentColor;
}
.passwort-toggle:hover {
  color: var(--gold-bright);
  background-color: rgba(177, 140, 255, 0.12);
}
/* Zustand "Passwort sichtbar": Strich quer durchs Auge statt einem
   zweiten SVG-Icon — reicht als klares Umschalt-Signal, ohne eine
   zweite Grafik pflegen zu müssen. */
.passwort-toggle.aktiv {
  color: var(--gold-bright);
}
.passwort-toggle.aktiv::after {
  content: '';
  position: absolute;
  left: 22%;
  right: 22%;
  top: 50%;
  height: 1.5px;
  background: currentColor;
  transform: rotate(-40deg);
}

/* ---- Browser-Autofill überschreiben (2026-08-08, Sven: "das Gelbe
   passt absolut nicht ins Design") ----
   Chrome/Edge/Safari färben ausgefüllte Felder standardmäßig grell
   gelb ein — nicht per background-color korrigierbar (der Browser
   erzwingt seine eigene Farbe mit höherer Priorität als Autoren-CSS).
   Der Standard-Workaround: ein input-interner box-shadow so groß, dass
   er das komplette Feld ausfüllt, wird von Autofill NICHT überschrieben
   und verdeckt die gelbe Füllung optisch mit unserer eigenen Farbe.
   -webkit-text-fill-color ersetzt außerdem die vom Browser erzwungene
   (meist schwarze) Autofill-Textfarbe. */
#registrieren-formular input:-webkit-autofill,
#registrieren-formular input:-webkit-autofill:hover,
#registrieren-formular input:-webkit-autofill:focus,
#login-formular input:-webkit-autofill,
#login-formular input:-webkit-autofill:hover,
#login-formular input:-webkit-autofill:focus,
#passwort-reset-formular input:-webkit-autofill,
#passwort-reset-formular input:-webkit-autofill:hover,
#passwort-reset-formular input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--text);
  -webkit-box-shadow: 0 0 0 1000px var(--void) inset;
          box-shadow: 0 0 0 1000px var(--void) inset;
  caret-color: var(--text);
  transition: background-color 9999s ease-in-out 0s;
}
#login-formular .konto-absenden,
#passwort-reset-formular .konto-absenden {
  align-self: stretch;
  text-align: center;
}
.login-schliessen {
  position: absolute;
  top: calc(var(--s) * 14);
  right: calc(var(--s) * 14);
  width: calc(var(--s) * 32);
  height: calc(var(--s) * 32);
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  border-radius: 50%;
  font-size: calc(var(--s) * 22);
  line-height: 1;
  color: var(--text-dim);
  cursor: pointer;
  transition: color var(--duration-standard) var(--ease-soft),
              background-color var(--duration-standard) var(--ease-soft);
}
.login-schliessen:hover {
  color: var(--gold-bright);
  background-color: rgba(177, 140, 255, 0.12);
}

/* ---- Charakter-Popup (2026-08-13, siehe ROADMAP.md "Charakter-Popup-
   Vorschau", Sven: "im LunatiX Stil") ----
   Nutzt dasselbe .login-dialog-Panel-Idiom wie Login/Passwort-Reset,
   nur breiter (Ausrüstungsspalten brauchen Platz) und mit eigenem
   3-Spalten-Innenlayout. */
.charakter-popup-dialog {
  /* Breiter als --popup-w-mittel (2026-08-13, nachgebessert — die
     3-Spalten-Ausrüstungsansicht + größeres Charakterbild brauchten
     mehr Platz, vorher wirkte alles gequetscht). Fester Wert statt
     eines der drei Standard-Tokens, weil dieses Popup als einziges
     eine echte 3-Spalten-Innenstruktur hat.
     820px -> 980px -> 1200px -> 1080px -> fit-content (2026-08-14,
     Sven: "kann man das nicht individuell anpassen? so dass sich das
     Popup eben immer dementsprechend anpasst von der Breite?") — das
     Popup wächst jetzt mit seinen tatsächlichen Inhalten (längster
     Item-Name bestimmt die Spaltenbreite, siehe minmax(...) im Grid),
     begrenzt nach unten (nie schmaler als 900px) und nach oben (nie
     breiter als 1400px bzw. 96% des Fensters). Das Ellipsis-Kürzen
     der Namen bleibt nur als Sicherheitsnetz für Extremfälle, in
     denen die Obergrenze greift. */
  width: fit-content;
  min-width: min(900px, 96vw);
  max-width: min(1400px, 96vw);
}
/* Kopf wie bei Armory (2026-08-14, Sven: "90 Untoter Schatten
   Priester <LunatiX> Blackhand gerne" + "das Horde-/Allianz-Symbol
   neben dem Charnamen") — Wappen links, Name + Meta-Zeile rechts
   daneben, Gildenname in Gold wie im Original. */
.charakter-popup-kopf {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 14);
  /* 14 -> 28 (2026-08-14, Sven: "kann man nach '90 Untoter...' Platz
     nach unten machen? das sieht so gedrückt aus"). */
  margin-bottom: calc(var(--s) * 28);
}
/* Lade-Zustand ECHT mittig im Popup-Fenster, nicht nur horizontal
   (2026-08-14, Sven: "kann man das 'Lädt' mittig ins Fenster setzen
   und etwas größer darstellen?" — die reine Padding-Lösung zentrierte
   den Text zwar horizontal, saß aber weiterhin oben direkt unter der
   Kopfzeile statt in der Mitte des ganzen Fensters). display:flex +
   eine moderate Mindesthöhe zentrieren jetzt auch vertikal.
   BUGFIX (2026-08-14, Sven: "das hat nicht geklappt!" — "Lädt..."
   blieb sichtbar OBEN AUF dem längst geladenen Inhalt stehen, das
   ganze Popup wirkte dadurch riesig). Exakt derselbe Spezifitäts-Bug
   wie beim Charakterbild weiter oben: display:flex auf einem
   ID-Selektor gewinnt gegen die Browser-Regel [hidden]{display:none},
   das hidden-Attribut wurde dadurch wirkungslos. Gleiche Lösung:
   [hidden] explizit gewinnen lassen. Mindesthöhe zusätzlich von
   500 auf 320 reduziert — reicht zum Zentrieren, bläht das Popup vor
   dem Laden aber nicht unnötig auf. */
#charakter-popup-status[hidden] {
  display: none;
}
#charakter-popup-status {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: calc(var(--s) * 320);
  text-align: center;
  font-size: calc(var(--s) * 20);
  color: var(--text-dim);
}
.charakter-popup-fraktion {
  width: calc(var(--s) * 44);
  height: calc(var(--s) * 44);
  object-fit: contain;
}
.charakter-popup-gilde {
  color: var(--gold-bright);
}
/* Kennwerte-Leiste unterhalb (2026-08-14, Sven zeigte Armorys
   Werte-Leiste) — 4 Spalten, Wert farbig über gedimmtem Label. */
.charakter-popup-werte {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: calc(var(--s) * 12) calc(var(--s) * 20);
  margin-top: calc(var(--s) * 18);
  padding-top: calc(var(--s) * 14);
  border-top: 1px solid var(--violet-deep);
}
.charakter-popup-wert {
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 2);
}
.charakter-popup-wert-zahl {
  font-size: calc(var(--s) * 17);
  font-weight: bold;
  font-variant-numeric: tabular-nums;
}
.charakter-popup-wert-label {
  font-size: calc(var(--s) * 11);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
}
/* WoW-Icon-Link oben rechts ENTFERNT (2026-08-14, Sven) — der
   Armory-Link liegt jetzt direkt auf dem Charakterbild, siehe
   .charakter-popup-armory-hinweis unten. */
#charakter-popup-name {
  margin: 0 0 calc(var(--s) * 4);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 22);
  letter-spacing: 0.08em;
  color: var(--violet-glow);
}
.charakter-popup-meta {
  /* Abstand übernimmt jetzt .charakter-popup-kopf (2026-08-14). */
  margin: 0;
  font-size: calc(var(--s) * 13);
  color: var(--text);
}
.charakter-popup-inhalt {
  position: relative;
  display: grid;
  /* Seitenspalten passen sich dem längsten Item-Namen an (2026-08-14,
     Sven: "so dass sich das Popup dementsprechend anpasst von der
     Breite") — minmax(0, max-content): wächst bis zur natürlichen
     Namensbreite (der Dialog hat width:fit-content und wächst mit),
     kann aber bei Erreichen der Dialog-Obergrenze trotzdem schrumpfen
     (das 0-Minimum), dann greift das Ellipsis-Sicherheitsnetz. */
  grid-template-columns: minmax(0, max-content) calc(var(--s) * 420) minmax(0, max-content);
  gap: calc(var(--s) * 24);
  align-items: stretch;
  /* BUGFIX Runde 2 (2026-08-14, Sven: Waffen saßen immer noch nicht
     ganz unten, sondern mittig auf Höhe des Bauchs). Ursache: ECHTE
     height statt nur min-height nötig — height:100% auf Kindelementen
     (Bild-Wrap, Mittelspalte) ist in CSS nur zuverlässig, wenn der
     ELTERN-Container selbst eine FESTE, nicht aus dem Inhalt
     berechnete Höhe hat. Mit bloßer min-height blieb die tatsächlich
     genutzte Höhe je nach Browser uneindeutig ("auto"), height:100%
     weiter unten griff dadurch nur unzuverlässig. Fester Wert statt
     min-height behebt das an der Wurzel. */
  height: calc(var(--s) * 620);
}
/* Charakterbild als zentrierter HINTERGRUND (2026-08-14-Umbau, Sven:
   "du ziehst dir doch den Charakter von Armory? den einfach mittig
   ins Fenster setzen als 'Hintergrund'" — vorher in eine schmale
   Mittelspalte gequetscht, wirkte "winzig/verloren"). Liegt jetzt
   absolut HINTER allen drei Spalten (z-index 0), deutlich breiter als
   die Mittelspalte selbst — genau wie bei der echten Armory-Seite
   reicht der Charakter optisch bis unter die Ausrüstungsspalten
   links/rechts. object-fit:cover + leichtes Hineinzoomen gleicht den
   von Battle.net mitgelieferten Leerraum um den Charakter herum aus. */
.charakter-popup-bild-wrap {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  /* 520 -> 900 (2026-08-14, Sven: "ja, aber viel zu klein" + "wir
     haben doch oberhalb der Nebenhand nach oben soviel Platz") — das
     Battle.net-Rendering ist BREITER als hoch; im schmalen Rahmen
     limitierte bei object-fit:contain die BREITE und staucht den
     Charakter klein, obwohl nach oben viel Luft war. Im breiten
     Rahmen limitiert jetzt die HÖHE — der Charakter füllt die volle
     Popup-Höhe aus. Der Rahmen liegt als Hintergrund (z-index 0)
     hinter den Spalten, die Breite stört dort nicht. */
  width: calc(var(--s) * 900);
  max-width: 100%;
  height: 100%;
  overflow: hidden;
  z-index: 0;
  /* Ist jetzt ein Link (2026-08-14, Sven: "Verlinkung zu Armory wenn
     man auf den Charakter klickt"). */
  display: block;
  cursor: pointer;
}
/* BUGFIX (2026-08-14, Sven per Screenshot: beim Laden eines neuen
   Charakters blieb der VORHERIGE samt "Zu Armory" im Popup stehen) —
   das display:block oben hat dieselbe Spezifität wie die
   Browser-Standardregel [hidden]{display:none} und gewann als spätere
   Autorenregel, das hidden-Attribut wirkte dadurch nicht mehr. Exakt
   derselbe Spezifitäts-Bug wie damals beim Login-Overlay (siehe
   dortiger Kommentar), gleiche Lösung: [hidden] explizit gewinnen
   lassen. */
.charakter-popup-bild-wrap[hidden] {
  display: none;
}
/* Kleiner "Zu Armory"-Hinweis beim Hovern über dem Charakter
   (2026-08-14, Sven: "soll ein kleines 'Fenster' aufgehen 'zu
   Armory'") — dezenter Panel-Kasten mittig über der Fußlinie,
   erscheint nur solange die Maus über dem Bild ist. */
.charakter-popup-armory-hinweis {
  position: absolute;
  left: 50%;
  bottom: calc(var(--s) * 40);
  transform: translateX(-50%);
  padding: calc(var(--s) * 8) calc(var(--s) * 18);
  background: rgba(8, 5, 16, 0.92);
  border: 1px solid var(--gold-bright);
  border-radius: var(--radius-card);
  box-shadow: var(--glow-gold-soft);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 13);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gold-bright);
  opacity: 0;
  transition: opacity var(--duration-standard) var(--ease-soft);
  pointer-events: none;
}
.charakter-popup-bild-wrap:hover .charakter-popup-armory-hinweis {
  opacity: 1;
}
/* KEIN Zoom mehr, Charakter KOMPLETT einpassen (2026-08-14, Sven per
   Orc-Screenshot: "der Charakter ist immer noch nicht stimmig" — der
   feste Zoom [scale 1.55] passte je nach Rasse/Pose mal und mal gar
   nicht, beim Orc mit Rucksack landete der Ausschnitt mitten auf dem
   Rücken. WICHTIGE KORREKTUR einer früheren Fehleinschätzung: die
   Live-Screenshots zeigen, dass Battle.nets "main-raw"-Rendering sehr
   wohl FREIGESTELLT/transparent ankommt [kein Spielszenen-Hintergrund]
   — damit ist die frühere Ausblend-Maske überflüssig [sie hätte sogar
   die Füße mit ausgeblendet] und object-fit:contain die sauberste
   Lösung: ganzer Charakter immer sichtbar, unten am "Boden" der
   Waffen-Zeile ausgerichtet, funktioniert für jede Rasse/Pose gleich). */
.charakter-popup-bild {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center bottom;
}
.charakter-popup-spalte {
  position: relative;
  z-index: 1;
  /* BUGFIX (2026-08-14, Sven: "die Items passen weiterhin nicht
     richtig ins Fenster" — die rechte Spalte lief über den Popup-Rand
     hinaus statt lange Namen mit "…" zu kürzen). Grid-Spalten
     schrumpfen standardmäßig NICHT unter die Breite ihres längsten
     nicht umbrechenden Inhalts (min-width:auto) — die nowrap-Namen
     drückten das ganze Grid breiter als den Dialog. min-width:0 lässt
     die 1fr-Spalten wirklich schrumpfen, erst dadurch greift das
     text-overflow:ellipsis der Namen. */
  min-width: 0;
}
/* BUGFIX (2026-08-14, Sven per Screenshot: durchstechende Elemente im
   offenen Tooltip). Ursache: die Spalten sind eigene Stacking-Kontexte
   mit gleichem z-index — ein Tooltip INNERHALB der linken Spalte kann
   Elemente einer später gezeichneten Spalte nie überdecken. Fix:
   solange man über einer Spalte hovert, hebt sich die GANZE Spalte
   über die anderen. */
.charakter-popup-spalte:hover {
  z-index: 20;
}
.charakter-popup-spalte {
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 6);
}
/* Mittelspalte ist nur noch Abstandhalter (2026-08-14 — die Waffen
   sind in eine eigene Zeile UNTER das Bild-Grid gezogen, Sven:
   "Haupthand/Nebenhand/Fernwaffe nach unten versetzen also unterhalb
   des Charakters"). pointer-events:none, damit Klicks/Hover durch die
   leere Spalte hindurch das dahinterliegende, jetzt klickbare
   Charakterbild (Armory-Link) erreichen. */
.charakter-popup-mitte {
  pointer-events: none;
}
/* Waffen wie bei Armory: nebeneinander, mittig, in einer EIGENEN
   Zeile unterhalb des Charakter-/Spalten-Bereichs (2026-08-14, Sven:
   "unterhalb des Charakters ... das Popup so anpassen dass alles
   reinpasst ohne überzustehen" — vorher innerhalb des Bild-Bereichs,
   überlappten die Beine des Charakters). */
.charakter-popup-waffen {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  justify-content: center;
  gap: calc(var(--s) * 24);
  margin-top: calc(var(--s) * 12);
}
.charakter-popup-waffen .charakter-popup-item {
  flex-direction: column;
  align-items: center;
  text-align: center;
  width: calc(var(--s) * 125);
}
.charakter-popup-waffen .charakter-popup-item-name {
  white-space: normal;
  text-align: center;
}
.charakter-popup-item {
  position: relative;
  display: flex;
  align-items: center;
  /* Allgemein größer (2026-08-14, Sven: "wir haben doch Platz!" +
     "könnte man es allgemein etwas größer darstellen") — Popup ist
     980px breit, die vorherige kompakte Größe nutzte den Platz nicht
     aus. */
  gap: calc(var(--s) * 12);
  padding: calc(var(--s) * 6) calc(var(--s) * 8);
  border-radius: calc(var(--s) * 4);
  font-size: calc(var(--s) * 15);
}
/* Leerer Slot (2026-08-13) — bleibt als Zeile stehen, damit beide
   Spalten gleich hoch bleiben statt schief/uneinheitlich zu wirken. */
.charakter-popup-item-leer {
  opacity: 0.4;
}
.charakter-popup-item-leer .charakter-popup-item-name {
  color: var(--text-dim);
  font-style: italic;
}
/* Item-Icon mit qualitätsfarbenem Rahmen (2026-08-14, Sven: "kann man
   das nicht so bauen wie Armory? jeder Itemplatz wird so angezeigt
   mit dem jeweiligen Bild") — ersetzt das vorherige reine Farbfeld.
   Rahmenfarbe kommt inline von der jeweiligen Item-Qualität (siehe
   itemZeileHtml() in script.js), gleiches Prinzip wie im echten Spiel/
   Armory (grau/weiß/grün/blau/lila/orange). 36px -> 48px (2026-08-14,
   "wir haben doch Platz!"). */
.charakter-popup-item-icon-rahmen {
  flex-shrink: 0;
  display: flex;
  width: calc(var(--s) * 48);
  height: calc(var(--s) * 48);
  border: 2px solid var(--violet-deep);
  border-radius: calc(var(--s) * 5);
  background: var(--void);
  overflow: hidden;
}
.charakter-popup-item-icon {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Name + Itemlevel UNTEREINANDER statt nebeneinander (2026-08-14, Sven:
   "dass das Itemlevel danebensteht sieht kacke aus") — eigener
   Textblock rechts vom Icon, Itemlevel jetzt eine kleine, gedimmte
   Zeile UNTER dem Namen statt rechtsbündig daneben. */
.charakter-popup-item-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 2);
}
.charakter-popup-item-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.charakter-popup-item-ilvl {
  font-size: calc(var(--s) * 12);
  color: var(--text-dim);
}
/* Voller Armory-Stil-Tooltip (2026-08-14, dritter Anlauf nach Svens
   Armory-Vergleichsbild — vorherige Fassung war halbtransparent
   [Panel-Verlauf mit Alpha, der Text der nächsten Zeile schien durch]
   und öffnete NACH UNTEN über die folgenden Items). Jetzt: DECKENDER
   dunkler Kasten (kein Durchscheinen mehr) und öffnet SEITLICH zur
   Popup-Mitte hin (linke Spalte -> rechts, rechte Spalte -> links,
   siehe Spalten-Overrides unten) über dem Charakterbild — verdeckt
   dadurch nie die anderen Items der eigenen Spalte. Reiner CSS-Hover,
   gleiches Idiom wie die Footer-Hover-Panels. */
/* position:fixed statt am Item verankert (2026-08-14, Sven: "der
   Tooltip auf Armory ist nicht 'fest', der wirkt wie schwebend") —
   die konkrete Position setzt der mousemove-Handler in script.js, der
   Tooltip folgt dadurch der Maus wie beim Original. Die früheren
   Richtungs-Overrides pro Spalte sind damit überflüssig. */
.charakter-popup-tooltip {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 60;
  /* Immer linksbündig (2026-08-14, Sven: "Tooltip bei den Waffen
     passt nicht" — die Waffen-Kacheln zentrieren ihren Inhalt
     [text-align:center für Icon+Name untereinander], das vererbte
     sich auf den Tooltip und zentrierte dort ALLE Zeilen). */
  text-align: left;
  width: calc(var(--s) * 300);
  padding: calc(var(--s) * 14) calc(var(--s) * 16);
  background: rgba(8, 5, 16, 0.97);
  border: 1px solid var(--violet);
  border-radius: var(--radius-card);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--duration-standard) var(--ease-soft);
  pointer-events: none;
}
.charakter-popup-item:hover .charakter-popup-tooltip {
  opacity: 1;
  visibility: visible;
}
.charakter-popup-tooltip-name {
  margin: 0 0 calc(var(--s) * 6);
  font-size: calc(var(--s) * 16);
  font-weight: bold;
}
.charakter-popup-tooltip-ilvl {
  margin: 0 0 calc(var(--s) * 2);
  font-size: calc(var(--s) * 13);
  color: var(--gold-bright);
}
.charakter-popup-tooltip-transmog {
  margin: 0 0 calc(var(--s) * 2);
  font-size: calc(var(--s) * 13);
  color: #ff80ff;
}
.charakter-popup-tooltip-zeile {
  margin: 0 0 calc(var(--s) * 2);
  font-size: calc(var(--s) * 13);
  color: var(--text);
}
/* Kleiner Absatz vor Haltbarkeit/Anforderung/Preis — trennt den
   unteren Info-Block optisch von den Werten, wie im Original. */
.charakter-popup-tooltip-abstand {
  margin-top: calc(var(--s) * 8);
}
/* Slot links + Rüstungsart rechts in EINER Zeile (wie im Original:
   "Kopf ..... Leder"). */
.charakter-popup-tooltip-slotzeile {
  display: flex;
  justify-content: space-between;
  gap: calc(var(--s) * 12);
  margin: 0 0 calc(var(--s) * 2);
  font-size: calc(var(--s) * 13);
  color: var(--text);
}
.charakter-popup-tooltip-wert {
  margin: 0 0 calc(var(--s) * 2);
  font-size: calc(var(--s) * 13);
}
.charakter-popup-tooltip-ver {
  margin: calc(var(--s) * 6) 0 calc(var(--s) * 2);
  font-size: calc(var(--s) * 13);
  color: #1eff00;
}
/* Sockel-Zeile(n) (2026-08-14, Sven: "auch wenn ein Item einen Sockel
   hat kann man es darstellen wie bei Armory") — bestückte Sockel in
   Violett, leere gedimmt. */
.charakter-popup-tooltip-sockel {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 6);
  margin: 0 0 calc(var(--s) * 2);
  font-size: calc(var(--s) * 13);
  color: var(--violet-glow);
}
.charakter-popup-tooltip-sockel-icon {
  width: calc(var(--s) * 16);
  height: calc(var(--s) * 16);
  border-radius: calc(var(--s) * 2);
  flex-shrink: 0;
}
/* Set-Block wie im Armory-Original (2026-08-14, Sven: "Set-Teile
   werden nicht richtig dargestellt") — Titel in Gold mit (x/5),
   darunter die Teile leicht eingerückt, aktive Set-Boni in Grün,
   fehlende Teile/inaktive Boni gedimmt. */
.charakter-popup-tooltip-set-titel {
  margin: 0 0 calc(var(--s) * 2);
  font-size: calc(var(--s) * 13);
  color: var(--gold-bright);
}
.charakter-popup-tooltip-set-teil {
  margin: 0 0 calc(var(--s) * 2);
  padding-left: calc(var(--s) * 12);
  font-size: calc(var(--s) * 13);
  color: var(--gold-bright);
}
.charakter-popup-tooltip-set-bonus {
  margin: calc(var(--s) * 4) 0 calc(var(--s) * 2);
  font-size: calc(var(--s) * 13);
  color: #1eff00;
}
.charakter-popup-tooltip-set-fehlt {
  color: var(--text-dim);
}
/* Kontext-Zeile unter dem Namen ("Mythisch+"), wie im Original. */
.charakter-popup-tooltip-kontext {
  margin: 0 0 calc(var(--s) * 2);
  font-size: calc(var(--s) * 13);
  color: #e5a840;
}
/* Münz-Punkte im Verkaufspreis (2026-08-14, Sven: "fehlen auch die
   Symbole von den Gold-, Silber- und Bronzemünzen") — kleine farbige
   Kreise wie bei Armory, reine CSS-Kreise statt Grafik-Dateien. */
.charakter-popup-muenze {
  display: inline-block;
  width: calc(var(--s) * 10);
  height: calc(var(--s) * 10);
  margin-left: calc(var(--s) * 3);
  border-radius: 50%;
  vertical-align: baseline;
}
.charakter-popup-muenze-gold   { background: #ffd24a; box-shadow: inset 0 0 2px #8a6d1a; }
.charakter-popup-muenze-silber { background: #c8c8d0; box-shadow: inset 0 0 2px #6e6e78; }
.charakter-popup-muenze-kupfer { background: #c47a3d; box-shadow: inset 0 0 2px #6e3d1a; }

/* ---- Hinweis-Toast (2026-08-08) ----
   Ersetzt window.alert() (Sven: "muss dieses extra Popup-Fenster
   kommen?") — dasselbe Panel-Idiom wie der Login-Dialog, fadet aber
   nur kurz ein/aus statt die Seite zu blockieren. z-index über dem
   Login-Popup (40), falls beide sich kurz überschneiden (Popup
   schließt currently gerade erst, wenn der Toast erscheint). */
/* Mittig + größer (2026-08-08, Sven: "kann diese Meldung allgemein
   mittig ins Bild stellen und größer?") — vorher unten mittig, klein.
   Jetzt echte Bildmitte (top:50%/left:50%, beide Achsen), deutlich
   größere Schrift/Innenabstand. */
.hinweis-toast {
  position: fixed;
  top: 50%;
  left: 50%;
  z-index: 60;
  transform: translate(-50%, -50%) scale(0.96);
  padding: calc(var(--s) * 22) calc(var(--s) * 44);
  /* 2026-08-11, Sven: "diese Meldungen müssen nicht so leicht
     transparent sein, dürfen mehr dichter sein" — der globale
     --panel-grad-a/b-Token ist bewusst halbtransparent für Karten, die
     ÜBER dem Hintergrundbild liegen (siehe Kommentar bei
     .archiv-popup-kopf). Ein Toast blendet aber frei über der ganzen
     Seite ein, braucht deshalb die "blickdichte" Variante, die im
     Projekt schon an mehreren Stellen für genau diesen Fall existiert
     (z.B. .klassen-chip). */
  background: linear-gradient(rgba(24, 14, 40, 0.96), rgba(10, 6, 18, 0.99));
  border: 1px solid var(--violet-deep);
  outline: 1px solid rgba(200, 170, 78, 0.3);
  outline-offset: calc(var(--s) * -6);
  border-radius: var(--radius-card);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 24);
  letter-spacing: 0.08em;
  text-align: center;
  color: var(--gold-bright);
  box-shadow: var(--glow-violet-strong);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-atmospheric) var(--ease-soft),
              transform var(--duration-atmospheric) var(--ease-soft);
}
.hinweis-toast.sichtbar {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}
@media (prefers-reduced-motion: reduce) {
  .hinweis-toast { transition: opacity var(--duration-standard) linear; transform: translate(-50%, -50%); }
}

/* ---- Klassenverteilung als Balkendiagramm (2026-08-08) ----
   Sven: "damit man sieht welche Klasse wieviel". Dieselben Zahlen wie
   die Chips oben, hier als Vergleich nebeneinander. Ein einzelnes
   CSS-Grid für ALLE Zeilen (nicht pro Zeile ein eigenes Flex-Layout)
   — dadurch teilen sich alle Zeilen dieselbe Namensspalten-Breite
   (automatisch so breit wie der längste Klassenname), nichts wird
   abgeschnitten oder muss geraten werden, genau die Lektion aus dem
   Klassen-Chip-Fix weiter oben. */
/* Fest unten verankert (2026-08-08, Sven) — flex-shrink:0 in der
   .roster-Flexspalte, siehe oben. Etwas kompakter als beim ersten Wurf
   (Zeilenabstand/Balkenhöhe verkleinert): Das Diagramm ist jetzt
   DAUERHAFT sichtbar statt nur am Seitenende, 13 Zeilen sollen dem
   scrollbaren Kartenbereich nicht zu viel Platz wegnehmen. */
/* Fest unten verankert, IMMER (2026-08-08, Sven: Position sprang je
   nachdem, ob eine Klasse gefiltert war — mit Filter drückte
   .roster-grid-wrap als flex:1 die Diagrammleiste ans untere Ende, ohne
   Filter (Wrapper display:none, kein Platzverbrauch) rutschte sie direkt
   hinter die Chips nach oben). margin-top:auto ist der Standard-
   Flexbox-Trick, um ein Element ans Ende eines Flex-Containers zu
   schieben, wenn Platz übrig ist — funktioniert unabhängig davon, ob
   .roster-grid-wrap gerade sichtbar ist oder nicht. */
/* Kompakter (2026-08-08, Sven: "Bereich Klassenverteilung von der Höhe
   etwas verkleinern") — margin/padding, Zeilenabstand, Balkenhöhe und
   Schriftgrößen jeweils leicht reduziert, damit das Diagramm insgesamt
   weniger Vertikalraum beansprucht und der Kartenbereich darüber mehr
   Platz bekommt. */
/* .klassen-diagramm (die feste Zeile unter den Mitgliederkacheln) ist
   2026-08-09 entfallen, siehe Kommentar bei rosterHtmlBauen() in
   script.js — Sven: "die Klassenverteilung muss weichen", ist jetzt
   ein Button oberhalb der Klassen-Chips, der ein Popup öffnet (siehe
   .klassenverteilung-popup weiter unten). Die darunterliegenden
   .diagramm-*-Klassen bleiben, werden jetzt nur vom Popup benutzt. */

/* Zurück auf die Zeilenversion (2026-08-08, Sven: "das mit den Balken
   sieht hässlich aus, wir gehen bitte zurück") — der Säulen-Anlauf ist
   komplett raus, hier wieder die ursprüngliche Zeilenliste. */
.diagramm-liste {
  display: grid;
  grid-template-columns: max-content 1fr max-content;
  align-items: center;
  gap: calc(var(--s) * 3) calc(var(--s) * 14);
}
.diagramm-zeile {
  display: contents;
}
.diagramm-name {
  font-family: var(--font-display);
  font-size: calc(var(--s) * 13);
  letter-spacing: 0.02em;
  white-space: nowrap;
  color: var(--klassenfarbe, var(--text));
  transition: text-shadow var(--duration-standard) var(--ease-soft);
}
.diagramm-track {
  height: calc(var(--s) * 6);
  background: rgba(255, 255, 255, 0.06);
  border-radius: calc(var(--s) * 3);
  overflow: hidden;
  cursor: default;
}
.diagramm-fuellung {
  position: relative;
  overflow: hidden; /* fängt den Lichtschimmer-Streif am Balkenende ab */
  height: 100%;
  border-radius: inherit;
  background: var(--klassenfarbe, var(--violet));
  box-shadow: 0 0 calc(var(--s) * 6) 0 color-mix(in srgb, var(--klassenfarbe, var(--violet)) 55%, transparent);
  transition: filter var(--duration-standard) var(--ease-soft),
              box-shadow var(--duration-standard) var(--ease-soft);
}
/* Lichtschimmer-Puls, verstärkt (2026-08-08, Sven: "der Effekt ist viel
   zu schwach" — bei nur 8px Balkenhöhe geht ein dünner Streif optisch
   fast unter, egal wie hell er ist). Jetzt zweiteilig:
   1) Der ganze Balken hellt spürbar auf + bekommt kräftigeren Schein,
      solange gehovert wird (dauerhaft, kein Einmal-Impuls) — das ist
      die Basis-Rückmeldung, die man aus jedem Blickwinkel sofort sieht.
   2) Zusätzlich läuft weiterhin EINMAL ein breiterer, hellerer Streif
      durch (jetzt 55% statt 35% Breite, 0.95 statt 0.65 Deckkraft,
      länger sichtbar) — der eigentliche "Impuls"-Akzent obendrauf. */
.diagramm-zeile:hover .diagramm-fuellung {
  filter: brightness(1.5);
  box-shadow: 0 0 calc(var(--s) * 10) calc(var(--s) * 1) color-mix(in srgb, var(--klassenfarbe, var(--violet)) 80%, transparent);
}
.diagramm-fuellung::after {
  content: '';
  position: absolute;
  top: 0;
  left: -70%;
  width: 55%;
  height: 100%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.95), transparent);
  transform: skewX(-20deg);
  opacity: 0;
}
.diagramm-zeile:hover .diagramm-fuellung::after {
  animation: balken-schimmer 1.1s ease-out;
}
@keyframes balken-schimmer {
  0%   { left: -70%;  opacity: 0; }
  15%  { opacity: 1; }
  100% { left: 140%;  opacity: 0; }
}
.diagramm-wert {
  min-width: calc(var(--s) * 22);
  text-align: right;
  font-family: var(--font-body);
  font-size: calc(var(--s) * 12);
  color: var(--text-dim);
  transition: color var(--duration-standard) var(--ease-soft);
}

/* ---- Hover-Effekt auf Name/Wert (2026-08-08) ----
   Ergänzt das Aufhellen+Leuchten des Balkens (siehe .diagramm-fuellung
   oben) — Name/Wert leuchten mit, damit die ganze Zeile einheitlich
   reagiert. :hover auf .diagramm-zeile funktioniert trotz
   display:contents (kein eigener Box), weil der Browser den
   Hover-Zustand aus den tatsächlich gerenderten Kind-Elementen
   ableitet. */
.diagramm-zeile:hover .diagramm-name {
  text-shadow: 0 0 calc(var(--s) * 6) color-mix(in srgb, var(--klassenfarbe, var(--violet)) 55%, transparent);
}
.diagramm-zeile:hover .diagramm-wert {
  color: var(--text);
}

/* ---- Mitglieder-Werkzeugzeile: Suchfeld + Klassenverteilung-Button
   (2026-08-09, Sven: "hier oberhalb der Klassen-Buttons folgendes
   hinzufügen: Suchfeld um Spieler zu suchen, Klassenverteilung als
   Button") — sitzt zwischen dem Seitenkopf und den Klassen-Chips. */
.mitglieder-werkzeuge {
  display: flex;
  gap: calc(var(--s) * 12);
  margin-bottom: calc(var(--s) * 14);
}
/* Wiederverwendung von .mitglieder-suche im Mitgliederverlauf
   (2026-08-22, Sven: "kann man hier ein Suchfeld wie bei 'Mitglieder'
   einbinden?") — dort brauch es zusätzlich Abstand nach unten zur
   Tabelle, .mitglieder-werkzeuge übernimmt das sonst über den
   umgebenden Flex-Container. */
.mitgliederverlauf-suche {
  display: block;
  margin-bottom: calc(var(--s) * 20);
}
.mitglieder-suche {
  /* Fest statt flex:1 (2026-08-09, Sven: "das Suchfeld kann man gerne
     verkleinern") — vorher nahm es die gesamte verbleibende Breite der
     Werkzeugzeile ein, wirkte neben dem eher kompakten "Klassenver-
     teilung"-Button überdimensioniert. */
  flex: 0 0 calc(var(--s) * 220);
  min-width: 0;
  padding: calc(var(--s) * 8) calc(var(--s) * 14);
  /* Deckender Hintergrund statt durchscheinend (2026-08-22, Sven:
     "könnte das Eingabefeld dunkler dargestellt werden, gibts wohl
     schon eine Grundlage dafür") — dieselbe Basis wie die übrigen
     Formularfelder (.verwaltung-rolle-auswahl) und die Raidkader-
     Kacheln. */
  background: var(--void);
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
  font-family: var(--font-body);
  font-size: calc(var(--s) * 14);
  color: var(--text);
}
.mitglieder-suche::placeholder {
  color: var(--text-dim);
}
.mitglieder-suche:focus {
  outline: none;
  border-color: var(--violet);
  box-shadow: var(--glow-violet-soft);
}
/* Gleiche Chip-Sprache wie .klassen-chip (Rahmen/Radius/Übergänge),
   damit der neue Button nicht wie ein Fremdkörper neben den
   bestehenden Klassen-Chips wirkt. */
.klassenverteilung-oeffnen {
  flex-shrink: 0;
  /* Vereinheitlicht auf exakt die .verwaltung-tab-Maße (2026-08-24,
     Sven: "kann man ... alle identisch darstellen. größe, effekt(e),
     darstellung") — dieselbe Werkzeugleisten-Familie wie
     .mehrfachauswahl-schalter/.roster-neu-umschalten daneben, alle drei
     jetzt Größe/Effekt-identisch mit den Verwaltung-/Raidplanung-Reitern. */
  padding: calc(var(--s) * 14) calc(var(--s) * 40);
  background: linear-gradient(rgba(24, 14, 40, 0.9), rgba(10, 6, 18, 0.96));
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 16);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  cursor: pointer;
  transition: border-color var(--duration-standard) var(--ease-soft),
              color var(--duration-standard) var(--ease-soft);
}
.klassenverteilung-oeffnen:hover {
  border-color: var(--violet);
  color: var(--text);
}
/* Extra-Abstand, jetzt hinter "Neu" (2026-08-22, Sven: "bitte hinter
   'Neu' aber mit etwas Abstand") — margin-left statt eines neuen
   Flex-Gaps, betrifft dadurch nur diesen einen Button, nicht die
   Abstände zwischen den übrigen Werkzeugleisten-Buttons. */
.klassenverteilung-oeffnen--abgesetzt {
  margin-left: calc(var(--s) * 16);
}

/* ---- Einzeln/Mehrfach-Umschalter (2026-08-10) ----
   Gleiche "Chip-Sprache" wie .klassenverteilung-oeffnen daneben
   (Rahmen/Radius/Übergänge), .active nutzt dasselbe Gold-Muster wie
   .klassen-chip.active, damit sofort erkennbar ist, welcher Modus
   gerade aktiv ist. */
.mehrfachauswahl-schalter {
  flex-shrink: 0;
  /* Vereinheitlicht auf exakt die .verwaltung-tab-Maße (2026-08-24,
     siehe .klassenverteilung-oeffnen-Kommentar oben). */
  padding: calc(var(--s) * 14) calc(var(--s) * 40);
  background: linear-gradient(rgba(24, 14, 40, 0.9), rgba(10, 6, 18, 0.96));
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 16);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  cursor: pointer;
  transition: border-color var(--duration-standard) var(--ease-soft),
              color var(--duration-standard) var(--ease-soft);
}
.mehrfachauswahl-schalter:hover {
  border-color: var(--violet);
  color: var(--text);
}
.mehrfachauswahl-schalter.active {
  border-color: var(--gold);
  box-shadow: var(--glow-gold-strong);
  color: var(--gold-bright);
}

/* ---- "Neu"-Umschalter + Neuzugänge/Abgänge (2026-08-13, Sven: "wie
   wäre es wenn man hier dann einfach die neu Zugänge anzeigt und
   diejenigen die gegangen sind? farblich eher in den Vordergrund
   stellen") ---- Gleiche Chip-Sprache wie die beiden Buttons oben, aber
   GRÜN statt Violett/Gold — setzt sich bewusst von einem normalen
   Filter ab (--online, dieselbe Farbe wie überall sonst für "positiv/
   aktiv" im Projekt, z.B. der Online-Punkt im Footer). */
.roster-neu-umschalten {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 6);
  /* Vereinheitlicht auf exakt die .verwaltung-tab-Maße (2026-08-24,
     siehe .klassenverteilung-oeffnen-Kommentar oben) — der grüne
     Rahmen/Glow bleibt bewusst als Signalfarbe erhalten (Sven:
     Farb-Codierung soll bleiben, nur Größe/Effekt-Sprache wird
     angeglichen). */
  padding: calc(var(--s) * 14) calc(var(--s) * 40);
  background: linear-gradient(rgba(24, 14, 40, 0.9), rgba(10, 6, 18, 0.96));
  border: 1px solid rgba(127, 232, 154, 0.4);
  border-radius: var(--radius-card);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 16);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--online);
  cursor: pointer;
  transition: border-color var(--duration-standard) var(--ease-soft),
              box-shadow var(--duration-standard) var(--ease-soft);
}
.roster-neu-umschalten:hover,
.roster-neu-umschalten.active {
  border-color: var(--online);
  box-shadow: 0 0 16px rgba(127, 232, 154, 0.3);
}
/* Zwei getrennte, unterschiedlich eingefärbte Zahlen statt eines
   einzigen Textes (2026-08-13, siehe script.js-Kommentar) — der
   Button-Rahmen selbst bleibt neutral grün als Grundfarbe (Hover/
   active oben), die eigentliche Grün/Rot-Unterscheidung sitzt auf den
   Badges selbst, damit ein reiner Abgänge-Fall (kein Beitritt) nicht
   trotzdem grün wirkt. */
.roster-neu-umschalten {
  color: var(--text);
}
/* Schlichter Text statt Pillen-Badge (2026-08-13, Sven: "hat mir
   vorher besser gefallen 'Neu (1)'") — kein eigener Hintergrund/
   Radius mehr, nur noch die Farbe unterscheidet Beitritt/Abgang,
   fügt sich damit unauffälliger in den Button ein. */
.roster-neu-umschalten-anzahl {
  font-size: calc(var(--s) * 13);
  letter-spacing: normal;
  text-transform: none;
}
.roster-neu-umschalten-anzahl--beigetreten {
  color: var(--online);
}
.roster-neu-umschalten-anzahl--verlassen {
  color: #e0879a;
}

/* .roster-raidkader-umschalten/.roster-raidkader-bereich entfernt
   (2026-08-23, Sven: "bei Mitglieder kann 'Raidkader' raus") — der
   Button/Bereich auf der Mitglieder-Seite existiert nicht mehr, Kader
   lebt jetzt als eigener Reiter unter Raidplanung. */
.raidkader-laedt,
.raidkader-leer {
  padding: calc(var(--s) * 40) 0;
  text-align: center;
  color: var(--text-dim);
  font-size: calc(var(--s) * 16);
}
/* Drei Spalten Tank/Heiler/Schaden nebeneinander, wie im Vorbild-
   Screenshot — bricht auf schmalen Bildschirmen automatisch um
   (auto-fit statt fester 3er-Reihe), kein separater Media-Query nötig. */
.raidkader-spalten {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(calc(var(--s) * 180), 1fr));
  gap: calc(var(--s) * 32);
}
.raidkader-spalte-titel {
  font-family: var(--font-display);
  font-size: calc(var(--s) * 17);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gold);
  margin: 0 0 calc(var(--s) * 12);
  padding-bottom: calc(var(--s) * 8);
  border-bottom: 1px solid var(--violet-deep);
}
/* Volle Mitglieder-Kacheln statt schlichter Liste (2026-08-22, Sven
   per Screenshot: "so dargestellt") — .raidkader-karten ist bewusst
   KEIN .roster-grid (das hat eigene Scroll-/Bleed-Mechanik für die
   volle Seitenhöhe der normalen Mitglieder-Seite, hier soll der
   Bereich einfach mit dem Inhalt mitwachsen). Stapelt jetzt
   .raidkader-klassen-reihe-Zeilen statt eines freien Grids (2026-08-22,
   Sven: "bitte immer Klasse in eine Reihe"), gleiches Prinzip wie
   .roster-grid/.roster-klassen-reihe.
   */
/* Wieder normal fließend statt Zeile-pro-Klasse (2026-08-23, Sven:
   "kann man beim Kader die Mitglieder wieder normal anreihen, nicht
   mehr pro Klasse eine neue Reihe. sollten nach Klasse natürlich
   sortiert sein aber fortlaufend") — Sortierung bleibt (siehe
   raidkaderNachKlasseSortiert() in script.js), nur der erzwungene
   Zeilenumbruch pro Klasse ist wieder weg, gleiches einfaches
   flex-wrap wie vor der Zeilen-Umstellung. */
.raidkader-karten {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--s) * 16);
}
.raidkader-karten .roster-card {
  flex: 0 0 calc(var(--s) * 200);
}
/* .roster-klassen-reihe bleibt NUR für die normale Mitglieder-Seite
   (dort weiterhin Zeile-pro-Klasse, siehe karteZeilenHtmlBauen()). */
.roster-klassen-reihe {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--s) * 16);
}
.roster-klassen-reihe .roster-card {
  flex: 0 0 calc(var(--s) * 200);
}
/* Klassen-Icon oben rechts auf jeder Mitglieder-/Raidkader-Kachel
   (2026-08-22, Sven: "wäre bei Mitglieder in den Kacheln sicherlich
   auch nice to have, immer oben rechts in der Ecke dargestellt"). */
.roster-card-icon {
  position: absolute;
  top: calc(var(--s) * 12);
  right: calc(var(--s) * 12);
  width: calc(var(--s) * 26);
  height: calc(var(--s) * 26);
  border-radius: calc(var(--s) * 4);
  border: 1px solid rgba(200, 170, 78, 0.4);
  object-fit: cover;
}
.raidkader-punkt {
  flex-shrink: 0;
  width: calc(var(--s) * 9);
  height: calc(var(--s) * 9);
  border-radius: 50%;
  background: var(--klassenfarbe, #9088a8);
  align-self: center;
}
/* Pool als eigene Unter-Kategorie am Ende jeder Rollen-Spalte
   (2026-08-22, Sven per Screenshot: "eigene Kategorie in der
   jeweiligen Rolle") — kleinere, gedämpfte Zwischenüberschrift statt
   einer vollen .raidkader-spalte-titel-Zeile, damit klar bleibt: es
   ist eine Unterteilung INNERHALB der Rolle, keine neue Rolle. */
.raidkader-pool-untertitel {
  margin: calc(var(--s) * 20) 0 calc(var(--s) * 10);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 14);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  /* Gold statt gedämpftem Grau (2026-08-22, Sven per Screenshot: "wie
     im Bild nicht wie 'Tank' als Kategorie einbringen auch als Gelb
     dargestellt") — dieselbe Goldfarbe wie .raidkader-spalte-titel,
     nur etwas kleiner, damit "Pool" klar als Kategorie INNERHALB der
     Rolle erkennbar bleibt, nicht mit der Rolle selbst verwechselt wird. */
  color: var(--gold);
}
.raidkader-unbekannt {
  /* border-top entfernt (2026-08-22, Sven: "jetzt sind hier 2
     Striche, kannst bitte den oberen entfernen") — der eigentliche
     Trennstrich sitzt jetzt direkt unter der Überschrift selbst
     (.raidkader-verwaltung-untertitel), dieser zusätzliche obere
     Rahmen war doppelt gemoppelt. */
  margin-top: calc(var(--s) * 32);
}
/* In der Verwaltung jetzt ZUERST statt zuletzt (2026-08-22, Sven:
   "hier könnte man 'nicht eingeordnet' nach oben bringen?") — braucht
   Abstand NACH statt VOR sich, öffentliche Ansicht (ohne dieses
   Modifier) bleibt unverändert am Ende mit margin-top. */
.raidkader-unbekannt--oben {
  margin-top: 0;
  margin-bottom: calc(var(--s) * 32);
}
/* Pool-Kacheln optisch abgesetzt (Sven-Screenshot: gedämpfter Look) —
   verfügbar für die Rolle, aber kein fester Stammplatz. Gestrichelter
   Rahmen statt durchgezogen, gleiches Prinzip wie .raidkader-
   verwaltung-kachel--pool in der Verwaltung. */
.roster-card--pool {
  border-style: dashed;
  opacity: 0.85;
}
.roster-pool-tag {
  display: inline-block;
  vertical-align: middle;
  margin-left: calc(var(--s) * 6);
  padding: 0 calc(var(--s) * 6);
  border-radius: calc(var(--s) * 8);
  background: rgba(200, 170, 78, 0.18);
  color: var(--gold-bright);
  font-family: var(--font-body);
  font-size: calc(var(--s) * 11);
  letter-spacing: 0.04em;
  text-transform: none;
}

/* ---- Raidkader-Verwaltung (2026-08-22) ---- Rang-Checkboxen im
   selben "Chip"-Look wie .klassen-chip, Kacheln darunter im Stil der
   übrigen Verwaltungs-Buttons (.verwaltung-btn), Popup nutzt das
   bestehende .neuigkeit-popup-Gerüst. */
.raidkader-verwaltung {
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 16);
  /* Gemeinsamer äußerer Rahmen um den GANZEN Bereich (2026-08-22, Sven
     per Screenshot der Mitglieder-Tabelle: "diesen transparenten
     Rahmen wie auf dem Bild") — bisher hatten nur einzelne Elemente
     (Häkchen/Kacheln) einen Rahmen, das eigentlich Gemeinte war der
     äußere Rahmen wie bei .verwaltung-tabelle-wrap, 1:1 übernommen
     (gleicher Verlauf --panel-grad-a/b, gleicher Rahmen/Radius). */
  margin-top: calc(var(--s) * 32);
  padding: calc(var(--s) * 24);
  background: linear-gradient(var(--panel-grad-a), var(--panel-grad-b));
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
}
.raidkader-raenge-liste {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--s) * 10);
}
.raidkader-rang-checkbox {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 8);
  padding: calc(var(--s) * 8) calc(var(--s) * 16);
  /* KORREKTUR (2026-08-22, Sven: "wie ein Rahmen hinterlegt der
     transparent dargestellt wird aber dennoch den Hintergrund der
     Seite etwas abschirmt") — mein erster Versuch (var(--void), 100%
     deckend) war zu hart/flach, --panel-grad-a/b davor zu transparent
     (55-75%). Das tatsächliche Vorbild (.neuigkeiten-verwaltung-zeile,
     "Classic Raids" im Raidplaner) nutzt einen Mittelweg (90-96%
     deckend) — jetzt exakt derselbe Verlauf, 1:1 übernommen. */
  background: linear-gradient(rgba(24, 14, 40, 0.9), rgba(10, 6, 18, 0.96));
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
  font-size: calc(var(--s) * 14);
  color: var(--text);
  cursor: pointer;
}
/* "Ränge speichern"-Button, gleiche Größe wie die Rang-Häkchen daneben
   (2026-08-22, Sven: "den Button neben die Ränge platzieren bitte und
   gleich Größe") — exakt dieselben Maße/Farben wie .raidkader-rang-
   checkbox, nur als Button statt Label+Checkbox, Gold statt Violett
   als Hervorhebung (Aktions-Button, keine reine Auswahl-Chip). */
.raidkader-raenge-speichern-btn {
  padding: calc(var(--s) * 8) calc(var(--s) * 16);
  background: linear-gradient(rgba(24, 14, 40, 0.9), rgba(10, 6, 18, 0.96));
  border: 1px solid var(--gold);
  border-radius: var(--radius-card);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 14);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gold-bright);
  cursor: pointer;
  transition: box-shadow var(--duration-standard) var(--ease-soft);
}
.raidkader-raenge-speichern-btn:hover {
  box-shadow: var(--glow-gold-soft);
}
.raidkader-verwaltung-untertitel {
  font-family: var(--font-display);
  font-size: calc(var(--s) * 16);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--gold);
  margin: 0 0 calc(var(--s) * 12);
  /* Gleicher Trennstrich wie unter "Tank"/"Heiler"/"Schadensklasse"
     (2026-08-22, Sven: "kann man diesen Trennstrich ebenfalls so
     nutzen wie bei Tanks?") — dieselbe Regel wie .raidkader-spalte-
     titel. */
  padding-bottom: calc(var(--s) * 8);
  border-bottom: 1px solid var(--violet-deep);
}
.raidkader-verwaltung-kacheln {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(calc(var(--s) * 170), 1fr));
  gap: calc(var(--s) * 12);
}
.raidkader-verwaltung-kachel {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: calc(var(--s) * 4);
  padding: calc(var(--s) * 10) calc(var(--s) * 14);
  /* KORREKTUR (2026-08-22, Sven: "wie ein Rahmen hinterlegt der
     transparent dargestellt wird aber dennoch den Hintergrund der
     Seite etwas abschirmt") — siehe ausführliche Begründung bei
     .raidkader-rang-checkbox, exakt derselbe Verlauf wie
     .neuigkeiten-verwaltung-zeile ("Classic Raids" im Raidplaner). */
  background: linear-gradient(rgba(24, 14, 40, 0.9), rgba(10, 6, 18, 0.96));
  border: 1px solid var(--violet-deep);
  border-radius: calc(var(--s) * 4);
  cursor: pointer;
  text-align: left;
  font-family: var(--font-body);
  transition: border-color var(--duration-standard) var(--ease-soft),
              box-shadow var(--duration-standard) var(--ease-soft);
}
.raidkader-verwaltung-kachel:hover {
  border-color: var(--violet);
  box-shadow: var(--glow-violet-soft);
}
.raidkader-verwaltung-kachel--pool {
  border-style: dashed;
}
.raidkader-verwaltung-kachel-zeile {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 8);
}
/* Klassen-Icon (2026-08-22, Sven: "kann man hier auch das
   Klassensymbol mit einbringen?") — ersetzt den vorherigen reinen
   Farbpunkt, gleiche Bildgröße/-optik wie .mitgliederverlauf-klasse-
   icon, damit es sich ins bestehende Icon-Idiom einfügt. */
.raidkader-kachel-icon {
  width: calc(var(--s) * 22);
  height: calc(var(--s) * 22);
  border-radius: calc(var(--s) * 4);
  border: 1px solid rgba(200, 170, 78, 0.4);
  object-fit: cover;
  flex-shrink: 0;
}
.raidkader-kachel-icon[hidden] {
  display: none;
}
.raidkader-rolle-popup-titel {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 10);
}
.raidkader-verwaltung-kachel-name {
  /* 15->17 (2026-08-22, Sven: "könnte bisschen größer dargestellt
     werden" — die Spielerkacheln in der Raidkader-Verwaltung waren
     beim vorherigen Schriftgrößen-Durchgang übersehen worden, andere
     CSS-Klassen als die öffentliche Ansicht/.roster-card). */
  font-size: calc(var(--s) * 17);
  color: var(--text);
}
.raidkader-verwaltung-kachel-rolle {
  font-size: calc(var(--s) * 14);
  color: var(--text-dim);
}
.raidkader-rolle-radios {
  display: flex;
  flex-direction: column;
  /* Deutlich größer (2026-08-22, Sven: "könnte größer dargestellt
     werden, haben soviel Platz") — das Popup hat reichlich Leerraum,
     Zeilenabstand + Schrift + die Radios/Checkbox selbst spürbar
     angehoben. */
  gap: calc(var(--s) * 16);
  margin: calc(var(--s) * 20) 0;
  font-size: calc(var(--s) * 19);
}
.raidkader-rolle-radios label {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 12);
  cursor: pointer;
}
.raidkader-rolle-radios input[type="radio"],
.raidkader-pool-checkbox-zeile input[type="checkbox"] {
  width: calc(var(--s) * 20);
  height: calc(var(--s) * 20);
  accent-color: var(--gold);
}
.raidkader-pool-checkbox-zeile {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 12);
  font-size: calc(var(--s) * 17);
  color: var(--text-dim);
  cursor: pointer;
}

/* "Neu beigetreten"-Kacheln: gleiche .roster-card wie überall, nur mit
   sanftem grünem Rahmen/Glow statt des normalen Violett-Rahmens — im
   "Neu"-Bereich selbst (Kontext ist eindeutig) UND bei gefilterter
   Einzelklasse (.roster-grid--einzelklasse) taucht zusätzlich der
   kleine Text-Tag neben dem Namen auf, siehe unten. */
/* Glow verstärkt (2026-08-13, Sven: "könnte man diese Kachel auch mit
   einem extra Glow belegen?") — gilt automatisch NUR für den
   Zeitraum, den roster_verlauf_kuerzlich_anhaengen() als "kürzlich"
   liefert (4 Tage), läuft dann von selbst wieder als ganz normale
   Kachel weiter, kein manuelles Zurücksetzen nötig (siehe
   rosterKarteHtmlBauen() in script.js). Zweite, weichere Schicht
   ergänzt (größerer Radius, geringere Deckkraft) für einen echten
   Leucht-Effekt statt nur eines kräftigeren Rahmens. */
/* Grün -> Klassenfarbe (2026-08-14, Sven: "alle die hier angezeigt
   werden haben den grünen Glow! bitte auf die Klassenfarbe ändern") —
   jede Kachel trägt ihre --klassenfarbe schon als CSS-Variable (siehe
   rosterKarteHtmlBauen()), color-mix erzeugt daraus die
   halbtransparenten Glow-Abstufungen, die vorher fest grün waren. */
.roster-card--neu {
  border-color: var(--klassenfarbe) !important;
  box-shadow: 0 0 14px color-mix(in srgb, var(--klassenfarbe) 55%, transparent),
              0 0 40px color-mix(in srgb, var(--klassenfarbe) 35%, transparent);
}
/* BUGFIX (2026-08-13, Sven: "soll beim Hovern nicht dunkler werden,
   darf gerne noch heller werden, die normalen Kacheln werden ja auch
   heller") — .roster-card:hover (Klasse+Pseudoklasse, siehe oben) ist
   spezifischer als das einfache .roster-card--neu und überschrieb den
   grünen Glow beim Hover mit dem normalen klassenfarbenen Hover-Glow,
   wirkte dadurch wie ein Rückschritt statt einer Steigerung. Eigene,
   noch hellere Hover-Fassung mit derselben Spezifität wie die
   Basis-Hover-Regel (Klasse+Pseudoklasse) gewinnt jetzt zuverlässig. */
.roster-card--neu:hover {
  border-color: var(--klassenfarbe) !important;
  box-shadow: 0 0 18px color-mix(in srgb, var(--klassenfarbe) 75%, transparent),
              0 0 52px color-mix(in srgb, var(--klassenfarbe) 50%, transparent);
}
.roster-neu-tag {
  display: none;
  vertical-align: middle;
  margin-left: calc(var(--s) * 6);
  padding: 0 calc(var(--s) * 6);
  border-radius: calc(var(--s) * 8);
  background: rgba(127, 232, 154, 0.18);
  color: var(--online);
  font-family: var(--font-body);
  font-size: calc(var(--s) * 11);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.roster-grid--einzelklasse .roster-neu-tag,
.roster-grid--neu-bereich .roster-neu-tag {
  display: inline-block;
}

.roster-neu-bereich-titel {
  margin: calc(var(--s) * 24) 0 calc(var(--s) * 12) calc(var(--s) * 8);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 16);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--online);
}
.roster-neu-bereich-titel--verlassen {
  color: #e0879a;
}
/* .roster ist ein fixierter, exakt viewport-hoher Flex-Container OHNE
   eigenes overflow-y (jede Ansicht regelt ihren Scroll-Innenbereich
   selbst, siehe .roster-grid weiter oben) — .roster-neu-bereich ist
   ein direktes Geschwister-Element von .roster-grid-wrap und braucht
   deshalb GENAU DASSELBE flex:1+min-height:0+eigener Scroll-Mechanismus,
   sonst würde langer Inhalt (viele Neuzugänge/Abgänge) einfach über
   den festen Rahmen hinaus verschwinden, ohne scrollbar zu sein. Die
   verschachtelte .roster-grid-wrap DARIN bekommt dafür bewusst KEINE
   eigene Höhe/Scroll/Maske mehr (überschreibt die Basis-Regeln) — nur
   noch EIN scrollender Bereich (.roster-neu-bereich selbst), nicht
   zwei ineinander verschachtelte. */
.roster-neu-bereich {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding-right: calc(4vw + var(--s) * 14);
  margin-right: -4vw;
  padding-bottom: calc(var(--s) * 24);
  scrollbar-width: thin;
  scrollbar-color: var(--violet-deep) transparent;
}
.roster-neu-bereich .roster-grid-wrap {
  display: block;
  flex: none;
}
.roster-neu-bereich .roster-grid {
  height: auto;
  overflow: visible;
  margin-right: 0;
  padding-right: 0;
  padding-bottom: 0;
  -webkit-mask-image: none;
          mask-image: none;
}
/* "Kürzlich gegangen" — bewusst KEINE Kachel/kein Armory-Link (siehe
   script.js-Kommentar: die Person steht nicht mehr im echten Roster,
   ein Link könnte ins Leere oder auf jemand anderen zeigen). Schlichte
   Zeilenliste statt Kachel-Raster, klar abgesetzt/gedämpfter als die
   "Neu beigetreten"-Kacheln. */
.roster-verlassen-liste {
  list-style: none;
  margin: 0 0 calc(var(--s) * 8);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 8);
}
.roster-verlassen-zeile {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 14);
  padding: calc(var(--s) * 10) calc(var(--s) * 14);
  background: rgba(10, 6, 18, 0.35);
  border: 1px solid rgba(179, 57, 76, 0.25);
  border-radius: var(--radius-card);
  font-size: calc(var(--s) * 14);
}
.roster-verlassen-name {
  color: var(--text);
  font-weight: 600;
}
.roster-verlassen-meta {
  color: var(--text-dim);
}
.roster-verlassen-zeit {
  margin-left: auto;
  flex-shrink: 0;
  color: var(--text-faint);
  font-size: calc(var(--s) * 13);
}

/* ---- Klassenverteilung-Popup (2026-08-09) ----
   Gleiche Optik wie die übrigen Popups (.neuigkeit-popup), nur der
   Inhalt ist die Balkenliste. Wichtig: .diagramm-zeile ist im
   ALLGEMEINEN Fall `display:contents` (siehe oben, für die
   spaltengenaue Ausrichtung in einem CSS-Grid) — ein Element mit
   `display:contents` hat aber gar keine eigene Box, `opacity`/
   `transform` darauf hätte deshalb KEINE Wirkung (bekannte CSS-Falle).
   Für die "erst erscheinen, dann Balken laufen"-Animation braucht die
   Zeile hier im Popup zwingend eine eigene Box — deshalb innerhalb des
   Popups auf echtes Flex umgeschaltet, nur hier, die alte Grid-Variante
   bleibt für eventuelle andere Verwendungen unangetastet. */
.klassenverteilung-popup .diagramm-liste {
  display: flex;
  flex-direction: column;
  /* BUGFIX (2026-08-09, Sven per Screenshot: keine Balken sichtbar,
     Zahlen klebten direkt am Namen) — die alte Grid-Variante von
     .diagramm-liste setzt `align-items: center`, das gilt PRO
     EIGENSCHAFT und wird deshalb NICHT automatisch durch das bloße
     Umschalten auf `display:flex` hier verdrängt: `center` schrumpfte
     jede Zeile auf ihre Inhaltsbreite zusammen, der Balken (flex:1)
     hatte dadurch schlicht keinen Platz mehr, auf den er sich
     ausdehnen konnte (Breite 0). `stretch` zwingt jede Zeile auf die
     volle Popup-Breite, danach kann der Balken seinen Anteil bekommen. */
  align-items: stretch;
  gap: calc(var(--s) * 10);
}
.klassenverteilung-popup .diagramm-zeile {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 14);
  /* Start-Zustand der Erscheinen-Animation (Phase 1, siehe
     rosterKlassenverteilungVerdrahten() in script.js) — wird per JS um
     ".diagramm-zeile-sichtbar" ergänzt, sobald die Zeile dran ist. */
  opacity: 0;
  transform: translateX(calc(var(--s) * -14));
  transition: opacity 0.4s var(--ease-soft), transform 0.4s var(--ease-soft);
}
.klassenverteilung-popup .diagramm-zeile.diagramm-zeile-sichtbar {
  opacity: 1;
  transform: translateX(0);
}
.klassenverteilung-popup .diagramm-name {
  flex: 0 0 calc(var(--s) * 130);
}
.klassenverteilung-popup .diagramm-track {
  flex: 1;
}
/* Balken-Füllung braucht hier zusätzlich eine width-Transition (Phase 2)
   — im ursprünglichen, nicht-animierten Diagramm war width immer schon
   der Zielwert, eine Transition wäre dort nie aufgefallen/nötig
   gewesen. */
.klassenverteilung-popup .diagramm-fuellung {
  transition: width 0.9s var(--ease-soft),
              filter var(--duration-standard) var(--ease-soft),
              box-shadow var(--duration-standard) var(--ease-soft);
}

/* ================================================================
   HOCHFORMAT-LAYOUT (2026-08-11, siehe ROADMAP.md "Kein Mobil-/
   Hochformat-Layout") — Sven: Hintergrundbild soll erhalten bleiben,
   automatisch bei Hochformat greifen (nicht an eine feste Pixelzahl
   gebunden). Die ganze übrige Seite ist bewusst an EIN Breitbild-
   Kunstwerk mit exakter --s-Pixel-Verankerung gebunden (siehe
   header.php-Kommentare) — das funktioniert bei einem hochkant
   gehaltenen Gerät strukturell nicht (siehe Roadmap-Diagnose vom
   09.08.: Menüspalte wird bei dominanter Höhe absurd breit, drängt
   #content fast komplett aus dem sichtbaren Bereich).
   Lösung hier: Hintergrundbild bleibt sichtbar (einfaches Cover-Crop
   statt exakter --s-Box), alle pixelgenau verankerten Detail-Ebenen
   (Feuer/Wasser/Burgfenster/Wolken/Funken/Sterne, alle in #scene-fx
   bzw. #content-sterne) werden ausgeblendet, weil sie im gecroppten
   Ausschnitt nicht mehr zu den Bildkoordinaten passen würden. Die
   Sidebar (#menu) wird zu einer schlichten Kopfleiste mit
   aufklappbarem Menü, #content läuft in normalem Fluss über die volle
   Breite. Betrifft NUR echtes Hochformat (Handy/Tablet hochkant),
   Landscape-Tablets/Desktop bleiben komplett unverändert. */
@media (orientation: portrait) {
  /* Hintergrundbild: einfaches Cover-Crop statt --s-exakter Box (siehe
     #site-background-Basisregel weiter oben) — kein Versatzrisiko mehr,
     weil die pixelgenauen Overlays hier ohnehin ausgeblendet sind. */
  #site-background {
    position: fixed;
    inset: 0;
    left: 0;
    top: 0;
    width: 100vw;
    height: 100vh;
    object-fit: cover;
    object-position: left top;
  }
  /* Feindetail-Ebenen aus, Grundstimmung des Kunstwerks bleibt. */
  #scene-fx,
  #content-sterne {
    display: none;
  }
  /* Abdunkelung, damit Text/Menü auf dem Kunstwerk lesbar bleiben
     (vorher übernahmen das die --s-exakt platzierten Overlay-Ebenen
     mit, die jetzt fehlen). */
  body::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 1;
    background: rgba(6, 4, 12, 0.55);
    pointer-events: none;
  }

  /* ---- Sidebar wird zur Kopfleiste ---- */
  .brand,
  .emblem {
    display: none;
  }
  #menu {
    position: fixed;
    inset: 0 0 auto 0;
    z-index: 50;
    width: 100%;
    height: auto;
    max-height: 100vh;
    overflow-y: auto;
    /* Violetter Scrollbalken (2026-08-12) statt Browser-Standard, siehe
       .raidplan-klasse-palette für den Anlass. */
    scrollbar-width: thin;
    scrollbar-color: var(--violet-deep) transparent;
    background: linear-gradient(rgba(24, 14, 40, 0.98), rgba(10, 6, 18, 0.98));
    border-bottom: 1px solid var(--violet-deep);
  }
  #mobil-leiste {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: calc(var(--s) * 12) 16px;
  }
  #mobil-leiste .mobil-logo {
    height: 36px;
    width: auto;
  }
  #mobil-menu-btn {
    width: 40px;
    height: 40px;
    background: none;
    border: 1px solid var(--violet-deep);
    border-radius: calc(var(--s) * 4);
    color: var(--text);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
  }
  /* Aufklappbarer Teil: standardmäßig eingeklappt, #menu.mobil-offen
     (per JS gesetzt, siehe script.js) blendet ihn ein. KEIN
     "display:block" direkt hier (gleiches Bugfix-Muster wie an
     mehreren anderen Stellen im Projekt, siehe z.B.
     .gildeninfo-emoji-palette-Kommentar) — steht deshalb nur unten in
     der .mobil-offen-Regel. */
  #menu-unten {
    display: none;
    position: static;
    width: 100%;
    padding: 0 16px 16px;
  }
  #menu.mobil-offen #menu-unten {
    display: block;
  }
  #mainnav {
    min-height: 0;
  }
  #mainnav a {
    height: auto;
    padding: calc(var(--s) * 14) 4px;
  }
  #sidebar-aktionen {
    position: static;
    width: 100%;
    margin-top: 16px;
  }
  /* .konto-zeile/.musik-zeile haben auf Desktop einen festen
     translateY(-20px)/(-5px)-Verschub für die exakte Pixel-Position auf
     dem Hintergrundbild (siehe Basisregel weiter oben) — im neuen
     normalen Dokumentfluss hier zieht das die Konto-Zeile stattdessen
     ein Stück nach OBEN in die letzte Nav-Zeile hinein, statt sauber
     danach zu sitzen ("Login + Icons sitzen komisch", 2026-08-11).
     Im Hochformat schlicht zurückgesetzt. */
  .konto-zeile,
  .musik-zeile {
    transform: none;
  }

  /* ---- Inhalt über die volle Breite, normaler Fluss ---- */
  #content {
    margin-left: 0;
    padding: 16px;
    /* Platz für die fixierte Kopfleiste (Höhe schwankt leicht mit dem
       Logo, großzügig genug bemessen) + für den fixierten Footer unten
       (--footer-h gilt hier weiter, siehe #content-Basisregel). */
    padding-top: calc(var(--s) * 70);
  }

  /* ---- Footer über die volle Breite ---- */
  #main-footer {
    left: 0;
    padding: 0 16px;
  }

  /* .roster ist die eigentliche Inhalts-Bühne JEDER Ansicht
     (Neuigkeiten/Gildeninfo/Mitglieder/...) und positioniert sich
     GRUNDSÄTZLICH selbst per position:fixed + festem
     "left: calc(var(--s)*640)" — komplett unabhängig von #content, das
     Zurücksetzen von #content weiter oben hat darauf KEINE Wirkung
     (2026-08-11, echter Bug: Inhalt blieb dadurch bei einem festen
     Linksversatz von ~640*--s hängen, auf einem Tablet bereits mehr als
     die halbe Bildschirmbreite, der Inhalt landete faktisch komplett
     außerhalb des sichtbaren Bereichs). Im Hochformat auf volle Breite
     unterhalb der Kopfleiste zurückgesetzt. */
  .roster {
    left: 0;
    top: calc(var(--s) * 70);
    padding-right: 0;
  }
}

/* ---- 404-Fehlerseite (2026-08-13) ----
   Eigenständige Seite (404.php), läuft bewusst OHNE Sidebar/Menü — ein
   echter 404 wird fast immer von außen ausgelöst (kaputter/getippter
   Link), nicht durch Klicks innerhalb der SPA. Reiner CSS-Nachthimmel
   statt eines neuen Bild-Assets, bis Svens eigener (textfreier)
   Entwurf vorliegt — dann hier einfach die auskommentierte
   background-image-Zeile aktivieren, restliche Regeln bleiben gültig
   (funktionieren als Overlay über jedem Hintergrundbild). Text bewusst
   ECHTES HTML (Cinzel/Marcellus wie überall sonst), nicht ins Bild
   eingebrannt — scharf bei jeder Auflösung, "Zurück zur Startseite"
   ein echter, klickbarer Link. */
.fehlerseite-404-body {
  margin: 0;
  min-height: 100vh;
  background: var(--void);
  font-family: var(--font-body);
}
.fehlerseite-404 {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  overflow: hidden;
  /* Svens eigener textfreier Bildentwurf (2026-08-13, assets/images/
     404.jpg) — Verlauf oben drüber gelegt (gleiche Ebene, VOR dem Bild
     genannt = liegt optisch darüber), sorgt für genug Kontrast, damit
     der echte HTML-Text darüber lesbar bleibt, ohne das Bild komplett
     zu verdunkeln.
     PNG -> JPG (2026-08-14, Ladezeiten-Thema) — das Bild ist komplett
     deckend (keine Transparenz genutzt), JPG komprimiert bei
     gleichbleibender Bildqualität deutlich besser: 2,7 MB -> 373 KB.
     Altes 404.png bleibt zusätzlich im Ordner liegen (falls doch noch
     irgendwo referenziert), wird aber von hier aus nicht mehr geladen. */
  background:
    radial-gradient(ellipse at 50% 38%, rgba(8, 6, 16, 0.35), rgba(8, 6, 16, 0.75) 70%),
    url('/assets/images/404.jpg');
  background-size: cover;
  background-position: center;
}
/* Sparsames Sternenfeld, gleiche Technik wie #content-sterne (mehrere
   radiale Punkte in einem einzigen background-image statt echter
   Elemente) — bewusst dezent, soll die neue Seite nicht überladen. */
.fehlerseite-404::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(1px 1px at 12% 18%, rgba(217, 210, 232, 0.7), transparent),
    radial-gradient(1px 1px at 82% 24%, rgba(217, 210, 232, 0.5), transparent),
    radial-gradient(1.5px 1.5px at 68% 12%, rgba(217, 210, 232, 0.8), transparent),
    radial-gradient(1px 1px at 24% 68%, rgba(217, 210, 232, 0.4), transparent),
    radial-gradient(1px 1px at 90% 72%, rgba(217, 210, 232, 0.5), transparent),
    radial-gradient(1.5px 1.5px at 45% 85%, rgba(217, 210, 232, 0.6), transparent);
  pointer-events: none;
}
.fehlerseite-404-inner {
  position: relative;
  z-index: 1;
  max-width: 45rem;
  padding: 2.5rem;
  text-align: center;
}
/* Bewusst NICHT mehr mit var(--s) skaliert (2026-08-13, Sven auf einem
   Curved-/Ultrawide-Monitor: "sieht komisch aus, haben wir wieder das
   Problem mit den Größen") — --s ist auf die 3440×1440-Referenzgrafik
   der Haupt-Seite zugeschnitten (Ranken-Menü-Koordinaten), bei anderen
   Seitenverhältnissen skaliert das unkontrolliert hoch. Diese Seite
   braucht dieses Koordinatensystem gar nicht (kein Ranken-Menü, kein
   pixelgenaues Andocken) — normale rem-Werte + clamp() für die
   Ziffer/den Titel sind hier robuster über alle Bildschirmformen
   hinweg (Curved-Ultrawide, Hochformat, normale Monitore). */
.fehlerseite-404-ziffer {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(4rem, 9vw, 8rem);
  line-height: 1;
  color: var(--violet-glow);
  text-shadow: 0 0 40px rgba(177, 140, 255, 0.55), 0 0 80px rgba(109, 61, 255, 0.35);
}
.fehlerseite-404-titel {
  margin: 0.5rem 0 0;
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 2.4vw, 1.75rem);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text);
}
.fehlerseite-404-text {
  margin: 1.1rem auto 0;
  max-width: 32rem;
  font-size: 1rem;
  line-height: 1.6;
  color: var(--text-dim);
}
.fehlerseite-404-trenner {
  width: 5.6rem;
  height: 1px;
  margin: 1.6rem auto;
  background: linear-gradient(90deg, transparent, var(--gold), transparent);
}
/* Gleiche CTA-Sprache wie .konto-absenden (Panel-Verlauf, Gold-Outline,
   Cinzel-Versalien) — bewusst wiederverwendet statt neu erfunden, damit
   die Fehlerseite trotz eigenständigem Layout erkennbar zur Seite
   gehört. Größen hier aber fest in rem, siehe Kommentar oben. */
.fehlerseite-404-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  height: 3.375rem;
  padding: 0 2.5rem;
  background: linear-gradient(var(--panel-grad-a), var(--panel-grad-b));
  border: 1px solid var(--violet-deep);
  outline: 1px solid rgba(200, 170, 78, 0.35);
  outline-offset: -5px;
  border-radius: var(--radius-card);
  font-family: var(--font-display);
  font-size: 1rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold-bright);
  text-decoration: none;
  transition: box-shadow var(--duration-standard) var(--ease-soft),
              border-color var(--duration-standard) var(--ease-soft);
}
.fehlerseite-404-button:hover {
  border-color: var(--violet);
  box-shadow: var(--glow-violet-strong);
}

/* ---- Gildenwerbung-Generator (2026-08-14, siehe ROADMAP.md
   "Gildenwerbung-Generator in der Verwaltung", Weg B) ----
   Formular links, Export-Aktionen rechts — die eigentliche Vorschau
   lebt seit Svens Wunsch ("Live-Vorschau wäre ein Popup-Fenster gut")
   in einem eigenen Popup statt eingebetteter zweiter Spalte, deshalb
   rechte Spalte jetzt bewusst schmaler. */
.werbung-verwaltung {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: calc(var(--s) * 32);
  align-items: start;
}
.werbung-formular {
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 4);
}
.werbung-formular .gildeninfo-editor-label {
  margin-top: calc(var(--s) * 14);
}
.werbung-formular .gildeninfo-editor-label small {
  font-weight: 400;
  text-transform: none;
  letter-spacing: normal;
  color: var(--text-dim);
}
.werbung-input,
.werbung-textarea {
  width: 100%;
  box-sizing: border-box;
}
.werbung-textarea {
  resize: vertical;
  font-family: var(--font-body);
  line-height: 1.5;
}
.werbung-hinweis {
  font-size: calc(var(--s) * 14);
  color: var(--text-dim);
  line-height: 1.6;
}
.werbung-hinweis code {
  color: var(--gold);
}

/* Hintergrundbild-Auswahl (2026-08-14, ZWEITER Anlauf) — jetzt Klapp-
   Button+Palette wie bei Neuigkeiten (.neuigkeit-bild-palette/-option
   als Basis, siehe dortige Regeln), PLUS eigene Upload-Zeile und
   Löschen-Knopf je Bild (Sven: "gib mir die Möglichkeit selbst Bilder
   hochzuladen und zu löschen"). .werbung-bild-palette überschreibt nur
   die Breite/Spaltenzahl der geerbten .neuigkeit-bild-palette-Basis,
   kein doppeltes Grundgerüst. */
.werbung-bild-palette {
  width: calc(var(--s) * 480);
}
.werbung-bild-palette:not([hidden]) {
  grid-template-columns: repeat(3, 1fr);
}
.werbung-bild-upload-zeile {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 12);
  margin-bottom: calc(var(--s) * 10);
  padding-bottom: calc(var(--s) * 10);
  border-bottom: 1px solid var(--violet-deep);
}
.werbung-upload-label {
  cursor: pointer;
}
.werbung-bild-raster {
  display: contents;
}
.werbung-bild-option-wrap {
  position: relative;
}
/* .werbung-bild-option erbt Layout/Optik von .neuigkeit-bild-option
   (siehe dort), hier nur die Kachel-Rahmen-Variante on top — dieselbe
   aktive-Zustand-Farbgebung wie überall sonst im Projekt (Gold-Rahmen
   + Glow), .neuigkeit-bild-option kennt "aktiv" bisher nicht, weil die
   Neuigkeiten-Auswahl das über die Vorschau daneben löst statt über
   eine Kachel-Markierung. */
.werbung-bild-option {
  position: relative;
  border: 1px solid var(--violet-deep);
  border-radius: calc(var(--s) * 6);
}
.werbung-bild-option input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.werbung-bild-option img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: calc(var(--s) * 4);
}
.werbung-bild-option-aktiv {
  border-color: var(--gold);
  box-shadow: var(--glow-gold-soft);
}
.werbung-bild-option-aktiv span {
  color: var(--gold-bright);
}
.werbung-bild-loeschen-btn {
  position: absolute;
  top: calc(var(--s) * 2);
  right: calc(var(--s) * 2);
  width: calc(var(--s) * 20);
  height: calc(var(--s) * 20);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: rgba(10, 6, 18, 0.85);
  border: 1px solid var(--violet-deep);
  border-radius: 50%;
  color: var(--text-dim);
  font-size: calc(var(--s) * 14);
  line-height: 1;
  cursor: pointer;
  transition: border-color var(--duration-standard) var(--ease-soft),
              color var(--duration-standard) var(--ease-soft);
}
.werbung-bild-loeschen-btn:hover {
  border-color: var(--gold);
  color: var(--gold-bright);
}

/* Klassen-Auswahl "Wen wir suchen" (2026-08-14, siehe
   werbungKlassenAuswahlHtmlBauen() in script.js) — BUGFIX: diese
   Klassen hatten bisher GAR KEIN eigenes CSS, die Icons liefen dadurch
   in voller Originalgröße (mehrere hundert Pixel) ohne jede Begrenzung
   (Sven per Screenshot: "die Symbole ... viel zu groß!"). Kompaktes
   Chip-Raster statt Karten wie bei der Bildauswahl — hier geht es um
   13 kleine Ja/Nein-Kacheln, keine großen Vorschaubilder. */
.werbung-klassen-raster {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--s) * 8);
  margin-top: calc(var(--s) * 6);
}
.werbung-klassen-option {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--s) * 6);
  padding: calc(var(--s) * 6) calc(var(--s) * 10);
  background: var(--void);
  border: 1px solid var(--violet-deep);
  border-radius: calc(var(--s) * 6);
  cursor: pointer;
  transition: border-color var(--duration-standard) var(--ease-soft);
}
.werbung-klassen-option input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.werbung-klassen-option img {
  width: calc(var(--s) * 22);
  height: calc(var(--s) * 22);
  border-radius: calc(var(--s) * 3);
  flex-shrink: 0;
}
.werbung-klassen-option span {
  font-size: calc(var(--s) * 13);
  color: var(--text-dim);
  white-space: nowrap;
}
.werbung-klassen-option:hover {
  border-color: var(--violet);
}
.werbung-klassen-option-aktiv {
  border-color: var(--klassenfarbe, var(--gold));
  box-shadow: var(--glow-gold-soft);
}
.werbung-klassen-option-aktiv span {
  color: var(--text);
}

/* ---- Live-Vorschau: Bild oben unangetastet, Text darunter
   (2026-08-14, Sven per Beispielbild: "das Bild darunter... damit es
   stimmig wäre" — Text-Kästen DIREKT auf dem Bild hätten die Figur bei
   randvoll ausgefüllten Motiven angeschnitten). Ein gemeinsamer
   goldener Rahmen um Bild+Text zusammen, dieselbe Optik wie der
   Canvas-Export (werbungPosterZeichnen() in script.js zeichnet exakt
   dasselbe Layout von Hand nach). */
.werbung-vorschau-wrap {
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 14);
}
.werbung-poster {
  border: 3px solid var(--gold);
  border-radius: var(--radius-card);
  overflow: hidden;
  box-shadow: var(--glow-gold-soft);
}
.werbung-poster-bildwrap img {
  display: block;
  width: 100%;
  height: auto;
}
.werbung-poster-bild-platzhalter {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 4 / 3;
  background: linear-gradient(#180e28, #0a0612);
  color: var(--text-dim);
  font-size: calc(var(--s) * 15);
  text-align: center;
  padding: calc(var(--s) * 20);
}
.werbung-poster-text {
  background: linear-gradient(var(--panel-grad-a), var(--panel-grad-b));
  border-top: 3px solid var(--gold);
  padding: calc(var(--s) * 32) calc(var(--s) * 24);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--s) * 22);
}
.werbung-poster-titel {
  text-align: center;
}
.werbung-poster-titel .werbung-poster-titel-klein {
  display: block;
  font-family: var(--font-display);
  font-size: calc(var(--s) * 14);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold);
}
.werbung-poster-titel h2 {
  margin: calc(var(--s) * 4) 0;
  font-family: var(--font-display);
  font-size: calc(var(--s) * 46);
  letter-spacing: 0.06em;
  color: var(--text);
}
.werbung-poster-titel p {
  margin: 0;
  font-size: calc(var(--s) * 16);
  /* --werbung-akzent statt fest var(--violet-glow) (2026-08-14, Sven:
     "das Lila passt nicht zu dem Weiß/Gold" + "wir müssen das hier
     individuell an die Bilder anpassen!" — eine feste Farbe passt
     nicht zu jedem Hintergrundbild, jetzt pro Poster im Formular
     wählbar, siehe #werbung-akzentfarbe in script.js). Fallback
     warmes Creme, falls die Variable mal nicht gesetzt ist. */
  color: var(--werbung-akzent, #d9cba8);
}
.werbung-poster-boxen {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: calc(var(--s) * 14);
  width: 100%;
}
.werbung-poster-box {
  background: rgba(24, 14, 40, 0.9);
  border: 1px solid rgba(200, 170, 78, 0.55);
  border-radius: var(--radius-card);
  padding: calc(var(--s) * 16);
}
.werbung-poster-box h4 {
  margin: 0 0 calc(var(--s) * 10);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 15);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-align: center;
  color: var(--gold);
}
.werbung-poster-box ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 6);
}
.werbung-poster-box li {
  font-size: calc(var(--s) * 13);
  line-height: 1.4;
  color: var(--text);
  text-align: center;
}
.werbung-poster-box li.werbung-poster-leer {
  color: var(--text-dim);
}
.werbung-poster-box p {
  margin: 0;
  font-size: calc(var(--s) * 13);
  text-align: center;
  color: var(--text);
}
.werbung-poster-fortschritt,
.werbung-poster-kontakt {
  width: 100%;
  text-align: center;
  padding: calc(var(--s) * 14);
  background: rgba(24, 14, 40, 0.9);
  border: 1px solid rgba(200, 170, 78, 0.55);
  border-radius: var(--radius-card);
  font-size: calc(var(--s) * 14);
  color: var(--werbung-akzent, #d9cba8);
}
.werbung-poster-fortschritt strong,
.werbung-poster-kontakt strong {
  display: block;
  margin-top: calc(var(--s) * 4);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 22);
  color: var(--text);
}
.werbung-poster-tagline {
  font-family: var(--font-display);
  font-size: calc(var(--s) * 26);
  letter-spacing: 0.05em;
  text-align: center;
  color: var(--gold-bright);
}
.werbung-poster-logo {
  /* Größer + sanfter Glow (2026-08-14, Sven: "kann doch stehen 'Werde
     ein Teil von' und dann mittig darunter das Logo" — Logo trägt
     jetzt den Marken-Namen alleine, wirkt dafür etwas prominenter als
     vorher). */
  width: calc(var(--s) * 170);
  height: auto;
  filter: drop-shadow(0 0 16px rgba(109, 61, 255, 0.4));
}
.werbung-vorschau-buttons {
  display: flex;
  gap: calc(var(--s) * 12);
  flex-wrap: wrap;
}

/* Vorschau-Popup (2026-08-14, Sven: "Live-Vorschau wäre ein Popup-
   Fenster gut, nicht so wie jetzt" — das hohe Hochkant-Poster wirkte
   eingebettet in der schmalen Formular-Spalte gequetscht). Breiter als
   das Standard-.neuigkeit-popup (Poster braucht mehr Raum), max-height/
   overflow-y:auto erbt es unverändert von dort — wichtig, das Poster
   selbst kann je nach Bildhöhe deutlich höher als der Bildschirm sein. */
.werbung-vorschau-popup {
  max-width: calc(var(--s) * 560);
}
.werbung-vorschau-popup-inhalt .werbung-poster {
  margin-top: calc(var(--s) * 10);
}

/* Text-Kopieren-Popup — gleiche Optik wie .verwaltung-rollen-popup,
   nur mit einer schreibgeschützten Textarea statt einer Tabelle. */
.werbung-text-popup {
  max-width: calc(var(--s) * 640);
}
.werbung-text-textarea {
  width: 100%;
  box-sizing: border-box;
  min-height: calc(var(--s) * 260);
  font-family: var(--font-body);
  line-height: 1.6;
  resize: vertical;
}

/* "Willkommen zurück"-Popup (2026-08-19, siehe script.js) — gleiche
   Basis-Optik wie .werbung-text-popup/.verwaltung-rollen-popup, keine
   feste Breite nötig (kurzer Inhalt), erbt einfach die
   .neuigkeit-popup-Standardbreite. */
.willkommen-zurueck-einleitung {
  margin: 0 0 calc(var(--s) * 4);
  color: var(--text);
}
.willkommen-zurueck-rubrik {
  margin: calc(var(--s) * 14) 0 calc(var(--s) * 6);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 14);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gold);
}
.willkommen-zurueck-liste {
  margin: 0;
  padding-left: calc(var(--s) * 20);
  display: flex;
  flex-direction: column;
  gap: calc(var(--s) * 6);
  color: var(--text);
}

/* ---- Loot-Historie (2026-08-15, siehe ROADMAP.md "Loot") ----
   Session-Karten nutzen bewusst dasselbe .raidplan-termin-Aussehen
   (siehe dort) statt neuer Kartenoptik — Loot-Import-Box und
   Detail-Tabelle sind das einzige wirklich Neue hier. */
/* Kacheln statt Liste (2026-08-27, Sven: "könnte man die raids auch in
   kacheln (nebeneinander + fortlaufend) darstellen? ähnlich wie das
   2. bild") — gleiches Fließ-Kachel-Prinzip wie .news-card--karussell,
   feste Breite statt einer vollen Zeile pro Session, bricht auf
   schmalen Bildschirmen automatisch in mehrere Reihen um. */
.loot-session-liste {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--s) * 24);
}
.loot-session-karte {
  flex: 0 0 calc(var(--s) * 300);
  cursor: pointer;
  transition: border-color 0.15s ease, outline-color 0.15s ease;
}
.loot-session-karte:hover,
.loot-session-karte:focus-visible {
  border-color: var(--violet-glow);
  outline-color: rgba(200, 170, 78, 0.5);
}
/* Bild oben auf der Kachel, exakt dasselbe Muster wie
   .news-card--karussell .card-hero (automatisch befüllt über
   raid_bild aus loot_api.php/wow_raid_bild_pfad(), fehlt das Bild
   bleibt die Kachel einfach eine reine Text-Karte). */
.loot-session-karte .card-hero {
  margin: calc(var(--s) * -24) calc(var(--s) * -24) calc(var(--s) * 18);
  border-radius: var(--radius-card) var(--radius-card) 0 0;
  overflow: hidden;
}
.loot-session-karte .card-hero-img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}
/* Raidname in EINER Zeile statt umzubrechen (2026-08-27, Sven:
   "kann man den Raid nicht komplett in zeile 1 packen?") — kleinere
   Schrift/engeres Letter-Spacing NUR auf der Loot-Kachel (der
   allgemeine .raidplan-termin-kopf h4-Stil bleibt für Raidplan-Termine
   unangetastet), passt "Der Giftige Abgrund" bei 300px Kachelbreite
   auf eine Zeile. */
.loot-session-karte .raidplan-termin-kopf h4 {
  font-size: calc(var(--s) * 17);
  letter-spacing: 0.04em;
}
/* Schwierigkeit unter dem Titel, mittig + größer (2026-08-27, Sven:
   "darunter mittig die schwierigkeitstufe und etwas größer"). */
.loot-kachel-schwierigkeit {
  margin-top: calc(var(--s) * 4);
  text-align: center;
  font-size: calc(var(--s) * 17);
  color: var(--text-dim);
}
/* Popup statt Vollseiten-Drilldown (2026-08-27, Sven: "bei klick
   öffnet sich einfach ein popup fenster ... aber nicht so breit wie
   die seite") — nutzt die bereits bestehende breiteste Popup-Stufe
   (siehe :root --popup-w-breit), weil die Loot-Tabelle 5 Spalten hat
   und sonst zu eng würde. */
.loot-popup {
  max-width: var(--popup-w-breit);
}
.loot-fortschritt {
  color: var(--gold-bright);
  font-weight: 600;
}
.loot-dim {
  color: var(--text-dim);
}

/* Item-Name in der Loot-Tabelle mit Hover-Tooltip (2026-08-15, siehe
   ROADMAP.md "Item-Tooltips für die Loot-Seite") — nutzt dieselbe
   .charakter-popup-item/-tooltip-Mechanik wie das Charakter-Popup
   (siehe dortige Kommentare), hier aber ohne Icon-Rahmen/Padding, da es
   nur eine schmale Tabellenzelle statt einer eigenen Zeile ist.
   inline-flex statt dem geerbten flex, damit es sich wie normaler
   Zellen-Inhalt verhält statt Blockbreite einzunehmen. */
.loot-item-tooltip {
  display: flex;
  max-width: 100%;
  min-width: 0;
  padding: 0;
  border-radius: 0;
  font-size: inherit;
  cursor: default;
}
/* min-width:0 ALLEIN reichte nicht (2026-08-15, Sven: "auch der Item-
   Name bricht nicht") — es erlaubt nur das Schrumpfen, erzwingt es aber
   nicht: ein Flex-Kind ohne flex-grow nimmt standardmäßig einfach seine
   eigene Inhaltsbreite und überläuft munter weiter. flex:1 zwingt den
   Namen, sich tatsächlich an die verfügbare Breite der Zelle zu halten
   — erst DANN greifen overflow:hidden/text-overflow:ellipsis (schon in
   der Basis-Regel .charakter-popup-item-name vorhanden). */
.loot-item-tooltip .charakter-popup-item-name {
  flex: 1;
  min-width: 0;
}
/* Zweiter Überlauf-Fund (2026-08-15, Sven: "da ragt ALLES raus, selbst
   der grüne Text läuft bis Seitenende"): der Tooltip-Kasten selbst hat
   zwar `width: 300*s`, aber lange Absätze (z.B. der neue Proc-Text)
   brachen darin nicht mehr zuverlässig um, seit das Elternelement
   display:flex ist — Kasten-Rahmen blieb schmal, der Text lief optisch
   drüber hinaus. DevTools bestätigte max-width/overflow-wrap als
   korrekt berechnet auf dem Kasten selbst, trotzdem kein sichtbarer
   Effekt — daher jetzt zusätzlich DIREKT auf jedem <p> im Tooltip
   erzwungen (!important, damit nichts mehr dazwischenfunken kann), statt
   nur auf dem umschließenden Kasten. `overflow:hidden` auf dem äußeren
   Flex-Container (voriger Versuch) wieder entfernt — das kann bei
   position:fixed-Kindern (unser Tooltip-Kasten) zu browserabhängigem
   Clipping-Verhalten führen und war nicht nötig (Name hat sein eigenes
   overflow:hidden schon in der Basis-Regel). */
.loot-item-tooltip .charakter-popup-tooltip {
  max-width: calc(var(--s) * 300);
  box-sizing: border-box;
}
.loot-item-tooltip .charakter-popup-tooltip p {
  white-space: normal !important;
  overflow-wrap: break-word !important;
  word-break: break-word !important;
  max-width: 100% !important;
}

.loot-import-box {
  margin-bottom: calc(var(--s) * 24);
  padding: calc(var(--s) * 20) calc(var(--s) * 24);
  background: linear-gradient(var(--panel-grad-a), var(--panel-grad-b));
  border: 1px solid var(--violet-deep);
  outline: 1px solid rgba(200, 170, 78, 0.2);
  outline-offset: -6px;
  border-radius: var(--radius-card);
}
/* Klappbar statt fest offen (2026-08-23, Sven: "bitte das Loot-
   Import-Fenster klappbar machen und soll einfach geschlossen sein")
   — .loot-import-titel (reiner Text) wird zum .loot-import-klapp-btn
   (echter Button), gleiche Typografie wie vorher, zusätzlich mit
   Pfeil-Indikator, der sich beim Öffnen dreht. */
.loot-import-klapp-btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 0;
  background: none;
  border: none;
  font-family: var(--font-display);
  font-size: calc(var(--s) * 20);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--violet-glow);
  cursor: pointer;
  text-align: left;
}
.loot-import-klapp-btn::after {
  content: '\2039';
  transform: rotate(-90deg);
  font-size: calc(var(--s) * 24);
  transition: transform var(--duration-standard) var(--ease-soft);
}
.loot-import-klapp-btn[aria-expanded="true"]::after {
  transform: rotate(0deg);
}
.loot-import-inhalt {
  margin-top: calc(var(--s) * 14);
}
.loot-import-hinweis {
  color: rgba(232, 228, 248, 0.65);
  font-size: calc(var(--s) * 13);
  margin: 0 0 calc(var(--s) * 12);
}
.loot-import-hinweis code {
  color: var(--gold-bright);
}
#loot-import-payload {
  width: 100%;
  box-sizing: border-box;
  min-height: calc(var(--s) * 140);
  padding: calc(var(--s) * 12) calc(var(--s) * 14);
  background: var(--void);
  border: 1px solid var(--violet-deep);
  border-radius: var(--radius-card);
  font-family: var(--font-body);
  font-size: calc(var(--s) * 13);
  line-height: 1.5;
  color: var(--text);
  resize: vertical;
  margin-bottom: calc(var(--s) * 10);
  transition: border-color var(--duration-standard) var(--ease-soft),
              box-shadow var(--duration-standard) var(--ease-soft);
}
#loot-import-payload:focus {
  outline: none;
  border-color: var(--violet);
  box-shadow: var(--glow-violet-soft);
}
#loot-import-payload::placeholder {
  color: rgba(232, 228, 248, 0.35);
}
.loot-import-checkbox {
  display: flex;
  align-items: center;
  gap: calc(var(--s) * 6);
  font-size: calc(var(--s) * 13);
  margin-bottom: calc(var(--s) * 14);
  cursor: pointer;
  accent-color: var(--gold);
}

/* KEIN eigenes "position" hier (2026-08-15, endlich gefunden, Ursache
   des seit mehreren Runden gemeldeten Layout-Bugs "mittig, läuft nach
   rechts"): Dieses Element trägt GLEICHZEITIG die Klasse .roster
   (position:fixed, steuert die komplette Positionierung/Breite der
   Sektion). Eine eigene "position:relative"-Regel auf .loot-detail-
   ansicht (stand hier bis eben, gleiche Spezifität, aber später in der
   Datei) hat das fixed von .roster überschrieben und dadurch left/right
   komplett falsch berechnet - kein Bug in .roster selbst, sondern eine
   unnötige, konfliktverursachende Zeile. .loot-detail-fusszeile/-karte
   brauchen kein position:relative vom Elternelement. */
/* Unten statt oben (2026-08-15, Sven: "den Button 'Zur Übersicht' kann
   man bitte unten dann platzieren") — steht jetzt als eigenes Element
   NACH der Karte, nicht mehr davor. Echter Button-Stil statt Text-Link
   (2026-08-15, Sven: "bitte dem Button-Style anpassen") — 1:1 dieselbe
   Optik wie .konto-absenden (z.B. der "Importieren"-Button oben), nur
   schmaler (kürzeres Wort, kein ganzes Formular darunter). */
/* align-self:flex-start (2026-08-15, Sven: "Button ist zu groß, bitte
   an die Größen der anderen Buttons halten") — .roster ist
   display:flex;flex-direction:column OHNE align-items (Standard:
   stretch), direkte Flex-Kinder werden dadurch sonst auf volle Breite
   gezogen. .konto-absenden trifft das nie, weil der IMMER in einem
   <form>/Container steckt statt direktes .roster-Kind zu sein. */
.loot-detail-fusszeile {
  display: flex;
  gap: calc(var(--s) * 16);
  align-self: center; /* 2026-08-15, Sven: "den Button bitte mittig setzen" */
  justify-content: center;
}
.loot-zurueck-btn {
  box-sizing: border-box;
  height: calc(var(--s) * 54);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-top: calc(var(--s) * 20);
  padding: 0 calc(var(--s) * 32);
  background: linear-gradient(var(--panel-grad-a), var(--panel-grad-b));
  border: 1px solid var(--violet-deep);
  outline: 1px solid rgba(200, 170, 78, 0.35);
  outline-offset: calc(var(--s) * -5);
  border-radius: var(--radius-card);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 15);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gold-bright);
  cursor: pointer;
  transition: box-shadow var(--duration-standard) var(--ease-soft),
              border-color var(--duration-standard) var(--ease-soft);
}
.loot-zurueck-btn:hover {
  border-color: var(--violet);
  box-shadow: var(--glow-violet-strong);
}
/* "Session löschen" (2026-08-14, Sven: "den LunatiX Style hier bitte
   beachten") — bisher exakt dieselbe Optik wie "Zur Übersicht" (nur
   die zweite Klasse .loot-loeschen-btn, ohne eigene Regel), dadurch
   für eine unwiderrufliche Löschaktion nicht als solche erkennbar.
   Gleiche Rot-Behandlung wie überall sonst im Projekt bei löschenden
   Aktionen (siehe .verwaltung-btn-loeschen). */
.loot-loeschen-btn {
  color: #e0879a;
  border-color: rgba(179, 57, 76, 0.5);
}
.loot-loeschen-btn:hover:not(:disabled) {
  color: #fff;
  background: rgba(179, 57, 76, 0.35);
  border-color: #e0879a;
  box-shadow: var(--glow-rot-strong);
}
.loot-loeschen-btn:disabled {
  opacity: 0.5;
  cursor: default;
}
/* Titel+Meta+Tabelle in einer Karte (2026-08-15, Sven: "viel zu klein
   zum Lesen und sieht auch nicht schön aus") — dieselbe Kartenoptik wie
   die Übersicht (siehe .raidplan-termin), sorgt für genug Kontrast über
   dem hellen Himmel-Hintergrundbild und deutlich großzügigere Maße.
   width/box-sizing explizit gesetzt (2026-08-15, Sven per Screenshot:
   Karte blieb schmal statt die volle .roster-Breite zu füllen wie die
   Übersichts-Kacheln) — erzwingt die volle Breite unabhängig davon, ob
   der umgebende Flex-Kontext von sich aus stretcht. */
/* .loot-detail-karte entfernt (2026-08-27) — die Detailansicht ist
   jetzt ein Popup (.loot-popup), das schon von .neuigkeit-popup sein
   eigenes Padding erbt, keine eigene Karte mehr nötig. */
.loot-detail-meta {
  margin-bottom: calc(var(--s) * 22);
}
/* table-layout:fixed + feste Spaltenbreiten (2026-08-15, Sven: Spalten
   "verschwinden rechts irgendwo" — Ursache: bei table-layout:auto (Browser-
   Standard) verteilt sich freier Platz bei kurzen Zellinhalten (z.B.
   "Bedarf", "95") unvorhersehbar, GEWINNER/TYP/WURF rutschten dadurch
   weit nach rechts raus statt gleichmäßig zu verteilen. */
.loot-tabelle {
  width: 100%;
  table-layout: fixed;
  border-collapse: collapse;
  /* 16->17 (2026-08-22, Sven per Screenshot: die Loot-Tabelle wirkte
     im Verhältnis zum viel Leerraum drumherum am kleinsten von allen
     Stellen auf der Seite). */
  font-size: calc(var(--s) * 17);
}
.loot-tabelle col.loot-col-zeit    { width: 10%; }
.loot-tabelle col.loot-col-item    { width: 42%; }
.loot-tabelle col.loot-col-gewinner { width: 20%; }
.loot-tabelle col.loot-col-typ     { width: 16%; }
.loot-tabelle col.loot-col-wurf    { width: 12%; }
.loot-tabelle td,
.loot-tabelle th {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.loot-tabelle th {
  text-align: left;
  color: var(--gold);
  font-family: var(--font-display);
  /* 13->14, siehe Begründung bei .loot-tabelle oben. */
  font-size: calc(var(--s) * 14);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: calc(var(--s) * 10) calc(var(--s) * 12);
  border-bottom: 1px solid var(--violet-deep);
}
.loot-tabelle td {
  padding: calc(var(--s) * 10) calc(var(--s) * 12);
  border-bottom: 1px solid rgba(30, 22, 48, 0.6);
}
.loot-boss-zeile td {
  padding-top: calc(var(--s) * 20);
  color: var(--gold-bright);
  font-family: var(--font-display);
  font-size: calc(var(--s) * 17);
  letter-spacing: 0.03em;
  font-weight: 400;
}
.loot-boss-status {
  font-family: var(--font-body);
  font-weight: 400;
  color: var(--text-dim);
  font-size: calc(var(--s) * 14);
  letter-spacing: normal;
  text-transform: none;
}

/* Loot-Import-Freischaltung in der Mitglieder-Verwaltung (2026-08-15) —
   kleines Häkchen neben den übrigen Aktions-Buttons, nur bei
   guest/member sichtbar (siehe verwaltungHtmlBauen() in script.js). */
.verwaltung-loot-checkbox {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--s) * 4);
  font-size: calc(var(--s) * 12);
  color: rgba(232, 228, 248, 0.75);
  cursor: pointer;
  accent-color: var(--gold);
}
