/* ═══════════════════════════════════════════════════════════════════════
   pn-ui.css — die Rückmeldungs-Ebene
   ═══════════════════════════════════════════════════════════════════════

   Die EINZIGE Stildatei, die BEIDE Shells laden: die öffentliche Seite
   (`pn_oe_styles()`) und den Hub (`pn_hub_styles()`). Darin steht, was an
   beiden Orten gleich aussehen und gleich funktionieren muss:

     Toast · Klapp-Übergang · Button-Ladezustand · Live-Blitz · Tabellenzeilen

   ⚠ WARUM EINE EIGENE EBENE `reaktion`
     Die beiden Systeme haben getrennte Ebenenordnungen (`oe-*` draußen,
     `token…contentarea` drinnen). `reaktion` steht in BEIDEN Ordnungen als
     LETZTE — eine Rückmeldung muss den Bestand überschreiben dürfen, ohne
     spezifischer zu sein. Sie ist in beiden Token-Dateien ausdrücklich
     deklariert; sie hängt nicht stillschweigend hinten dran.

   ⚠ WARUM FARBEN ALS RÜCKFALLKETTE
     Die Token heißen in beiden Systemen anders: drinnen `--flaeche`,
     `--linie`, `--akzent`, draußen `--surface`, `--line`, `--red`. Eine
     geteilte Datei kann sich auf keinen der beiden Namen verlassen. Darum
     `var(--flaeche-2, var(--surface-2, #1C1A23))` — erst der Hub-Name,
     dann der öffentliche, dann der Wert selbst. Seit der Farbangleichung
     (31.10.2026) ergeben alle drei Stufen dasselbe Schwarz.

   ⚠ WARUM DIE ALIASSE IN `:root` SICHER SIND
     Ein Alias wird dort aufgelöst, wo er steht. Das ist hier richtig:
     `.th-light` und `.th-dark` sitzen am WURZELELEMENT, also an derselben
     Stelle wie `:root`. Die Klasse gewinnt über die Spezifität, und der
     Alias nimmt den Gewinner mit. Würde eine Theme-Klasse an einem
     Nachfahren hängen, wäre das falsch — tut sie aber nicht.

   ⚠ KEIN GRÜN IM TOAST
     Der öffentliche Guide verbietet Fremdfarben ausdrücklich („kein
     Neon, kein Twitch-Lila, kein Discord-Blurple, kein Grün"). Der Hub
     hat ein grünes `--ok-500`, die öffentliche Seite nicht. Weil der
     Toast an beiden Orten derselbe Baustein ist, trägt er die Bedeutung
     im ICON und in der Kante, nicht in der Farbe: Erfolg neutral mit
     Haken, Fehler mit roter Kante und Warnzeichen. Das ist auch die
     S/N-Regel des Guides — Farbe nie als einziger Informationsträger.
   ═══════════════════════════════════════════════════════════════════════ */

