/* ============================================================================
   Komponenten — erster grober Stand.
   Keine Komponente kennt ihre Zone, ihren Modus oder einen Hex-Wert (FE-11/13).
   Inventar und die deutschen Namen: docs/frontend-plan.md § 6.
   ========================================================================== */

/* --- Eyebrow · Modultitel mit Indikator ----------------------------------- *
 * Einen Modultitel gibt es nie ohne Indikator (Bereichsmarke 4 × 26). Im
 * Styleguide steckt der Indikator in allen sechs Varianten, deshalb zeichnet
 * ihn die Klasse selbst — vergessen kann ihn so niemand.
 *
 * Auf einer gepolsterten Fläche sitzt der Indikator auf deren Kante, der Text
 * auf der Inhaltskante. Den Abstand dazwischen nennt die Fläche selbst über
 * --content-inset (Panel, Karte …), und zwar derselbe Wert, mit dem sie sich
 * polstert. Wo keine Fläche ihn nennt, ist var() ungültig, der Rand fällt auf
 * 0 zurück und der Indikator steht einfach vor dem Text.                    */
.eyebrow {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-small);
  font-weight: 700;
  color: var(--zone-text-quiet);
}

.eyebrow::before {
  content: "";
  flex: none;
  inline-size: var(--border-indicator);
  block-size: var(--size-indicator);
  border-radius: 0 var(--radius-indicator) var(--radius-indicator) 0;
  background: currentcolor;
  margin-inline: calc(-1 * var(--content-inset))
    calc(var(--content-inset) - var(--border-indicator) - var(--space-2));
}

/* Das Icon im Modultitel ist im Styleguide ein Solid-Icon, dessen Aussparungen die
   Fläche dahinter durchscheinen lassen. */
.eyebrow > .icon {
  --size-icon: var(--size-icon-eyebrow);
}

/* --- Abschnittstitel · freiliegende Flächen -------------------------------- *
 * Überschrift eines Abschnitts, der KEINE eigene Fläche hat — direkt auf dem
 * Seitengrund. Dort ist der Modultitel (.eyebrow) fehl am Platz: Sein
 * Indikator zieht sich aus dem --content-inset einer Karte oder eines Panels,
 * den es hier nicht gibt (Issue #66).                                       */
.section-title {
  font-size: var(--font-size-card-title);
  font-weight: 700;
  color: var(--zone-text-strong);
}

/* --- Button · „Knopf" ----------------------------------------------------- *
 * Drei Töne. „Auf Grün" und „auf Tinte" sind KEINE Varianten — das macht die
 * Zone. Aus acht Varianten werden so drei Regeln (FE-21).
 *
 * Reihenfolge der Regeln: aufsteigende Spezifität, sonst kann eine spätere
 * Regel eine frühere nicht mehr sicher überschreiben (K-24).                 */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--button-gap);
  min-block-size: var(--size-button);
  min-inline-size: var(--size-target-min);
  padding-block: var(--button-padding-block);
  padding-inline: var(--button-padding-inline);
  border: var(--border-hairline) solid transparent;
  border-radius: var(--radius-button);
  box-shadow: 0 var(--shadow-offset-control) 0 0 var(--shadow-control);
  font: inherit;
  font-size: var(--font-size-small);
  font-weight: 700;
  line-height: 1.2;
  cursor: pointer;
  background: none;
  color: inherit;

  /* Ein Knopf ist nie unterstrichen, auch nicht als <a> und nicht beim
     Überfahren (FE-81). */
  text-decoration: none;
  transition:
    transform var(--motion-fast) var(--motion-ease),
    box-shadow var(--motion-fast) var(--motion-ease);
}

.button--primary {
  background: var(--zone-action-bg);
  color: var(--zone-action-text);
}

.button--secondary {
  background: var(--zone-carrier-bg);
  color: var(--zone-carrier-text);
  border-color: var(--zone-border); /* FE-36: helle Fläche braucht die Kante */
}

.button--outline {
  background: transparent;
  color: var(--zone-text-strong);
  border-color: var(--zone-border-interactive);
}

/* Ohne Rahmen, ohne Fläche, ohne Versatz — für Aktionen, die in einer Tabelle
   oder Liste stehen und das Raster nicht zerschneiden sollen. */
.button--ghost {
  background: transparent;
  color: var(--zone-text-strong);
  border-color: transparent;
  box-shadow: none;
}

/* Beim Überfahren zieht der Knopf leicht hoch und der harte Versatz wird zum
   weichen Schatten — er hebt sich von der Fläche ab, statt auf ihr zu liegen. */
.button:hover {
  transform: translateY(calc(-1 * var(--lift-hover)));
  box-shadow: 0 var(--shadow-offset-control-hover) var(--shadow-blur-control-hover) 0
    var(--shadow-control);
}

/* Gesperrt — wartet auf eine Antwort oder darf gerade nichts: bleibt an Ort
   und Stelle, ohne Versatz, im leisen Ton. aria-disabled statt disabled, wo
   ein Tooltip den Grund nennt — ein disabled-Knopf ist nicht fokussierbar,
   der Hinweis wäre per Tastatur unerreichbar (FE-25a). Dann type="button"
   ohne Formular, damit er nichts auslöst. */
.button:disabled,
.button[aria-disabled="true"],
.button:disabled:hover,
.button[aria-disabled="true"]:hover {
  transform: none;
  color: var(--zone-text-quiet);
  border-color: var(--zone-border);
  background: transparent;
  box-shadow: none;
  cursor: not-allowed;
}

/* Der rahmenlose Knopf hebt sich zwar, bekommt aber keinen Schatten — und
   keine Unterstreichung, die ihn zum Link machen würde (FE-81). */
.button--ghost:hover {
  box-shadow: none;
}

/* Wer Bewegung abbestellt hat, bekommt den Schattenwechsel ohne den Sprung. */
@media (prefers-reduced-motion: reduce) {
  .button:hover {
    transform: none;
  }
}

/* Mit Icon wird die Polsterung auf der Icon-Seite schmaler — kein Modifier,
   die Komponente sieht selbst nach, ob ein Icon darin steht. */
.button:has(> .icon:first-child) {
  padding-inline-start: var(--button-padding-inline-icon);
}

/* Ein Icon am Ende — der Pfeil eines „Weiter"-Knopfs. :has() kann es nicht
   von einem am Anfang unterscheiden, weil Textknoten nicht als Kinder zählen:
   Das einzige Icon ist immer zugleich erstes und letztes Kind. Deshalb hier
   ausnahmsweise ein Modifier; er nimmt die Regel darüber wieder zurück. */
.button--trailing:has(> .icon:last-child) {
  padding-inline: var(--button-padding-inline) var(--button-padding-inline-icon);
}

/* Nur das Icon: die sichtbare Beschriftung liegt versteckt, der Knopf bleibt
   quadratisch und die Zielgrösse gilt weiter (NFA-18). */
.button:not(:has(> :not(.icon, .visually-hidden))) {
  padding-inline: var(--button-padding-inline-icon);
}

/* --- Chip · Info ---------------------------------------------------------- *
 * Fakten in der Spielrunde: Zeit, Ort, Zusagen. Kein Bedienelement — NFA-18 gilt hier
 * nicht. Die Kante ist Pflicht, sonst 1,39:1 auf Grün (FE-36).              */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--chip-gap);
  min-block-size: var(--size-chip);
  padding-block: var(--chip-padding-block);
  padding-inline: var(--chip-padding-inline);
  border: var(--border-hairline) solid
    color-mix(in srgb, var(--zone-carrier-border) var(--border-alpha-soft), transparent);
  border-radius: var(--radius-full);
  font-size: var(--font-size-chip);
  color: var(--zone-carrier-text);
  background: var(--zone-carrier-bg);
}

.chip:has(> .icon:first-child) {
  padding-inline-start: var(--chip-padding-inline-icon);
}

.chip--outline {
  background: transparent;
  color: var(--zone-text-strong);
}

/* Ein Chip als Filter: Der gewaehlte traegt `aria-current` und hebt sich ab —
   durch Fettung UND kraeftigen Rand, nicht durch Farbe allein (FE-33). Ein
   Chip, der irgendwohin fuehrt, ist nie unterstrichen; er sieht aus wie ein
   Bedienelement, also verhaelt er sich auch so (FE-81).                     */
a.chip {
  text-decoration: none;
}

.chip[aria-current] {
  border-color: var(--zone-text-strong);
  color: var(--zone-text-strong);
  font-weight: 700;
}

.chip strong {
  font-weight: 700;
}

/* --- Pill · Statuspille --------------------------------------------------- *
 * Sechs Töne, je ein Paar Text/Fläche. Der Ton wird gesetzt, nicht die Farbe. */
/* Die Pille nimmt auch ein Icon auf — so entstehen die Vertrauens-Abzeichen
   aus M3.2 („Zuverlässig", „Guter Gastgeber", „Erklärbär") ohne eigenes
   Bauteil. Wichtig: Bei Personen nie Zahlen, nie Sterne (M3.3). */
.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-0-5) var(--space-2);
  border-radius: var(--radius-full);
  font-size: var(--font-size-eyebrow);
  font-weight: 700;
  color: var(--pill-tx);
  background: var(--pill-bg);
}

.pill--success {
  --pill-tx: var(--pill-success-tx);
  --pill-bg: var(--pill-success-bg);
}
.pill--warning {
  --pill-tx: var(--pill-warning-tx);
  --pill-bg: var(--pill-warning-bg);
}
.pill--danger {
  --pill-tx: var(--pill-danger-tx);
  --pill-bg: var(--pill-danger-bg);
}
.pill--neutral {
  --pill-tx: var(--pill-neutral-tx);
  --pill-bg: var(--pill-neutral-bg);
}
.pill--olive {
  --pill-tx: var(--pill-olive-tx);
  --pill-bg: var(--pill-olive-bg);
}
.pill--coral {
  --pill-tx: var(--pill-coral-tx);
  --pill-bg: var(--pill-coral-bg);
}

/* --- Panel · „Bereich" ---------------------------------------------------- *
 * Trägt data-zone und bekommt Fläche + Textfarbe daher automatisch.
 * Jedes Panel trägt denselben Haarlinien-Rand aus durchscheinendem Schwarz
 * (--border-panel) — auf jedem Grund dieselbe leise Kante, und Polsterung
 * und Maße bleiben in allen Zonen identisch.                               */
.panel {
  --content-inset: var(--space-5);
  padding: var(--content-inset);

  /* Durchscheinendes Schwarz statt einer Farbe je Zone: dieselbe leise Kante
     auf Weiß, Sand, Grün und Tinte. */
  border: var(--border-hairline) solid var(--border-panel);
  border-radius: var(--radius-panel);

  /* Harter Versatz ohne Weichzeichner — die Farbe kommt aus der Zone. */
  box-shadow: 0 var(--shadow-offset) 0 0 var(--zone-shadow);
}

.panel__title {
  font-size: var(--font-size-title);
  font-weight: 700;
}
.panel__subtitle {
  color: var(--zone-text-subtle);
  font-size: var(--font-size-small);
}
.panel__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.panel__actions {
  margin-inline-start: auto;
  display: flex;
  gap: var(--space-2);
}

/* --- Card · „Karte" ------------------------------------------------------- *
 * Die Fläche kommt aus der Zone (--zone-card-bg) und ist überall die hellste:
 * weiß auf dem Panel, weiß auf dem sandfarbenen Grund, weiß auf den festen Zonen.
 * Eine Karte trägt deshalb selbst nie data-zone — sonst läse sie ihren eigenen
 * Ton statt den ihres Grundes und höbe sich nicht mehr ab.                  */
/* Eine Karte ist ein kleines Panel: dieselbe durchscheinende Kante, derselbe
   harte Versatz aus der Zone — nur enger und mit kleinerem
   Radius. */
.card {
  --content-inset: var(--space-4);
  padding: var(--content-inset);
  border: var(--border-hairline) solid var(--border-panel);
  border-radius: var(--radius-card);
  background: var(--zone-card-bg);
  color: var(--zone-carrier-text);

  /* Derselbe Versatz wie unter einem Panel. Er war einmal halb so tief; das
     traegt nicht mehr, seit Karten auch frei auf dem Grund stehen und nicht
     nur IN einem Panel (Issue #18) — dort verlor die flachere Kante die
     Karte gegen den Untergrund.                                            */
  box-shadow: 0 var(--shadow-offset) 0 0 var(--zone-shadow);
}

/* Eine Karte, die irgendwohin fuehrt, verhaelt sich wie ein Knopf: Beim
   Ueberfahren zieht sie leicht hoch, und der harte Versatz wird zum weichen
   Schatten — sie hebt sich von der Flaeche ab, statt auf ihr zu liegen.
   Dieselben Werte wie beim Knopf, damit es eine Bewegung im Haus gibt und
   nicht zwei.

   `:focus-within` steht daneben, weil der Weg aus der Karte heraus ein Link
   IN der Karte ist: Wer mit der Tastatur dorthin kommt, soll dieselbe
   Rueckmeldung bekommen wie mit der Maus.                                  */
.card--link {
  transition:
    transform var(--motion-fast) var(--motion-ease),
    box-shadow var(--motion-fast) var(--motion-ease);
}

.card--link:hover,
.card--link:focus-within {
  transform: translateY(calc(-1 * var(--lift-hover)));
  box-shadow: 0 var(--shadow-offset-control-hover) var(--shadow-blur-control-hover) 0
    var(--shadow-control);
}

/* Wer Bewegung abbestellt hat, bekommt den Schattenwechsel ohne den Sprung. */
@media (prefers-reduced-motion: reduce) {
  .card--link:hover,
  .card--link:focus-within {
    transform: none;
  }
}

/* --- Progress · „Fortschrittsbahn" ---------------------------------------- *
 * Breite in PROZENT setzen, nie in Pixeln (Styleguide-Vorgabe).                   */
.progress {
  --progress-fill: var(--zone-action-bg);
  display: flex;
  block-size: var(--size-progress);
  border-radius: var(--radius-full);
  background: var(--zone-track);
  overflow: hidden;
}

