/* ==========================================================================
   Ergänzende Stile für die neue Mehrstandort-App (Landing-Page,
   Lade-Ansicht) — nutzt dieselben Design-Tokens (Farben, Radius, Schatten)
   wie die aus dem bisherigen Tool übernommene style.css, damit die neue
   App optisch nahtlos zum bisherigen Design passt.
   ========================================================================== */

#app-root {
  /* Bugfix (Ausbaustufe 55): vorher nur "min-height:100dvh" -- das setzt zwar
     eine UNTERGRENZE, deckelt #app-root aber NICHT auf die Bildschirmhöhe,
     wenn der Inhalt (z.B. die "Top Spieler"-Folie mit allen drei Spiel-
     Boards untereinander auf einem schmalen Hochformat-Handy) mehr Platz
     braucht als ein Viewport hoch ist. In dem Fall wächst #app-root/.tv-stage
     einfach ungebremst über die Bildschirmhöhe hinaus (kein Höhenlimit, an
     dem .tv-scroll's "overflow-y:auto" je hätte greifen können), und body
     (overflow:hidden, siehe style.css) schneidet den Überschuss kommentarlos
     ab -- ohne jede Möglichkeit zu scrollen. Genau das wurde live gemeldet
     ("Top Spieler"-Folie unten abgeschnitten, kein Scrollen möglich).
     "height:100%" (statt min-height) macht #app-root stattdessen exakt so
     hoch wie sein Elternelement body -- das ist 100dvh (fest, scrollbar
     NICHT nötig) im normalen TV-/Gast-/Team-Modus, ODER "auto" (unbegrenzt,
     body scrollt selbst) im Mitarbeiterbereich (body.staff-scroll setzt
     body auf height:auto -- eine Prozentangabe gegen ein Elternelement ohne
     feste Höhe wird dann automatisch zu "auto", siehe CSS-Spezifikation).
     min-height:100dvh bleibt zusätzlich bestehen, rein für den
     Mitarbeiterbereich bei SEHR wenig Inhalt (z.B. leere Team-Liste) --
     damit die Seite dort weiterhin mindestens bildschirmfüllend bleibt. */
  height: 100%;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

.muted { color: var(--ink-dim); }
.error { color: var(--danger); font-weight: 600; }
.footnote { margin-top: 24px; }
.footnote a, .landing a { color: var(--accent-2); text-decoration: none; }
.footnote a:hover, .landing a:hover { text-decoration: underline; }

/* Nachtrag (20.09.2026, Nutzerwunsch): identischer "Zurück"-Link wie
   .footnote, aber oben auf den Mitarbeiterseiten (staff.js/roster.js/
   scores.js) -- bei einer langen Team-/Verlaufs-Liste musste man bisher
   immer erst ganz nach unten scrollen, um zurückzukommen. Der bisherige
   Link unten (.footnote) bleibt zusätzlich bestehen. */
.staff-top-back { padding: 14px 24px 0; }
.staff-top-back a {
  color: var(--accent-2);
  text-decoration: none;
  font-size: 14px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.staff-top-back a:hover { text-decoration: underline; }

/* Nachtrag (20.09.2026, Nutzerwunsch "Hinweis bei leerem neuen Spielerfeld"):
   dritte, NEUTRALE Variante von .name-dupe-hint (style.css hat bisher nur
   .show-error/.show-ok) -- für ein leeres Kader-Namensfeld, das beim
   Speichern einfach entfällt (siehe checkRosterDraftLive() in js/roster.js).
   Bewusst gedämpfte Info-Farbe statt Rot/Grün, damit ein leeres, frisch per
   "+ Spieler hinzufügen" angelegtes Feld nicht wie ein Fehler wirkt. */
.name-dupe-hint.show-info { display: flex; color: var(--ink-dim); }

.loading-screen {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: 10px;
  text-align: center;
  padding: 24px;
}

.landing {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 28px;
  padding: 32px 16px;
  text-align: center;
}

.landing h1 {
  font-size: clamp(22px, 5vw, 34px);
  margin: 0;
  letter-spacing: .04em;
  text-transform: uppercase;
}

.location-tiles {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  justify-content: center;
  max-width: 640px;
}

.location-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  min-width: 160px;
  padding: 24px 20px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--panel-grad);
  box-shadow: var(--shadow-card);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
  transition: transform .15s ease, box-shadow .15s ease;
}
.location-tile:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-accent);
  border-color: var(--accent);
}
.tile-icon { font-size: 28px; }
.tile-name { font-weight: 700; letter-spacing: .02em; }

.staff-head { padding: 20px 24px 8px; }
.staff-head h1 { margin: 0 0 4px; font-size: clamp(20px, 4vw, 30px); }

/* ---------------------------------------------------------------------- *
 * Login-Overlay (Mitarbeiter-Zugang) + dezenter Zugangspunkt
 * ---------------------------------------------------------------------- */
.staff-escape-btn {
  position: fixed;
  right: 14px;
  bottom: 14px;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--ink-dim);
  opacity: .35;
  cursor: pointer;
  font-size: 14px;
  transition: opacity .15s ease;
}
.staff-escape-btn:hover { opacity: 1; }

.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.6);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 900;
  padding: 16px;
}
.modal-overlay.active { display: flex; }

.auth-box {
  width: 100%;
  max-width: 360px;
  background: var(--panel-grad);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.auth-box h3 { margin: 0 0 4px; }
.auth-box input[type="email"],
.auth-box input.auth-code-input {
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--ink);
  font: inherit;
}
/* Zahlencode aus der Anmelde-Mail (siehe js/auth.js, verifyOtp): groß und
   gesperrt gesetzt, damit die Ziffern auf dem iPad gut lesbar/abtippbar sind. */
.auth-box input.auth-code-input {
  font-size: 1.5rem;
  letter-spacing: 0.35em;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.auth-code-section {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 6px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}

/* ---------------------------------------------------------------------- *
 * Großer QR-Code für Gäste (#guestQrOverlay, Button im Mitarbeiterbereich,
 * siehe js/staff.js openGuestQrPanel()) -- zum Zeigen/Aushängen. Selbe
 * Bildsprache wie das kleinere .tv-qr-widget oben, nur größer/zentriert
 * statt fest in einer Ecke.
 * ---------------------------------------------------------------------- */
.guest-qr-box {
  width: 100%;
  max-width: 400px;
  background: var(--panel-grad);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  padding: 36px 32px 28px;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 4px;
  animation: riseIn .3s ease both;
}
.guest-qr-close {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--ink-dim);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
}
.guest-qr-close:hover { color: var(--ink); border-color: var(--accent); }
.guest-qr-title {
  font-family: 'Space Grotesk', sans-serif;
  font-weight: 800;
  font-size: 21px;
  letter-spacing: 2px;
  color: var(--ink);
  margin-top: 6px;
}
.guest-qr-title .accent { color: var(--accent-2); }
.guest-qr-sub { font-size: 13px; color: var(--ink-dim); margin: 4px 0 22px; }
.guest-qr-sub b { color: var(--gold); }
.guest-qr-code {
  width: 240px;
  height: 240px;
  border-radius: var(--radius-sm);
  overflow: hidden;
  box-shadow: 0 0 0 9px #fff, var(--shadow-card);
  margin-bottom: 18px;
}
.guest-qr-code svg { display: block; width: 100%; height: 100%; }
.guest-qr-url {
  font-family: 'Space Grotesk', sans-serif;
  font-weight: 600;
  font-size: 12.5px;
  color: var(--ink);
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 7px 16px;
  letter-spacing: .2px;
}
.guest-qr-foot {
  margin-top: 16px;
  font-size: 11.5px;
  color: var(--ink-dim);
  display: flex;
  align-items: center;
  gap: 7px;
}
@media (max-width: 480px) {
  .guest-qr-code { width: 200px; height: 200px; }
}

/* ---------------------------------------------------------------------- *
 * Mitarbeiterbereich
 * ---------------------------------------------------------------------- */
.staff-panel {
  margin: 0 24px 20px;
  padding: 18px 20px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--panel);
}
.staff-panel h3 { margin: 0 0 12px; }

.staff-form-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
}
.staff-form-row input[type="text"], .staff-form-row input[type="number"], .staff-form-row select {
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--ink);
  font: inherit;
}
.staff-form-row input[type="text"] { flex: 1; min-width: 160px; }
.staff-form-row input[type="number"] { width: 120px; }

.staff-team-list { display: flex; flex-direction: column; gap: 8px; }
.staff-team-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--panel-2);
}
.staff-team-row .name { flex: 1; font-weight: 700; }

/* ---------------------------------------------------------------------- *
 * Verlauf (Score-Historie)
 * ---------------------------------------------------------------------- */
/* Feste Spalten statt frei umbrechender Flex-Reihe: jede Zeile nutzt exakt
   dasselbe Raster, dadurch stehen Spiel, Markierungen, Score und Zeitpunkt
   in allen Einträgen sauber untereinander, und die Buttons sitzen immer in
   einer eigenen zweiten Zeile (vorher je nach Textlänge mal rechts neben
   der Zeit, mal darunter -- siehe js/scores.js renderHistoryRows()). */
/* Zeile 1: Team | Spiel | Ø-Score | Zeitpunkt
   Zeile 2: Markierungen (unter dem Team) | Buttons (rechtsbündig unter der Zeit)
   Spaltenbreiten auf das Mitarbeiter-iPad im Hochformat (810px) abgestimmt:
   dort bleiben dem Teamnamen ~240px, lange Namen brechen nur an
   Wortgrenzen um. */
.staff-team-row.history-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 70px 165px 178px;
  grid-template-areas:
    "name game score time"
    "tags actions actions actions";
  column-gap: 12px;
  row-gap: 8px;
  align-items: center;
}
.staff-team-row.history-row .h-name  { grid-area: name; flex: none; overflow-wrap: break-word; min-width: 0; }
.staff-team-row.history-row .h-game  { grid-area: game; }
.staff-team-row.history-row .h-tags  { grid-area: tags; display: flex; flex-wrap: wrap; gap: 6px; align-self: center; }
.staff-team-row.history-row .h-score { grid-area: score; white-space: nowrap; font-variant-numeric: tabular-nums; }
.staff-team-row.history-row .h-players { font-size: .85em; opacity: .8; }
.staff-team-row.history-row .h-time  { grid-area: time; white-space: nowrap; text-align: right; font-variant-numeric: tabular-nums; }
.staff-team-row.history-row .h-actions { grid-area: actions; display: flex; flex-wrap: wrap; gap: 10px; justify-content: flex-end; }

/* Handy / sehr schmale Fenster: alles untereinander, Spiel + Score in
   einer gemeinsamen Zeile. */
@media (max-width: 640px) {
  .staff-team-row.history-row {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "name name"
      "tags tags"
      "game score"
      "time time"
      "actions actions";
    row-gap: 6px;
  }
  .staff-team-row.history-row .h-score { text-align: right; }
  .staff-team-row.history-row .h-time  { text-align: left; }
  .staff-team-row.history-row .h-actions { justify-content: flex-start; }
}

/* ---------------------------------------------------------------------- *
 * TV-/Kiosk-Komfort: Mauszeiger nach Inaktivität ausblenden (siehe
 * initCursorAutoHide() in js/app.js) -- "* " damit auch Kind-Elemente wie
 * der Schloss-Button nicht ihren eigenen cursor:pointer erzwingen.
 * ---------------------------------------------------------------------- */
.tv-stage.cursor-hidden, .tv-stage.cursor-hidden * { cursor: none !important; }

/* ---------------------------------------------------------------------- *
 * Abstands-Anzeige auf dem Podium (Platz 2/3: Punkte-Rückstand zu Platz 1)
 * -- siehe medalCardHtml() in js/tv.js.
 * ---------------------------------------------------------------------- */