@layer reaktion {

  :root {
    --ui-flaeche:  var(--flaeche-2, var(--surface-2, #1C1A23));
    --ui-grund:    var(--flaeche, var(--surface, #131218));
    --ui-linie:    var(--linie-stark, var(--line, #2C2935));
    --ui-akzent:   var(--akzent, var(--red, #E0093C));
    --ui-text:     var(--text, #E6E6E6);
    --ui-text-2:   var(--text-leise, var(--text-2, #B9B5C2));
    --ui-radius:   var(--r-m, 6px);
    /* Zeilen-Tönung: im Hub gibt es `--hover`, draußen nicht. */
    --ui-hover:    var(--hover, rgba(230, 230, 230, .05));
    --ui-kurve:    var(--kurve, cubic-bezier(.22, 1, .36, 1));
    /* 220 ms für Panels, 160 ms für kleine Elemente — Kapitel 3.1 der
       Designsystem-Doku. Kein Wert über 600 ms in dieser Datei. */
    --ui-panel:    .22s;
    --ui-klein:    .16s;
  }

  /* ⚠ Die öffentliche Seite hat KEIN `--hover`. Ohne diese Zeile wäre
     die Zeilen-Tönung im hellen Theme draußen eine helle Fläche auf
     Weiß — also nicht zu sehen. Im Hub greift dagegen das dortige
     `.th-light { --hover: … }` und gewinnt über die Rückfallkette. */
  .th-light { --ui-hover: var(--hover, rgba(26, 24, 34, .045)); }

  /* ───────────────────────── 1 · Toast ─────────────────────────────────

     Kommt von unten, bleibt 5 s, geht — so steht es in Kapitel 3.2 der
     Designsystem-Doku, und so ist es gebaut.

     ⚠ Der Toast ist für Rückmeldungen AUSSERHALB des Blickfelds gedacht:
       Hintergrundaufgaben, Live-Änderungen, Kopieren in die Ablage. Das
       Speichern eines Formulars bleibt am Ort der Handlung (Button wird
       kurz „Gespeichert", Band oben im Hub) — eine Fehlermeldung zu
       einem Feld gehört an das Feld, nicht an den unteren Bildschirmrand.

     ⚠ `pointer-events` steht auf der Region auf `none` und nur auf dem
       Toast selbst auf `auto`. Sonst liegt ein unsichtbarer Streifen über
       dem unteren Seitenrand und fängt Klicks auf Links ab. */

  .pn-toasts {
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    /* Über allem: der höchste Wert im Bestand ist 1100 (Modal). */
    z-index: 1200;
    display: grid;
    justify-items: center;
    align-content: end;
    gap: 8px;
    padding: 16px;
    pointer-events: none;
  }

  .pn-toast {
    pointer-events: auto;
    display: flex;
    align-items: flex-start;
    gap: 10px;
    width: 100%;
    max-width: 420px;
    padding: 12px 14px;
    background: var(--ui-flaeche);
    color: var(--ui-text);
    border: 1px solid var(--ui-linie);
    border-left: 3px solid var(--ui-linie);
    border-radius: var(--ui-radius);
    font-size: 14px;
    line-height: 1.45;
    box-shadow: 0 8px 24px rgba(0, 0, 0, .45);
    animation: pn-toast-auf var(--ui-panel) var(--ui-kurve) both;
  }

  .pn-toast.err { border-left-color: var(--ui-akzent); }
  .pn-toast.geht { animation: pn-toast-weg var(--ui-klein) ease-in both; }

  .pn-toast > svg,
  .pn-toast > .pn-toast-zeichen {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    margin-top: 1px;
  }

  .pn-toast > .pn-toast-text { flex: 1 1 auto; align-self: center; }

  /* Der Aktionsknopf („Aktualisieren") — nur an Toasts, die eine
     Entscheidung verlangen. Er ist rot, weil er das Einzige ist, was hier
     etwas tut; der Toast selbst bleibt neutral. */
  .pn-toast-aktion {
    flex: 0 0 auto;
    align-self: center;
    padding: 5px 10px;
    background: var(--ui-akzent);
    color: #fff;
    border: 0;
    border-radius: var(--r-s, 3px);
    font: inherit;
    font-weight: 600;
    font-size: 13px;
    cursor: pointer;
  }
  .pn-toast-aktion:hover { filter: brightness(1.07); }

  /* Schließen: echter Button, 44px Ziel über das Padding. */
  .pn-toast-zu {
    flex: 0 0 auto;
    margin: -8px -6px -8px 0;
    padding: 8px 6px;
    background: none;
    border: 0;
    color: var(--ui-text-2);
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
  }
  .pn-toast-zu:hover { color: var(--ui-text); }

  @keyframes pn-toast-auf {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: none; }
  }
  @keyframes pn-toast-weg {
    from { opacity: 1; transform: none; }
    to   { opacity: 0; transform: translateY(8px); }
  }

  @media (max-width: 640px) {
    /* Auf dem Handy über die ganze Breite — 420px wären dort ein Rand
       von je 6px, und das sieht nach Versehen aus. */
    .pn-toast { max-width: none; }
  }

  /* ──────────────────── 2 · Klapp-Übergang ─────────────────────────────

     Gilt für JEDES `<details>` im Projekt (zwölf Stellen: Bewerbungen,
     Werbung, Anmeldungen, Markt, News, Contentplan, Assets, Match,
     Team-Squad, Crossfire-Match, Layout, und das Klappmenü der mobilen
     Navigation). Kein Markup ändert sich dafür.

     Zwei Wege, weil kein einzelner überall trägt:

       a) `::details-content` + `interpolate-size` — der richtige Weg,
          reines CSS, aber erst in Chrome 131+ und Safari 18.4+.
       b) `.pn-klapp-auf/-zu` — dieselbe Bewegung über `max-height`,
          gesetzt von `pn-ui.js`, wenn (a) fehlt (heute: Firefox).

     ⚠ `height`/`max-height` zu animieren widerspricht der Regel
       „animiert werden nur transform und opacity". Das ist hier bewusst:
       Ein Klappbereich MUSS seine Höhe ändern, sonst springt der Inhalt
       darunter. Die Regel zielt auf Dauerbewegung — hier läuft 220 ms
       einmal, ausgelöst durch einen Klick. */

  @supports (interpolate-size: allow-keywords) {
    /* ⚠ NICHT auf `:root`. `interpolate-size` wird geerbt und würde
       jeden `height: auto`-Übergang im ganzen Projekt animierbar
       machen — eine Nebenwirkung, die niemand sucht, wenn er später
       irgendwo eine Höhe anfasst. Hier braucht es nur `details`. */
    details { interpolate-size: allow-keywords; }

    details::details-content {
      height: 0;
      overflow: hidden;
      transition: height var(--ui-panel) var(--ui-kurve),
                  content-visibility var(--ui-panel) allow-discrete;
    }
    details[open]::details-content { height: auto; }
  }

  /* Rückfall (b): `pn-ui.js` setzt die Klasse und die Zielhöhe. */
  .pn-klapp-lauf {
    overflow: hidden;
    transition: max-height var(--ui-panel) var(--ui-kurve);
  }

  /* ⚠ Hier stand eine Regel fuer ein eigenes Klapp-Zeichen
     (`.pn-klapp-pfeil`). Sie ist entfernt: Keines der vierzehn `<summary>`
     im Projekt hat ein eigenes Zeichen, alle nutzen den `::marker` des
     Browsers — und der dreht sich von selbst. Eine Regel ohne Markup
     dahinter ist toter Stil, und davon hatte `04-besonderes.css` genug. */

  /* ──────────────────── 3 · Button-Ladezustand ─────────────────────────

     Der Hub-Guide beschreibt es wörtlich: Spinner statt Icon, gesperrt.
     Hier steht nur das Aussehen — wann es greift, entscheidet
     `pn-ui.js` beim Absenden.

     ⚠ Die Breite wird vorher festgehalten (inline `min-width` aus
       `offsetWidth`). Ohne das schrumpft der Button in dem Moment, in
       dem man ihn getroffen hat, und das fühlt sich wie ein Fehlklick an.

     ⚠ Der Button wird `disabled`, nicht nur optisch gesperrt. Das ist
       der eigentliche Gewinn: doppeltes Absenden hat im Hub bisher
       doppelte Datensätze erzeugt. */

  .pn-laedt { cursor: progress; }
  .pn-laedt[disabled] { opacity: .8; }

  .pn-spinner {
    display: inline-block;
    flex: 0 0 auto;
    width: 14px;
    height: 14px;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: pn-dreh .7s linear infinite;
  }
  @keyframes pn-dreh { to { transform: rotate(360deg); } }

  /* Nach der Antwort: 2 s Bestätigung am Ort der Handlung. */
  .pn-fertig { animation: pn-fertig-puls .5s var(--ui-kurve); }
  @keyframes pn-fertig-puls {
    0%   { transform: scale(1); }
    40%  { transform: scale(1.04); }
    100% { transform: scale(1); }
  }

  /* ──────────────────── 4 · Live-Blitz ─────────────────────────────────

     „Live-Score: kurzes Aufleuchten bei Änderung (nicht der Zahlensprung
     selbst)" — Kapitel 3.2. Greift an zwei Stellen:

       · nach der EIGENEN Änderung: der Server merkt sich die geänderte
         Zeile, die Seite blitzt sie nach dem Neuladen auf
       · bei fremden Änderungen dort, wo `live.js` schon läuft
         (Match-Seite, Tabelle)

     ⚠ KEIN `animation-fill-mode`. Die Animation endet bei `transparent`,
       und danach gilt wieder, was die Zeile selbst als Hintergrund hat.
       Mit `both` würde `transparent` festgehalten — und eine markierte
       Zeile (eigenes Team, Playoff-Platz) hätte ihre Fläche für immer
       verloren. */

  .pn-blitz { animation: pn-blitz .9s ease-out; }

  @keyframes pn-blitz {
    0%, 12% { background-color: var(--ui-akzent); }
    100%    { background-color: transparent; }
  }

  /* Auf einer Tabellenzeile tönt die Zelle, nicht die Zeile: `tr` trägt
     in manchen Engines keinen eigenen Hintergrund über die Breite. */
  tr.pn-blitz { animation: none; }
  tr.pn-blitz > * { animation: pn-blitz-zelle .9s ease-out; }

  @keyframes pn-blitz-zelle {
    0%, 12% { background-color: color-mix(in srgb, var(--ui-akzent) 28%, transparent); }
    100%    { background-color: transparent; }
  }

  /* ──────────────────── 5 · Tabellenzeilen ─────────────────────────────

     Jede echte Tabelle bekommt dieselbe Zeileninteraktion: Fläche tönt,
     rote Kante links, Tastaturfokus sieht aus wie Hover.

     Die fünf Klassen sind NACHGESEHEN, nicht geraten:

       `.liga-tabelle`    Division (öffentlich) und Startseiten-Abschnitt
       `.pn-tabelle`      Tabelle im Crossfire-Hub
       `.pn-maptab`       Map-Tabelle im Casterhub
       `.sp-tabelle`      Bestenlisten der Spielmodule
       `.standings-table` das alte CRM (41 Seiten, bleiben bis Phase 9)

     ⚠ `.raster` ist mit Absicht NICHT dabei. Das sind die beiden
       Kalendertabellen — dort ist eine Zeile eine WOCHE, kein
       Datensatz. Eine ganze Woche hervorzuheben, weil die Maus
       irgendwo darin steht, beantwortet keine Frage.

     ⚠ Die Kante sitzt als `inset box-shadow` auf der ERSTEN ZELLE, nicht
       auf der Zeile. Ein `box-shadow` auf `tr` wird nicht überall
       gezeichnet; auf `td` schon.

     ⚠ `:focus-within` ist mitgenommen: Wer sich mit der Tastatur durch
       die Teamlinks bewegt, soll dieselbe Zeile hervorgehoben sehen wie
       mit der Maus. Ohne das springt der Fokusring durch eine Tabelle,
       in der sonst nichts passiert. */

  :where(.liga-tabelle, .pn-tabelle, .pn-maptab, .sp-tabelle, .standings-table) tbody tr {
    transition: background-color var(--ui-klein) ease-out;
  }
  :where(.liga-tabelle, .pn-tabelle, .pn-maptab, .sp-tabelle, .standings-table) tbody tr > *:first-child {
    box-shadow: inset 2px 0 0 transparent;
    transition: box-shadow var(--ui-klein) ease-out;
  }
  /* ⚠ `.liga-tabelle` fehlt hier mit Absicht. `oe-liga.css:66` gibt ihr
     schon einen Hover: `background: var(--flaeche-2)` — eine ganze
     Flaeche statt einer Toenung, und das ist die Entscheidung aus dem
     Rebrand der oeffentlichen Seite. `reaktion` liegt spaeter in der
     Ordnung und wuerde sie ueberschreiben: Eine geteilte Datei haette
     dann eine gestaltete Entscheidung durch eine allgemeine ersetzt,
     ohne dass jemand danach gefragt hat.
     Was dieser Datei bleibt, ist das, was dort FEHLT: die rote Kante
     und der Gleichstand von Tastatur und Maus (unten). */
  :where(.pn-tabelle, .pn-maptab, .sp-tabelle, .standings-table) tbody tr:hover,
  :where(.pn-tabelle, .pn-maptab, .sp-tabelle, .standings-table) tbody tr:focus-within {
    background-color: var(--ui-hover);
  }

  /* Tastatur und Maus zeigen dasselbe — auch in der Liga-Tabelle, die
     ihre eigene Hover-Flaeche mitbringt. Ohne das springt der Fokusring
     durch eine Tabelle, in der sich sonst nichts ruehrt. */
  .liga-tabelle tbody tr:focus-within {
    /* Derselbe Wert wie der Hover in `oe-liga.css:66` — aber mit
       Rueckfall. `--flaeche-2` gibt es nur draussen, und `.liga-tabelle`
       kommt auch nur dort vor; in einer GETEILTEN Datei muss man das
       trotzdem sehen koennen, ohne es zu wissen. */
    background: var(--flaeche-2, var(--surface-2, #1C1A23));
  }
  :where(.liga-tabelle, .pn-tabelle, .pn-maptab, .sp-tabelle, .standings-table) tbody tr:hover > *:first-child,
  :where(.liga-tabelle, .pn-tabelle, .pn-maptab, .sp-tabelle, .standings-table) tbody tr:focus-within > *:first-child {
    box-shadow: inset 2px 0 0 var(--ui-akzent);
  }
  /* Leerzustand ist keine Zeile, mit der man etwas tun kann. */
  :where(.pn-tabelle, .pn-maptab, .sp-tabelle, .standings-table) tbody tr:has(> .empty):hover {
    background-color: transparent;
  }
  /* Wo `pn-ui.js` die ganze Zeile anklickbar gemacht hat. */
  tr.pn-zeile-klick { cursor: pointer; }

  /* ──────────────── 6 · Umlaufender Rahmen ─────────────────────────────

     Der einzige Ort, an dem im Hub etwas von allein läuft — neben dem
     pulsierenden Punkt an der Glocke, den der Hub-Design-Guide als
     einzige Dauerbewegung erlaubt.

     ⚠ Das ist eine bewusste, abgestimmte Abweichung (31.10.2026): Der
       Rahmen läuft NUR um etwas, das wirklich gerade läuft. Er ist kein
       Schmuck, sondern dieselbe Aussage wie der „Live"-Chip daneben —
       nur sichtbar aus dem Augenwinkel. Wo nichts läuft, läuft er nicht.

       Vergeben wird er an genau drei Stellen:
         `.liga-match.live`   ein Match mit `cast_live = 1` ohne Ergebnis
         `.k-live.jetzt`      der Stream, wenn wirklich gesendet wird
         `[data-live-huelle]` sobald ein Countdown bei null ankommt

     ⚠ Masken-Technik statt `z-index: -1`. Ein Pseudo-Element hinter dem
       Element wäre hinter dessen eigener Fläche — also unsichtbar, sobald
       die Fläche deckend ist. Hier liegt der Verlauf DAVOR, und die Maske
       schneidet alles bis auf den Ring heraus. `pointer-events: none`,
       damit der Klick weiter durchgeht.

     ⚠ Ohne `@property` lässt sich ein Winkel nicht animieren — dann steht
       der Ring still, bleibt aber SICHTBAR. „Hier läuft etwas" ist eine
       Information; sie darf nicht an der Bewegung hängen. Dasselbe gilt
       bei `prefers-reduced-motion` weiter unten. */

  @property --pn-winkel {
    syntax: '<angle>';
    inherits: false;
    initial-value: 0deg;
  }

  .pn-umlauf { position: relative; }

  .pn-umlauf::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 2;
    padding: 1.5px;
    border-radius: inherit;
    pointer-events: none;
    background: conic-gradient(from var(--pn-winkel),
                  transparent 0turn,
                  transparent .58turn,
                  var(--ui-akzent) .78turn,
                  transparent .95turn,
                  transparent 1turn);
    /* Nur der Ring bleibt stehen: Füllung ausstanzen. */
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
            mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
            mask-composite: exclude;
    animation: pn-umlauf 2.6s linear infinite;
  }

  @keyframes pn-umlauf { to { --pn-winkel: 360deg; } }

  /* Ohne die Maske stanzt nichts aus — dann wäre die ganze Fläche
     überzogen. Lieber ein ruhiger Rahmen als ein roter Schleier. */
  @supports not (mask-composite: exclude) {
    .pn-umlauf::after {
      background: none;
      border: 1.5px solid var(--ui-akzent);
      padding: 0;
    }
  }

  /* ──────────────── PFLICHT: prefers-reduced-motion ───────────────────

     Alles, was diese Datei bewegt, hört hier auf — und bleibt dabei
     vollständig sichtbar. Der Toast erscheint ohne Anflug, der
     Klappbereich springt auf, der Blitz entfällt (die Änderung steht
     trotzdem in der Zeile), der Spinner steht still.

     ⚠ Der Spinner bleibt SICHTBAR, nur ohne Drehung — er ist das
       einzige Zeichen dafür, dass etwas läuft. Ihn abzuschalten hieße,
       die Rückmeldung abzuschalten. */

  @media (prefers-reduced-motion: reduce) {
    .pn-toast,
    .pn-toast.geht,
    .pn-fertig,
    .pn-blitz,
    tr.pn-blitz > *,
    .pn-spinner,
    .pn-umlauf::after {
      animation: none !important;
    }
    .pn-toast { opacity: 1; transform: none; }
    /* ⚠ Der Ring bleibt SICHTBAR, nur ohne Umlauf — sonst verschwindet
       mit der Bewegung auch die Aussage „hier läuft gerade etwas".
       Derselbe Grund wie beim Spinner.
       Nicht bloss angehalten: Ein stehengebliebener Verlauf zeigt einen
       Bogen an einer willkürlichen Stelle und sieht nach Fehler aus.
       Stattdessen derselbe ruhige Rahmen wie ohne Maskenunterstützung. */
    .pn-umlauf::after {
      background: none;
      border: 1.5px solid var(--ui-akzent);
      padding: 0;
    }
    details::details-content,
    .pn-klapp-lauf,
    :where(.liga-tabelle, .pn-tabelle, .pn-maptab, .sp-tabelle, .standings-table) tbody tr,
    :where(.liga-tabelle, .pn-tabelle, .pn-maptab, .sp-tabelle, .standings-table) tbody tr > *:first-child {
      transition: none !important;
    }
  }
}