.progress__value {
  block-size: 100%;
  border-radius: var(--radius-full);
  background: var(--progress-fill);
}

/* Für Fortschritt, der ein Ziel ist — Punktestand, erfüllte Kriterien. */
.progress--success {
  --progress-fill: var(--zone-text-success);
}

/* --- Sidebar · „Seitenmenü" ----------------------------------------------- */
.sidebar {
  inline-size: var(--size-sidebar);
  min-block-size: 100vh;
  padding-block: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* --- Brand · „Marke" ------------------------------------------------------ */
.brand {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-inline-start: var(--space-edge-left);
  font-size: var(--font-size-brand);
  font-weight: 700;
}

.brand__mark {
  inline-size: var(--size-brand-mark);
  block-size: var(--size-brand-mark);
  border-radius: var(--radius-sm);
  background: var(--bg-accent);
  flex: none;
}

/* --- Nav ------------------------------------------------------------------ *
 * Aktiv = Indikator UND gefülltes Icon UND Fettung — nie Farbe allein (FE-33) */
.nav {
  display: flex;
  flex-direction: column;
}

.nav__item {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-block-size: var(--size-nav-item);
  padding-inline-start: var(--space-edge-left);
  color: var(--zone-text-strong);
  text-decoration: none;
}

.nav__item:hover,
.nav__item[aria-current] {
  font-weight: 700;
}

.nav__item[aria-current]::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inline-size: var(--border-indicator);
  block-size: var(--size-indicator);
  border-radius: 0 var(--radius-indicator) var(--radius-indicator) 0;
  background: var(--zone-text-strong);
}

/* Unterpunkte sind flacher als die Hauptebene — 34 px wie im Styleguide. Damit
   liegen sie unter der selbst gesetzten Zielgröße von 44 px (NFA-18), aber
   deutlich über den 24 px, die WCAG 2.5.8 auf AA verlangt. Bewusste Abweichung,
   ein Token zurückzunehmen. */
.nav__subitem {
  display: flex;
  align-items: center;
  min-block-size: var(--size-nav-subitem);

  /* Der Text steht auf der Linie des Elterntextes, nicht auf der seines Icons
     (Issue #66): Kante + Icon + derselbe Zwischenraum, den der Haupteintrag
     zwischen Icon und Text haelt. */
  padding-inline-start: calc(
    var(--space-edge-left) + var(--size-icon-nav) + var(--space-3)
  );
  font-size: var(--font-size-nav-sub);
  color: var(--zone-text-quiet);
  text-decoration: none;
}

.nav__subitem:hover,
.nav__subitem[aria-current] {
  font-weight: 700;
  color: var(--zone-text-strong);
}

.nav__divider {
  block-size: var(--border-hairline);
  margin: var(--space-3) var(--space-edge-left);
  background: var(--zone-border);
  border: 0;
}

/* Rang sichtbar, Punktzahl nicht (FA-3.25 / FE-43) */
.nav__user {
  margin-block-start: auto;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-inline-start: var(--space-edge-left);
}

.nav__user-avatar {
  inline-size: var(--size-avatar);
  block-size: var(--size-avatar);
  border-radius: var(--radius-full);
  background: var(--bg-sunken);
  flex: none;
}

.nav__user-name {
  font-weight: 700;
  font-size: var(--font-size-small);
}
.nav__user-rank {
  font-size: var(--font-size-nav-sub);
  color: var(--zone-text-quiet);
}

/* --- Cover · 1:1 ---------------------------------------------------------- *
 * Spielcover, Gruppenbild, Ortsfoto. Immer quadratisch, damit die Liste nicht
 * springt, wenn ein Bild fehlt oder nachlaedt (FE-58).                       */
.cover {
  display: block;
  overflow: hidden;
  inline-size: var(--size-cover);
  block-size: var(--size-cover);
  border-radius: var(--radius-control);
  background: var(--bg-sunken);
  flex: none;

  /* Als innerer Schatten, nicht als border: So liegt die Kante über dem Bild
     und vergrößert das Element nicht. */
  box-shadow: inset 0 0 0 var(--border-hairline) var(--border-on-media);
}

.cover img {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  border-radius: inherit;
}

.cover--sm {
  --size-cover: var(--size-cover-sm);
}

/* --- Date badge · die Datumskachel in Listenzeilen ------------------------ */
.date-badge {
  inline-size: var(--size-cover);
  block-size: var(--size-cover);
  border-radius: var(--radius-control);
  background: var(--bg-sunken);
  color: var(--zone-text-strong);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  flex: none;
  line-height: 1.1;
}

.date-badge__day {
  font-size: var(--font-size-brand);
  font-weight: 700;
}

.date-badge__month {
  font-size: var(--font-size-eyebrow);
  font-weight: 700;
  text-transform: uppercase;
  color: var(--zone-text-quiet);
}

/* Die Uhrzeit gehört immer dazu — wer den Tag liest, will die Stunde gleich
   mit. Sie steht unter dem Monat in derselben leisen Farbe. */
.date-badge__time {
  font-size: var(--font-size-eyebrow);
  color: var(--zone-text-quiet);
}

/* --- Row · Listenzeile („Zeile · Abstimmung") ----------------------------- *
 * Datumskachel oder Cover links, Text in der Mitte, Status und Pfeil rechts.
 * Als <a> oder <li> verwendbar — die Zeile schreibt kein Element vor.        */
.row {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-block-size: var(--size-target-min);
  padding-block: var(--space-3);
  color: var(--zone-text-strong);
  text-decoration: none;
}

.row + .row {
  border-block-start: var(--border-hairline) solid var(--zone-border);
}

.row__body {
  min-inline-size: 0; /* damit lange Titel umbrechen statt zu überlaufen */
  flex: 1;
}

.row__title {
  font-weight: 700;
}

/* Die Meta-Zeile nimmt Pillen mit auf: Status, Datum, Entfernung in einer
   Zeile. Deshalb flex statt Fließtext. */
.row__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-small);
  color: var(--zone-text-quiet);
}

.row__meta strong {
  color: var(--zone-text-strong);
}

.row__end {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex: none;
}

/* --- Media card · Karte mit Bild, Text und Status ------------------------- *
 * Im Styleguide „Karte · Spielrunde in der Nähe". Der Name bleibt hier domänenfrei
 * (K-4): die generische Schicht kennt keine Spielrunden.                    */
.media-card {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3);
  border-radius: var(--radius-card);
  background: var(--bg-region);
  color: var(--zone-text-strong);
  text-decoration: none;
}

.media-card__body {
  min-inline-size: 0;
  flex: 1;
}

.media-card__title {
  font-weight: 700;
}

.media-card__meta {
  font-size: var(--font-size-small);
  color: var(--zone-text-quiet);
}

/* --- Header · Kopfzeile --------------------------------------------------- */
.header {
  display: flex;

  /* Eng bricht die Zeile um: Am Handy stehen die Aktionen sonst rechts aus dem
     Bild und sind nicht mehr zu erreichen (Issue #14).                      */
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  padding-block: var(--space-4);
}

.header__title {
  font-size: var(--font-size-title);
  font-weight: 700;
  flex: none;
}

.header__search {
  max-inline-size: var(--size-search-max);
  flex: 1;
}

.header__actions {
  display: flex;

  /* Ganz an den rechten Rand, nicht neben den Titel (Issue #66): Ein Knopf
     dicht am h1 liest sich wie ein Teil der Ueberschrift. */
  margin-inline-start: auto;

  /* Ohne `flex: none` gibt die Gruppe nach, sobald die Zeile nicht reicht, und
     bricht dann in sich um — sonst stuende der letzte Knopf am Handy rechts
     aus dem Bild (Issue #14).                                               */
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);

  /* Ein Flex-Kind gibt von sich aus nicht unter seinen Inhalt nach: Ohne
     diese Zeile schiebt eine lange Aktion (etwa der Name einer gemerkten
     Suche) die ganze Reihe ueber den Rand der Spalte. */
  min-inline-size: 0;
}

/* Icon-Knopf mit Meldepunkt. Der Punkt allein sagt nichts — die Zahl gehört
   als Textalternative dazu (FE-24, WCAG 1.4.1).                             */
.icon-button {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--size-target-min);
  block-size: var(--size-target-min);
  border: 0;
  border-radius: var(--radius-full);
  background: none;
  color: var(--zone-text-strong);
  text-decoration: none; /* FE-81 — auch als <a> */
  cursor: pointer;
}

.icon-button__dot {
  position: absolute;
  inset-block-start: var(--space-2);
  inset-inline-end: var(--space-2);
  inline-size: var(--size-dot);
  block-size: var(--size-dot);
  border-radius: var(--radius-full);
  background: var(--text-error);
}

/* --- Spinner -------------------------------------------------------------- *
 * Vorläufig / außerhalb Kern-Inventar. Ein Ring aus der Zonenfarbe, dessen offener Teil den
 * Rand durchscheinen lässt.
 *
 * Er wird NICHT abgeschaltet, wenn jemand Bewegung abbestellt hat — eine
 * stehende Ladeanzeige sieht aus wie ein Fehler. Stattdessen dreht er sich
 * deutlich langsamer, und die Ansage übernimmt ohnehin role="status".
 *
 *   <span class="spinner" role="status">
 *     <span class="visually-hidden">Lädt …</span>
 *   </span>
 */
.spinner {
  display: inline-block;
  inline-size: var(--size-spinner);
  block-size: var(--size-spinner);
  border: var(--border-spinner) solid var(--zone-border);
  border-block-start-color: var(--zone-text-strong);
  border-radius: var(--radius-full);
  animation: spinner-turn var(--motion-spinner) linear infinite;
}

.spinner--lg {
  --size-spinner: 32px;
  --border-spinner: 3px;
}

@keyframes spinner-turn {
  to {
    transform: rotate(1turn);
  }
}

@media (prefers-reduced-motion: reduce) {
  .spinner {
    animation-duration: calc(var(--motion-spinner) * 6);
  }
}

/* --- Wartender Knopf -------------------------------------------------------- *
 * Eine laufende Anfrage: in Ruhe die Sanduhr, beim Überfahren oder per
 * Tastatur das X — der Klick zieht die Anfrage zurück. Kein disabled: Der
 * Knopf tut etwas, und der Hinweis bleibt erreichbar (FE-25a).             */
.button--waiting > .icon--cancel,
.button--waiting:hover > .icon--hourglass,
.button--waiting:focus-visible > .icon--hourglass {
  display: none;
}

.button--waiting:hover > .icon--cancel,
.button--waiting:focus-visible > .icon--cancel {
  display: inline-block;
}

.button--waiting:hover,
.button--waiting:focus-visible {
  color: var(--zone-text-danger);
}

/* --- Sanduhr ---------------------------------------------------------------- *
 * Ein Icon, das sich alle 1,4 s halb dreht — wie eine Sanduhr, die jemand
 * umdreht. Für Knöpfe, die auf eine Antwort warten. Anders als der Spinner
 * steht sie still, wenn Bewegung abbestellt ist: Der Zustand steht ohnehin
 * im Text daneben, die Sanduhr selbst sagt schon „warten".              */
.icon--hourglass {
  animation: hourglass-turn calc(var(--motion-spinner) * 4) ease-in-out infinite;
}

@keyframes hourglass-turn {
  0%,
  40% {
    rotate: 0deg;
  }

  50%,
  90% {
    rotate: 180deg;
  }

  100% {
    rotate: 360deg;
  }
}

@media (prefers-reduced-motion: reduce) {
  .icon--hourglass {
    animation: none;
  }
}

/* --- Avatar --------------------------------------------------------------- *
 * Kreis mit Kürzel oder Bild. Vorläufig / außerhalb Kern-Inventar — die Maße stammen aus der
 * Nutzerzeile der Seitenleiste.
 */
.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--size-avatar);
  block-size: var(--size-avatar);
  border-radius: var(--radius-full);

  /* Nicht der weisse Traeger: Ein Avatar mit Initialen stuende sonst weiss
     auf weiss. Die Bahn ist in jeder Zone vom Grund unterscheidbar. */
  background: var(--zone-track);
  color: var(--zone-text-strong);
  font-size: var(--font-size-nav-sub);
  font-weight: 700;
  flex: none;
  object-fit: cover;
}

.avatar--sm {
  --size-avatar: var(--size-avatar-sm);

  font-size: var(--font-size-avatar-sm);
}

/* Übereinandergereihte Avatare. Der Ring in der Zonenfläche trennt sie
   voneinander, ohne eine zusätzliche Farbe zu brauchen. */
.avatar-stack {
  display: flex;
  align-items: center;
}

.avatar-stack > .avatar {
  margin-inline-start: calc(-1 * var(--avatar-overlap));
  box-shadow: 0 0 0 var(--border-spinner) var(--zone-surface);
}

.avatar-stack > .avatar:first-child {
  margin-inline-start: 0;
}

/* --- Tooltip -------------------------------------------------------------- *
 * Vorläufig / außerhalb Kern-Inventar. Eine schwebende Fläche mit weichem
 * Schatten und kleinem Zeiger.
 *
 * Kurzer Hinweis beim Überfahren — Tinte:
 *
 *   <span class="tooltip-host">
 *     <span class="chip" tabindex="0" aria-describedby="tt-zusagen">…</span>
 *     <span class="tooltip" role="tooltip" id="tt-zusagen">…</span>
 *   </span>
 *
 * Viele Infos (Spiel, Person) — hell, nur auf Klick, als <details>:
 *
 *   <details class="tooltip-host tooltip-host--click">
 *     <summary>…</summary>
 *     <span class="tooltip tooltip--lg">
 *       <span class="tooltip__body" data-zone="surface">…</span>
 *     </span>
 *   </details>
 *
 * Warum so:
 * - :focus-within statt nur :hover — sonst kommt niemand per Tastatur heran.
 * - aria-describedby — sonst existiert der Inhalt für Screenreader nicht.
 * - Kein Abstand zwischen Wirt und Tooltip, den die Maus überqueren muesste:
 *   Der Zwischenraum gehoert zum Wirt (WCAG 1.4.13 „hoverable").
 * - Klick-Variante: natives <details>, ohne JavaScript, schliesst mit einem
 *   zweiten Klick. Dieselbe name-Gruppe hält nur eines offen. Bewegung und
 *   Einblendung sind dieselben wie beim kurzen Hinweis, in beide Richtungen.
 *
 * Was hier NICHT geht und Christoph ergänzen muss: Schliessen per Esc
 * (WCAG 1.4.13 „dismissible") braucht JavaScript — für die Klick-Variante
 * erledigt das apps/web (TipDismiss: Esc, Klick daneben, Fokus weiter). Und
 * der Tooltip darf nicht in einem Element mit overflow: hidden|auto stehen —
 * in .table-wrap etwa würde er abgeschnitten.
 */