.medal-gap {
  margin-top: 4px;
  font-size: 13px;
  font-weight: 700;
  color: #ff8a5c;
  background: rgba(255, 90, 30, 0.12);
  border-radius: 8px;
  padding: 2px 10px;
  display: inline-block;
}

/* ---------------------------------------------------------------------- *
 * Kleiner Hinweis (Team-Eigenrekord) + Meilenstein-Feier -- gestapelt im
 * freien Bereich rechts, unterhalb der Kopfzeile. Läuft ohne Vollbild/Ton
 * und ohne die Folien-Rotation zu pausieren (siehe showMiniHighscoreNotice()/
 * showMilestoneNotice() in js/tv.js).
 * ---------------------------------------------------------------------- */
#miniNoticeStack {
  position: fixed;
  top: 178px;
  right: 22px;
  z-index: 500;
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 250px;
  align-items: flex-end;
  pointer-events: none;
}
.mini-hs-notice, .milestone-notice {
  width: 100%;
  box-sizing: border-box;
  border-radius: 12px;
  padding: 10px 16px;
  color: #fff;
  font-family: inherit;
  box-shadow: 0 8px 26px rgba(0, 0, 0, 0.45);
  display: flex;
  align-items: center;
  gap: 10px;
  animation: miniNoticeIn .3s ease;
  transition: opacity .4s ease, transform .4s ease;
}
.mini-hs-notice.mini-notice-fading, .milestone-notice.mini-notice-fading { opacity: 0; transform: translateX(14px); }
.mini-hs-notice {
  background: linear-gradient(135deg, rgba(60, 20, 90, 0.96), rgba(30, 10, 50, 0.96));
  border: 1px solid rgba(255, 180, 90, 0.5);
}
.milestone-notice {
  background: linear-gradient(135deg, rgba(20, 60, 50, 0.96), rgba(10, 35, 30, 0.96));
  border: 1px solid rgba(120, 255, 190, 0.5);
}
.mini-hs-notice .ico, .milestone-notice .ico { font-size: 22px; flex: none; }
.mini-hs-notice .txt, .milestone-notice .txt { font-size: 13px; line-height: 1.4; }
.mini-hs-notice .txt b { color: #ffb15c; }
.milestone-notice .txt b { color: #7dffc0; }
/* Nachtrag (18.09.2026): seltene "neuer Gesamt-Erster"-Karte (auch auf dem
   stillen/mobilen Weg) optisch etwas festlicher vom normalen Team-Bestwert
   absetzen -- goldener statt violetter Rahmen. */
.mini-hs-notice-overall {
  background: linear-gradient(135deg, rgba(90, 60, 10, 0.96), rgba(50, 30, 5, 0.96));
  border: 1px solid rgba(255, 210, 110, 0.75);
}
@keyframes miniNoticeIn { from { transform: translateX(12px); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
@media (max-width: 760px) {
  #miniNoticeStack { top: auto; bottom: 60px; right: 12px; width: calc(100% - 24px); max-width: 320px; }
}

/* ---------------------------------------------------------------------- *
 * QR-Code zur Gast-Rangliste (nur TV-/Kiosk-Route, siehe js/tv.js
 * renderTvQrWidget()) -- unten links, unabhängig von der aktiven Folie.
 * "Variante A" aus der Mockup-Runde vom 19.09.2026, ohne das anfänglich
 * vorgeschlagene Kürbis-Icon (auf Nutzer-Wunsch entfernt).
 *
 * Bugfix (19.09.2026, Nutzer-Hinweis nach nochmaligem Überlegen): war
 * ursprünglich "position:fixed" mit einer festen Pixelgröße (140px-QR),
 * basierend auf der Annahme, unten links sei auf JEDEM Bildschirm frei.
 * Das stimmte nur für den 16:9-Testbildschirm (1920x1080), auf dem
 * gemockt wurde -- das echte Gerät ist aber ein iPad 9. Generation im
 * Querformat (1080x810 CSS-Punkte, Seitenverhältnis 4:3), das per AirPlay
 * auf den TV gespiegelt wird. Bei diesem Format ist die Rangliste (feste
 * Zeilenhöhe, bis zu 10 Zeilen/Seite) bereits fast genauso hoch wie die
 * gesamte verfügbare Fläche -- unten links blieb dadurch kein echter
 * Leerraum mehr übrig, das fest positionierte Widget lag über den
 * unteren 1-2 Zeilen der Liste (per Playwright mit der echten
 * iPad-Viewportgröße nachgestellt und nachgewiesen).
 * Fix: das Widget ist jetzt kein "position:fixed"-Overlay mehr, sondern
 * ein normales Element im Flex-Fluss von .tv-stage (Geschwister von
 * .tv-scroll/.dots/.footer-note, wie zuvor -- nur ohne die feste
 * Bildschirm-Positionierung). Dadurch reserviert es strukturell echten
 * Platz: .tv-scroll bekommt automatisch entsprechend weniger Höhe, und
 * die bestehende Pagination (rowsPerPage() in tv.js, misst die ECHTE
 * verfügbare Höhe von .tv-scroll zur Laufzeit) berechnet dadurch von
 * selbst weniger Zeilen pro Seite, statt dass das Widget etwas verdeckt --
 * funktioniert dadurch automatisch korrekt auf jedem Seitenverhältnis,
 * nicht nur auf dem einen getesteten Format. Zusätzlich deutlich
 * kompakter (60px-QR, horizontale statt gestapelte Anordnung), damit es
 * auf dem knapperen iPad-Format nicht unnötig viele Zeilen "kostet".
 * ---------------------------------------------------------------------- */
.tv-qr-widget {
  /* Nachtrag (21.09.2026): steckt als Geschwister der Info-Zeile in
     .tv-footer-row.
     Nachtrag 2 (21.09.2026, Nutzer-Korrektur "QR-Code soll an Ort und Stelle
     links in der Ecke bleiben, Info mittig"): statt als normales Flex-Kind
     (das hätte die Info-Zeile immer etwas aus der echten Mitte gedrückt,
     je nach QR-Breite) jetzt per position:absolute links in der Zeile
     verankert -- dadurch ist .footer-note das einzige verbleibende
     Flex-Kind von .tv-footer-row, füllt die komplette Zeile und ist per
     text-align:center wirklich mittig, unbeeinflusst vom QR-Code. Auf
     schmalen Bildschirmen (Handy) blendet sich der QR-Code komplett aus
     (Nutzer-Wunsch: "in der mobilen Ansicht soll der QR-Code gar nicht zu
     sehen sein, nur die Info"), siehe @media weiter unten. */
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  flex: none;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 10px;
  padding: 9px 14px 9px 9px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--panel-grad);
  box-shadow: var(--shadow-card);
}
/* Nachtrag (21.09.2026, dreizehnte Fortsetzung): Nutzer-Meldung "Bestwerte
   der einzelnen Spiele verschwinden hinter dem QR-Code". Ursache: weil
   .tv-qr-widget seit dem Nachtrag oben per position:absolute NICHT mehr am
   Flex-Fluss von .tv-footer-row teilnimmt, zentriert .footer-note (per
   text-align:center) seinen Text ueber die VOLLE Zeilenbreite -- inklusive
   der Flaeche, die der QR-Code (fest links, ca. 264px breit) optisch
   belegt. Auf dem breiten Full-HD-TV (1920px) faellt das nicht auf, weil
   die Zeilenmitte weit genug rechts vom QR-Code liegt. Auf dem tatsaechlich
   genutzten Anzeige-Format (iPad 9. Generation per AirPlay, 1080x810
   CSS-Punkte, siehe Nachtrag weiter oben) reicht der linke Rand des
   zentrierten Textes dagegen bei JEDEM laengeren Zustand (nicht nur den
   Bestwerten) bis unter den QR-Code und wird dort vom Widget-Panel
   ueberdeckt -- per Playwright mit der echten iPad-Viewportgroesse
   nachgestellt und nachgewiesen (Ueberlappung von ca. 30px bei kurzen bis
   ca. 150px bei langen Zustaenden).
   Fix: .footer-note bekommt links reservierten Platz in exakter QR-Breite +
   Sicherheitsabstand (padding-left), aber NUR wenn die Zeile tatsaechlich
   ein QR-Widget enthaelt (.has-qr-Klasse, von app.js nur im TV-Modus
   gesetzt) -- dadurch zentriert sich der Text jetzt in der freien Flaeche
   RECHTS vom QR-Code, statt ueber die komplette Zeile hinweg, und kann den
   QR-Code bei keiner Textlaenge mehr ueberlappen. Auf dem Handy (dort ist
   der QR-Code ohnehin komplett ausgeblendet) wird dieser Platz im
   @media-Block weiter unten wieder freigegeben, damit die Info-Zeile dort
   erneut die volle Breite nutzen kann. */
.tv-footer-row.has-qr .footer-note {
  padding-left: 292px;
  /* Nachtrag (23.09.2026, Nutzerwunsch "Info-Anzeige direkt mittig der
     Seite, darf den QR-Code aber nicht überdecken"): rechts derselbe
     Abstand wie links -- dadurch liegt die Mitte der Info-Zeile exakt auf
     der Seitenmitte (statt in der Mitte der Fläche rechts vom QR-Code) und
     kann den QR-Code trotzdem nie berühren. Längere Texte brechen auf
     schmaleren Bildschirmen eher zweizeilig um bzw. die Preise wählen
     automatisch eine kürzere Fassung (fitPrizeToFooter() in js/tv.js). */
  padding-right: 292px;
}
@media (max-width: 640px) {
  .tv-qr-widget { display: none; }
  .tv-footer-row { min-height: 0; }
  .tv-footer-row.has-qr .footer-note { padding-left: 0; padding-right: 0; }
  /* Nachtrag (21.09.2026, Nutzer-Korrektur "Schrift auf dem Handy soll wieder
     die gleiche dezente Größe wie vorher haben, quetscht die mittlere
     Ansicht zu sehr"): die letzte Schriftvergrößerung (15px -> 19px in
     style.css) war als Desktop/TV-Verbesserung gedacht (dort ist neben dem
     QR-Code viel frei gewordener Platz), auf dem Handy nimmt der größere,
     öfter zweizeilig umbrechende Text aber unnötig Höhe von der
     scrollbaren Mitte weg. Deshalb hier gezielt nur für schmale
     Bildschirme wieder auf die vorherige, dezentere Größe zurückgesetzt --
     Desktop/TV bleibt bei 19px unverändert. */
  .footer-note { font-size: 15px; }
  .footer-note .icon { width: 16px; height: 16px; }
}
.tv-qr-widget .tv-qr-text {
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.tv-qr-widget .tv-qr-title {
  font-family: 'Space Grotesk', sans-serif;
  font-weight: 700;
  font-size: 10.5px;
  letter-spacing: 1.2px;
  color: var(--accent-2);
  white-space: nowrap;
}
.tv-qr-widget .tv-qr-code {
  width: 60px;
  height: 60px;
  flex: none;
  border-radius: var(--radius-sm);
  overflow: hidden;
  box-shadow: 0 0 0 4px #fff;
}
.tv-qr-widget .tv-qr-code svg { display: block; width: 100%; height: 100%; }
.tv-qr-widget .tv-qr-caption {
  font-size: 9.5px;
  font-weight: 600;
  color: var(--ink-dim);
  letter-spacing: .2px;
  white-space: nowrap;
}

/* ---------------------------------------------------------------------- *
 * Admin-Werkzeuge (Testdaten laden / Turnierdaten komplett leeren) --
 * "Läuft noch"-Liste für unvollständige Teams (Prüfbericht-Befund K4),
 * PIN-Dialoge. (Nachtrag 16.09.2026: die früheren TESTMODUS-Banner sowohl
 * auf der öffentlichen Rangliste/TV-Ansicht [.test-mode-banner] als auch im
 * Mitarbeiterbereich [.staff-test-banner] entfielen auf Nutzerwunsch
 * ersatzlos -- der Testdaten-Zustand ist jetzt ausschließlich am
 * "Testdaten laden"-Button [.btn.testdata-active unten] erkennbar.)
 * ---------------------------------------------------------------------- */

/* Ausbaustufe (13.09.2026): Der frühere lange Hinweistext ".incomplete-intro"
   ("👻 Diese Teams haben noch nicht alle 3 Experiences gespielt...") wurde auf
   Wunsch ersetzt -- auf kleinen Bildschirmen wirkte er zu wuchtig/schlecht
   platziert. Ersatz: ein kurzer Untertitel in der Kopfzeile (.tv-subtitle,
   wird von tv.js nur auf dieser Folie befüllt/eingeblendet) + eine kompakte
   Hinweis-Pille über der Liste (.mini-hint-row/.mini-hint), dazu eine
   dezente "Geist"-Atmosphäre (Nebel/Asche-Partikel + pulsierender
   Energie-Kern) im Hintergrund der Folie -- passend zur Idee, dass Teams
   ohne Rang wie eine "Erscheinung" im Turnier wirken. Ausführlich mit dem
   Team abgestimmtes Design (siehe Projekt-Dokumentation), bewusst NICHT im
   klassischen Cartoon-Halloween-Look, sondern mystisch/hochwertig/dezent.
   Die alte .incomplete-intro-Regel entfällt ersatzlos. */
.tv-subtitle {
  font-size: 12px;
  color: var(--ink-dim);
  margin: 4px auto 0;
  max-width: 360px;
}
.tv-subtitle b { color: var(--accent-2); }

.mini-hint-row { display: flex; justify-content: center; margin: 0 0 14px; }
.mini-hint {
  font-size: 11px;
  font-weight: 700;
  color: var(--ink-dim);
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 5px 14px;
  display: flex;
  align-items: center;
  gap: 7px;
}
.mini-hint .dot { width: 5px; height: 5px; border-radius: 50%; background: var(--accent-2); flex: none; }

/* =========================================================
   PREIS-HINWEIS (20.09.2026, Nutzerwunsch): "auf der Rangliste in den
   Einblendungen unten sollen die Preise ... eingeblendet werden" -- explizit
   KEINE eigene Folie (Nutzer: "Ich will keine weitere Folie. ich möchte
   unten bei den hinweisen die preise eingeblendet haben.").
   Nachtrag (21.09.2026): ursprünglich als eigene .mini-hint-Pille INNERHALB
   von Podium-/MVP-Folie umgesetzt -- Nutzer-Feedback danach: dadurch nur auf
   2 von 9 Folien sichtbar, und der ohnehin schon vorhandene, bereits
   rotierende Fusszeilen-Hinweis (.footer-note, siehe footerStateHtml() in
   js/tv.js) blieb ungenutzt direkt daneben. Jetzt stattdessen vier weitere
   Zustände in GENAU dieser bestehenden Rotation (auf jeder Folie sichtbar,
   kein eigenes Element) -- nur noch die Gold-Färbung während ein Preis
   gerade dran ist, per .footer-note-prize (wird von renderFooterState() in
   js/tv.js per classList.toggle gesetzt).
   ========================================================= */
/* Hülle um den jeweiligen Fußzeilen-Inhalt (siehe renderFooterState() in
   js/tv.js): nimmt die volle Breite, Text läuft darin normal um. */
.footer-note-text { flex: 1 1 auto; min-width: 0; text-align: center; }
.footer-note.footer-note-prize { color: var(--gold); }
.footer-note.footer-note-prize b { color: var(--ink); font-weight: 700; }

/* Nachtrag (23.09.2026): echte Preise, "Variante B" -- goldenes Etikett
   ("FINALE · 1. PLATZ" / "TURNIER-MVP") + die Preise einzeln aufgezählt
   (siehe PRIZES / footerStateHtml() in js/tv.js). */
.prize-line {
  display: flex; /* block-level: kein zusätzlicher Zeilen-Abstand durch die umgebende Textzeile */
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 6px 14px;
}
.prize-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: 'Space Grotesk', sans-serif;
  font-size: .74em;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: #1a1206;
  background: linear-gradient(180deg, #ffd978, #e0a63a);
  padding: 4px 10px 3px;
  border-radius: 999px;
  white-space: nowrap;
  box-shadow: 0 0 18px rgba(255, 190, 80, .25);
}
.prize-items {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 4px 22px;
  color: var(--gold);
}
.prize-item { white-space: nowrap; }
/* Handy (Nutzerwunsch 23.09.): die Info-Leiste soll durch die Preise NICHT
   höher werden. Die Leiste hat dort eine feste Mindesthöhe von zwei
   Textzeilen (so hoch wie die übrigen Infos ohnehin sind); ein Preis zeigt
   darin das verkleinerte Etikett + genau eine Zeile Kurzfassung
   (PRIZES[..].short in js/tv.js). Nebeneffekt: auch einzeilige Infos lassen
   die Leiste nicht mehr springen. */
@media (max-width: 640px) {
  .tv-footer-row { min-height: 51px; }
  .prize-line { flex-direction: column; gap: 1px; width: 100%; }
  .prize-label { font-size: 10px; padding: 1px 8px 0; line-height: 1.4; box-shadow: none; }
  .prize-items { display: block; width: 100%; line-height: 1.25; }
  .prize-item { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: center; }
}

/* Kühle "Geist"-Palette, bewusst getrennt von der warmen Orange-Akzentfarbe
   des restlichen Tools (--accent/--accent-2) -- nur für die Atmosphäre auf
   dieser einen Folie, damit sie sich klar von normalen Hervorhebungen
   (progress-tag, Podium-Glanzfarben etc.) abhebt. */
:root {
  --spectral: #9db8ff;
  --spectral-2: #b98dff;
  --spectral-3: #7fe0e8;
}

/* Positionierungs-Kontext für die Atmosphäre-Ebene dahinter: dasselbe Prinzip
   wie .tv-stage::before oben in style.css (position:relative am Container +
   position:absolute;inset:0;z-index:-1 an der Ebene selbst). Dadurch liegt
   die Atmosphäre garantiert HINTER dem normalen Inhalt (Hinweis-Pille,
   Team-Liste) -- unabhängig von der Reihenfolge im Markup -- ohne dass die
   Inhalts-Elemente selbst ein z-index brauchen. Die Team-Liste (.board-row)
   hat weiterhin einen voll deckenden Hintergrund (siehe style.css), d.h.
   selbst wenn sich Nebel/Kern optisch mit der Liste überschneiden, kann die
   Liste dadurch nie verdeckt oder schlechter lesbar werden -- das ist
   strukturell durch die vorhandenen Panel-Hintergründe garantiert, nicht nur
   durch sorgfältiges Austarieren der Effekt-Stärke.
   Bewusst nur innerhalb von #slide-incomplete verankert (NICHT rund um
   .tv-head) -- die Kopfzeile bleibt dadurch auf jeder Folie strukturell und
   optisch exakt gleich. */
#slide-incomplete { position: relative; }
.incomplete-atmo {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
}
.incomplete-ash {
  position: absolute;
  border-radius: 50%;
  filter: blur(1px);
  animation-name: incompleteAshDrift;
  animation-duration: 15s;
  animation-timing-function: ease-in;
  animation-iteration-count: infinite;
}
@keyframes incompleteAshDrift {
  0% { transform: translateY(0) translateX(0); opacity: 0; }
  10% { opacity: .6; }
  90% { opacity: .4; }
  100% { transform: translateY(-140px) translateX(14px); opacity: 0; }
}
.incomplete-fog {
  position: absolute;
  right: -20px;
  top: -30px;
  width: 280px;
  height: 210px;
  border-radius: 50%;
  filter: blur(46px);
  background: radial-gradient(circle, rgba(157, 184, 255, .1), transparent 72%);
}
.incomplete-orb {
  /* Bugfix (13.09.2026, direkt bei Erstumsetzung entdeckt): mit einem
     prozentualen "top" ragte der Kern auf breiten/TV-Bildschirmen viel zu
     tief in die Team-Liste hinein und verschwand fast komplett dahinter
     (#slide-incomplete wächst per flex:1 auf die volle Resthöhe des
     Bildschirms, wodurch z.B. "top:6%" schnell 100px+ waren) -- auf dem
     Handy sah es dagegen gut aus, weil dort weniger Resthöhe zur Verfügung
     steht. Fester Pixel-Versatz ab dem oberen Rand der Folie löst das
     unabhängig von der tatsächlichen Höhe: der Kern schwebt dadurch klar
     sichtbar über der Liste (in der Lücke zwischen Hinweis-Pille und
     Team-Liste), statt fast komplett verdeckt zu sein. */
  position: absolute;
  right: 5%;
  top: -34px;
  width: 150px;
  height: 170px;
  animation: incompleteOrbFloat 6s ease-in-out infinite;
}
@keyframes incompleteOrbFloat {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-9px); }
}
.incomplete-orb-halo {
  position: absolute;
  left: 50%;
  top: 40%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  filter: blur(22px);
}
.incomplete-orb-halo.h1 { width: 150px; height: 150px; background: radial-gradient(circle, rgba(157, 184, 255, .28), transparent 70%); }
.incomplete-orb-halo.h2 { width: 86px; height: 86px; background: radial-gradient(circle, rgba(185, 141, 255, .35), transparent 70%); }
.incomplete-orb-core {
  position: absolute;
  left: 50%;
  top: 40%;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: radial-gradient(circle, #ffffff 0%, #cddcff 35%, rgba(157, 184, 255, .5) 65%, transparent 85%);
  box-shadow: 0 0 26px 7px rgba(157, 184, 255, .6), 0 0 68px 20px rgba(185, 141, 255, .25);
  animation: incompleteOrbPulse 3.4s ease-in-out infinite;
}
@keyframes incompleteOrbPulse {
  0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
  50% { transform: translate(-50%, -50%) scale(1.1); opacity: .85; }
}
.incomplete-orb-trail {
  position: absolute;
  left: 50%;
  border-radius: 50%;
  filter: blur(11px);
  opacity: .4;
  background: radial-gradient(ellipse, rgba(157, 184, 255, .5), transparent 75%);
}
.incomplete-orb-trail.t1 { width: 30px; height: 46px; top: 62%; transform: translateX(-72%) rotate(-8deg); }
.incomplete-orb-trail.t2 { width: 22px; height: 36px; top: 70%; transform: translateX(28%) rotate(10deg); opacity: .7; }

@media (max-width: 640px) {
  /* Auf dem Handy bleibt weniger Breite neben der Liste -- Kern kleiner und
     weiter außen, sonst überlappt er zu stark mit der ersten Zeile. */
  .incomplete-orb { right: 2%; top: 0; width: 92px; height: 106px; }
  .incomplete-orb-halo.h1 { width: 92px; height: 92px; }
  .incomplete-orb-halo.h2 { width: 54px; height: 54px; }
  .incomplete-orb-core { width: 26px; height: 26px; }
  .incomplete-fog { width: 170px; height: 130px; }
}

@media (prefers-reduced-motion: reduce) {
  .incomplete-ash, .incomplete-orb, .incomplete-orb-core { animation: none !important; }
}

/* Eigene, gedämpfte Kennzeichnung statt einer echten Rang-Farbe/-Zahl -- soll
   auf den ersten Blick "das ist noch kein Rang" signalisieren (Nutzer-
   Entscheidung, Variante A). */
.board-row.incomplete { border-left-color: var(--ink-dim); }
.board-row .incomplete-points { color: var(--ink-dim) !important; }

/* =========================================================
   AMBIENT-HINTERGRUNDEFFEKT ("Konzept B", alle drei Routen)
   ==========================================================
   Nachtrag (20.09.2026): ursprünglich nur TV-/Kiosk-Route, auf
   ausdrücklichen Nutzer-Wunsch ("für alle Anzeigen") jetzt auf Gast- UND
   Mitarbeiter-Route ebenfalls aktiv -- siehe js/app.js renderTournament()
   (kein mode==='tv'-Gate mehr für #tvAmbient). Genau wie die schon
   bestehende Eckglühen-Basis (.tv-stage::before, style.css) und die
   "Läuft noch"-Atmosphäre (.incomplete-atmo) war auch dieser Effekt nie
   inhaltlich an den Modus gebunden, nur die anfängliche Umsetzung.
   Siehe js/tv.js renderTvAmbient()/updateTvAmbientTint() für die volle
   Begründung (Nutzer-Entscheidung 19.09.2026, Mockup-Runde). Technik
   bewusst an der bereits produktiven .incomplete-*-Atmosphäre angelehnt
   (driftende Ash-Partikel + Nebel-Blob), aber:
   - hängt an .tv-stage selbst (#tvAmbient, position:absolute; inset:0;
     z-index:-1 -- .tv-stage hat bereits position:relative + overflow:
     hidden, siehe style.css), NICHT an einer einzelnen Folie, damit es
     auf JEDER Folie sichtbar ist, ohne von deren Panel verdeckt zu werden.
   - Farbe kommt aus CSS-Variablen (--amb-*), die pro "tint-*"-Klasse neu
     gesetzt werden -- so kann updateTvAmbientTint() nur die Klasse
     wechseln, ohne die Partikel selbst (und damit ihre laufende Drift-
     Animation) neu zu erzeugen.
   ========================================================= */
.tv-ambient {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  transition: opacity .6s ease;
}
/* Nachtrag (20.09.2026): "Läuft noch" bleibt bewusst ausgenommen (siehe
   updateTvAmbientTint() in js/tv.js) -- diese Folie hat mit .incomplete-atmo
   schon ihre eigene, extra gestaltete Atmosphäre, die unverändert bleiben
   soll. Blendet die neue Ebene weich aus/ein statt hart zu verstecken, damit
   der Folienwechsel nicht ruckt. */
.tv-ambient.hidden { opacity: 0; }
.tv-ambient-ash {
  position: absolute;
  border-radius: 50%;
  filter: blur(1px);
  animation-name: tvAmbientAshDrift;
  animation-timing-function: ease-in;
  animation-iteration-count: infinite;
}
.tv-ambient-ash.a { background: radial-gradient(circle, var(--amb-ash-a), transparent 70%); }
.tv-ambient-ash.b { background: radial-gradient(circle, var(--amb-ash-b), transparent 70%); }
@keyframes tvAmbientAshDrift {
  0%   { transform: translateY(0) translateX(0); opacity: 0; }
  10%  { opacity: .55; }
  90%  { opacity: .35; }
  100% { transform: translateY(-130px) translateX(12px); opacity: 0; }
}
.tv-ambient-fog { position: absolute; border-radius: 50%; filter: blur(70px); }
.tv-ambient-fog.f1 { width: 460px; height: 340px; left: -100px; top: -80px; background: radial-gradient(circle, var(--amb-fog-a), transparent 72%); }
.tv-ambient-fog.f2 { width: 420px; height: 320px; right: -110px; bottom: -70px; background: radial-gradient(circle, var(--amb-fog-b), transparent 72%); }
/* Untere Horizont-Glut -- deutlich sichtbarer als die Eck-Blobs allein,
   macht die Farb-Identität pro Spiel auch dort erkennbar, wo (noch) kein
   Panel-Rand in der Nähe ist. Bewusst nur am unteren Rand (wie ein
   Lagerfeuer-/Horizontschein), damit die Kopfzeile textlich klar bleibt. */
.tv-ambient-horizon {
  position: absolute; left: 0; right: 0; bottom: 0; height: 46%;
  background: linear-gradient(to top, var(--amb-horizon-c), transparent 100%);
}
.tv-ambient-vignette {
  position: absolute; inset: 0;
  background: radial-gradient(ellipse at center, transparent 50%, rgba(6, 7, 10, .46) 100%);
}

/* Farb-Varianten -- Mansion/Valley/Phobia an dieselben Marken-Farben
   angelehnt wie GAME_COLOR/--mansion/--valley/--phobia (Spiel-Badges an
   anderer Stelle im Tool); Podium/Gesamt-Liste/"Läuft noch"/MVP/Top-Spieler
   bleiben beim warmen Gold-Ton (--gold, passt zum Pokal-/Sieger-Thema). */
.tv-ambient.tint-warm {
  --amb-ash-a: rgba(255, 207, 92, .5); --amb-ash-b: rgba(255, 167, 89, .42);
  --amb-fog-a: rgba(255, 207, 92, .16); --amb-fog-b: rgba(255, 138, 61, .13);
  --amb-horizon-c: rgba(255, 177, 74, .16);
}
.tv-ambient.tint-mansion {
  --amb-ash-a: rgba(180, 184, 192, .46); --amb-ash-b: rgba(130, 134, 143, .42);
  --amb-fog-a: rgba(150, 154, 163, .30); --amb-fog-b: rgba(96, 100, 109, .22);
  --amb-horizon-c: rgba(130, 134, 143, .20);
}
.tv-ambient.tint-valley {
  --amb-ash-a: rgba(120, 170, 255, .5); --amb-ash-b: rgba(90, 140, 255, .44);
  --amb-fog-a: rgba(90, 150, 255, .26); --amb-fog-b: rgba(59, 130, 246, .18);
  --amb-horizon-c: rgba(64, 120, 255, .22);
}
.tv-ambient.tint-phobia {
  --amb-ash-a: rgba(255, 120, 100, .5); --amb-ash-b: rgba(239, 68, 68, .44);
  --amb-fog-a: rgba(255, 90, 74, .28); --amb-fog-b: rgba(220, 38, 38, .20);
  --amb-horizon-c: rgba(230, 58, 50, .22);
}
@media (prefers-reduced-motion: reduce) {
  .tv-ambient-ash { animation: none !important; }
}

/* =========================================================
   "LÄUFT NOCH" -- Variante C + Pagination (17.09.2026, Nutzerwunsch)
   ==========================================================
   Ersetzt die fruehere, per Bisektion bis 1.3x hochskalierte Einheits-Liste
   (Nachtrag 16.09.2026) durch zwei feste, fuer ihren jeweiligen Fall bewusst
   gestaltete Layouts:
   - <=3 unvollstaendige Teams: "Spotlight"-Kacheln (.incomplete-spotlight-*)
     -- gross, nebeneinander, maximale Lesbarkeit.
   - >3 Teams: kompakte "Ring-Liste" (.incomplete-ring-list, nutzt weiter die
     .board-row/.board-list-Basis wie die Einzelspiel-Ranglisten), gedeckelt
     auf ROWS_PER_PAGE=10 Zeilen/Seite -- mehr Teams blaettern per Pagination
     automatisch weiter (siehe FOLIEN-ROTATION in tv.js).
   Beide teilen sich denselben Ring-Fortschrittsanzeiger (.incomplete-ring)
   und Segment-Balken (.incomplete-segbar, ein Segment je Experience, farbig
   nach GAME_COLOR eingefaerbt) statt der fruehereren Text-Badge ("2/3
   Experiences"). --incomplete-scale ist seit diesem Umbau nur noch ein
   SCHRUMPF-Sicherheitsnetz (siehe fitIncompleteList() in tv.js) -- die feste
   Zeilen-/Kachelzahl macht das fruehere Hochskalieren ueberfluessig. */
#slide-incomplete.active {
  display: flex;
  flex-direction: column;
  min-height: 100%;
}
#slide-incomplete #incompleteListWrap {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
#slide-incomplete #incompleteListWrap .empty-note { margin: auto; }

/* Ring-Fortschrittsanzeiger -- Basisgroesse via --ring-size, wird je nach
   Einsatzort (Spotlight-Kachel vs. Ring-Listen-Zeile) weiter unten
   ueberschrieben. Reiner CSS-conic-gradient statt SVG, damit keine
   zusaetzliche Grafik pro Zeile erzeugt werden muss. */
.incomplete-ring {
  --ring-size: 38px;
  width: calc(var(--ring-size) * var(--incomplete-scale, 1));
  height: calc(var(--ring-size) * var(--incomplete-scale, 1));
  border-radius: 50%;
  flex: none;
  background: conic-gradient(var(--spectral) calc(var(--ring-pct, 0) * 1%), rgba(255, 255, 255, .12) 0);
  display: flex;
  align-items: center;
  justify-content: center;
}
.incomplete-ring-inner {
  width: 76%;
  height: 76%;
  border-radius: 50%;
  background: var(--panel-2);
  display: flex;
  /* Nachtrag (17.09.2026, Nutzerwunsch "Zahl mittig statt oben im Kreis"):
     war "align-items: baseline" -- das richtet den Flex-Container an der
     TEXT-Grundlinie aus, nicht an seiner Mitte, wodurch die große Ziffer
     spürbar über der Kreismitte "schwebte" statt zentriert zu wirken
     (per Screenshot vom Nutzer entdeckt). "center" zentriert die ganze
     Zeile (Ziffer + kleines "/3") als Einheit senkrecht im Kreis. */
  align-items: center;
  justify-content: center;
  font-family: 'Bebas Neue';
  color: var(--ink);
  font-size: calc(var(--ring-size) * var(--incomplete-scale, 1) * .34);
  line-height: 1;
}
.incomplete-ring-inner i { font-style: normal; font-size: .55em; color: var(--ink-dim); margin-left: 1px; }

/* Segment-Balken -- ein Segment je Experience (GAMES-Reihenfolge), gefuellt
   und farbig, sobald das jeweilige Spiel gespielt wurde (r.playedGames,
   siehe computeOverall() in app.js). */
.incomplete-segbar { display: flex; gap: 6px; }
.incomplete-seg {
  flex: 1;
  height: calc(8px * var(--incomplete-scale, 1));
  border-radius: 99px;
  background: rgba(255, 255, 255, .1);
}
.incomplete-seg.on { background: var(--seg-color); box-shadow: 0 0 10px -2px var(--seg-color); }

/* ---- Spotlight-Kacheln (<=3 Teams) ---- */
.incomplete-spotlight-wrap {
  display: flex;
  gap: 28px;
  width: 100%;
  align-items: stretch;
  justify-content: center;
  flex-wrap: wrap;
}
.incomplete-spotlight {
  flex: 1 1 260px;
  max-width: 420px;
  min-width: 220px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: calc(32px * var(--incomplete-scale, 1)) calc(28px * var(--incomplete-scale, 1));
  box-shadow: 0 20px 50px -24px rgba(0, 0, 0, .7);
}
.incomplete-spotlight .incomplete-ring { --ring-size: 128px; }
.incomplete-spotlight .incomplete-seg { height: calc(12px * var(--incomplete-scale, 1)); }
.incomplete-spotlight-name {
  font-family: 'Space Grotesk';
  font-weight: 700;
  font-size: calc(28px * var(--incomplete-scale, 1) * var(--name-fit-scale, 1));
  color: var(--ink);
  text-align: center;
}
.incomplete-spotlight-score {
  font-family: 'Bebas Neue';
  font-size: calc(52px * var(--incomplete-scale, 1));
  color: var(--spectral);
}
.incomplete-spotlight .incomplete-segbar { width: 82%; }

/* ---- Ring-Liste (>3 Teams, gedeckelt auf ROWS_PER_PAGE Zeilen/Seite) ---- */
@media (min-width: 641px) {
  #slide-incomplete .board-row {
    /* Nachtrag (17.09.2026): bewusst kompakter als der erste Entwurf --
       zusammen mit rowsPerPage() (tv.js) misst diese Größe bei genau
       10 Zeilen auf einem Full-HD-Bildschirm (1920x1080) real nach, mit
       spürbarer Reserve statt knapp am Limit (das erste Maß landete nur ca.
       5% über der verfügbaren Höhe -- das Schrumpf-Sicherheitsnetz griff
       dadurch staendig UND pendelte bei jedem Nachschaerf-Timer leicht
       zwischen knapp-passt/knapp-nicht, sichtbar am ".incomplete-scale"
       Wert, der zwischen 0.90-0.95 hin- und herschwankte). */
    grid-template-columns: calc(38px * var(--incomplete-scale, 1)) 1fr calc(126px * var(--incomplete-scale, 1)) auto;
    gap: calc(12px * var(--incomplete-scale, 1));
    padding: calc(11px * var(--incomplete-scale, 1)) calc(20px * var(--incomplete-scale, 1));
    align-items: center;
  }
  #slide-incomplete .board-row .name {
    font-size: calc(17px * var(--incomplete-scale, 1) * var(--name-fit-scale, 1));
  }
  #slide-incomplete .board-row .points {
    font-size: calc(21px * var(--incomplete-scale, 1));
    min-width: calc(56px * var(--incomplete-scale, 1));
  }
}
@media (max-width: 640px) {
  /* Mobile-Entscheidung (17.09.2026, s. Nutzerfrage "passt Variante C auch
     aufs Handy?"): auf schmalen Bildschirmen zeigt renderIncompletePage()
     (tv.js) IMMER die komplette Liste ohne Auto-Pagination -- ein Gast liest
     hier aktiv mit und kann selbst scrollen, statt dass ihm die Seite
     waehrend des Lesens automatisch unter dem Finger wegblaettert (anders als
     beim unbeaufsichtigten TV, wo das Umblaettern noetig UND unproblematisch
     ist). Deshalb bleiben die Zeilen hier bewusst bei einer festen,
     kompakten Groesse (an die uebrige mobile .board-row-Optik angelehnt)
     statt --incomplete-scale mitzuskalieren -- fitIncompleteList() in tv.js
     ueberspringt das Schrumpf-Sicherheitsnetz auf schmalen Bildschirmen
     ebenfalls (s. dortiger Kommentar), das Scrollen selbst uebernimmt
     #tvScroll (overflow-y:auto, siehe style.css). Der Segment-Balken bekommt
     dieselbe volle-Breite-Behandlung wie .breakdown beim normalen
     .board-row-Mobil-Layout weiter oben in dieser Datei (eigene Zeile unter
     Name/Punkten). */
  #slide-incomplete .board-row .incomplete-ring { --ring-size: 34px; }
  #slide-incomplete .board-row .name { font-size: 14px; }
  #slide-incomplete .board-row .points { font-size: 16px; }
  #slide-incomplete .board-row .incomplete-segbar { order: 5; flex: 1 1 100%; margin-top: 2px; }
  #slide-incomplete .board-row .incomplete-seg { height: 6px; }
  .incomplete-spotlight-wrap { flex-direction: column; gap: 16px; }
  .incomplete-spotlight { max-width: none; padding: 22px 20px; }
  .incomplete-spotlight .incomplete-ring { --ring-size: 88px; }
  .incomplete-spotlight-name { font-size: 20px; }
  .incomplete-spotlight-score { font-size: 36px; }
}

/* ---- Seiten-Anzeige in der Kopfzeile ("Seite 2/3", #tvPageIndicator) ----
   Erscheint nur, solange die aktive Folie tatsaechlich mehrere Seiten hat
   (siehe updatePageIndicator() in tv.js) -- selbe Pille-Optik wie
   .mini-hint, damit sie sich optisch als "derselbe Bauteil-Typ" einordnet. */
.tv-page-indicator {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  font-weight: 700;
  color: var(--ink-dim);
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px 12px;
  margin: 6px auto 0;
  letter-spacing: .3px;
}
/* Bugfix: ".tv-page-indicator{display:inline-flex}" und die
   Browser-Standardregel "[hidden]{display:none}" haben dieselbe
   Spezifität (je ein Selektor-Teil) -- welche gewinnt, entscheidet dann nur
   die Reihenfolge im Stylesheet, und Autoren-CSS kommt immer NACH den
   Browser-Standardstilen. Ohne diese Zeile blieb die Pille dadurch als
   leeres, leicht sichtbares Pillen-Oval stehen, obwohl JS "hidden" gesetzt
   hatte (per Screenshot entdeckt). "[hidden]" als zusätzliches
   Attribut-Selektor-Merkmal erhöht die Spezifität gezielt über die reine
   Klassen-Regel hinaus und stellt das normale hidden-Verhalten sicher. */