.tooltip-host {
  position: relative;
  display: inline-flex;
}

.tooltip {
  position: absolute;
  inset-block-end: 100%;
  inset-inline-start: 50%;
  z-index: var(--layer-tooltip);
  padding-block-end: var(--space-2); /* Brücke für die Maus, kein Loch */
  inline-size: max-content;
  max-inline-size: var(--size-tooltip-max);
  visibility: hidden;
  opacity: 0;

  /* Dieselbe Bewegung wie beim Knopf: Er hebt sich beim Überfahren um
     --lift-hover, der Tooltip kommt aus derselben Tiefe herauf. Der erste
     Wert hält dabei die waagerechte Zentrierung. */
  translate: -50% var(--lift-hover);
  transition:
    opacity var(--motion-fast) var(--motion-ease),
    translate var(--motion-fast) var(--motion-ease),
    visibility var(--motion-fast) var(--motion-ease);
}

.tooltip--lg {
  max-inline-size: var(--size-tooltip-max-lg);
}

/* Die Fläche holt sich Farbe und Textton aus ihrer Zone. Kurze Hinweise
   tragen data-zone="ink", Flächen mit vielen Infos data-zone="surface" —
   hell mit Schatten, nicht die Primärfarbe. */
.tooltip__body {
  position: relative;
  display: block; /* ist im Markup ein <span> — ohne das läuft die Fläche aus */
  padding: var(--space-3);
  border: var(--border-hairline) solid var(--zone-border);
  border-radius: var(--radius-control);
  background: var(--zone-surface);
  font-size: var(--font-size-chip);
  font-weight: 400;
  text-align: start;
  box-shadow: 0 var(--shadow-offset-float) var(--shadow-blur-float) 0
    var(--shadow-control);
}

/* Der helle Hinweis schwebt über einer Karte — und die ist die hellste Fläche.
   Er nimmt deshalb die Trägerfläche, die in jeder Zone weiß ist, statt der
   Zonenfläche: Was oben liegt, darf nicht dunkler sein als sein Untergrund.
   Dialog und Dropdown machen es genauso; der dunkle Hinweis auf Tinte behält
   seine Zonenfläche.

   Und er trägt auch sonst das Dropdown-Design (Issue #36): Eine schwebende
   Fläche hebt sich durch ihren Schatten ab, nicht zusätzlich durch einen
   Strich — mit beidem stand der helle Hinweis als einziges Menü der
   Anwendung in einem Kasten. Der dunkle Hinweis behält seinen Rahmen: Auf
   Tinte trägt er ihn in der eigenen Farbe, dort ist er keine Kante.        */
.tooltip__body[data-zone="surface"],
.tooltip__body[data-zone="surface"]::after {
  background: var(--zone-carrier-bg);
}

.tooltip__body[data-zone="surface"] {
  border: 0;
}

/* Der Zeiger ist ein gedrehtes Quadrat am unteren Rand der Fläche. */
.tooltip__body::after {
  content: "";
  position: absolute;
  inset-block-end: var(--space-1);
  inset-inline-start: 50%;
  inline-size: var(--space-3);
  block-size: var(--space-3);
  background: var(--zone-surface);
  rotate: 45deg;
  translate: -50% 0;
}

.tooltip__title {
  display: block;
  font-weight: 700;
  margin-block-end: var(--space-2);
}

.tooltip-host:hover > .tooltip,
.tooltip-host:focus-within > .tooltip,
.tooltip-host--click[open] > .tooltip {
  visibility: visible;
  opacity: 1;
  translate: -50% 0;
}

/* Klick-Variante: nicht beim Überfahren, nur wenn das details offen ist. */
.tooltip-host--click:not([open]):hover > .tooltip,
.tooltip-host--click:not([open]):focus-within > .tooltip {
  visibility: hidden;
  opacity: 0;
  translate: -50% var(--lift-hover);
}

/* Am Rand: Die Fläche hängt an der Start- oder Endkante des Wirts und
   wächst nach innen, statt mittig aus dem Fenster zu ragen. Der Zeiger
   rückt mit und bleibt unter der Mitte eines Knopfs in Zielgrösse.
   Grundstellung ist immer mittig; die Seite wählt apps/web per Skript
   (TipPlacement), sobald die Mitte über den Fensterrand hinausragte. */
.tooltip--start,
.tooltip--end {
  translate: 0 var(--lift-hover);
}

.tooltip--start {
  inset-inline: 0 auto;
}

.tooltip--end {
  inset-inline: auto 0;
}

.tooltip-host:hover > :is(.tooltip--start, .tooltip--end),
.tooltip-host:focus-within > :is(.tooltip--start, .tooltip--end),
.tooltip-host--click[open] > :is(.tooltip--start, .tooltip--end) {
  translate: 0 0;
}

.tooltip--start .tooltip__body::after {
  inset-inline: calc(var(--size-target-min) / 2) auto;
  translate: -50% 0;
}

.tooltip--end .tooltip__body::after {
  inset-inline: auto calc(var(--size-target-min) / 2);
  translate: 50% 0;
}

.tooltip-host--click[open] {
  z-index: var(--layer-open);
}

/* Ein geschlossenes <details> rendert seinen Inhalt nicht — die Transition
   hätte keinen Ausgangspunkt, die Fläche spränge auf und verschwände
   schlagartig. So bleibt sie gerendert und ist wie beim kurzen Hinweis nur
   über visibility verborgen, auch für Tab und Screenreader. Browser ohne
   ::details-content verwerfen die Regel und zeigen sie ohne Bewegung. */
.tooltip-host--click::details-content {
  content-visibility: visible;
}

/* Fragezeichen statt Hand: Der Klick öffnet Infos, keine Aktion. */
.tooltip-host--click > summary {
  list-style: none;
  cursor: help;
}

.tooltip-host--click > summary::marker,
.tooltip-host--click > summary::-webkit-details-marker {
  display: none;
}

.tooltip__facts,
.tooltip__links {
  display: grid;
  gap: var(--space-1);
}

.tooltip__text {
  display: -webkit-box;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 4;
}

/* Ohne Bewegung erscheint er an Ort und Stelle — sonst bliebe ein Sprung
   statt einer Bewegung übrig. */
@media (prefers-reduced-motion: reduce) {
  .tooltip {
    translate: -50% 0;
    transition: none;
  }

  .tooltip--start,
  .tooltip--end {
    translate: 0 0;
  }
}

/* --- Answer · Ja / Wenn nötig / Nein --------------------------------------- *
 * Das Kernbedienelement der Terminfindung (FA-5.2). Native Radios, nur anders
 * gezeichnet — damit bleiben Tastatur, Screenreader und Pfeiltasten erhalten.
 *
 *   <fieldset class="answer">
 *     <legend class="visually-hidden">Do 12.9. 19:00</legend>
 *     <label class="answer__option">
 *       <input class="visually-hidden" type="radio" name="slot-1" />
 *       <svg class="icon" aria-hidden="true"><use href="#check"></use></svg> Ja
 *     </label>
 *     …
 *   </fieldset>
 *
 * Der gewählte Zustand zeigt sich in Fläche UND Icon UND Fettung — nie in der
 * Farbe allein (FE-33).
 */
.answer {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: 0;
  border: 0;
}

/* Kein flex: 1 — die Optionen sind so breit wie ihr Text. Gleich breite
   Spalten zwaengen „Wenn noetig" in zwei Zeilen. */
.answer__option {
  display: flex;
  align-items: center;
  white-space: nowrap;
  justify-content: center;
  gap: var(--space-2);
  min-block-size: var(--size-button);
  padding-inline: var(--space-3);
  border: var(--border-hairline) solid var(--zone-border-interactive);
  border-radius: var(--radius-button);
  color: var(--zone-text-strong);
  font-size: var(--font-size-small);
  font-weight: 700;
  cursor: pointer;
}

/* Gewaehlt wird nicht ueber Farbe allein erkennbar (FE-33): Die Option wechselt
   von Kontur auf Flaeche — eine Form-, keine Farbunterscheidung. Der Ton kommt
   zusaetzlich dazu und macht die Zusage auf einen Blick lesbar. */
.answer__option:has(:checked) {
  background: var(--answer-fill, var(--zone-action-bg));
  color: var(--answer-text, var(--zone-action-text));
  border-color: transparent;
}

.answer__option--positive {
  --answer-fill: var(--zone-success-bg);
  --answer-text: var(--zone-success-text);
}

.answer__option--caution {
  --answer-fill: var(--zone-warning-bg);
  --answer-text: var(--zone-warning-text);
}

.answer__option--negative {
  --answer-fill: var(--zone-danger-bg);
  --answer-text: var(--zone-danger-text);
}

.answer__option:has(:focus-visible) {
  outline: var(--border-focus) solid var(--zone-focus);
  outline-offset: var(--space-0-5);
}

/* --- Banner · Hinweis, Erfolg, Warnung, Gefahr ----------------------------- *
 * Für Aussagen, die zum Inhalt gehören und bleiben — anders als ein Toast,
 * der vorbeizieht. Etwa FA-4.2d („Adresse verschwindet 24 h nach dem Termin")
 * oder FA-9.15 (Warnhinweis beim Erstkontakt).
 *
 * Die Kante links trägt die Aussage mit: Der Ton ist auch ohne Farbe am Icon
 * und am Text erkennbar (WCAG 1.4.1).
 */
/* Das Icon sitzt auf der Mitte der ERSTEN Zeile, nicht an der Oberkante des
   Kastens: (Zeilenhoehe minus Icon) / 2. Die lh-Einheit rechnet das selbst,
   auch wenn sich Schriftgroesse oder Zeilenhoehe aendern. */
.banner > .icon {
  block-size: 1lh;
}

.banner {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);

  /* Platz fuer den Balken — der Text steht genauso weit innen wie vorher. */
  padding-inline-start: calc(var(--space-4) + var(--border-indicator));
  border-radius: var(--radius-control);
  background: var(--banner-bg);
  color: var(--banner-tx);
  font-size: var(--font-size-small);
}

/* Die Kante links ist ein gerader Balken ueber die ganze Hoehe, kein Rand:
   Ein Rand folgt dem Radius und rundet sich an den Ecken weg (Issue #69).
   Dieselbe Marke wie am Modultitel — aussen gerade, innen rund —, nur hoch. */
.banner::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  inline-size: var(--border-indicator);
  border-radius: 0 var(--radius-indicator) var(--radius-indicator) 0;
  background: currentcolor;
}

.banner--info {
  --banner-tx: var(--pill-neutral-tx);
  --banner-bg: var(--pill-neutral-bg);
}
.banner--success {
  --banner-tx: var(--pill-success-tx);
  --banner-bg: var(--pill-success-bg);
}
.banner--warning {
  --banner-tx: var(--pill-warning-tx);
  --banner-bg: var(--pill-warning-bg);
}
.banner--danger {
  --banner-tx: var(--pill-danger-tx);
  --banner-bg: var(--pill-danger-bg);
}

.banner__title {
  display: block;
  font-weight: 700;
}

/* --- Empty · Leerzustand -------------------------------------------------- *
 * § 10.2 verlangt ihn für jeden Screen. Er ist kein Fehler, sondern eine
 * Einladung: Er sagt, was hier stehen wird, und bietet den nächsten Schritt an.
 */
.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-6) var(--space-4);
  text-align: center;
  color: var(--zone-text-quiet);
}

.empty > .icon {
  --size-icon: var(--size-cover);

  color: var(--icon-tone, var(--zone-border-interactive));
}

.empty__title {
  font-size: var(--font-size-body);
  font-weight: 700;
  color: var(--zone-text-strong);
}

.empty__text {
  max-inline-size: 44ch;
}

/* --- Steps · Assistent ---------------------------------------------------- *
 * „Spielrunde erstellen" läuft über vier Schritte (Wireframe E). Als <ol> mit
 * aria-current="step" — die Reihenfolge ist Inhalt, nicht Dekoration.
 */
.steps {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  padding: 0;
  margin: 0;
  list-style: none;
}

.steps__item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--zone-text-quiet);
  font-size: var(--font-size-small);
}

.steps__marker {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--size-step-marker);
  block-size: var(--size-step-marker);
  border: var(--border-hairline) solid var(--zone-border-interactive);
  border-radius: var(--radius-full);
  font-weight: 700;
  flex: none;
}

/* Erledigt und aktuell unterscheiden sich in Fläche, Icon und Fettung. */
.steps__item[data-state="done"] {
  color: var(--zone-text-strong);
}

/* Erledigt ist ein positives Signal — grün, nicht tintenfarben. Die Fläche und
   das Häkchen tragen den Zustand weiterhin allein, die Farbe kommt dazu. */
.steps__item[data-state="done"] .steps__marker {
  background: var(--zone-success-bg);
  color: var(--zone-success-text);
  border-color: transparent;
}

.steps__item[aria-current="step"] {
  color: var(--zone-text-strong);
  font-weight: 700;
}

.steps__item[aria-current="step"] .steps__marker {
  border-color: var(--zone-text-strong);
  border-width: var(--border-spinner);
}

/* --- Stat · Kennzahl ------------------------------------------------------ *
 * „Brettabend in Zahlen" (FA-6.9, Wireframe G). Nur Summen — nie Namen, nie
 * Orte, keine Region unter fünf Spielrunden (FA-6.12).
 */