.tv-page-indicator[hidden] { display: none; }

/* Kurzer, leichter Auf/Ab-Fade fuer einen reinen Seitenwechsel INNERHALB
   derselben Folie (Pagination) -- bewusst schneller/dezenter als der volle
   Folien-Crossfade (fx-out/fx-in in style.css), damit auf dem TV spuerbar
   bleibt: "gleiche Liste, naechste Seite", kein neues Thema. */
.page-swap-out { animation: pageSwapOut .18s ease forwards; }
.page-swap-in { animation: pageSwapIn .26s ease forwards; }
@keyframes pageSwapOut { to { opacity: 0; transform: translateY(-4px); } }
@keyframes pageSwapIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .page-swap-out, .page-swap-in { animation: none !important; }
}

/* ---- Admin-Bereich (staff.js) ---- */
.staff-admin-panel { border-color: rgba(224, 82, 74, .35); }
.staff-admin-panel h3 { color: var(--ink); }
.staff-admin-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 4px; }
.btn.danger-outline {
  background: transparent;
  border: 1px solid var(--danger);
  color: var(--danger);
}
.btn.danger-outline:hover { background: rgba(224, 82, 74, .1); }

/* Nachtrag (16.09.2026, Nutzerwunsch "Testdaten laden"-Button optisch als
   aktiv/inaktiv erkennbar machen): wenn Testdaten geladen sind
   (DATA.isTestData, siehe staff.js), wird aus dem neutralen Sekundär-Button
   dieser "aktiv"-Zustand -- der gestreifte Gold-Look greift den frueheren
   TESTMODUS-Banner-Stil auf (der Banner selbst wurde inzwischen entfernt,
   siehe Kommentar weiter oben in dieser Datei): dieser Button ist jetzt die
   EINZIGE Stelle, an der der Testdaten-Zustand sichtbar ist. Klick darauf
   oeffnet KEIN erneutes Laden, sondern den Deaktivieren-Dialog (siehe
   openAdminActionDialog('deactivate') in staff.js). */