.stat__value {
  display: block;
  font-size: var(--font-size-stat);
  font-weight: 700;
  line-height: 1.1;
  color: var(--zone-text-strong);
}

.stat__label {
  display: block;
  margin-block-start: var(--space-2);
  font-size: var(--font-size-small);
  color: var(--zone-text-quiet);
}

/* --- Tabbar · mobile Navigation ------------------------------------------- *
 * Pflichtenheft § 10.5: 75 × 60 px je Eintrag. Beschriftung immer sichtbar,
 * nie nur ein Icon (FE-34). Aktiv = gefülltes Icon UND Fettung UND Fläche.
 */
.tabbar {
  display: flex;
  justify-content: space-around;
  padding-block: var(--space-1);
  background: var(--zone-surface);
  border-block-start: var(--border-hairline) solid var(--zone-border);
}

/* `flex: 0 1` statt fester Breite: Fuenf Eintraege zu 75 px sind 375 px, und
   es gibt Geraete mit 360 px. Dann ruecken sie zusammen, statt hinauszulaufen
   — die Zielgroesse aus NFA-18 bleibt dabei erhalten. */
.tabbar__item {
  display: flex;
  flex: 0 1 var(--size-tabbar-item);
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-inline-size: 0;
  inline-size: var(--size-tabbar-item);
  block-size: var(--size-tabbar);
  border-radius: var(--radius-control);
  color: var(--zone-text-quiet);
  font-size: var(--font-size-eyebrow);
  text-decoration: none;
  cursor: pointer;
  list-style: none; /* auch als <summary> ohne Dreieck */
}

.tabbar__item::-webkit-details-marker {
  display: none;
}

.tabbar__label {
  max-inline-size: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tabbar__item > .icon {
  --size-icon: var(--size-icon-nav);
}

.tabbar__item[aria-current] {
  background: var(--bg-sunken);
  color: var(--zone-text-strong);
  font-weight: 700;
}

/* --- List · Aufzählungen --------------------------------------------------- *
 * Punkte und Zahlen bleiben native Listenmarken (::marker) — damit sagt ein
 * Screenreader „Liste mit drei Einträgen" an. Nachgebaute Punkte tun das nicht.
 */
.list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-inline-start: var(--space-5);
  margin: 0;
}

.list li::marker {
  color: var(--zone-text-quiet);
}

.list--bullet {
  list-style: disc;
}

.list--number {
  list-style: decimal;
}

/* Mit Icon statt Marke: Die Marke entfällt, das Icon steht im Markup — nur so
   kann es je Eintrag ein anderes sein. */
.list--icon {
  padding-inline-start: 0;
  list-style: none;
}

.list--icon > li {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}

/* Der Icon-Kasten ist genau eine Zeile hoch; das SVG zentriert sich darin von
   selbst (preserveAspectRatio). Das sitzt auch dann richtig, wenn die Zeile
   umbricht — anders als ein geschaetzter Abstand von oben. */
.list--icon > li > .icon {
  block-size: 1lh;
  color: var(--icon-tone, var(--zone-text-strong));
}

/* --- Data list · Angabe und Wert ------------------------------------------ *
 * Für Details der Spielrunde: Ort, Zeit, Kapazität, Sprache, Mindestalter (FA-4.1).
 * Als <dl> — die Zuordnung von Angabe zu Wert ist Inhalt, kein Layout.
 */
.data-list {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-2) var(--space-4);
  margin: 0;
}

/* Die Angabe kann ein Icon tragen — es steht im Markup, weil es je Zeile ein
   anderes ist. Gleiche Behandlung wie in der Icon-Liste. */
.data-list dt {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  color: var(--zone-text-quiet);
}

.data-list dt > .icon {
  block-size: 1lh;
  color: var(--icon-tone, var(--zone-text-strong));
}

.data-list dd {
  margin: 0;
  font-weight: 700;
  color: var(--zone-text-strong);
}

/* --- Accordion ------------------------------------------------------------ *
 * Als <details>/<summary>. Damit sind Tastatur, Screenreader und der Zustand
 * „auf/zu" ohne eine Zeile JavaScript richtig.
 *
 * Mit gleichem name-Attribut schliessen sich die Einträge gegenseitig aus —
 * ein Akkordeon im Wortsinn, seit 2024 ohne JavaScript möglich.
 */
.accordion {
  border-block-end: var(--border-hairline) solid var(--zone-border);
}

.accordion > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-block-size: var(--size-target-min);
  padding-block: var(--space-2);
  font-weight: 700;
  color: var(--zone-text-strong);
  cursor: pointer;
  list-style: none;
}

.accordion > summary::-webkit-details-marker {
  display: none;
}

.accordion > summary > .icon {
  color: var(--zone-text-quiet);
  transition: rotate var(--motion-fast) var(--motion-ease);
  flex: none;
}

.accordion[open] > summary > .icon {
  rotate: 180deg;
}

.accordion__body {
  padding-block-end: var(--space-3);
  color: var(--zone-text-quiet);
}

@media (prefers-reduced-motion: reduce) {
  .accordion > summary > .icon {
    transition: none;
  }
}

/* --- Tabs ----------------------------------------------------------------- *
 * Karte/Liste (Wireframe C), Chart-Schnitte (FA-1.18).
 *
 * Darunter liegen Radios, keine role="tab"-Konstruktion: Eine echte Tabliste
 * verlangt Pfeiltastensteuerung und damit JavaScript. Eine Radiogruppe ist
 * ohne JavaScript vollständig bedienbar und für Screenreader korrekt — sie
 * heisst dann eben „Auswahl" statt „Registerkarte". Das ist der ehrlichere
 * Kompromiss als eine Tabliste, die nur so aussieht.
 */
.tabs {
  display: flex;
  gap: var(--space-1);
  padding: var(--space-1);
  border: 0;
  border-radius: var(--radius-button);
  background: var(--bg-sunken);
}

.tabs__tab {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-block-size: var(--size-button);
  padding-inline: var(--space-4);
  border-radius: var(--radius-control);
  color: var(--zone-text-quiet);
  font-size: var(--font-size-small);
  cursor: pointer;
}

.tabs__tab:has(:checked) {
  background: var(--zone-carrier-bg);
  color: var(--zone-carrier-text);
  font-weight: 700;
  box-shadow: 0 var(--border-hairline) 0 0 var(--zone-border);
}

.tabs__tab:has(:focus-visible) {
  outline: var(--border-focus) solid var(--zone-focus);
  outline-offset: var(--space-0-5);
}

/* --- Filterleiste --------------------------------------------------------- *
 * Die Felder sind zugeklappt, bis jemand sie aufmacht (Issue #26), und sie
 * klappen auf und zu, statt zu springen. Zu steht hier, WAS eingestellt ist —
 * der Name der gemerkten Suche oder eine kurze Aufzaehlung — und ist nichts
 * eingestellt, steht dort nichts. Der Knopf, das Speichersymbol und die
 * gemerkten Suchen stehen zusammen in der Kopfzeile (.button-group).       */
/* `display: contents` statt eines eigenen Kastens: Die Leiste ist eine
   Klammer um drei Teile, keine Flaeche. Als Kasten stuende sie auch dann im
   Abschnitt, wenn sie nichts zeigt — null Pixel hoch, aber mit dem Abstand
   des Stapels davor und dahinter. Ihre Teile ordnen sich jetzt direkt im
   Abschnitt ein und tragen dessen Abstand.                               */
.filter-bar {
  display: contents;
}

.filter-bar__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

/* Was gerade gilt: je Wert ein Badge, nicht eine Aufzaehlung mit Punkten.
   Steht nur, wenn die Felder zu sind. */
.filter-bar__state {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
}

/* Traegt die Zeile nichts, nimmt sie auch keinen Platz: Ein leerer Kasten
   zoege sonst den Abstand der Leiste mit und schoebe die Liste nach unten. */
.filter-bar__head:empty {
  display: none;
}

/* Der Name der geladenen Suche am Knopf: Gekuerzt wird in Zeichen (siehe
   SEARCH_ALERT_LABEL_MAX), hier bleibt nur zu verhindern, dass er umbricht —
   ein Knopf mit zwei Zeilen waere kein Knopf mehr. */
.search-alerts__label {
  white-space: nowrap;

  /* Gekuerzt wird in Zeichen (SEARCH_ALERT_LABEL_MAX). Wird der Platz noch
     enger — am Handy neben „Filter" —, gibt das Etikett weiter nach, statt
     den Knopf ueber den Rand zu schieben. */
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Eine Zeile im Menue der gemerkten Suchen: der Weg zurueck und das Kreuz,
   das sie vergisst. Der Eintrag nimmt den Platz, das Kreuz nur sich selbst. */
.search-alerts__row {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.search-alerts__row > .dropdown__item {
  flex: 1;
  min-inline-size: 0;
}

/* Die Felder klappen auf und zu, statt zu springen. Sie bleiben dafuer IMMER
   im Baum: Waeren sie zugeklappt weg, gaebe es nichts zu bewegen. Die Strecke
   faehrt `grid-template-rows` von 0fr auf 1fr — die einzige Art, eine
   unbekannte Hoehe ohne Skript und ohne feste Zahl zu bewegen.

   Am Ende des Zuklappens faellt das Feld auf `display: none` (eine Stufe,
   `allow-discrete`, deshalb `step-end` hin und `step-start` zurueck). Sonst
   bliebe eine Flaeche von null Hoehe im Fluss stehen — und der Abstand der
   Liste zaehlte sie mit: ein Streifen Luft ohne Inhalt.                   */
.filter-bar__panel {
  display: none;
  grid-template-rows: 0fr;
  opacity: 0;
  transition:
    grid-template-rows var(--motion-panel) var(--motion-ease),
    opacity var(--motion-panel) var(--motion-ease),
    display var(--motion-panel) step-end allow-discrete;
}

.filter-bar__panel[data-open="true"] {
  display: grid;
  grid-template-rows: 1fr;
  opacity: 1;
  transition:
    grid-template-rows var(--motion-panel) var(--motion-ease),
    opacity var(--motion-panel) var(--motion-ease),
    display var(--motion-panel) step-start allow-discrete;
}

/* Beim Aufklappen faengt die Strecke bei null an — ohne diesen Anfangswert
   waere das Feld beim ersten Bild schon offen (es kam aus `display: none`). */
@starting-style {
  .filter-bar__panel[data-open="true"] {
    grid-template-rows: 0fr;
    opacity: 0;
  }
}

/* Das Kind muss schrumpfen duerfen — sonst haelt seine Mindesthoehe die Zeile
   auf und es klappt nichts zu.

   Zugeklappt schneidet es ab, offen laesst es ueberlaufen: Sonst liegen die
   Menues der Felder (Umkreis, Zeitraum, Spiel) hinter dem Rand. Der Wechsel
   ist eine Stufe, kein Verlauf — und er faellt in jede Richtung anders:
   beim Aufklappen ans Ende (`step-end`, vorher wuerde der Inhalt ueber den
   wachsenden Rand haengen), beim Zuklappen an den Anfang (`step-start`).
   Die Zeitfunktion kommt dabei immer aus der ZIEL-Regel.               */
.filter-bar__fields {
  min-block-size: 0;
  overflow: hidden;
  transition: overflow var(--motion-panel) step-start allow-discrete;
}

.filter-bar__panel[data-open="true"] > .filter-bar__fields {
  overflow: visible;
  transition: overflow var(--motion-panel) step-end allow-discrete;
}

/* Das Namensfeld im Fenster „Diese Suche merken": Beschriftung ueber dem
   Feld, das Feld ueber die ganze Breite — ein Name ist keine Zahl. */
.search-alerts__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.search-alerts__form > .input {
  inline-size: 100%;
}

/* Ein leiser Satz statt einer Liste: im Fenster, wenn das Hoechstmass
   erreicht ist; im Menue, wenn noch nichts gemerkt wurde. */
.search-alerts__hint {
  margin: 0;
  color: var(--zone-text-quiet);
}

/* Im Menue traegt er dessen Rand — sonst klebt er an der Kante. */
.dropdown__menu > .search-alerts__hint {
  padding-inline: var(--space-3);
  padding-block: var(--space-2);
  max-inline-size: var(--size-dropdown-min);
  font-size: var(--font-size-small);
}

@media (prefers-reduced-motion: reduce) {
  .filter-bar__panel,
  .filter-bar__panel[data-open="true"],
  .filter-bar__fields,
  .filter-bar__panel[data-open="true"] > .filter-bar__fields {
    transition: none;
  }
}

/* --- Breadcrumb ----------------------------------------------------------- */
.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding: 0;
  margin: 0;
  list-style: none;
  font-size: var(--font-size-small);
  color: var(--zone-text-quiet);
}

/* Die Trennzeichen und ein etwaiges Icon stehen im Markup; der Abstand kommt
   aus dem gap oben — eine eigene Regel auf .icon liefe sonst der Spezifität
   des Akkordeons hinterher (K-24). */
.breadcrumb a {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: inherit;
}

.breadcrumb [aria-current] {
  color: var(--zone-text-strong);
  font-weight: 700;
}

/* --- Pagination ----------------------------------------------------------- *
 * NFA-13: Seiten sind verlinkbar und serverseitig gerendert — sonst ist der
 * Inhalt ab Seite 2 nicht indexierbar.
 */
/* Statt Seitenzahlen: ein Knopf, der den nächsten Stapel holt (Issue #67).
   Steht wie die Seitenzahlen mittig unter der Liste — wer am Ende ankommt,
   findet ihn dort, wo vorher geblaettert wurde. */
.listing-more {
  display: flex;
  justify-content: center;
  margin: 0;
}

.pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center; /* mittig unter der Liste, die sie blättert */
  gap: var(--space-1);
  padding: 0;
  margin: 0;
  list-style: none;
}

/* Nur die direkten Kinder eines Eintrags — ein Tooltip im Eintrag (der
   Pfeil trägt seine Beschriftung so) darf kein Kästchen werden. Der Wirt
   des Tooltips ist das li selbst, kein zusätzlicher Wrapper. */