.btn.testdata-active {
  background: repeating-linear-gradient(135deg, #3a2a06, #3a2a06 10px, #241a04 10px, #241a04 20px);
  border: 1px solid var(--gold);
  color: var(--gold);
  box-shadow: none;
}
.btn.testdata-active:hover {
  filter: none;
  transform: none;
  box-shadow: 0 8px 18px -10px rgba(255, 207, 92, .4);
}

.pin-dialog-warning {
  background: rgba(224, 82, 74, .1);
  border: 1px solid var(--danger);
  border-radius: var(--radius-sm);
  padding: 10px 14px;
  color: var(--danger);
  font-size: 13px;
  font-weight: 700;
  margin: 0 0 12px;
  line-height: 1.4;
}
.auth-box input[type="password"], .auth-box input[type="text"].pin-confirm-input {
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--ink);
  font: inherit;
  letter-spacing: 2px;
}

/* ---------------------------------------------------------------------- *
 * Phase 4: Verlauf bearbeiten / als ungültig markieren + PDF-konforme
 * Bestätigungsnachricht nach dem Speichern (Prüfbericht W2/W3/W4/W5).
 * ---------------------------------------------------------------------- */

/* "Als ungültig markieren"-Dialog (#voidOverlay/#voidPanel) -- select/
   textarea im selben .auth-box-Kasten wie Login-/Admin-Dialoge, bisher gab
   es dort nur Text-/Passwort-Eingaben. */
.auth-box select, .auth-box textarea {
  width: 100%;
  box-sizing: border-box;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--ink);
  font: inherit;
  resize: vertical;
}

/* Verlauf: ungültig markierte Zeile deutlich gedämpft + eigener Hinweis-Tag,
   damit sie auf den ersten Blick als "zählt nicht mehr" erkennbar ist, ohne
   den Eintrag optisch ganz verschwinden zu lassen (er bleibt ja bewusst
   nachvollziehbar erhalten). */
.staff-team-row.history-row.voided {
  opacity: .55;
  border-style: dashed;
}
.staff-team-row.history-row.voided .name { text-decoration: line-through; }
.void-tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--danger);
  background: rgba(224, 82, 74, .1);
  border-radius: 6px;
  padding: 3px 9px;
}
.void-tag-ts { font-weight: 500; color: var(--ink-dim); }

.tag-edited {
  display: inline-block;
  padding: 3px 9px;
  border-radius: 6px;
  font-size: 11px;
  font-weight: 700;
  font-family: 'Space Grotesk';
  background: rgba(255, 163, 92, .14);
  color: var(--accent-2);
  white-space: nowrap;
}

/* Bestätigungsnachricht nach dem Speichern (#scoreMsg, "Variante B" aus der
   Vorschau): Punktestand + Gesamt-Rang + Rang in der gespielten Experience
   als Badges, plus Bestwert-/Gesamt-Erster-Hinweise. */
.confirm-box {
  margin-top: 4px;
  border-radius: var(--radius-sm);
  border: 1px solid rgba(74, 222, 128, .3);
  background: linear-gradient(160deg, rgba(74, 222, 128, .1), rgba(74, 222, 128, .03));
  padding: 12px 14px;
}
.confirm-line1 { color: var(--ink); font-size: 13.5px; line-height: 1.5; }
.confirm-line1 b { color: #4ade80; }
.confirm-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}
.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: 'Space Grotesk';
  font-weight: 600;
  font-size: 12px;
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--ink);
  white-space: nowrap;
}
.badge .num { font-family: 'Bebas Neue'; font-size: 14px; letter-spacing: .3px; }
.badge.rank { border-color: rgba(255, 207, 92, .4); color: var(--gold); }
.badge.game { border-color: rgba(255, 122, 41, .4); color: var(--accent-2); }
.badge.trophy { border-color: rgba(255, 207, 92, .5); background: rgba(255, 207, 92, .08); color: var(--gold); }
.badge.pending { border-color: var(--border); color: var(--ink-dim); }

/* =========================================================
   LIVE-TICKER "ZULETZT GESPIELT" (20.09.2026, Nutzerwunsch)
   Eigene .ticker-*-Namensräume statt .board-row-Wiederverwendung, damit
   folienspezifische .board-row-Overrides (z.B. #slide-overall-list
   .board-row) den Ticker nicht ungewollt mittreffen.
   ========================================================= */
.ticker-list { display: flex; flex-direction: column; gap: 9px; max-width: 860px; margin: 0 auto; width: 100%; }
.ticker-row {
  display: grid;
  /* 29.09.2026: letzte Spalte fest statt auto -- jede Zeile ist ein eigenes
     Grid, bei "auto" war die Score-Spalte je nach Zahl (Ø 20 vs. Ø 15.447)
     unterschiedlich breit und die gelben Labels standen versetzt. */
  grid-template-columns: 96px minmax(0, 1fr) 170px 118px;
  gap: 16px;
  align-items: center;
  padding: 13px 20px;
  border-radius: 12px;
  background: var(--panel);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-card);
}
.ticker-time { font-family: 'Space Grotesk'; font-weight: 600; font-size: 13px; color: var(--ink-dim); }
.ticker-main { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.ticker-team { font-family: 'Space Grotesk'; font-weight: 700; font-size: 17px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ticker-game { font-size: 12px; color: var(--ink-dim); display: flex; align-items: center; gap: 6px; }
.ticker-game .dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.ticker-badge {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .3px;
  text-transform: uppercase;
  color: #241a04;
  background: linear-gradient(135deg, var(--gold), #ffe6a8);
  padding: 4px 10px;
  border-radius: 999px;
  text-align: center;
  white-space: nowrap;
  visibility: hidden;
  justify-self: end;
}
.ticker-row.is-record { border-color: rgba(255, 207, 92, .5); box-shadow: inset 0 0 0 999px rgba(255, 207, 92, .05), var(--shadow-card); }
.ticker-row.is-record .ticker-badge { visibility: visible; }
.ticker-score { font-family: 'Bebas Neue'; font-size: 24px; color: var(--gold); text-align: right; white-space: nowrap; }

/* Nachtrag (21.09.2026, Nutzer-Meldung "mobile Ansicht muss angepasst werden
   dass man alles lesbar hat"): .ticker-row nutzt oben ein festes
   4-Spalten-Grid (96px Zeit / 1fr Team+Spiel / 170px Preis-Pille / auto
   Wert), das für TV/iPad-Breiten ausgelegt ist. Auf einem Handy (~390px)
   blieben von der "1fr"-Spalte nach Abzug der beiden festen Spalten +
   Gaps nur noch ~35px übrig -- der Teamname wurde dadurch praktisch
   unsichtbar gequetscht (anders als bei allen übrigen Folien hatte der
   Live-Ticker bislang KEINE eigene @media-Anpassung, siehe Analyse im
   Chat). Konzept A ("Gestapelt", vom Nutzer aus 3 Vorschlägen gewählt):
   Zeit + Wert stehen oben in einer Zeile, der Teamname bekommt darunter
   die volle Zeilenbreite, die Preis-Pille erscheint nur bei einem
   Rekord als eigene, dritte Zeile (display:none bei normalen Runs,
   damit dort keine leere Zeile Platz frisst). */
@media (max-width: 640px) {
  .ticker-row {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-rows: auto auto auto;
    row-gap: 6px;
    column-gap: 10px;
    padding: 12px 14px;
  }
  .ticker-time { grid-column: 1; grid-row: 1; font-size: 12px; }
  .ticker-score { grid-column: 2; grid-row: 1; font-size: 22px; }
  .ticker-main { grid-column: 1 / -1; grid-row: 2; }
  .ticker-team { font-size: 16px; white-space: normal; overflow: visible; text-overflow: clip; }
  .ticker-game { font-size: 12px; margin-top: 2px; }
  .ticker-badge { grid-column: 1 / -1; grid-row: 3; justify-self: start; visibility: visible; position: static; font-size: 10px; }
  .ticker-row:not(.is-record) .ticker-badge { display: none; }
}

/* ---------------------------------------------------------------------- *
 * Link zur offiziellen Turnier-Infoseite (TOURNAMENT_INFO_URL in js/app.js)
 * -- nur auf der Gast-Route, in der Kopfzeile unter/neben "N Teams im
 * Turnier" (Nutzerwunsch 23.09.2026). Auf dem Handy in derselben Zeile wie
 * die Team-Anzahl, damit die Kopfzeile nicht höher wird.
 * ---------------------------------------------------------------------- */
.tv-info-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  border-radius: var(--radius-pill, 999px);
  border: 1px solid rgba(255, 207, 92, .45);
  background: rgba(255, 207, 92, .08);
  color: var(--gold);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .2px;
  text-decoration: none;
  white-space: nowrap;
  transition: background .2s ease, border-color .2s ease;
}
.tv-info-link:hover { background: rgba(255, 207, 92, .16); border-color: var(--gold); }
.tv-info-link:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.tv-info-link .lbl-short { display: none; }
@media (max-width: 760px) {
  .tv-head-meta.right { flex-direction: row; flex-wrap: wrap; justify-content: center; align-items: center; gap: 6px 8px; }
  .tv-info-link { padding: 1px 8px; font-size: 12px; line-height: 1.4; gap: 4px; }
  /* kürzere Beschriftung, damit der Button neben "N Teams im Turnier" passt */
  .tv-info-link .lbl-long { display: none; }
  .tv-info-link .lbl-short { display: inline; }
}

/* ---------------------------------------------------------------------- *
 * Stammspieler / Gastspieler (Phase 7, 23.09.2026) -- Hinweise im
 * Kader-Editor (js/roster.js) und bei der Highscore-Eingabe (js/scores.js).
 * ---------------------------------------------------------------------- */
.core-hint {
  margin: 6px 0 10px;
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  border: 1px solid rgba(255, 207, 92, .3);
  background: rgba(255, 207, 92, .06);
  color: var(--ink-dim);
  font-size: 13px;
  line-height: 1.45;
}
.core-hint:empty { display: none; }
.core-hint b { color: var(--ink); }
.core-hint.warn { border-color: rgba(224, 82, 74, .5); background: rgba(224, 82, 74, .08); }
.tag-core, .tag-guest {
  display: inline-block;
  padding: 3px 9px;
  border-radius: 6px;
  font-size: 11px;
  font-weight: 700;
  font-family: 'Space Grotesk', sans-serif;
  white-space: nowrap;
}
.tag-core { background: rgba(255, 207, 92, .14); color: var(--gold); }
.tag-guest { background: rgba(148, 163, 184, .16); color: #cbd5e1; }
.participant-tag:empty { display: none; }
.participant-tag { display: inline-flex; align-items: center; }
.confirm-badges .badge.core { border-color: rgba(255, 207, 92, .45); color: var(--gold); background: rgba(255, 207, 92, .1); }
.confirm-badges .badge.mvp { border-color: rgba(255, 163, 92, .5); color: var(--accent-2); background: rgba(255, 163, 92, .1); }

/* ---------------------------------------------------------------------- *
 * Phase 8 (24.09.2026): Fristen-Hinweise, Kader-Zeilen mit eigenem
 * Entfernen-Knopf, Team umbenennen.
 * ---------------------------------------------------------------------- */
.deadline-banner {
  margin: 0 0 12px;
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  font-size: 14px;
  line-height: 1.45;
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--ink);
}
.deadline-banner.soon   { border-color: rgba(255, 207, 92, .45); background: rgba(255, 207, 92, .07); }
.deadline-banner.late   { border-color: rgba(255, 163, 92, .55); background: rgba(255, 163, 92, .09); }
.deadline-banner.closed { border-color: rgba(224, 82, 74, .55);  background: rgba(224, 82, 74, .09); }
.roster-row input.roster-name-input { flex: 1 1 auto; min-width: 0; }
.roster-row .roster-slot-tag:empty { display: none; }
.staff-team-row .team-rename-box { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; width: 100%; }
.staff-team-row .team-rename-input {
  flex: 1 1 220px;
  min-width: 0;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--ink);
  font: inherit;
}
.staff-team-row .team-rename-hint { flex-basis: 100%; }

/* Nachtrag (24.09.2026): "Erster Run" vs. "Neuer Team-Bestwert (+Punkte)" */
.mini-hs-notice.is-first {
  background: linear-gradient(135deg, rgba(110, 38, 10, 0.96), rgba(55, 16, 6, 0.96));
  border: 1px solid rgba(255, 140, 60, 0.55);
}
.mini-hs-notice.is-first .txt b { color: #ffc58a; }
.mini-hs-notice .nw { white-space: nowrap; }
.hs-delta {
  display: inline-block; margin-left: 4px; padding: 1px 7px; border-radius: 999px;
  background: rgba(61, 220, 132, 0.18); border: 1px solid rgba(61, 220, 132, 0.55);
  color: #7dffb4; font-weight: 700; font-size: 12px; line-height: 1.35; white-space: nowrap;
}

/* Vorschau-Hinweis im Mitarbeiterbereich (vor Turnierstart) */
.staff-preview-hint { margin: 0 24px 18px; line-height: 1.5; }
.staff-preview-hint a { color: inherit; font-weight: 700; word-break: break-all; }

/* Nachtrag (24.09.2026): Einspringer "dauerhaft in den Kader" + frühere
   Einspringer als Vorschläge (js/scores.js) */
.score-participant-new { flex-wrap: wrap; }
.score-to-roster {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 13px; color: var(--ink-dim); cursor: pointer; white-space: nowrap;
  text-transform: none; letter-spacing: normal; font-weight: 500;
}
.score-to-roster input { width: 18px; height: 18px; accent-color: var(--accent); }
.score-to-roster:has(input:checked) { color: var(--ink); }
.score-to-roster:has(input:disabled) { cursor: not-allowed; opacity: .6; }
.prev-guests { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 4px 0 6px; }
.prev-guests:empty { display: none; }
.prev-guests-label { font-size: 13px; color: var(--ink-dim); margin-right: 2px; }
.prev-guest-chip {
  font: 600 13px/1 'Inter', sans-serif; color: var(--ink);
  background: var(--panel-2); border: 1px dashed var(--border); border-radius: var(--radius-pill);
  padding: 8px 12px; cursor: pointer; transition: border-color .15s, background .15s;
}
.prev-guest-chip:hover:not(:disabled) { border-color: var(--accent); background: rgba(255, 122, 41, .08); }
.prev-guest-chip:disabled { opacity: .45; cursor: not-allowed; }

/* Nachtrag (24.09.2026): Run-Typ automatisch + Inklusiv-Übersicht je Team
   (js/scores.js renderRunInfo()) */
.run-info:empty, .team-runs:empty { display: none; }
.run-info {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px;
  margin-top: 10px; padding: 10px 14px; border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--panel-2);
}
.run-info.inclusive { border-color: rgba(74, 222, 128, .45); background: rgba(74, 222, 128, .07); }
.run-info.extra { border-color: rgba(255, 163, 92, .55); background: rgba(255, 163, 92, .08); }
.run-info-type { font: 700 15px/1.2 'Space Grotesk', sans-serif; white-space: nowrap; }
.run-info.inclusive .run-info-type { color: #86efac; }
.run-info.extra .run-info-type { color: var(--accent-2); }
.run-info-text { font-size: 14px; color: var(--ink-dim); }
.team-runs { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; margin-top: 8px; font-size: 13px; }
.team-runs-label { color: var(--ink-dim); }
.team-run-chip {
  padding: 4px 10px; border-radius: var(--radius-pill); font-weight: 600; white-space: nowrap;
  border: 1px solid var(--border); background: var(--panel-2); color: var(--ink-dim);
}
.team-run-chip.done { border-color: rgba(74, 222, 128, .45); color: #86efac; background: rgba(74, 222, 128, .08); }
.team-run-chip.current { box-shadow: 0 0 0 2px rgba(255, 255, 255, .18); }
.team-runs-extra { margin-left: 6px; color: var(--ink-dim); }
.team-runs-extra b { color: var(--ink); }
.tag-runtype.extra { background: rgba(255, 163, 92, .14); color: var(--accent-2); }


/* Nachtrag (24.09.2026, Nutzerwunsch): "Läuft noch" ohne eigene, am
   Folienrand abgeschnittene Atmosphäre -- stattdessen die ganzflächige
   Partikel-Ebene (#tvAmbient) in kühlen Geist-Farben wie auf allen anderen
   Folien, und die Lichtkugel sitzt auf der Oberkante der Liste. */
.incomplete-atmo { display: none; }
.tv-ambient.tint-spectral {
  --amb-ash-a: rgba(200, 216, 255, .5); --amb-ash-b: rgba(185, 141, 255, .44);
  --amb-fog-a: rgba(157, 184, 255, .20); --amb-fog-b: rgba(185, 141, 255, .16);
  --amb-horizon-c: rgba(127, 150, 255, .16);
}
.incomplete-list-frame { position: relative; }
.incomplete-list-frame > .board-list,
.incomplete-list-frame > .incomplete-spotlight-wrap { position: relative; z-index: 1; }
/* feiner Lichtstreifen entlang der Oberkante, zur Kugel hin heller */
.incomplete-list-frame::after {
  content: ''; position: absolute; left: 12px; right: 12px; top: 0; height: 2px; z-index: 2;
  pointer-events: none;
  background: linear-gradient(90deg, transparent 0%, rgba(157, 184, 255, .10) 45%, rgba(185, 160, 255, .85) 88%, rgba(157, 184, 255, .25) 97%, transparent 100%);
}
.incomplete-edge-orb {
  position: absolute; right: 12%; top: 0; width: 0; height: 0; z-index: 3;
  pointer-events: none;
  animation: incompleteEdgeOrbFloat 6s ease-in-out infinite;
}
/* flacher, breiter Schein (Ellipse) -- endet ca. 50 px über der Kante */
.incomplete-edge-orb .glow {
  position: absolute; left: -150px; top: -52px; width: 300px; height: 104px; border-radius: 50%;
  background: radial-gradient(ellipse closest-side, rgba(200, 185, 255, .30) 0%, rgba(157, 184, 255, .14) 45%, rgba(157, 184, 255, 0) 100%);
}
.incomplete-edge-orb .core {
  position: absolute; left: -16px; top: -16px; width: 32px; height: 32px; border-radius: 50%;
  background: radial-gradient(circle, #ffffff 0%, #d9e2ff 38%, rgba(157, 184, 255, .45) 68%, transparent 86%);
  box-shadow: 0 0 18px 6px rgba(157, 184, 255, .5);
  animation: incompleteOrbPulseEdge 3.4s ease-in-out infinite;
}
@keyframes incompleteEdgeOrbFloat {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-5px); }
}
@keyframes incompleteOrbPulseEdge {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.1); opacity: .85; }
}
@media (max-width: 640px) {
  /* Abstand zum rechten Rand > halbe Scheinbreite, sonst schneidet der
     Scrollbereich den Schein seitlich ab (harte Kante). */
  .incomplete-edge-orb { right: 30%; }
  .incomplete-edge-orb .glow { left: -80px; top: -32px; width: 160px; height: 64px; }
  .incomplete-edge-orb .core { left: -12px; top: -12px; width: 24px; height: 24px; box-shadow: 0 0 12px 4px rgba(157, 184, 255, .5); }
}
/* Karten-Ansicht (bis 3 Teams): die Kugel sitzt auf der Oberkante der
   rechten Karte (js/tv.js renderIncompletePage()). */