.pagination > li > a,
.pagination > li > span:not(.tooltip) {
  display: flex;
  align-items: center;
  justify-content: center;
  min-inline-size: var(--size-page-item);
  min-block-size: var(--size-page-item);
  padding-inline: var(--space-2);
  border-radius: var(--radius-control);
  color: var(--zone-text-strong);
  text-decoration: none;
}

/* Die aktuelle Seite ist bereits gefuellt — ein Hover-Grund darauf wuerde den
   hellen Text auf hellen Grund setzen. */
.pagination a:not([aria-current]):hover {
  background: var(--bg-sunken);
}

.pagination [aria-current] {
  background: var(--zone-action-bg);
  color: var(--zone-action-text);
  font-weight: 700;
}

/* --- Dialog --------------------------------------------------------------- *
 * Das native <dialog>. showModal() bringt Fokusfang, Esc und inert für den
 * Rest der Seite mit — alles, was ein nachgebauter Dialog mühsam braucht.
 */
.dialog {
  /* Der Reset setzt jedem Element `margin: 0` — damit verliert das native
     <dialog> das `margin: auto`, mit dem der Browser es mittig stellt. Ohne
     diese Zeile klebt jedes Fenster oben links in der Ecke. */
  margin: auto;
  max-inline-size: var(--size-dialog-max);
  padding: var(--space-5);
  border: 0;
  border-radius: var(--radius-card);
  background: var(--bg-surface);
  color: var(--text-primary);
  box-shadow: 0 var(--shadow-offset-float) var(--shadow-blur-float) 0
    var(--shadow-control);
}

.dialog::backdrop {
  background: var(--backdrop);
}

/* Ohne Skript kann niemand showModal() rufen: Dann steht das Fenster offen im
   Fluss, wo es steht — mit Kante, damit es als eigene Flaeche lesbar bleibt.
   Mit Skript kommt diese Klasse nie zum Einsatz.                          */
.dialog--inline {
  position: static;
  inline-size: 100%;
  border: var(--border-hairline) solid var(--zone-border);
}

.dialog__title {
  font-size: var(--font-size-title);
  font-weight: 700;
  margin-block-end: var(--space-3);
}

.dialog__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-block-start: var(--space-5);
}

/* --- Toast ---------------------------------------------------------------- *
 * Kurze Bestätigung, zieht vorbei. Der Bereich liegt fest am unteren Rand;
 * die Meldungen selbst stapeln sich darin.
 *
 * Der Bereich trägt aria-live="polite" — sonst bleibt die Meldung für einen
 * Screenreader unsichtbar. Und „polite", nicht „assertive": Eine Bestätigung
 * darf keine laufende Vorlesung unterbrechen.
 */
.toast-area {
  position: fixed;
  inset-block-end: var(--space-5);
  inset-inline: var(--space-5);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  pointer-events: none;
}

.toast {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  max-inline-size: var(--size-tooltip-max-lg);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-control);
  font-size: var(--font-size-small);
  pointer-events: auto;
  box-shadow: 0 var(--shadow-offset-float) var(--shadow-blur-float) 0
    var(--shadow-control);
}

/* Ein Toast bestätigt meistens etwas — dann trägt er den Positivton. Der
   Grundton bleibt die Zone, damit der Schatten und der Rahmen stimmen. */
.toast--success {
  background: var(--zone-success-bg);
  color: var(--zone-success-text);
}

.toast--danger {
  background: var(--zone-danger-bg);
  color: var(--zone-danger-text);
}

/* --- Ring · Fortschritt als Kreis ----------------------------------------- *
 * Ein Wert zwischen 0 und 100 %, gesetzt über --ring-progress. Groß im
 * Dashboard, klein in der Zeile — dieselbe Regel, zwei Größen.
 *
 * Das Loch in der Mitte ist ein ::before in der Zonenfarbe, kein Verlauf mit
 * transparent: So bleibt die Kante sauber und der Ring funktioniert auf jedem
 * Grund. Liegt der Ring auf einer abweichenden Fläche, setzt die Komponente
 * --ring-hole.                                                               */
.ring {
  --ring-size: var(--size-ring);
  --ring-track: var(--size-ring-track);
  --ring-progress: 0%;
  --ring-hole: var(--zone-surface);
  --size-icon: calc(var(--ring-size) / 3);
  position: relative;
  display: grid;
  place-items: center;
  inline-size: var(--ring-size);
  aspect-ratio: var(--ratio-image);
  border-radius: var(--radius-full);
  background: conic-gradient(
    var(--zone-text-success) var(--ring-progress),
    var(--zone-track) 0
  );
  flex: none;
}

.ring::before {
  content: "";
  position: absolute;
  inset: var(--ring-track);
  border-radius: inherit;
  background: var(--ring-hole);
}

/* position sorgt für die Malreihenfolge: Inhalt über dem Loch. */
.ring > * {
  position: relative;
}

.ring--sm {
  --ring-size: var(--size-ring-sm);
  --ring-track: var(--size-ring-track-sm);
}

/* --- Level · Punktestand mit Rang und Stufe ------------------------------- *
 * Die Dashboard-Ansicht auf der eigenen Profilseite. FA-3.25: Die Punktzahl
 * ist privat, der Rang öffentlich — deshalb steht die Zahl NUR hier. Für die
 * öffentliche Anzeige gibt es .rank, das keine Zahl kennt.                   */
.level {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  flex-wrap: wrap;
}

.level__body {
  min-inline-size: 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.level__value {
  font-size: var(--font-size-stat);
  font-weight: 700;
  line-height: 1.1;
  color: var(--zone-text-strong);
}

.level__rank {
  font-size: var(--font-size-body);
  font-weight: 700;
  color: var(--zone-text-strong);
}

.level__hint {
  font-size: var(--font-size-small);
  color: var(--zone-text-quiet);
}

/* --- Rank · derselbe Stand in klein --------------------------------------- *
 * Neben dem Namen, in Listenzeilen, im Profilkopf. Zeigt Rang und Fortschritt,
 * nie die Punktzahl. Die Marke ist ein .emblem--sm — derselbe Rahmen wie beim
 * Abzeichen, nur kleiner.                                                    */
.rank {
  display: inline-flex;
  align-items: center;
  gap: var(--chip-gap);
  font-size: var(--font-size-small);
  font-weight: 700;
  color: var(--zone-text-strong);
}

/* --- Emblem · der gemeinsame Rahmen --------------------------------------- *
 * Abzeichen, Stufen und Ränge zeigen alle dasselbe: etwas, das man sich
 * verdient hat. Deshalb EIN Baustein und eine Form — das Sechseck.
 *
 * Warum sechseckig: Das Hexfeld ist die Form des Spielbretts. Alles, was man
 * sich erspielt, trägt sie; der Kreis gehört den Menschen (Avatar, Statuspunkt)
 * und dem Fortschritt (.ring). Damit ist „welche Form?" beantwortbar, ohne
 * dass jemand Geschmack bemühen muss (FE-74).
 *
 * Die Form ist ein Polygon und keine `clip-path: shape()`: Ein nicht
 * verstandenes clip-path bedeutet GAR KEIN Beschnitt, also ein Quadrat — nicht
 * ein Sechseck mit harten Ecken. Firefox kennt shape() nicht. Das Polygon
 * NICHT von Hand ändern, `python3 scripts/build-hexagon.py` erzeugt es neu
 * (FE-73).
 *
 * Die Fläche liegt in einem ::before und nicht auf dem Element selbst, weil
 * clip-path sonst auch die Schleife abschneiden würde — die ragt absichtlich
 * über die Kante hinaus.                                                     */
.emblem {
  --emblem-tone: var(--zone-text-strong);
  --emblem-ground: var(--zone-carrier-bg);

  /* Das Material: normalerweise der Ton, bei den beiden höchsten Stufen ein
     Metallverlauf. Eine `color` kann kein Metall sein, deshalb eine eigene
     Eigenschaft. */
  --emblem-material: var(--emblem-tone);

  /* Was den Rahmen malt. Normalerweise das Material — die Fassung mit
     Fortschritt setzt hier einen Verlauf ein. */
  --emblem-frame: var(--emblem-material);

  /* Rahmenbreite als ANTEIL der Kantenlänge, nicht in Pixeln: So skaliert das
     Wappen als Ganzes (FE-75). Der Haarstrich ist die Untergrenze, sonst
     verschwände der Rahmen in der kleinen Fassung. */
  --emblem-weight: 0.028;

  /* Die untere Gruppe — Schleife und Fortschritt — teilt sich Lage und
     Breite, damit die Leiste exakt unter dem Band sitzt und genauso breit
     ist. Beides Anteile der Kantenlänge (FE-75). */
  --ribbon-bottom: 7%;
  --ribbon-inline: calc(-1 * var(--size-emblem) * 0.085);
  --ribbon-progress: calc(var(--size-emblem) * 0.05);

  /* Nur gesetzt, wo der Stand gezeigt werden DARF (FE-79) — die Komponente
     kennt die Regel nicht, sie bekommt einfach keinen Wert. */
  --emblem-progress: 0%;

  /* Der Fortschritt trägt die Farbe DES WAPPENS, aufgehellt: nicht das Grün
     der Signalfarben — das gehört nicht zur Auszeichnung — und nicht den
     vollen Ton des Bandes, sonst läse er sich als dessen Fortsetzung. Auf
     Gold wird er golden, auf Platin silbrig, von selbst. */
  --emblem-progress-fill: color-mix(
    in oklab,
    var(--emblem-tone) 62%,
    var(--emblem-ground)
  );
  --emblem-frame-progress: conic-gradient(
    var(--emblem-progress-fill) var(--emblem-progress),
    var(--zone-track) 0
  );
  --emblem-inset: max(
    var(--border-hairline),
    calc(var(--size-emblem) * var(--emblem-weight))
  );
  --size-icon: calc(var(--size-emblem) / 2.4);
  --emblem-shape: polygon(
    56.1% 3.5%,
    53% 2.2%,
    50% 1.8%,
    47% 2.2%,
    43.9% 3.5%,
    12.8% 21.5%,
    10.1% 23.5%,
    8.2% 25.9%,
    7.1% 28.7%,
    6.7% 32%,
    6.7% 68%,
    7.1% 71.3%,
    8.2% 74.1%,
    10.1% 76.5%,
    12.8% 78.5%,
    43.9% 96.5%,
    47% 97.8%,
    50% 98.2%,
    53% 97.8%,
    56.1% 96.5%,
    87.2% 78.5%,
    89.9% 76.5%,
    91.8% 74.1%,
    92.9% 71.3%,
    93.3% 68%,
    93.3% 32%,
    92.9% 28.7%,
    91.8% 25.9%,
    89.9% 23.5%,
    87.2% 21.5%
  );
  position: relative;
  display: grid;
  place-items: center;
  inline-size: var(--size-emblem);
  aspect-ratio: var(--ratio-image);
  padding: var(--emblem-inset);
  color: var(--emblem-tone);
  flex: none;
}

/* Zwei Ebenen ergeben den Rahmen: das äußere Sechseck im Ton, darüber das
   innere im Grund. Der Unterschied ist genau --emblem-inset breit — eine
   Linie, kein Doppelring. */
.emblem::before,
.emblem::after {
  content: "";
  position: absolute;
  clip-path: var(--emblem-shape);
}

.emblem::before {
  inset: 0;
  background: var(--emblem-frame);
}

.emblem::after {
  inset: var(--emblem-inset);
  background: var(--emblem-ground);
}

/* z-index, nicht nur position: ::after gilt als letztes Kind und läge sonst
   über dem Inhalt. */
.emblem > * {
  position: relative;
  z-index: 1;
}

/* --- Emblem mit Cover ----------------------------------------------------- *
 * Für die Spielabzeichen: Das Cover steckt IM Rahmen, nicht dahinter. Als
 * eigenes <img> und nicht als background-image, damit width/height,
 * loading="lazy" und der Platzhalter greifen (FE-58).
 *
 * Hat ein Spiel kein Cover, steht ein Icon drin — das ist kein Sonderfall,
 * sondern der Normalfall bei einer Datenbank, die aus Beiträgen wächst.     */
/* Das Cover steckt IM Rahmen. Als eigenes <img> und nicht als background-image,
   damit width/height, loading="lazy" und der Platzhalter greifen (FE-58).
   Derselbe Beschnitt wie die Fläche — nur auf den kleineren Inhaltskasten
   angewandt, deshalb entsteht der Rahmen ganz von selbst. */
.emblem__image {
  display: block; /* auch als <picture>, das sonst inline bliebe */
  inline-size: 100%;
  block-size: 100%;
  clip-path: var(--emblem-shape);
  object-fit: cover;
  background: var(--bg-sunken);
}

/* Die Anwendung liefert das Cover als <picture> mit WebP und JPG (ADR 0008) —
   dann liegt das Bild eine Ebene tiefer. Der Beschnitt sitzt am <picture>,
   die Fuellung am Bild darin; beide Fassungen sitzen damit gleich.          */
.emblem__image > img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

/* --- Die sechs Stufen ------------------------------------------------------ *
 * Die Stufe hing an der RAHMENSTÄRKE. Das trägt nicht: Zwischen 0,9 px und
 * 1,3 px sieht niemand einen Unterschied, und genau dort landet die Skala,
 * sobald das Wappen in eine schmale Spalte rutscht. Außerdem kollidierte sie
 * mit dem Fortschritt, der denselben Rahmen braucht.
 *
 * Jetzt tragen es drei Dinge, die alle mitskalieren:
 *   1. die ZIFFER am Scheitel — immer da, auch ohne Schleife,
 *   2. das WORT in der Schleife, solange Platz ist,
 *   3. das MATERIAL bei den beiden höchsten — Platin und Gold.
 * Stufe I bis IV sehen bewusst gleich aus. Sie sind der Alltag.             */

/* --- Ziffer im eigenen Sechseck -------------------------------------------- *
 * Trägt die Stufe dort, wo KEIN Band liegt — bei den Abzeichen der Familien
 * A–E. Ein kleines Sechseck in der unteren Ecke, gefüllt mit dem Material des
 * großen: auf Gold golden, auf Platin silbrig, von selbst.
 *
 * Dieselbe --emblem-shape wie das große Wappen, nur auf den kleineren Kasten
 * angewandt — die Form muss nirgends zweimal stehen.                        */
.emblem__tier {
  position: absolute;
  inset-block-end: 0;
  inset-inline-start: 50%;
  translate: -50% 0;
  display: grid;
  place-items: center;
  inline-size: calc(var(--size-emblem) * 0.34);
  aspect-ratio: var(--ratio-image);
  clip-path: var(--emblem-shape);
  background: var(--emblem-material);
  color: var(--emblem-ground);
  font-size: calc(var(--size-emblem) * 0.125);
  font-weight: 700; /* schwerster ausgelieferter Schnitt — siehe fonts.css */
  line-height: 1;
}

/* Das Icon rückt nach oben, damit es nicht auf der Ziffer sitzt. */
.emblem:has(.emblem__tier) {
  --size-icon: calc(var(--size-emblem) / 2.8);
  padding-block-end: calc(var(--emblem-inset) + var(--size-emblem) * 0.17);
}

/* Platin und Gold liegen in OKLCH auf den Achsen von Indigo (285°) und der
   Warnfarbe (88°) — beide gehören damit zur Palette. Kühl gegen warm ist der
   Unterschied, den man auch ohne Beschriftung sieht. */
.emblem--tier-5 {
  --emblem-tone: var(--platinum-ink);
  --emblem-ground: var(--platinum-ground);
  --emblem-material: linear-gradient(
    145deg,
    var(--platinum-shade) 0%,
    var(--platinum-sheen) 34%,
    var(--platinum-face) 44%,
    var(--platinum-ink) 74%,
    var(--platinum-shade) 100%
  );
}

/* Der Glanz ist ein Verlauf: eine helle Kante, die schräg über die dunkle
   Fläche läuft — kein Bild, kein Filter. Der Ton für Ziffer und Schleife
   bleibt eine echte Farbe, denn Text auf einem Verlauf lässt sich nicht
   rechnen (FE-77). */
.emblem--tier-6 {
  --emblem-tone: var(--gold-ink);
  --emblem-ground: var(--gold-ground);
  --emblem-material: linear-gradient(
    145deg,
    var(--gold-shade) 0%,
    var(--gold-sheen) 34%,
    var(--gold-face) 44%,
    var(--gold-ink) 74%,
    var(--gold-shade) 100%
  );
}

/* --- Schleife ------------------------------------------------------------- *
 * Trägt das Schlagwort — „Profi", „Meister", „Legende". Sie ist absichtlich
 * breiter als das Wappen und ragt an beiden Seiten heraus; die Kerben an den
 * Enden machen daraus ein Band statt eines Balkens.                          */
.emblem__ribbon {
  /* Auch die Schleife ist ein Anteil der Kantenlänge — Kerbe, Überstand und
     Schriftgrad wachsen mit dem Wappen (FE-75). */
  --ribbon-notch: calc(var(--size-emblem) * 0.073);
  position: absolute;
  inset-block-end: var(--ribbon-bottom);
  inset-inline: var(--ribbon-inline);
  padding-block: calc(var(--size-emblem) * 0.02);

  /* Der Innenabstand hält den Text aus den Kerben heraus — „Großmeister" ist
     lang genug, dass es sonst hineinliefe. */
  padding-inline: var(--ribbon-notch);

  /* Solide Fläche, kein Verlauf: Text auf einem Verlauf hat keinen
     rechenbaren Kontrast. */
  background: var(--emblem-tone);
  color: var(--emblem-ground);
  clip-path: polygon(
    0 0,
    100% 0,
    calc(100% - var(--ribbon-notch)) 50%,
    100% 100%,
    0 100%,
    var(--ribbon-notch) 50%
  );
  /* Ohne Untergrenze: Der Text soll wirklich mitskalieren, sonst sprengt er
     bei kleinen Wappen das Band. */
  font-size: calc(var(--size-emblem) * 0.115);
  font-weight: 700;
  text-transform: uppercase;
  line-height: 1.5;
  text-align: center;
}

/* Wird der Platz zu schmal, fällt die Schleife weg: Sie ragt über das Wappen
   hinaus, und „Großmeister" braucht nun einmal eine Mindestbreite. Zulässig
   nur, weil die Stufe doppelt steht — im Titel und in der Beizeile (FE-76).

   Die Bedingung fragt den CONTAINER, nicht das Fenster. Ein Abzeichen in einer
   schmalen Spalte verliert die Schleife auch auf einem großen Bildschirm.
   Die Ziffer am Scheitel bleibt — das Wappen ist also auch ohne Band
   zuzuordnen. Dann rückt der Fortschritt an ihre Stelle. */
/* --- Fortschritt unter dem Band -------------------------------------------- *
 * Teil des Wappens, nicht ein Element daneben: dieselbe Breite wie die
 * Schleife, direkt darunter angedockt. Beide lesen --ribbon-inline und
 * --ribbon-bottom, deshalb können sie gar nicht auseinanderlaufen.         */
.emblem__progress {
  position: absolute;
  inset-inline: var(--ribbon-inline);
  inset-block-end: calc(var(--ribbon-bottom) - var(--ribbon-progress));
  block-size: var(--ribbon-progress);
  background: var(--zone-track);
  overflow: hidden;
}

.emblem__progress::before {
  content: "";
  display: block;
  inline-size: var(--emblem-progress);
  block-size: 100%;

  /* Nicht der volle Ton des Bandes — sonst sieht die Leiste aus wie dessen
     Fortsetzung statt wie eine eigene Aussage. */
  background: var(--emblem-progress-fill);
}

@container badge (width < 6rem) {
  .emblem__ribbon {
    display: none;
  }

  /* Ohne Band gibt es auch keine Leiste darunter — den Stand übernimmt der
     Rahmen, genau wie bei den einfachen Wappen. */
  .emblem__progress {
    display: none;
  }

  .emblem:has(.emblem__progress) {
    --emblem-weight: 0.07;
    --emblem-frame: var(--emblem-frame-progress);
  }
}

/* --- Fortschritt im Rahmen ------------------------------------------------- *
 * Für die einfachen Wappen — die ohne Band. Der Rahmen füllt sich selbst, ein
 * conic-gradient, vom Sechseck beschnitten. Dieselbe Formensprache, kein
 * zusätzliches Element, und nichts, was aus der Silhouette ragt.            */
.emblem--progress {
  --emblem-weight: 0.07; /* kräftiger, sonst ist der Fortschritt ein Strich */
  --emblem-frame: var(--emblem-frame-progress);
}

.emblem--sm {
  --size-emblem: var(--size-emblem-sm);
}

/* Am Avatar: kleiner Kasten, das Icon aber relativ größer, sonst bleibt
   nur der Rahmen übrig. */
.emblem--xs {
  --size-emblem: var(--size-emblem-xs);
  --size-icon: calc(var(--size-emblem) / 1.65);
}

.emblem--lg {
  --size-emblem: var(--size-emblem-lg);
}

/* Familien. Die Paare stammen aus den Statuspillen und sind dort schon
   gegeneinander gerechnet — ein Emblem erfindet keine neue Farbe.
 *
 * Die Verteilung ist nicht beliebig: Oliv und Grün liegen in OKLCH nur 27°
 * auseinander und waren als Nachbarn im Raster nicht zu unterscheiden.
 * Deshalb sitzen die beiden Familien, die am häufigsten nebeneinander
 * stehen (Gastgeben und Mitspielen), jetzt auf Koralle und Grün.            */
.emblem--host {
  --emblem-tone: var(--pill-coral-tx);
  --emblem-ground: var(--pill-coral-bg);
}

.emblem--play {
  --emblem-tone: var(--pill-success-tx);
  --emblem-ground: var(--pill-success-bg);
}

.emblem--community {
  --emblem-tone: var(--pill-neutral-tx);
  --emblem-ground: var(--pill-neutral-bg);
}

.emblem--knowledge {
  --emblem-tone: var(--pill-olive-tx);
  --emblem-ground: var(--pill-olive-bg);
}

.emblem--trade {
  --emblem-tone: var(--pill-warning-tx);
  --emblem-ground: var(--pill-warning-bg);
}

/* Ursprung (S1–S3): kein sechster Ton — Neutral wie Gemeinschaft, eigene Sektion. */
.emblem--origin {
  --emblem-tone: var(--pill-neutral-tx);
  --emblem-ground: var(--pill-neutral-bg);
}

/* Noch nicht erreicht. Gedämpft, aber nicht ausgegraut — der Kontrast muss
   auch hier stimmen, sonst kann man nicht lesen, was einem fehlt. Steht nach
   den Stufen, damit es auch die höchste übersteuert. */
.emblem--locked {
  --emblem-tone: var(--zone-text-quiet);
  --emblem-ground: var(--zone-track);
  --emblem-frame: var(--emblem-tone); /* nimmt auch der höchsten Stufe das Metall */
}

/* Ein Cover, das noch nicht verdient ist, bleibt sichtbar — aber farblos. Man
   soll erkennen, um welches Spiel es geht, ohne es für erreicht zu halten. */
.emblem--locked .emblem__image {
  filter: grayscale(1);
  opacity: 0.7;
}

/* --- Wappen gross ansehen -------------------------------------------------- *
 * Ein Wappen in einer Zeile ist 32 px gross — zu klein, um es anzusehen. Der
 * Klick auf die Zeile zeigt es in voller Groesse.
 *
 *   <span class="emblem-zoom">
 *     <button class="emblem-zoom__trigger" popovertarget="tt-a1">
 *       <span class="emblem emblem--sm emblem--host">…</span>
 *       <span>Gastgeber</span>
 *     </button>
 *     <span class="emblem-zoom__pop" id="tt-a1" popover>…</span>
 *   </span>
 *
 * Warum `popover` und NICHT der Tooltip weiter oben: Der Tooltip ist absolut
 * positioniert und wird von jedem Vorfahren mit `overflow` beschnitten — in
 * einer Tabelle also immer, denn die steckt in `.table-wrap`. Ein `popover`
 * liegt in der obersten Ebene des Browsers und kennt diese Grenze nicht. Esc,
 * Klick daneben und Fokusfang bringt es selbst mit, ohne eine Zeile
 * JavaScript (NFA-13).
 *
 * Kein Ueberfahren: Eine Flaeche, die nur beim Ueberfahren steht, ist am
 * Finger nicht erreichbar — und ein Wappen anzusehen ist am Handy dasselbe
 * Beduerfnis wie an der Maus.                                                */
.emblem-zoom {
  display: inline-flex;
}

.emblem-zoom__trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-block-size: var(--size-target-min);
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: start;
  cursor: pointer;
}

.emblem-zoom__trigger:hover {
  color: var(--zone-text-strong);
}

/* KEIN `display` auf der Flaeche selbst: Das Auf und Zu gehoert dem Browser
   (`[popover]:not(:popover-open) { display: none }` im Browser-Stylesheet),
   und eine eigene Angabe schlaegt das — die Flaeche stuende dann immer offen.
   Deshalb stapeln die Kinder selbst, nicht der Kasten.                       */
.emblem-zoom__pop {
  inset: 0;
  margin: auto;

  /* Der Seitenrand gehoert in die Hoechstbreite: Ein `popover` misst sich am
     Bild, nicht an der Seite — ohne das klebte die Flaeche am Handy an beiden
     Raendern.                                                                */
  max-inline-size: min(var(--size-tooltip-max-lg), calc(100% - 2 * var(--space-5)));
  padding: var(--space-5);
  border: 0;
  border-radius: var(--radius-card);
  background: var(--bg-surface);
  color: var(--text-primary);
  text-align: center;
  box-shadow: 0 var(--shadow-offset-float) var(--shadow-blur-float) 0
    var(--shadow-control);
}

.emblem-zoom__pop::backdrop {
  background: var(--backdrop);
}

.emblem-zoom__pop > .emblem {
  margin-inline: auto;
}

.emblem-zoom__title {
  display: block;
  margin-block-start: var(--space-4);
  color: var(--text-primary);
  font-weight: 700;
}

.emblem-zoom__meta {
  display: block;
  margin-block-start: var(--space-1);
  color: var(--text-secondary);
  font-size: var(--font-size-small);
}

/* Kennt ein Browser `popover` nicht, fehlt ihm auch die Regel, die die
   Flaeche schliesst — sie stuende in jeder Zeile offen. Dann lieber gar
   keine Grossansicht: Das kleine Wappen und der Name bleiben ja stehen.     */
@supports not selector(:popover-open) {
  .emblem-zoom__pop {
    display: none;
  }
}

/* --- Badge · Abzeichen ---------------------------------------------------- *
 * Emblem, Titel, Bedingung. Der Katalog steht in docs/abzeichen.md — hier
 * steht nur, wie eines aussieht.                                             */
.badge-grid {
  display: grid;

  /* min(9rem, 45%) statt 9rem: Auf dem Telefon fiele das Raster sonst auf eine
     einzige Spalte zurueck — 2 × 9 rem plus Abstand passen in 280 px nicht.
     Mit dem Prozentwert bleiben es immer mindestens zwei Spalten, ohne dass
     dafuer ein Umbruchpunkt im Stylesheet steht. */
  grid-template-columns: repeat(auto-fill, minmax(min(9rem, 45%), 1fr));
  gap: var(--space-5) var(--space-4);
  align-content: start;
}

.badge {
  /* Das Wappen haengt am PLATZ, nicht am Fenster (FE-76). Ein Abzeichen in
     einer schmalen Spalte ist schmal — egal, wie breit das Fenster ist.
     Damit stimmt es auch in einer Seitenleiste, einem Dialog oder einer
     Karte, ohne dass dafuer ein Umbruchpunkt existiert.

     Kein Rueckfall noetig: Kennt ein Browser cqi nicht, ist die Deklaration
     zur Rechenzeit ungueltig — und eine ungueltige Eigenschaft ERBT. Dann
     gilt die 96 px aus measures.css, und das Wappen steht einfach in seiner
     vollen Groesse da. */
  container-type: inline-size;
  container-name: badge;
  --size-emblem: min(var(--size-emblem-max), 70cqi);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  text-align: center;
}