.incomplete-spotlight { position: relative; overflow: visible; }
.incomplete-spotlight .incomplete-edge-orb { right: 22%; }
@media (prefers-reduced-motion: reduce) {
  .incomplete-edge-orb, .incomplete-edge-orb .core { animation: none !important; }
}

/* Score-Skala-Pruefung (24.09.2026): Rueckstand auf dem Handy-Podium immer
   einzeilig ("–13.282 zu Platz 1"), statt "1" allein in Zeile 2. */
@media (max-width: 640px) {
  .medal-card .medal-gap { white-space: nowrap; letter-spacing: -0.01em; }
}

/* Score-Skala-Pruefung (24.09.2026): der Leuchtpunkt der "Läuft noch"-Liste
   ragt ein paar Pixel ueber den Listenrand -- in .tv-scroll (overflow-y:auto,
   dadurch auch x=auto) entstand bei 1280 px/iPad ein 8-px-Querscrollen.
   Sichtbar aendert sich nichts, das Wackeln zur Seite entfaellt. */
.tv-scroll { overflow-x: hidden; }

/* Turnier-Logo unter der Folien-Ueberschrift (Variante A, Nutzerentscheidung
   24.09.2026): Original-Schriftzug vom Plakat/Countdown statt Kuerbis-Plakette. */
.event-badge.tv-head-cup {
  background: none; border: none; border-radius: 0; padding: 0;
  gap: 10px; margin-top: 8px;
}
.tv-head-cup .cup-logo {
  height: 22px; width: auto; display: block;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, .8)) drop-shadow(0 0 10px rgba(227, 38, 43, .25));
}
.tv-head-cup .cup-sep { width: 1px; height: 16px; background: rgba(255, 255, 255, .22); flex: none; }
.tv-head-cup .cup-loc {
  font: 600 11px/1 'Inter', sans-serif; letter-spacing: .2em; text-transform: uppercase;
  color: var(--ink-dim); white-space: nowrap;
}
@media (min-width: 900px) {
  .tv-head-cup .cup-logo { height: 26px; }
  .tv-head-cup .cup-loc { font-size: 12px; }
}

/* ---------------------------------------------------------------------- *
 * Phase 10 (24.09.2026): Turnier-Kalender oben links (Variante A,
 * "Countdown-Kachel") -- ersetzt "📅 01.–30. Oktober" + Phasen-Etikett.
 * Farbe je Phase: blau Quali, orange Frist naht/letzter Tag, grau Samstag,
 * gold Finale-Woche, rot Finaltag. Siehe renderPhaseBadge() in js/tv.js.
 * ---------------------------------------------------------------------- */
.th-cal[hidden] { display: none; }
.th-cal { display: flex; flex-direction: column; min-width: 0; max-width: 100%; }
.th-tone-quali { --th: #7fb2ff; --th-bg: rgba(59, 130, 246, .10); --th-bd: rgba(59, 130, 246, .38); }
.th-tone-warn  { --th: #ffb35c; --th-bg: rgba(255, 140, 40, .11); --th-bd: rgba(255, 140, 40, .45); }
.th-tone-pause { --th: #a8aebb; --th-bg: rgba(160, 166, 180, .08); --th-bd: rgba(160, 166, 180, .30); }
.th-tone-final { --th: #ffcf5c; --th-bg: rgba(255, 207, 92, .10); --th-bd: rgba(255, 207, 92, .45); }
.th-tone-live  { --th: #ff8a7a; --th-bg: rgba(224, 82, 74, .14); --th-bd: rgba(224, 82, 74, .55); }
.thA {
  display: flex; align-items: stretch; width: max-content; max-width: 100%;
  border: 1px solid var(--th-bd); background: var(--th-bg); border-radius: 14px; overflow: hidden;
}
.th-tone-live .thA { animation: thLiveGlow 1.8s ease-in-out infinite; }
@keyframes thLiveGlow { 0%, 100% { box-shadow: 0 0 0 rgba(224, 82, 74, 0); } 50% { box-shadow: 0 0 18px rgba(224, 82, 74, .55); } }
@media (prefers-reduced-motion: reduce) { .th-tone-live .thA { animation: none; } }
.thA-num {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: 6px 14px 5px; border-right: 1px solid var(--th-bd); min-width: 64px; flex: none;
}
.thA-num b { font: 400 36px/.9 'Bebas Neue', sans-serif; color: var(--th); letter-spacing: .5px; }
.thA-num span {
  font: 800 9.5px/1.1 'Inter', sans-serif; letter-spacing: .14em; color: var(--th); opacity: .85;
  margin-top: 3px; text-transform: uppercase; text-align: center;
}
.thA-txt { display: flex; flex-direction: column; justify-content: center; gap: 4px; padding: 6px 14px 6px 12px; min-width: 0; }
.thA-t { font: 800 12px/1.15 'Inter', sans-serif; letter-spacing: .08em; text-transform: uppercase; color: var(--ink); } /* 25.09.: darf umbrechen (iPad 1024 schnitt ab) */
.thA-s { font: 500 12px/1.3 'Inter', sans-serif; color: var(--ink-dim); }
.thA-s b { color: var(--ink); font-weight: 700; }
@media (max-width: 760px) {
  .th-cal { align-items: center; width: 100%; }
  .thA-num { min-width: 52px; padding: 4px 10px; }
  .thA-num b { font-size: 28px; }
  .thA-t, .thA-s { font-size: 11px; }
  .thA-t { white-space: normal; }
  /* Die Kopfzeile ist hier eine zentrierte Spalte (Breite = Inhalt) --
     100 % greift daher nicht; an der Bildschirmbreite begrenzen. */
  .thA { max-width: calc(100vw - 32px); }
}

/* ---------------------------------------------------------------------- *
 * Turnier-MVP groesser (25.09.2026, Nutzerentscheidung "A + B"):
 *  - Querformat ab 900 px (TV, iPad quer, Desktop): ZWEI SPALTEN -- Icon,
 *    Name, Team, Score links gross, Plaetze 2-5 rechts als Liste.
 *  - Hochformat/Handy: Plaetze 2-5 als KOMPAKTE KARTEN (4 nebeneinander,
 *    auf dem Handy 2x2) -- dadurch bleibt mehr Hoehe fuers Icon.
 * Die Icon-Groesse berechnet computeMvpSpotlightSizing() (tv.js) wie bisher
 * per Bisektion, jetzt bis max. 560 px.
 * ---------------------------------------------------------------------- */
@media (min-width: 900px) and (min-aspect-ratio: 5/4) {
  .mvp-spotlight { flex-direction: row; align-items: center; justify-content: center; gap: clamp(24px, 4vw, 64px); }
  .mvp-hero { flex: 0 0 auto; width: calc(var(--mvp-img-w, 240px) * 1.35); max-width: none; }
  .mvp-hero-list { flex: 0 1 520px; align-self: center; }
  .mvp-list-row { padding: 14px 16px; }
  .mvp-list-name { font-size: 18px; }
  .mvp-list-team { font-size: 12.5px; }
  .mvp-list-rank, .mvp-list-score { font-size: 24px; }
}
@media (max-width: 899px), (max-aspect-ratio: 1249/1000) {
  .mvp-hero-list {
    display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; max-width: 980px;
    background: none; border: none; box-shadow: none; padding: 0; overflow: visible;
  }
  .mvp-list-row {
    display: grid; grid-template-columns: auto 1fr; grid-template-areas: "rank score" "name name";
    align-items: center; gap: 2px 8px; margin: 0; padding: 10px 12px; border: 1px solid var(--border);
  }
  .mvp-list-rank { grid-area: rank; }
  .mvp-list-score { grid-area: score; justify-self: end; font-size: 22px; }
  .mvp-list-name { grid-area: name; font-size: 14px; }
  .mvp-list-bar { top: auto; bottom: 0; height: 4px; }
}
@media (max-width: 640px) {
  .mvp-hero-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* Kalender-Kachel in schmalen 3-Spalten-Kopfzeilen (z. B. iPad hochkant,
   761-1100 px): nur Zahl + Titel, damit nichts abgeschnitten wird. */
@media (min-width: 761px) and (max-width: 1100px) {
  .thA-num { min-width: 50px; padding: 5px 9px 4px; }
  .thA-num b { font-size: 28px; }
  .thA-num span { font-size: 8.5px; letter-spacing: .08em; }
  .thA-txt { padding: 6px 10px; }
  .thA-t { font-size: 10.5px; letter-spacing: .04em; }
  .thA-s { display: none; }
}

/* ---------------------------------------------------------------------- *
 * Info-Leiste unten, Neugestaltung 25.09.2026 (Variante A + B): farbiges
 * Kategorie-Etikett + eine kurze Zeile. Siehe footerInfo() in js/tv.js.
 * Etikett-Farbe kommt per --c (auch CSS-Variablen der Spielfarben); Fläche
 * und Rand werden über Pseudo-Elemente mit Deckkraft erzeugt (ohne
 * color-mix, damit auch ältere Kiosk-Browser es korrekt zeigen).
 * ---------------------------------------------------------------------- */
.fi { display: flex; align-items: center; justify-content: center; gap: 14px; min-width: 0; max-width: 100%; animation: fiIn .45s ease both; }
@keyframes fiIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .fi { animation: none; } }
.fi-chip {
  position: relative; display: inline-flex; align-items: center; gap: 7px; flex: none;
  font: 800 12px/1 'Inter', sans-serif; letter-spacing: .14em; text-transform: uppercase;
  color: var(--c); padding: 6px 11px 5px; border-radius: 999px; white-space: nowrap;
}
.fi-chip::before, .fi-chip::after { content: ''; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; }
.fi-chip::before { background: currentColor; opacity: .13; }
.fi-chip::after { border: 1px solid currentColor; opacity: .45; }
.fi-chip i { width: 7px; height: 7px; border-radius: 50%; background: currentColor; box-shadow: 0 0 8px currentColor; flex: none; }
.fi-row { display: flex; align-items: baseline; gap: 12px; min-width: 0; }
.fi-main { font: 600 19px/1.25 'Space Grotesk', sans-serif; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.fi-main em { font-style: normal; font-family: 'Inter', sans-serif; font-weight: 500; font-size: .8em; color: var(--ink-dim); margin-left: 8px; }
.fi-val { font: 400 26px/1 'Bebas Neue', sans-serif; color: var(--gold); letter-spacing: .5px; flex: none; }
.fi-gold .fi-main, .fi-gold .fi-main b { color: var(--gold); }
@media (max-width: 640px) {
  .fi { flex-direction: column; gap: 4px; width: 100%; }
  .fi-chip { font-size: 9.5px; padding: 3px 8px 2px; gap: 5px; }
  .fi-chip i { width: 5px; height: 5px; }
  .fi-row { max-width: 100%; justify-content: center; align-items: center; gap: 8px; }
  .fi-main { font-size: 14px; line-height: 1.2; }
  .fi-main em { display: none; }
  .fi-val { font-size: 18px; line-height: 1; }
}

/* ---------------------------------------------------------------------- *
 * Prüfrunde 26.09.2026 (Nutzer-Freigabe Punkte 1, 2, 3, 6, 7)
 * ---------------------------------------------------------------------- */
/* 1) Handy: Kalender als schmale Pille, daneben Live-Punkt + Uhrzeit (spart
      ca. 55 px -- das Podium rückt nach oben). */
@media (max-width: 760px) {
  .tv-head-meta:not(.right) { flex-direction: row; flex-wrap: wrap; justify-content: center; align-items: center; gap: 6px 10px; }
  .th-cal { width: auto; max-width: 100%; }
  .thA { border-radius: 999px; max-width: calc(100vw - 110px); }
  .thA-num { flex-direction: row; align-items: baseline; gap: 5px; min-width: 0; padding: 5px 4px 4px 12px; border-right: none; }
  .thA-num b { font-size: 19px; line-height: 1; }
  .thA-num span { margin-top: 0; font-size: 9px; }
  .th-big-word .thA-num span { display: none; }
  .thA-txt { padding: 5px 12px 4px 4px; min-width: 0; }
  .thA-t { font-size: 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .thA-s { display: none; }
  .tv-sub { margin-top: 0; font-size: 11px; }
  .tv-sub .upd-lbl { display: none; }
}
/* 2) Abstand zu Platz 1: auf dem Handy zweizeilig (Zahl groß, darunter
      "zu Platz 1"), damit auch 6-stellige Werte auf dem Hintergrund stehen. */
@media (max-width: 640px) {
  .medal-card .medal-gap {
    white-space: normal; display: inline-flex; flex-direction: column; align-items: center; gap: 1px;
    line-height: 1.05; padding: calc(var(--p-text-scale, 1) * 3px) calc(var(--p-text-scale, 1) * 7px);
  }
  .medal-gap .mg-val { font-family: 'Bebas Neue', sans-serif; font-size: 1.45em; letter-spacing: .3px; white-space: nowrap; }
  .medal-gap .mg-lbl { font-size: .85em; opacity: .85; white-space: nowrap; }
}
/* 3) Handy, Gesamtliste: rechts nur in der Finalisten-Karte Platz für das
      "Finals Spot"-Band -- sonst brachen Namen mitten im Wort um. */
@media (max-width: 640px) {
  #slide-overall-list .board-list:not(.finale-cut-list) .board-row { padding-right: calc(14px * var(--list-scale, 1)); }
}
/* 6) Handy, MVP: Name ca. 20 % kleiner, der Score rückt nach oben. */
@media (max-width: 640px) {
  .mvp-hero-name { font-size: calc(var(--mvp-img-w, 240px) * .102); }
  .mvp-hero-team { font-size: calc(var(--mvp-img-w, 240px) * .07); }
}
/* 7) Handy, Podium: Runden-Anzeige deutlich flacher (ca. 36 statt 55 px). */
@media (max-width: 640px) {
  .stat-chip { gap: 1px; padding: 4px 2px 3px; }
  .stat-chip-label { font-size: 9.5px; }
  .stat-chip-count { font-size: 10px; }
  .stat-chip b { font-size: 15px; line-height: 1; }
}

/* ---------------------------------------------------------------------- *
 * 26.09.2026 (Nutzerwahl "A"): Podium im Hochformat -- Handy und iPad
 * hochkant -- mit Platz 1 groß allein oben, Platz 2 + 3 darunter. Nutzt die
 * Höhe statt der knappen Breite. Nur bei .podium-stack (jeder Rang einmal,
 * siehe renderPodium() in js/tv.js); die Größe berechnet computePodiumSizing().
 * ---------------------------------------------------------------------- */
@media (max-width: 640px), (orientation: portrait) and (max-width: 1100px) {
  .podium.podium-stack:not(.podium-row) {
    display: grid; grid-template-columns: auto auto; grid-template-areas: "one one" "two three";
    justify-content: center; justify-items: center; align-items: start;
    row-gap: calc(var(--p-text-scale, 1) * 8px); column-gap: calc(var(--p-text-scale, 1) * 18px);
  }
  .podium.podium-stack:not(.podium-row) .medal-card.rank1 { grid-area: one; width: auto; transform: none; }
  .podium.podium-stack:not(.podium-row) .medal-card.rank2 { grid-area: two; }
  .podium.podium-stack:not(.podium-row) .medal-card.rank3 { grid-area: three; }
  .podium.podium-stack:not(.podium-row) .medal-card.rank1 .medal-badge-clip { width: calc(var(--p-badge-h, 69px) * 1.45); height: calc(var(--p-badge-h, 69px) * 1.77); }
  .podium.podium-stack:not(.podium-row) .medal-card.rank1 .medal-name { font-size: calc(var(--p-text-scale, 1) * 15px * var(--name-fit-scale, 1)); max-width: calc(var(--p-badge-h, 69px) * 2.6); }
  .podium.podium-stack:not(.podium-row) .medal-card.rank1 .medal-points { font-size: calc(var(--p-text-scale, 1) * 32px); }
}
/* iPad hochkant: Zeilen höher statt Vergrößern (siehe stretchListRows()) */
[data-list-pad] .board-row, [data-list-pad] .player-row, [data-list-pad] .ticker-row {
  padding-top: var(--fill-pad) !important; padding-bottom: var(--fill-pad) !important;
}
@media (max-width: 640px) {
  .podium.podium-stack:not(.podium-row) .medal-card:not(.rank1) .medal-points-label { white-space: nowrap; letter-spacing: .06em; }
}

/* =========================================================
   30.09.2026: Leeres Podium („Team Toplist“, noch kein Team komplett)
   Platzhalter statt kleinem grauem Satz in großer leerer Fläche.
   ========================================================= */
.empty-podium { display: flex; flex-direction: column; align-items: center; gap: clamp(14px, 2.6vh, 30px); padding-top: clamp(4px, 2vh, 30px); width: 100%; }
.ep-row { display: flex; align-items: flex-end; justify-content: center; gap: clamp(14px, 2.2vw, 40px); width: 100%; }
.ep-ped { --c: #ffcf5c; --h: clamp(90px, 24vh, 270px); display: flex; flex-direction: column; align-items: center; gap: clamp(6px, 1.2vh, 14px); width: min(320px, 26vw); }
.ep-r2 { --c: #c9b6ff; --h: clamp(64px, 17vh, 190px); }
.ep-r3 { --c: #e6a06e; --h: clamp(44px, 12vh, 140px); }
.ep-q { width: clamp(60px, 10vh, 116px); aspect-ratio: 1; border-radius: 50%; border: 3px dashed var(--c); display: grid; place-items: center;
  font-family: 'Bebas Neue', sans-serif; font-size: clamp(36px, 6.4vh, 72px); line-height: 1; color: var(--c); padding-top: .08em; box-sizing: border-box;
  background: radial-gradient(circle, color-mix(in srgb, var(--c) 14%, transparent), transparent 70%);
  animation: epPulse 2.6s ease-in-out infinite; }
.ep-r2 .ep-q { animation-delay: .5s; } .ep-r3 .ep-q { animation-delay: 1s; }
@keyframes epPulse { 0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 transparent; }
  50% { transform: scale(1.07); box-shadow: 0 0 30px -4px color-mix(in srgb, var(--c) 60%, transparent); } }
.ep-free { font-family: 'Space Grotesk', sans-serif; font-weight: 700; font-size: clamp(15px, 2.4vh, 26px); color: color-mix(in srgb, var(--ink) 60%, transparent); }
.ep-block { width: 100%; height: var(--h); border: 2px dashed color-mix(in srgb, var(--c) 70%, transparent); border-bottom: 0; border-radius: 18px 18px 0 0;
  background: linear-gradient(180deg, color-mix(in srgb, var(--c) 10%, transparent), transparent); display: flex; justify-content: center; box-sizing: border-box; padding-top: clamp(6px, 1.4vh, 14px); }
.ep-block span { font-family: 'Bebas Neue', sans-serif; font-size: clamp(40px, 8vh, 92px); line-height: 1; color: var(--c); opacity: .6; }
.ep-title { font-family: 'Space Grotesk', sans-serif; font-weight: 700; font-size: clamp(18px, 3.1vh, 34px); line-height: 1.2; text-align: center; text-wrap: balance; max-width: 1200px; }
.ep-title em { font-style: normal; color: var(--accent); }
.ep-games { display: flex; flex-wrap: wrap; justify-content: center; gap: clamp(8px, 1vw, 14px); }
.ep-game { display: inline-flex; align-items: center; gap: 9px; border: 1px solid var(--border); border-radius: 999px; padding: clamp(6px, 1vh, 10px) clamp(12px, 1.2vw, 20px);
  font-size: clamp(13px, 1.9vh, 20px); color: var(--ink-dim); background: rgba(255, 255, 255, .02); }
.ep-game i { width: 9px; height: 9px; border-radius: 50%; opacity: .45; }
.ep-game b { font-weight: 600; color: var(--ink-dim); opacity: .8; }
.ep-game.on { color: var(--ink); border-color: color-mix(in srgb, var(--gold) 40%, var(--border)); }
.ep-game.on i { opacity: 1; box-shadow: 0 0 8px currentColor; }
.ep-game.on b { color: var(--gold); opacity: 1; }
.ep-hint { font-size: clamp(12px, 1.7vh, 18px); color: var(--ink-dim); text-align: center; }
@media (max-width: 640px) {
  .empty-podium { gap: 14px; padding-top: 10px; }
  .ep-row { gap: 10px; }
  .ep-ped { width: 30%; --h: 110px; }
  .ep-r2 { --h: 78px; } .ep-r3 { --h: 56px; }
  .ep-q { width: 52px; font-size: 32px; border-width: 2px; }
  .ep-free { font-size: 13px; }
  .ep-block span { font-size: 38px; }
  .ep-title { font-size: 17px; }
  .ep-game { font-size: 12.5px; padding: 6px 11px; gap: 6px; }
}
@media (prefers-reduced-motion: reduce) { .ep-q { animation: none; } }