.badge__title {
  font-size: var(--font-size-small);
  font-weight: 700;
  color: var(--zone-text-strong);
}

.badge__meta {
  font-size: var(--font-size-chip);
  color: var(--zone-text-quiet);
}

/* --- Tier · Stufenleiter mit Emblemen ------------------------------------- *
 * Die Ränge aus M3.8. Drei Zustände: hinter sich (.emblem--play, grün),
 * aktuell (das schmucklose .emblem im starken Grundton — es leiht sich keinen
 * Familienton) und noch offen (.emblem--locked). Sie unterscheiden sich in
 * Rahmen, Fettung und versteckter Textfassung, nicht in der Farbe allein
 * (FE-33).                                                                   */
.tier {
  display: flex;
  gap: var(--space-4);
  flex-wrap: wrap;
  list-style: none;
  padding: 0;
  margin: 0;
}

.tier__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  text-align: center;
  inline-size: 7rem;
  color: var(--zone-text-quiet);
}

.tier__item[aria-current="step"] {
  color: var(--zone-text-strong);
  font-weight: 700;
}

.tier__name {
  font-size: var(--font-size-small);
}

.tier__threshold {
  font-size: var(--font-size-chip);
  color: var(--zone-text-quiet);
}

/* --- Rating · Sterne ------------------------------------------------------ *
 * Nur für SPIELE (M1.4). Über Personen gibt es keine Sterne und keine Zahlen
 * — das ist keine Stilfrage, sondern § 0.4 und die Abwägung in § 2.2.
 *
 * Die Sterne tragen keine eigene Farbe: Gefüllt und offen unterscheiden sich
 * in der Form. Ein Gold gäbe es in der Palette nicht, und eines zu erfinden
 * wäre der Anfang vom Wildwuchs.                                             */
.rating {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.rating__stars {
  display: inline-flex;
  gap: var(--space-0-5);
  --size-icon: var(--size-star);
  color: var(--zone-text-strong);
}

.rating__stars .icon[data-state="empty"] {
  color: var(--zone-border-interactive);
}

.rating__value {
  font-weight: 700;
  color: var(--zone-text-strong);
}

.rating__count {
  font-size: var(--font-size-small);
  color: var(--zone-text-quiet);
}

/* Eingabe: darunter liegen echte Radios, deshalb in umgekehrter Reihenfolge
   im Markup — so kann :checked ~ * die Sterne rechts davon erreichen.
   Als <fieldset> braucht es die Zuruecksetzung: Der eigene Rahmen und das
   Innenpolster kommen aus dem Browser, nicht aus dem Entwurf. */
.rating--input {
  padding: 0;
  border: 0;
  margin: 0;
}

.rating--input .rating__stars {
  flex-direction: row-reverse;
  justify-content: flex-end;
}

/* Drei Stufen auf derselben Achse, keine neue Farbe: offen ist der leise
   Randton, die Vorschau beim Überfahren der mittlere, die gewählte Note der
   starke. So bleibt erkennbar, was gewählt ist und was man gerade wählen
   würde — ohne dass die Sterne eine eigene Farbe mitbringen. */
.rating__star {
  cursor: pointer;
  color: var(--zone-border-interactive);
}

.rating__star:has(:checked),
.rating__star:has(:checked) ~ .rating__star {
  color: var(--zone-text-strong);
}

/* Überfahren gilt AUCH über den gewählten Sternen: Wer eine Bewertung ändert,
   zeigt mit dem Zeiger auf einen Stern, der schon gefüllt ist — ohne eigene
   Vorschaufarbe gäbe es dort keine Rückmeldung. Solange der Zeiger in der
   Reihe steht, zählt deshalb nur er, und alles darüber fällt auf offen
   zurück. Die Regeln stehen nach den Aktiv-Regeln, damit sie bei gleicher
   Spezifität gewinnen.

   Die Vorschau liegt zwischen offen und gewählt — als Mischung aus genau
   diesen beiden Tönen, nicht als dritter Token: In der Tintenzone sind
   Randton und leiser Ton dieselbe Farbe, ein fester Token wäre dort
   unsichtbar. So gibt es in JEDER Zone drei unterscheidbare Stufen. */
.rating__stars:hover > .rating__star {
  color: var(--zone-border-interactive);
}

.rating__stars:hover > .rating__star:hover,
.rating__stars:hover > .rating__star:hover ~ .rating__star {
  color: color-mix(in srgb, var(--zone-text-strong) 55%, var(--zone-border-interactive));
}

.rating__star:has(:focus-visible) {
  outline: var(--border-focus) solid var(--zone-focus);
  outline-offset: var(--space-0-5);
  border-radius: var(--radius-sm);
}

/* Verteilung — fünf Zeilen mit der vorhandenen .progress. */
.rating-bars {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  font-size: var(--font-size-small);
  color: var(--zone-text-quiet);
}

/* --- Review-Prompt · „Wie war's?"-Karte ----------------------------------- *
 * Die eine Aufforderung nach dem Abend (FA-7.11): Cover, Frage, Sterne. Mehr
 * nicht — sie soll in unter 20 Sekunden erledigt sein. Überspringen steht in
 * der Fusszeile, gleich gross und gleich sichtbar wie das Bewerten (FA-7.15).
 * Den Grund kennt die Karte nicht; die Zone setzt der Aufrufer.              */
/* Modultitel links, das Schliessen-Kreuz rechts auf derselben Zeile. */
.review-prompt__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.review-prompt__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
}

/* Das Cover behält seine Kantenlänge, auch wenn die Zeile eng wird. */
.review-prompt__cover {
  flex: none;
}

/* Frage und Nebensatz nehmen den Rest — bis sie zu schmal würden, dann bricht
   die Zeile um, statt die Frage in Silben zu zerlegen. */
.review-prompt__body {
  flex: 1 1 var(--size-prompt-min);
  min-inline-size: 0;
}

/* Sterne und Knopf stehen am Ende der Zeile und dürfen selbst umbrechen. */
.review-prompt__actions {
  margin-inline-start: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

.review-prompt__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

/* --- Dropzone · Datei hierher ziehen -------------------------------------- *
 * Ein <label> um ein echtes <input type="file">. Damit funktionieren Klick,
 * Tastatur und Screenreader ohne eine Zeile JavaScript; das Skript braucht es
 * nur für das Ziehen selbst und setzt dann .dropzone--over.                  */
.dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-block-size: var(--size-dropzone-block);
  padding: var(--space-5);
  border: var(--border-spinner) dashed var(--zone-border-interactive);
  border-radius: var(--radius-card);
  background: var(--zone-track);
  color: var(--zone-text-quiet);
  text-align: center;
  cursor: pointer;
  transition:
    background var(--motion-fast) var(--motion-ease),
    border-color var(--motion-fast) var(--motion-ease);
}

.dropzone:hover,
.dropzone--over {
  border-color: var(--zone-text-strong);
  background: var(--zone-success-bg);
  color: var(--zone-success-text);
}

.dropzone:has(:focus-visible) {
  outline: var(--border-focus) solid var(--zone-focus);
  outline-offset: var(--space-0-5);
}

.dropzone > .icon {
  --size-icon: var(--font-size-title);
}

.dropzone__title {
  font-weight: 700;
  color: inherit;
}

.dropzone__hint {
  font-size: var(--font-size-small);
}

/* Die schon hochgeladenen Dateien. Baut auf .list-group auf — eine Datei ist
   eine Zeile mit Vorschau, Name und einer Aktion. */
.file-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
}

.file-item__body {
  min-inline-size: 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.file-item__name {
  font-size: var(--font-size-small);
  font-weight: 700;
  color: var(--zone-text-strong);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.file-item__meta {
  font-size: var(--font-size-chip);
  color: var(--zone-text-quiet);
}

/* --- Filter --------------------------------------------------------------- *
 * Eine Leiste aus Schaltchips plus die Zahl der Treffer. Die Chips sind
 * entweder <button aria-pressed> (mit JavaScript) oder <label> um ein
 * Kontrollkästchen (ohne JavaScript, NFA-13) — Optik und Zustand sind
 * dieselben: Fläche, Fettung und Haken, nie Farbe allein (FE-33).
 */
.filter {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.filter__group {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.filter__count {
  font-size: var(--font-size-small);
  color: var(--zone-text-quiet);
  margin-inline-start: auto;
}

/* Gedrückt zeigt sich in Fläche UND Haken, nicht in der Farbe allein. */
.chip--toggle {
  cursor: pointer;
  font: inherit;
  font-size: var(--font-size-chip);
}

.chip--toggle[aria-pressed="true"],
.chip--toggle:has(> input[type="checkbox"]:checked) {
  background: var(--zone-action-bg);
  color: var(--zone-action-text);
  border-color: transparent;
  font-weight: 700;
  padding-inline-start: var(--chip-padding-inline-icon);
}

/* Kontrollkästchen-Variante: Haken nur im aktiven Zustand, Fokus auf dem Chip. */
.chip--toggle:has(> input[type="checkbox"]) > .icon {
  display: none;
}

.chip--toggle:has(> input[type="checkbox"]:checked) > .icon {
  display: block;
}

.chip--toggle > input[type="checkbox"]:focus {
  outline: none;
}

.chip--toggle:has(> input[type="checkbox"]:focus-visible) {
  outline: var(--border-focus) solid var(--zone-focus, var(--text-primary));
  outline-offset: var(--space-0-5);
}

/* --- Avatar · Rahmen mit Status ------------------------------------------- */
.avatar--lg {
  --size-avatar: var(--size-avatar-lg);
  font-size: var(--font-size-body);
}

.avatar--xl {
  --size-avatar: var(--size-avatar-xl);
  font-size: var(--font-size-title);
}

/* Der Wirt fuer alles, was AM Avatar haengt: der Statuspunkt unten rechts und
   das Rangwappen oben rechts. Beide sitzen absolut, deshalb steht hier die
   Bezugsflaeche. Wer ein Wappen am Gesicht zeigt — Personenzeile, Kopfleiste —
   nimmt diesen Rahmen und nicht seine eigene Fassung (K-44).               */
.avatar-frame {
  position: relative;
  display: inline-flex;
  flex: none;
}

/* Das Wappen haengt an der oberen rechten Ecke des Kreises: 2 px darueber,
   10 px darueber hinaus. */
.avatar-frame > .emblem {
  position: absolute;
  z-index: var(--layer-badge);
  inset-block-start: calc(-1 * var(--space-0-5));
  inset-inline-end: calc(-1 * (var(--space-2) + var(--space-0-5)));
}

/* Was rechts folgt — meist der Name — beginnt hinter der Kante des Wappens,
   nicht darunter. Der Rahmen selbst bringt den Platz mit, sonst muesste ihn
   jede Verwendung neu abmessen (Personenzeile, Kopfleiste). */
.avatar-frame:has(> .emblem) {
  margin-inline-end: var(--space-1);
}

/* Der Punkt sitzt AUF dem Bild und trägt deshalb einen Ring in der Zonenfarbe
   — sonst verschwimmt er auf einem hellen Foto. */
.avatar-frame__dot {
  position: absolute;
  inset-block-end: 0;
  inset-inline-end: 0;
  inline-size: var(--size-status-dot);
  block-size: var(--size-status-dot);
  border-radius: var(--radius-full);
  background: var(--zone-text-success);
  box-shadow: 0 0 0 var(--border-spinner) var(--zone-surface);
}

/* --- Personenzeile -------------------------------------------------------- *
 * Avatar, Rangwappen und Anzeigename als ein Baustein — überall dort, wo eine
 * Person in einer Liste auftaucht: auf der Gesuchskarte, im Fuß der
 * Listenkarte, in der Rangliste. Das Wappen gehört zum Gesicht, nicht zum
 * Namen: Es sitzt oben rechts am Kreis und bleibt auch dann stehen, wenn der
 * Name gekürzt werden muss.
 *
 * Der Wirt ist der Klick-Tooltip (.tooltip-host--click), weil Rang, Niveau und
 * Note hineingehören und nicht in die Zeile — der Anzeigename trägt die
 * Aussage. Nie der Vorname und nie die Punktzahl (FA-3.25).
 *
 *   <details class="tooltip-host tooltip-host--click person-line">
 *     <summary class="person-line__who">
 *       <span class="avatar-frame">
 *         <img class="avatar avatar--sm" …>
 *         <span class="emblem emblem--xs">…</span>
 *       </span>
 *       <span class="person-line__name">Miriam S.</span>
 *     </summary>
 *     <span class="tooltip tooltip--lg">…<span class="person-line__tip">…
 *   </details>
 */
.person-line {
  display: flex;
  align-items: center;
  min-inline-size: 0;
  max-inline-size: 100%;
  margin: 0;
}

/* Kein overflow: hidden — das Wappen steht über die Zeile hinaus; den Namen
   kürzt seine eigene Regel. */
.person-line__who {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-inline-size: 0;
  max-inline-size: 100%;
}

.person-line .avatar {
  flex: none;
  overflow: hidden;
}

/* Das Profilbild kommt als <picture> mit der Avatar-Klasse — das Bild darin
   füllt den Kreis. */
.person-line .avatar img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

.person-line__name {
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 700;
}

.person-line__tip {
  display: grid;
  justify-items: start;
  gap: var(--space-2);
}

.person-line__tip > span {
  display: block;
}

.person-line__tip .tooltip__title {
  margin-block-end: 0;
}

.person-line__tip-rank {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: 700;
}

/* --- Switch --------------------------------------------------------------- *
 * Für Einstellungen, die sofort greifen (FA-12.2). Darunter liegt eine
 * Checkbox — role="switch" kommt aus dem Markup, nicht aus dem Stil.
 */
.switch {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-block-size: var(--size-target-min);
  cursor: pointer;
}

/* Mit erklärendem Satz darunter. Der Knopf sitzt dann auf der ersten Zeile
   statt in der Mitte beider — sonst steht er neben dem Kleingedruckten.
   Der Versatz ist die halbe Differenz aus Zeilenhöhe und Knopfhöhe; die
   lh-Einheit rechnet das selbst, wie beim Banner-Icon. */
.switch--stacked {
  align-items: flex-start;
}

.switch--stacked > .switch__control {
  margin-block-start: calc((1lh - var(--size-switch-block)) / 2);
}

.switch__hint {
  display: block;
  font-size: var(--font-size-small);
  color: var(--zone-text-quiet);
}

.switch__control {
  position: relative;
  inline-size: var(--size-switch);
  block-size: var(--size-switch-block);
  border-radius: var(--radius-full);

  /* Helle Bahn. Den geforderten 3:1-Rand (WCAG 1.4.11) traegt nicht die
     Fuellung, sondern die Kontur — sonst muesste die Bahn dunkel sein. */
  background: var(--zone-track);
  box-shadow: inset 0 0 0 var(--border-hairline) var(--zone-border-interactive);
  appearance: none;
  cursor: pointer;
  flex: none;
  transition: background var(--motion-fast) var(--motion-ease);
}

.switch__control::after {
  content: "";
  position: absolute;
  inset-block-start: var(--size-switch-inset);
  inset-inline-start: var(--size-switch-inset);
  inline-size: var(--size-switch-knob);
  block-size: var(--size-switch-knob);
  border-radius: var(--radius-full);
  background: var(--on-fixed-white-surface);

  /* Eigene Kante, damit der Knopf auch auf heller Bahn ablesbar bleibt. */
  box-shadow:
    inset 0 0 0 var(--border-hairline) var(--border-on-media),
    0 var(--shadow-offset-knob) var(--shadow-blur-knob) 0 var(--shadow-control);
  transition: translate var(--motion-fast) var(--motion-ease);
}

.switch__control:checked {
  background: var(--zone-action-bg);
  box-shadow: none;
}

.switch__control:checked::after {
  translate: calc(
      var(--size-switch) - var(--size-switch-knob) - 2 * var(--size-switch-inset)
    )
    0;
}

@media (prefers-reduced-motion: reduce) {
  .switch__control,
  .switch__control::after {
    transition: none;
  }
}

/* --- Skeleton ------------------------------------------------------------- *
 * Platzhalter, solange eine Liste lädt. Anders als der Spinner behält er das
 * Layout — nichts springt, wenn der Inhalt eintrifft (FE-58).
 */
.skeleton {
  border-radius: var(--radius-control);
  background: var(--bg-sunken);
  animation: skeleton-pulse calc(var(--motion-spinner) * 2) ease-in-out infinite;
}

@keyframes skeleton-pulse {
  50% {
    opacity: 0.55;
  }
}

@media (prefers-reduced-motion: reduce) {
  .skeleton {
    animation: none;
  }
}

/* --- Divider -------------------------------------------------------------- */
.divider {
  block-size: var(--border-hairline);
  margin-block: var(--space-5);
  background: var(--zone-border);
  border: 0;
}

/* --- Stat mit Icon -------------------------------------------------------- */
.stat__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--zone-text-quiet);
}

.stat__head > .icon {
  block-size: 1lh;
}

/* --- Tile · quadratische Kachel mit Icon --------------------------------- *
 * Ersatz für ein fehlendes Bild — in Karten, in Dateilisten, überall dort, wo
 * sonst ein Cover stünde. Bewusst KEIN Emblem: Ein Emblem ist etwas, das man
 * sich verdient hat (FE-74), eine Datei ist das nicht.                       */
.tile {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--size-cover);
  block-size: var(--size-cover);
  border-radius: var(--radius-control);
  background: var(--bg-sunken);
  color: var(--zone-text-strong);
  font-size: var(--font-size-title);
  flex: none;
}

.tile--sm {
  --size-cover: var(--size-cover-sm);
  font-size: var(--font-size-body);
}

/* --- Card · Varianten ----------------------------------------------------- *
 * .card gibt es schon als Fläche. Hier kommen die Bausteine dazu, aus denen
 * sich die üblichen Formen zusammensetzen — mit Bild, mit Icon, ohne beides.
 */
.card__media {
  display: block;
  inline-size: 100%;

  /* block-size: auto ist noetig, damit aspect-ratio greift: Das height-Attribut
     am <img> wirkt sonst als Praesentationshinweis und gewinnt. Die Attribute
     bleiben trotzdem stehen — sie verhindern den Sprung beim Laden (FE-58). */
  block-size: auto;
  aspect-ratio: var(--ratio-image);
  object-fit: cover;
  border-radius: var(--radius-control);
  background: var(--bg-sunken);
  box-shadow: inset 0 0 0 var(--border-hairline) var(--border-on-media);
}

.card__title {
  font-size: var(--font-size-card-title);
  font-weight: 700;
  color: var(--zone-text-strong);
}

.card__text {
  color: var(--zone-text-quiet);
  font-size: var(--font-size-small);
}

.card__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-block-start: var(--space-4);
}

/* --- List group ----------------------------------------------------------- *
 * Zeilen in einem gemeinsamen Rahmen — für Einstellungen, Mitgliederlisten,
 * Auswahlen. Anders als .row, die frei im Fluss steht.
 */
.list-group {
  padding: 0;
  margin: 0;
  list-style: none;
  border: var(--border-hairline) solid var(--zone-border);
  border-radius: var(--radius-card);
  overflow: hidden;
}

.list-group > li + li {
  border-block-start: var(--border-hairline) solid var(--zone-border);
}

.list-group__item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-block-size: var(--size-target-min);
  padding: var(--space-3) var(--space-4);
  color: var(--zone-text-strong);
  text-decoration: none;
}

a.list-group__item:hover {
  background: var(--bg-sunken);
}

.list-group__item > .icon {
  color: var(--icon-tone, var(--zone-text-quiet));
  flex: none;
}

.list-group__body {
  flex: 1;
  min-inline-size: 0;
}

/* --- Button group --------------------------------------------------------- *
 * Zusammenhängende Aktionen, die zusammengehören. Die Knöpfe stossen
 * aneinander; nur aussen bleibt der Radius.
 */
.button-group {
  display: inline-flex;
  /* Die Gruppe nimmt nie mehr als die Spalte — sonst steht am Handy ein
     langer Name ueber dem Rand des Inhalts. */
  max-inline-size: 100%;
}

/* Damit etwas nachgeben KANN, muss es schrumpfen duerfen: Knoepfe mit festem
   Text bleiben, das Etikett darin gibt nach (siehe .search-alerts__label).
   Der Auslöser folgt dabei der Breite seiner Huelle, sonst haengt er heraus. */
.button-group > .dropdown {
  min-inline-size: 0;
}

.button-group > .dropdown > .button {
  inline-size: 100%;
}

/* Ein Menue darf Teil der Gruppe sein: Dann traegt der Auslöser die Kante,
   nicht das <details> darum — das ist nur die Huelle fuer die Fläche darunter. */
.button-group > .button,
.button-group > .dropdown > .button {
  border-radius: 0;
}

.button-group > :first-child,
.button-group > :first-child > .button {
  border-start-start-radius: var(--radius-button);
  border-end-start-radius: var(--radius-button);
}

.button-group > :last-child,
.button-group > :last-child > .button {
  border-start-end-radius: var(--radius-button);
  border-end-end-radius: var(--radius-button);
}

/* Die Knoepfe stossen aneinander — eine Kante, nicht zwei. */
.button-group > * + * {
  margin-inline-start: calc(-1 * var(--border-hairline));
}

/* --- Input group · Vorsatz, Nachsatz, Icon -------------------------------- *
 * Für Einheiten („km", „€"), feste Vorsätze („brettabend.de/") und Felder mit
 * Icon. Der Vorsatz ist kein Platzhaltertext — er bleibt sichtbar, wenn das
 * Feld gefüllt ist.
 */
.input-group {
  display: flex;
  align-items: stretch;
}

.input-group > .input {
  border-radius: 0;
  min-inline-size: 0;
}

.input-group > :first-child {
  border-start-start-radius: var(--radius-button);
  border-end-start-radius: var(--radius-button);
}

.input-group > :last-child {
  border-start-end-radius: var(--radius-button);
  border-end-end-radius: var(--radius-button);
}

.input-group > * + * {
  margin-inline-start: calc(-1 * var(--border-hairline));
}

.input-group__affix {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding-inline: var(--space-3);
  border: var(--border-hairline) solid var(--zone-border-interactive);
  background: var(--bg-sunken);
  color: var(--zone-text-quiet);
  white-space: nowrap;
}

/* Icon IM Feld: Es liegt darüber, das Feld bekommt links Platz dafür.
   pointer-events: none, damit der Klick im Feld landet und nicht im Icon. */
.input-icon {
  position: relative;
  display: flex;
  align-items: center;
}

.input-icon > .icon {
  position: absolute;
  inset-inline-start: var(--space-3);
  color: var(--icon-tone, var(--zone-text-quiet));
  pointer-events: none;
}

.input-icon > .input {
  padding-inline-start: var(--space-6);
}

/* --- Dropdown ------------------------------------------------------------- *
 * Als <details>. Tastatur und Screenreader stimmen damit ohne JavaScript.
 *
 * Was <details> NICHT kann: sich beim Klick nach draussen schliessen. Das
 * braucht JavaScript — oder die Popover-API, die es mitbringt, deren
 * Positionierung aber CSS Anchor Positioning voraussetzt und damit heute noch
 * nicht in allen Browsern aus NFA-6 sitzt. Deshalb hier bewusst der
 * konservative Weg; der Umstieg ist eine Komponente, kein Umbau.
 *
 * Das Schliessen ergaenzt apps/web fuer jede schwebende Flaeche an einer
 * Stelle (OverlayDismiss: Klick daneben, Esc, Fokus weiter). Ohne Skript
 * bleibt der zweite Klick auf den Ausloeser.
 */
.dropdown {
  position: relative;
  display: inline-block;
}

.dropdown > summary {
  list-style: none;
  cursor: pointer;
}

.dropdown > summary::-webkit-details-marker {
  display: none;
}

.dropdown__menu {
  position: absolute;
  inset-block-start: calc(100% + var(--space-2));
  inset-inline-start: 0;
  z-index: var(--layer-open);
  min-inline-size: var(--size-dropdown-min);
  padding: var(--space-1);
  border-radius: var(--radius-control);
  background: var(--bg-surface);
  box-shadow: 0 var(--shadow-offset-float) var(--shadow-blur-float) 0
    var(--shadow-control);
}

/* Ein Menueeintrag ist kein Fliesstext: Er traegt dieselbe Groesse wie der
   Name im Kopf darueber und wie die Navigation daneben — im Fliesstextmass
   spannte der laengste Eintrag das Menue unnoetig weit auf (Issue #37). Die
   Zielflaeche bleibt 44 px, die schrumpft nicht mit. */
.dropdown__item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-block-size: var(--size-target-min);
  padding-inline: var(--space-3);
  border-radius: var(--radius-control);
  color: var(--text-primary);
  font-size: var(--font-size-small);
  text-decoration: none;
  white-space: nowrap;
}

.dropdown__item:hover {
  background: var(--bg-sunken);
}

/* Im Menü trennt die Linie Gruppen, nicht Abschnitte einer Seite — der weite
   Abstand der Trennlinie wäre hier ein Loch. Der Container stellt ihn um,
   nicht eine Variante der Linie (FE-13). */
.dropdown__menu > .divider {
  margin-block: var(--space-1);
}

/* Kopf eines Menüs: wer man ist. Kein Eintrag, deshalb ohne Zielgröße und
   ohne Überfahren. */
.dropdown__head {
  padding-inline: var(--space-3);
  padding-block: var(--space-2);
}

/* Rechts ausgerichtet — für Auslöser am rechten Rand, etwa das eigene Menü in
   der Kopfleiste. Links verankert liefe die Fläche aus dem Bild. */
.dropdown--end > .dropdown__menu {
  inset-inline: auto 0;
}

/* Nach oben aufklappend — für Auslöser am unteren Rand, etwa „Mehr" in der
   Tab-Leiste. Nach unten läge die Fläche ausserhalb des Bildes. */
.dropdown--up > .dropdown__menu {
  inset-block: auto calc(100% + var(--space-2));
}

/* Kein Eintrag, sondern eine Aussage im Menü — etwa, dass noch nichts da ist. */
.dropdown__empty {
  padding-inline: var(--space-3);
  padding-block: var(--space-2);
  /* Feste Breite statt Hoechstbreite: Ein absolut gesetztes Menue misst sich
     an seinem Bezug (hier ein 44-px-Knopf) und bliebe sonst schmal. */
  inline-size: var(--size-menu-text-max);
  color: var(--zone-text-quiet);
  font-size: var(--font-size-small);
}

/* Ein Eintrag, der eine Aktion abschickt, ist ein Knopf und kein Link — er
   bringt eigene Browserstile mit, die hier zurückgenommen werden. */
button.dropdown__item {
  inline-size: 100%;
  border: 0;
  background: transparent;
  font: inherit;
  text-align: start;
  cursor: pointer;
}

/* --- Carousel ------------------------------------------------------------- *
 * Ohne Bibliothek. CSS Scroll Snap macht das Wischen, die Tastatur kann die
 * Leiste ohnehin scrollen. Wo der Browser es schon unterstuetzt, kommen
 * Knoepfe und Punkte als native Pseudoelemente dazu — sonst bleibt es eine
 * saubere Wischleiste.
 */
.carousel {
  display: flex;
  gap: var(--space-4);
  overflow-x: auto;
  padding-block-end: var(--space-2);
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: thin;
}

.carousel > * {
  flex: none;
  inline-size: var(--size-carousel-item);
  scroll-snap-align: start;
}

@media (prefers-reduced-motion: reduce) {
  .carousel {
    scroll-behavior: auto;
  }
}
