/* Gestaltung des Spieltisches.
 *
 * Sämtliche Farben kommen aus tokens.css. Hier stehen nur Anordnung und Form — wer die
 * Anmutung ändern will, fasst die Marken an und nicht diese Datei.
 */

* { box-sizing: border-box; }

/* Bildlaufleisten in der Farbe der Oberfläche. Ohne das zeichnet Windows einen breiten,
   hellen Balken mit Pfeilkästchen an beiden Enden — auf einem dunklen Tisch ist das der
   auffälligste Streifen im Bild, obwohl er das Unwichtigste zeigt. */
* {
    scrollbar-width: thin;
    scrollbar-color: var(--scroll-thumb) transparent;
}

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }

/* Der durchsichtige Rahmen samt padding-box lässt den Balken schmaler wirken, als seine
   Spur breit ist — sonst klebt er an den Zeilen daneben. */
::-webkit-scrollbar-thumb {
    background: var(--scroll-thumb);
    background-clip: padding-box;
    border: 2px solid transparent;
    border-radius: var(--radius-pill);
}

::-webkit-scrollbar-thumb:hover { background: var(--scroll-thumb-hover); background-clip: padding-box; }
::-webkit-scrollbar-corner { background: transparent; }

html, body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font-family: var(--font);
    font-size: 15px;
    -webkit-font-smoothing: antialiased;

    /* Kein Markieren mit der Maus.
     *
     * Am Tisch wird gezogen und geklickt, nicht gelesen und kopiert — und beim Ziehen einer
     * Karte markierte der Zeiger sonst nebenbei den halben Bildschirm blau. Das ist der
     * Grund; ein Schutz gegen irgendetwas ist es nicht. */
    user-select: none;
    -webkit-user-select: none;
}

/* Wo geschrieben wird, wird auch markiert: Ohne das ließe sich in einem Feld kein Wort
   mehr korrigieren, ohne alles zu löschen. */
input, textarea, [contenteditable] {
    user-select: text;
    -webkit-user-select: text;
}

/* Der Tisch soll den Bildschirm ausfüllen, nicht nur mindestens so hoch sein.
   Deshalb durchgehend feste Höhe statt min-height und überall min-height: 0 — ohne das
   weigern sich Grid- und Flex-Kinder zu schrumpfen und wachsen über den Rand hinaus.

   <b>Zweimal dieselbe Höhe, und das ist kein Versehen.</b> `100vh` meint auf iOS nicht, was
   man sieht: Dort ist es die Höhe *ohne* die Leisten von Safari und Chrome, also mehr als
   das Fenster hergibt. Alles, was unten steht — hier die Fußzeile mit Versionsnummer und
   Aktualisierungsknopf —, rutscht damit unter den Rand, und weil `.app` nicht rollt, kommt
   man nicht mehr hin. Am Schreibtisch fällt das nie auf: Dort sind beide Werte gleich.

   `100dvh` ist die Höhe, die gerade wirklich sichtbar ist. Die erste Zeile bleibt als
   Rückfalloption für Browser stehen, die die Einheit nicht kennen — die zweite gewinnt,
   sobald sie verstanden wird. */
.app {
    height: 100vh;
    height: 100dvh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.top-bar { flex: none; }
main { flex: 1; min-height: 0; padding: 0.9rem 1.1rem 1.1rem; overflow: auto; }

/* ================================================================
   Kopfleiste
   ================================================================ */

.top-bar {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.85rem 1.25rem;
    background: var(--bg-bar);
    border-bottom: 1px solid var(--line-soft);
}

.brand {
    display: inline-flex;

    /* Mittig und nicht mehr auf der Grundlinie: Ein Bild richtet seine Unterkante an der
       Grundlinie aus, und das Emblem säße damit auf der Schrift statt neben ihr. */
    align-items: center;
    gap: 0.35rem;
    color: var(--text);
    text-decoration: none;
    font-size: 1.15rem;
    font-weight: 700;
    letter-spacing: -0.01em;
}

/* Das Emblem vor dem Namen. Nur der Kopf zwischen den beiden Assen, nicht der ganze
   Aufbau: Der liegt bei 4,25 : 1, und auf Leistenhöhe gebracht wäre sein Schriftzug elf
   Bildpunkte hoch. So bleibt der Name gesetzter Text — scharf in jeder Größe — und das
   Bild trägt nur das Zeichen.

   Die Höhe ist an den Knöpfen der Kopfleiste gemessen und nicht geraten: Die sind 34
   Bildpunkte hoch und bestimmen dort die Leistenhöhe. Genau so hoch darf das Zeichen also
   werden, ohne dem Filz darunter einen einzigen Punkt zu nehmen — und es braucht die
   Größe. Bei 26 Bildpunkten war es nur noch ein Fleck; die beiden Gesichter waren erst auf
   einem Bildschirm mit doppelter Punktdichte zu erkennen. */
.brand-mark {
    height: 2.1rem;
    width: auto;
    display: block;
}

/* Der Punkt der Wortmarke trägt die Betonungsfarbe — das einzige farbige Element links. */
.brand::after {
    content: "";
    width: 0.3rem;
    height: 0.3rem;
    border-radius: 50%;
    background: var(--accent);
}

/* Schiebt alles Nachfolgende an den rechten Rand.

   Die Regel war einmal auf .top-bar gemünzt. In der Lobby heißt die Leiste aber
   .lobby-bar — dort fiel der Halter auf null zusammen, und Wortmarke, Kontoname und
   Knöpfe klebten am linken Rand aneinander. Der Halter gehört keiner bestimmten Leiste,
   sondern tut überall dasselbe. */
.spacer { flex: 1; }

/* Der Raumcode wird am Telefon vorgelesen: einzeln stehende Zeichen, feste Breite. */
.room-code {
    font-family: var(--font-mono);
    font-size: 0.95rem;
    letter-spacing: 0.28em;
    color: var(--text-dim);
    text-transform: uppercase;
}

.room-code strong { color: var(--text); font-weight: 600; }

/* ================================================================
   Grundformen
   ================================================================ */

/* Runde Ecken ohne Treppe: das "padding-box" in den Flächenangaben weiter unten.
 *
 * Viele Marken hier tragen eine durchscheinende Füllung UND einen durchscheinenden Rahmen
 * — die Parteimarke etwa Füllung rgba(…, 0.22) unter Rahmen rgba(…, 0.55). Voreingestellt
 * ist background-clip: border-box, die Füllung liegt also auch unter dem Rahmen.
 *
 * An einer geraden Kante fällt das nicht auf. Am Bogen einer Rundung deckt jeder Bildpunkt
 * nur zum Teil, und dieser Anteil wirkt auf zwei durchscheinende Schichten statt auf eine.
 * Der Übergang nach außen verläuft dadurch nicht gleichmäßig, sondern in Stufen — der
 * Rahmen sieht am Bogen dicker und kantiger aus als an der Geraden.
 *
 * "padding-box" in der Kurzschreibweise schneidet die Füllung an der Rahmeninnenkante ab.
 * Am Bogen deckt dann nur noch eine Schicht, und der Rand verläuft in einer sauberen Rampe.
 * Die Größe ändert sich nicht — der Rahmen behält seinen Platz im Kasten.
 *
 * Warum es in jeder Angabe einzeln steht und nicht einmal zentral: background-clip gehört
 * zur Kurzschreibweise background. Jedes spätere "background: …" setzt es wieder auf den
 * Vorgabewert zurück — eine Sammelregel weiter oben wäre von jedem Zusatz überschrieben
 * worden. Nachgemessen: mit Sammelregel meldete getComputedStyle durchweg border-box. */

.chip {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.2rem 0.55rem;
    border-radius: var(--radius-sm);
    border: 1px solid var(--line);
    background: var(--panel);
    font-size: 0.7rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-dim);
    white-space: nowrap;
}

.chip.accent { border-color: var(--accent-line); background: var(--accent-soft) padding-box; color: var(--accent); }
.chip.danger { border-color: var(--danger-line); background: var(--danger-soft) padding-box; color: var(--danger); }

/* Feste Mindesthöhe, weil in dieser Zeile Marken mit ganz verschiedenem Inhalt
   nebeneinanderstehen: "Kontra" in 0,72 rem, ein Schwein in 0,85 rem. Ohne den Boden
   bestimmt jede Marke ihre Höhe selbst — gemessen 16,9 gegen 20,0 Bildpunkte, und in einer
   Reihe von vieren sieht man jede Abweichung sofort. */
.badge {
    display: inline-flex;
    align-items: center;
    min-height: 1.15rem;
    padding: 0.05rem 0.4rem;
    margin-left: 0.35rem;
    border-radius: var(--radius-pill);
    background: var(--panel-raised);
    border: 1px solid var(--line);
    font-size: 0.72rem;
    color: var(--text-dim);
}

.badge.party { font-weight: 700; letter-spacing: 0.03em; }
.badge.party.re { background: var(--re-soft) padding-box; border-color: var(--re-line); color: var(--re-ink); }
.badge.party.kontra { background: var(--kontra-soft) padding-box; border-color: var(--kontra-line); color: var(--kontra-ink); }

/* Laut: Die eigene Partei hat angesagt. Volle Farbe, genau wie an den Kacheln der anderen —
   der Unterschied zwischen "ich gehöre dazu" und "wir haben es gesagt" ist am Tisch der
   wichtigere von beiden, und er darf nicht nur im Wort davor stehen. */
.badge.party.loud.re { background: var(--re); border-color: var(--re); color: #fff; }
.badge.party.loud.kontra { background: var(--kontra); border-color: var(--kontra); color: #fff; }
.badge.bock { background: var(--danger-soft) padding-box; border-color: var(--danger-line); color: var(--danger); }

/* Die letzte Runde ist keine Warnung, sondern ein Hinweis auf den Ausgang des Abends —
   deshalb der ruhige Akzent und nicht das Rot der Bockrunde. */
/* Die eigene Rolle in der Statuszeile — wie die Rollenmarke an den Kacheln, nur als
   Wortmarke im Fluss der Zeile. */
.badge.role {
    background: rgba(255, 255, 255, 0.09) padding-box;
    border-color: rgba(255, 255, 255, 0.22);
    color: var(--text);
}

.badge.letzte-runde { background: var(--accent-soft) padding-box; border-color: var(--accent-line); color: var(--accent); }
.badge.away { background: rgba(224, 166, 60, 0.12) padding-box; border-color: var(--accent-line); color: var(--accent); }

button {
    padding: 0.5rem 0.85rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--panel);
    color: var(--text);
    font: inherit;
    font-size: 0.88rem;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
}

button:hover:not(:disabled) { background: var(--panel-raised); border-color: var(--accent-line); }
button:disabled { opacity: 0.55; cursor: not-allowed; }

/* Ein gesperrter Hauptknopf war fast unlesbar: Bernstein mit dunkler Schrift auf 40 %
   Deckkraft ergibt zwei Grautöne, die kaum auseinandergehen — und ausgerechnet beim
   Armutstausch ist er so lange gesperrt, bis die Auswahl vollständig ist. Statt zu
   verblassen wechselt er jetzt die Rolle: ein gewöhnlicher Knopf, der noch nicht dran ist. */
button.primary:disabled {
    opacity: 1;
    background: var(--panel-raised);
    border-color: var(--line);
    color: var(--text-dim);
}

button.primary {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-ink);
    font-weight: 600;
}

button.primary:hover:not(:disabled) { background: var(--accent-bright); border-color: var(--accent-bright); }

button.ghost { background: transparent; border-color: transparent; color: var(--text-dim); }
button.ghost:hover:not(:disabled) { color: var(--text); background: var(--panel); }

button.danger { background: var(--danger-soft) padding-box; border-color: var(--danger-line); color: var(--danger); }
button.danger:hover:not(:disabled) { background: rgba(200, 84, 74, 0.22) padding-box; }

.text {
    padding: 0.5rem 0.7rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--bg);
    color: var(--text);
    font: inherit;
    font-size: 0.9rem;
}

.text:focus { outline: none; border-color: var(--accent-line); }
.text::placeholder { color: var(--text-faint); }
.text.code { width: 7rem; text-transform: uppercase; letter-spacing: 0.2em; font-family: var(--font-mono); }
.text.tiny { width: 4.5rem; }

.row { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
.muted { color: var(--text-dim); font-size: 0.85rem; }
.error { color: var(--danger); }
.prompt { font-weight: 600; font-size: 0.85rem; }
.number { text-align: right; font-variant-numeric: tabular-nums; }
.negative { color: var(--danger); }

.panel {
    background: var(--panel);
    border: 1px solid var(--line-soft);
    border-radius: var(--radius);
    padding: 1rem;
    margin-bottom: 0.85rem;
}

.panel h2, .panel h3 {
    margin: 0 0 0.7rem;
    font-size: 0.72rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--text-faint);
    font-weight: 600;
}

/* ================================================================
   Sitzkennzeichen
   ================================================================ */

/* Jeder Platz hat eine feste Farbe. Sie taucht am Tisch, im Punktestand und in der
   Sprachrunde identisch auf — dadurch findet man einen Mitspieler überall wieder. */
.avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.6rem;
    height: 2.6rem;
    border-radius: var(--radius-sm);
    font-size: 0.98rem;
    font-weight: 700;
    letter-spacing: 0.01em;
    color: #fff;
    flex: none;
}

.seat-0 { background: var(--seat-0); color: var(--accent-ink); }
.seat-1 { background: var(--seat-1); }
.seat-2 { background: var(--seat-2); }
.seat-3 { background: var(--seat-3); }

/* ================================================================
   Lobby
   ================================================================ */

/* Die Startseite hat ihren eigenen Untergrund: ein Filzgrün, das nach unten ins Dunkle
   ausläuft. Der Spieltisch bleibt beim ruhigen Fast-Schwarz — dort soll nichts vom Blatt
   ablenken. */
.lobby-page {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    background:
        radial-gradient(ellipse 90% 55% at 50% -8%, #1e5c3d 0%, #164a32 30%, #0d2c1d 60%, #070d0a 100%);
}

/* Die Kopfzeile der Lobby.

   Sie steht auf derselben 58-rem-Spalte wie alles darunter — die Wortmarke sitzt damit
   genau über der linken Kante der Kacheln, die Kontoangaben über der rechten. Bewusst
   nicht mittig: Der Blick sucht die Wortmarke links und das eigene Konto rechts, so wie
   am Spieltisch auch. Mittig stünde beides an einer Stelle, an der man nichts sucht. */
.lobby-bar {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: min(58rem, 100% - 3rem);
    margin: 0 auto;

    /* Seitlich dasselbe Maß wie das <main> darunter, das es aus der globalen main-Regel
       erbt. Ohne das steht die Leiste 16 px weiter außen als die Kacheln — wenig genug,
       um es nicht zu benennen, und genug, um schief auszusehen. */
    padding: 1.5rem 1.1rem 1rem;
}

/* Konto, Passwort und Abmelden gehören zusammen und stehen deshalb enger beieinander als
   der Rest der Zeile. Der Strich davor trennt sie von der Tischanzeige, ohne einen
   weiteren Kasten aufzumachen. */
.lobby-account {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    padding-left: 0.7rem;
    border-left: 1px solid var(--line);
}

.lobby-account .chip { margin-right: 0.15rem; }
.lobby-bar button.ghost { padding: 0.35rem 0.6rem; font-size: 0.82rem; }

/* Eine Pille ist eine Pille: eine Zeile, ein runder Punkt.
 *
 * `flex: none` und `white-space: nowrap` sind hier keine Vorsichtsmaßnahme, sondern eine
 * Reparatur. Als gewöhnliches Flex-Kind gab sie nach, sobald es eng wurde — in der
 * Kopfzeile der Lobby wurde aus „7 Tische laufen" ein dreizeiliger Klotz, und die Zeile
 * wuchs von 69 auf 95 Bildpunkte. Wer zu wenig Platz hat, soll das zeigen, statt sich
 * unkenntlich zusammenzufalten. */
.live-pill {
    display: inline-flex;
    flex: none;
    align-items: center;
    gap: 0.4rem;
    padding: 0.25rem 0.7rem;
    border-radius: var(--radius-pill);
    background: rgba(0, 0, 0, 0.18) padding-box;
    border: 1px solid rgba(255, 255, 255, 0.09);
    font-size: 0.8rem;
    color: var(--text-dim);
    white-space: nowrap;
}

/* Auch der Punkt gab nach und wurde zum Oval — gemessen 2,8 statt 6 Bildpunkte breit. */
.live-pill .dot {
    flex: none;
    width: 0.4rem;
    height: 0.4rem;
    border-radius: 50%;
    background: #4ec27d;
    box-shadow: 0 0 0 3px rgba(78, 194, 125, 0.18);
}

.live-pill.small { font-size: 0.72rem; padding: 0.15rem 0.5rem; }

/* Ganz rechts im Reiterkopf, damit die beiden Reiter links beieinander bleiben.
 *
 * Versalien, Sperrung und Gewicht erbt sie von der Überschrift — genau wie „Zuletzt:"
 * daneben, und deshalb sieht sie dort nicht wie ein Fremdkörper aus. Die Schriftgröße
 * gehört mit dazu: Mit den eigenen 0,8 rem wäre sie 12 Bildpunkte groß neben Reitern mit
 * 10,2 und damit lauter als die Überschrift, unter der sie steht. */
.live-pill.running-count { margin-left: auto; font-size: inherit; }

/* Die Lobby rollt nicht als Ganzes: Der Aufmacher, das Namensfeld und die beiden Kacheln
   stehen fest, gerollt wird allein die Tischliste — und zwar dort, wo sie steht. Zuvor war
   das ganze <main> der Rollbereich; dessen Bildlaufleiste saß am äußersten Fensterrand,
   auf einem breiten Schirm eine halbe Bildschirmbreite neben der 58-rem-Spalte, auf die
   sie sich bezog. Sie sah damit aus, als betreffe sie die ganze Seite. */
.lobby {
    width: min(58rem, 100% - 3rem);
    margin: 0 auto;
    padding-bottom: 1.5rem;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

.lobby > .hero,
.lobby > .name-block,
.lobby > .lobby-cards,
.lobby > .notice { flex: none; }

.open-tables { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.open-tables h3 { flex: none; }

/* Etwas Luft rechts, damit die Bildlaufleiste nicht auf den Zeilen klebt.

   `overscroll-behavior: contain` hält den Schwung in der Liste: Ohne das gibt iOS die
   Wischbewegung an die Seite dahinter weiter, sobald die Liste oben oder unten anschlägt —
   die ganze Anwendung federt dann mit, obwohl es dort nichts zu rollen gibt. */
.table-list {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding-right: 0.45rem;
}

/* Hier stand eine Ausweichregel für niedrige Fenster: unter 46 rem Höhe sollte statt der
   Liste die ganze Lobby rollen, "eine Liste mit drei sichtbaren Pixeln wäre schlimmer als
   eine Bildlaufleiste weiter außen".

   <b>Sie ist ersatzlos weg.</b> Zwei Gründe, und der zweite wiegt schwerer.

   Erstens hat sie nie funktioniert: `.app` steht auf fester Höhe mit `overflow: hidden`, also
   rollte gar nichts — die Liste endete unter dem Bildschirmrand, die Fußzeile mit der
   Versionsnummer war unerreichbar, und mit dem Finger ließ sich nichts bewegen.

   Zweitens war der repariert ausgelieferte Zustand schlechter als das Übel: Auf dem iPad
   rollte dann die ganze Seite, die Tischliste wuchs ohne Boden weiter, und die Fußzeile kam
   erst hinter dem letzten Eintrag. Zwei Rollbereiche, wo einer gemeint war. Gemeldet mit
   genau dieser Beobachtung — "als wenn es keinen Boden hätte".

   In Bildpunkten gerechnet ist die Sorge auch unbegründet. Die Schwelle greift zwischen 680
   und 736 Bildpunkten Höhe; darunter zeigt ScreenGuard ohnehin nur noch seine Meldung und
   die Lobby wird gar nicht gebaut. Gemessen bleiben der Liste in diesem Band gut 200
   Bildpunkte — genug für drei Zeilen, nicht drei Bildpunkte.

   Achtung bei der Einheit: In einer Media-Abfrage zählt `rem` gegen die *anfängliche*
   Schriftgröße des Browsers (16 px), nicht gegen die 15 px, die diese Seite setzt. 46 rem
   waren hier also 736 und nicht 690 Bildpunkte. */

/* ---- Aufmacher ---- */

/* Der Aufmacher liegt quer statt hochkant: Karten links, Text daneben.

   Hochkant kostete er rund 380 Bildpunkte — der Kartenfächer allein den größten Teil —, und
   die Tischliste begann erst darunter. Wer die Lobby öffnet, will Tische sehen, nicht eine
   Begrüßung. Quer bleibt der ganze Block unter 160. */
.hero {
    display: flex;
    align-items: center;
    gap: 1.25rem;
    padding: 1rem 0 1.25rem;
}

.hero-cards { display: flex; flex: none; }

.hero-card {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 48px;
    border-radius: 6px;
    background: var(--card-face);
    color: var(--card-black);
    font-size: 0.85rem;
    box-shadow: 0 6px 14px rgba(0, 0, 0, 0.35);
    animation: hero-float 5s ease-in-out infinite;
}

/* Negativer Rand statt Abstand: Die Karten sollen sich überlappen wie ein Fächer in der
   Hand, nicht wie vier Kacheln nebeneinander liegen. */
.hero-card + .hero-card { margin-left: -11px; }

.hero-card.red { color: var(--card-red); }

/* Jede Karte etwas anders gekippt und zeitversetzt — sonst wippen alle im Gleichschritt
   und es wirkt mechanisch. */
.hero-card:nth-child(1) { transform: rotate(-9deg); animation-delay: 0s; }
.hero-card:nth-child(2) { transform: rotate(-3deg); animation-delay: 0.4s; }
.hero-card:nth-child(3) { transform: rotate(3deg); animation-delay: 0.8s; }
.hero-card:nth-child(4) { transform: rotate(9deg); animation-delay: 1.2s; }

@keyframes hero-float {
    0%, 100% { translate: 0 0; }
    50% { translate: 0 -7px; }
}

.hero-text { min-width: 0; }

/* Der Aufmacher der Anmeldeseite: hochkant und mittig statt quer.

   Quer wie in der Lobby geht dort nicht. Darunter steht das Anmeldefeld, und das ist mit
   22 rem mittig gesetzt — ein linksbündiger Aufmacher darüber gäbe der Seite zwei Achsen,
   und beide sieht man. Der Fließtext bekommt eine eigene Zeile und eine engere Spalte als
   in der Lobby: Mittig gesetzter Text liest sich nur, solange die Zeilen kurz bleiben. */
.hero.stacked {
    flex-direction: column;
    gap: 0.9rem;
    padding-bottom: 1.75rem;
    text-align: center;
}

.hero.stacked .lead { max-width: 26rem; margin: 0 auto; }

/* Die Oberzeile über der Überschrift: der Anspruch, der den Namen erklärt.

   Klein, gesperrt und in der Betonungsfarbe — so liest man sie als Beiwerk der Marke und
   nicht als weitere Ansage der Seite. Sie steht über der Überschrift und nimmt dafür deren
   Abstand nach unten weg, damit der Aufmacher nicht um eine Zeile wächst: Er ist einmal
   quergelegt worden, weil er hochkant 380 Bildpunkte kostete. */
.hero-claim {
    margin: 0 0 0.2rem;
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--accent);
}

.hero h1 {
    margin: 0 0 0.35rem;
    font-size: 25px;
    font-weight: 800;
    letter-spacing: -0.02em;
}

/* Kein "margin: 0 auto" mehr: Der Text steht links neben den Karten, nicht mittig unter
   ihnen. */
.lead { margin: 0; max-width: 34rem; color: var(--text-dim); line-height: 1.5; }

.page-title { margin: 0 0 0.35rem; font-size: 25px; font-weight: 800; letter-spacing: -0.02em; }

/* ---- Anmeldung ---- */

.sign-in { width: min(22rem, 100%); margin: 0 auto; }
.sign-in form { display: flex; flex-direction: column; gap: 0.35rem; }
.sign-in .field-label { margin-top: 0.6rem; margin-bottom: 0.2rem; }
.sign-in .text { width: 100%; padding: 0.7rem 0.9rem; background: rgba(0, 0, 0, 0.15); }
.sign-in button.primary { margin-top: 1.1rem; padding: 0.75rem; font-size: 0.95rem; }
.sign-in .switch { width: 100%; margin-top: 0.6rem; }
.sign-in .hint { margin-top: 0.7rem; text-align: center; }

/* Die Felder im Passwort-Fenster sitzen untereinander wie in der Anmeldung. */
#passwort-formular { display: flex; flex-direction: column; gap: 0.2rem; }
#passwort-formular .field-label { margin-top: 0.6rem; margin-bottom: 0.2rem; }
#passwort-formular .text { width: 100%; padding: 0.6rem 0.8rem; background: rgba(0, 0, 0, 0.15); }
#passwort-formular .field-label:first-child { margin-top: 0; }

/* ---- Name ---- */

.name-block { text-align: center; margin-bottom: 1.6rem; }

.field-label {
    display: block;
    margin-bottom: 0.6rem;
    font-size: 0.68rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--text-faint);
    font-weight: 600;
}

.name-field {
    width: min(25rem, 100%);
    padding: 0.85rem 1rem;
    text-align: center;
    font-size: 1.05rem;
    background: rgba(0, 0, 0, 0.15);
    border-radius: var(--radius);
}

/* ---- Die drei Wege ins Spiel ----

   Beitreten, Aufmachen, Sofort spielen — gleich hoch nebeneinander. Vorher waren es zwei
   Kästen verschiedener Höhe, der linke unten leer, und der schnellste Weg steckte als Kasten
   im Kasten "Aufmachen". Dabei ist er der einzige ohne Eingabe. */
/* Drei Wege ins Spiel nebeneinander.

   minmax(0, 1fr) und nicht 1fr: Ein blankes "1fr" heißt "minmax(auto, 1fr)", und dieses
   auto ist der Mindestinhalt der Spalte — bei „Tisch aufmachen“ also Eingabefeld, Knopf und
   zwei Regelpillen nebeneinander. Darunter ging die Spalte nicht, und weil die dritte mit
   200 Bildpunkten festgenagelt ist, wurde die Reihe schlicht breiter als ihr Kasten:
   gemessen 865 gegen 849, und der „Sofort spielen“-Knopf stand 14 Bildpunkte über den
   rechten Rand hinaus. Auf einem Tablet mehr, weil dort weniger Platz ist.

   Mit der Null als Untergrenze darf die Spalte kleiner werden als ihr Inhalt, und der
   Inhalt bricht um, statt hinauszuragen. */
.lobby-cards {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
    align-items: stretch;
    gap: 1rem;
    margin-bottom: 1.25rem;
}

@media (max-width: 60rem) {
    .lobby-cards { grid-template-columns: 1fr; }
}

.lobby-card {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    background: rgba(0, 0, 0, 0.17) padding-box;
    border: 1px solid rgba(255, 255, 255, 0.07);
    border-radius: var(--radius-lg);
    padding: 1.2rem;
}

/* Eine Überschrift, die sich nicht wichtig macht: Die Kästen sind an ihrem Inhalt zu
   erkennen, die Zeile darüber ordnet nur ein. */
.card-label {
    margin: 0;
    font-family: ui-monospace, "Cascadia Mono", "SFMono-Regular", Menlo, Consolas, monospace;
    font-size: 0.6rem;
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--text-faint);
}

/* Der schnellste Weg soll aussehen wie ein Knopf, nicht wie ein Hinweis.

   Die 200 Bildpunkte sind eine Wunsch-, keine Festbreite: max-width lässt den Knopf
   schrumpfen, wenn seine Spalte enger wird. Ohne das ragte er auf schmalen Fenstern über
   den Rand hinaus, und „Allein gegen drei Bots“ war rechts abgeschnitten. */
.solo-card {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    width: 200px;
    max-width: 100%;
    padding: 1.2rem;
    border-radius: var(--radius-lg);
    background: rgba(224, 166, 60, 0.1) padding-box;
    border: 1px solid rgba(224, 166, 60, 0.35);
    text-align: left;
}

.solo-card:hover:not(:disabled) { background: rgba(224, 166, 60, 0.18) padding-box; }

/* min-width: 0, sonst bricht die Zeile darin nicht um.

   Ein Flex-Kind hat von Haus aus "min-width: auto" und schrumpft nie unter seinen
   Mindestinhalt. Für „Allein gegen drei Bots“ ist das die ganze Zeile — sie stand deshalb
   einzeilig da und lief rechts aus dem Knopf heraus, statt sich auf zwei Zeilen zu legen.
   Höhe kostet das nichts: Alle drei Kästen der Reihe sind ohnehin gleich hoch, und die
   beiden anderen sind höher. */
.solo-text { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.solo-text strong { font-size: 1rem; }
.solo-text .muted { font-size: 0.78rem; }

/* Zwei angedeutete Kartenrücken — dasselbe Sinnbild wie im Aufmacher, nur kleiner. */
.solo-cards { position: relative; flex: none; width: 30px; height: 40px; }

.solo-back {
    position: absolute;
    inset: 0;
    border-radius: 4px;
    background: var(--felt-line);
    border: 1px solid rgba(255, 255, 255, 0.18);
}

.solo-back:first-child { transform: rotate(-10deg) translateX(-3px); }
.solo-back:last-child { transform: rotate(6deg) translateX(3px); }

.lobby-card h2 {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    margin: 0 0 0.5rem;
    font-size: 1rem;
    font-weight: 700;
    text-transform: none;
    letter-spacing: 0;
    color: var(--text);
}

.card-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: var(--radius-sm);
    background: rgba(78, 194, 125, 0.16);
    color: #6fd396;
    font-size: 0.95rem;
}

.lobby-card p.muted { margin: 0 0 0.9rem; }
.lobby-card form.row { flex-wrap: nowrap; }
.lobby-card form.row .text { flex: 1; min-width: 0; background: rgba(0, 0, 0, 0.18); }

.lobby-card .code {
    width: auto;
    font-family: var(--font-mono);
    letter-spacing: 0.3em;
    text-transform: uppercase;
}

button.outline { background: transparent; border-color: var(--accent-line); color: var(--accent); font-weight: 600; }
button.outline:hover:not(:disabled) { background: var(--accent-soft); }

.hint { margin: 0.7rem 0 0; font-family: var(--font-mono); font-size: 0.72rem; color: var(--text-faint); }
.hint.error { color: var(--danger); font-family: var(--font); }

.preset-pills { display: flex; gap: 0.4rem; margin-top: 0.9rem; flex-wrap: wrap; }

/* Die Schriftstärke steht fest — auch im Ruhezustand 600.

   Vorher wurde erst der gewählte Knopf fett. Fette Schrift ist breiter, der Knopf wuchs also
   beim Auswählen und schob die Nachbarn zur Seite: Die ganze Reihe sprang in der Breite.
   Ausgewählt wird jetzt allein über Farbe und Grund — beides ändert nichts am Maß. */
.pill {
    flex: 1;
    padding: 0.45rem 0.6rem;
    border-radius: var(--radius-sm);
    background: transparent;
    border: 1px solid var(--line);
    font-size: 0.82rem;
    font-weight: 600;
    white-space: nowrap;
}

.pill.active { background: var(--accent-soft) padding-box; border-color: var(--accent-line); color: var(--accent); }

/* Hier stand .solo-button — der gestrichelte Kasten im Kasten "Aufmachen". Er ist zur
   eigenen Spalte geworden (.solo-card weiter oben). */

/* Links in der Kopfleiste: Bestenliste und Regeln. Sie sehen aus wie die Knöpfe daneben,
   sind aber Verweise — sie führen auf eigene Seiten, und der Zurück-Knopf soll wirken. */
.ghost-link {
    padding: 0.35rem 0.7rem;
    border-radius: var(--radius-sm);
    color: var(--text-dim);
    font-size: 0.85rem;
    text-decoration: none;
}

.ghost-link:hover { background: var(--panel-raised); color: var(--text); }

/* ---- Offene Tische ---- */

.open-tables h3 {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    margin: 0 0 0.7rem;
    font-size: 0.68rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--text-faint);
    font-weight: 600;
}

.open-tables .empty { padding: 1rem; text-align: center; }

/* ---- Die Filterzeile ----

   Sie steht zwischen Reitern und Liste und nimmt keine Höhe von der Liste weg, weil die
   ohnehin rollt. Links der Schalter, in der Mitte die Regeln, rechts die Auskunft, was der
   Filter übriglässt. */
.table-filter {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin-bottom: 0.7rem;
    padding-bottom: 0.7rem;
    border-bottom: 1px solid var(--line-soft);
}

/* Auch hier die Schriftstärke fest — aus demselben Grund wie bei den Pillen: Fett ist
   breiter, und ein Schalter, der beim Umlegen wächst, schiebt die halbe Zeile. */
.filter-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.3rem 0.75rem;
    border-radius: var(--radius-pill);
    background: transparent;
    font-size: 0.82rem;
    font-weight: 600;
    white-space: nowrap;
}

/* Der gestrichelte Punkt ist derselbe wie in der Tischzeile — der Schalter zeigt damit
   selbst, wonach er sucht. */
.filter-toggle .seat-dot { width: 13px; height: 13px; flex: none; }

.filter-toggle.active {
    background: var(--accent-soft) padding-box;
    border-color: var(--accent-line);
    color: var(--accent);
}

.filter-toggle.active .seat-dot.free { border-color: var(--accent); }

/* Ein Strich trennt den Schalter von den Regeln: Der eine fragt nach Platz, die anderen
   nach dem Regelwerk — zwei Fragen, nicht eine Reihe. */
.filter-presets {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    flex-wrap: wrap;
    padding-left: 0.5rem;
    border-left: 1px solid var(--line-soft);
}

.pill-count { margin-left: 0.3rem; color: var(--text-faint); font-weight: 400; }
.pill.active .pill-count { color: inherit; }

/* Leere Voreinstellungen bleiben stehen, damit die Reihe ihre Breite behält — aber sie
   laden nicht zum Drücken ein. */
.pill.quiet { opacity: 0.4; }

.filter-state { margin-left: auto; font-size: 0.8rem; }

/* Rechts in der Reiterzeile: der kürzeste Weg zurück in die eigene Runde. */
.recent { display: flex; align-items: center; gap: 0.4rem; letter-spacing: 0; text-transform: none; }

.recent-code {
    padding: 0.15rem 0.45rem;
    border-radius: var(--radius-sm);
    background: transparent;
    font-family: ui-monospace, "Cascadia Mono", "SFMono-Regular", Menlo, Consolas, monospace;
    font-size: 0.75rem;
    letter-spacing: 0.08em;
    color: var(--accent);
}

.table-row {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    padding: 0.8rem 1rem;
    margin-bottom: 0.5rem;
    border-radius: var(--radius);
    background: rgba(0, 0, 0, 0.17) padding-box;
    border: 1px solid rgba(255, 255, 255, 0.07);
}

.table-row .room-code { color: var(--accent); flex: none; }
.table-ident { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.3rem; }
.table-name { font-weight: 600; font-size: 0.95rem; }

/* ---- Die Tischzeile ----

   Zwei Zeilen statt einer: oben, welcher Tisch das ist und wonach dort gespielt wird; unten
   die Antwort auf "kann ich da rein". */
.table-row.seatable { cursor: pointer; }
.table-row.seatable:hover { border-color: rgba(255, 255, 255, 0.16); }

.table-head { display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; }

/* Der Code in Monospace und gedeckt: Er ist zum Weitersagen da, nicht zum Lesen. */
.table-head .room-code {
    font-family: ui-monospace, "Cascadia Mono", "SFMono-Regular", Menlo, Consolas, monospace;
    font-size: 0.8rem;
    letter-spacing: 0.08em;
    color: var(--text-faint);
}

.table-head .preset { font-size: 0.75rem; }

.table-seats { display: flex; align-items: center; gap: 0.55rem; font-size: 0.82rem; }

.seat-dots { display: flex; gap: 4px; flex: none; }

/* Vier Punkte für vier Plätze. Die Farbe allein trägt nichts — daneben steht dieselbe
   Auskunft in Worten; die Punkte sind nur der schnellere Weg dorthin. */
.seat-dot {
    width: 19px;
    height: 19px;
    border-radius: 50%;
    box-sizing: border-box;
}

.seat-dot.human { background: #4d8f6c; }
.seat-dot.bot { background: rgba(255, 255, 255, 0.2); }
.seat-dot.free { border: 1.5px dashed rgba(255, 255, 255, 0.24); }

/* Alle Knöpfe der Liste gleich breit.
   Die Beschriftung wechselt je Zeile zwischen "Beitreten" und "Zuschauen", und die beiden
   Wörter sind unterschiedlich lang — gemessen 83,2 gegen 94,0 Bildpunkte. Untereinander
   ergab das eine Kante, die bei jeder Zeile ein Stück wandert. 6,5rem fasst das längere
   Wort mit etwas Luft; damit steht die linke Kante aller Knöpfe auf einer Linie. */
.table-row button { min-width: 6.5rem; flex: none; }

/* ================================================================
   Raum
   ================================================================ */

/* Keine Breitenbegrenzung: Ein Kartentisch darf den Bildschirm nutzen, den er bekommt. */
.room { height: 100%; display: flex; flex-direction: column; min-height: 0; }

.room-body {
    flex: 1;
    min-height: 0;
    display: grid;
    grid-template-columns: minmax(0, 1fr) 21rem;
    gap: 1rem;
    align-items: stretch;
}

.table-area { min-height: 0; display: flex; flex-direction: column; }
.sidebar { min-height: 0; overflow-y: auto; display: flex; flex-direction: column; }

@media (max-width: 74rem) {
    .room-body { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr) auto; }
    .app { height: auto; min-height: 100vh; overflow: visible; }
}

.notice {
    background: var(--accent-soft) padding-box;
    border: 1px solid var(--accent-line);
    border-radius: var(--radius-sm);
    padding: 0.55rem 0.8rem;
    margin-bottom: 0.8rem;
    font-size: 0.88rem;
    cursor: pointer;
}

.notice.error { background: var(--danger-soft) padding-box; border-color: var(--danger-line); color: var(--danger); }

/* Platzanfragen: Der Kasten ist kein Hinweis zum Wegklicken, sondern trägt Knöpfe. Er steht
   unter den schwebenden Meldungen — deshalb muss er selbst anklickbar bleiben. */
.notice.seat-request {
    cursor: default;
    pointer-events: auto;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
}

.notice.seat-request button { margin-left: auto; }
.notice.seat-request button + button { margin-left: 0; }

/* Meldungen am Tisch schweben, statt den Tisch nach unten zu drücken. Ohne das rutscht
   das eigene Blatt jedes Mal, wenn der Server etwas mitteilt. */
.toasts {
    position: fixed;
    right: 1.25rem;
    top: 4.5rem;
    z-index: 60;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    width: min(26rem, calc(100vw - 2.5rem));
    pointer-events: none;
}

.toasts .notice { margin: 0; pointer-events: auto; box-shadow: var(--shadow); background: var(--panel-raised); }
.toasts .notice.error { background: var(--panel-raised); }

/* ---- Modale Fenster ---- */

.modal-backdrop {
    position: fixed;
    inset: 0;
    z-index: 80;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: clamp(1rem, 5vh, 3rem);
    background: rgba(4, 8, 6, 0.66);
    outline: none;
}

.modal {
    display: flex;
    flex-direction: column;
    width: min(38rem, 100%);
    max-height: 100%;
    border-radius: var(--radius-lg);
    border: 1px solid var(--line);
    background: var(--panel);
    box-shadow: var(--shadow-lift);
}

.modal.wide { width: min(64rem, 100%); }

.modal-head {
    display: flex;
    align-items: baseline;
    gap: 0.7rem;
    padding: 0.9rem 1.1rem;
    border-bottom: 1px solid var(--line-soft);
}

.modal-head h2 { margin: 0; font-size: 1rem; font-weight: 600; letter-spacing: 0.01em; }
.modal-head .muted { font-size: 0.82rem; }

/* Der Schließer wird nach rechts geschoben, damit zwischen Titel und Untertitel kein
   künstlicher Abstand entsteht. */
.modal-head .close { margin-left: auto; align-self: center; padding: 0.2rem 0.55rem; font-size: 0.9rem; }

.modal-body { padding: 1.1rem; overflow: auto; }
.modal-body > :first-child { margin-top: 0; }

.modal-foot { padding: 0.8rem 1.1rem; border-top: 1px solid var(--line-soft); display: flex; gap: 0.5rem; }

/* ---- Platzverwaltung vor der Partie ---- */

/* Die vier Plätze teilen sich eine Spalteneinteilung.

   Vorher war jede Zeile ein eigener Flex-Halter: Zeichen, Himmelsrichtung, Name, Knopf —
   jedes so breit wie sein Inhalt. Weil „Jupp“ kürzer ist als „Karl-Heinz“, begann jeder
   Knopf woanders, und die Spalte franste aus.

   Die Liste ist deshalb das Raster, die Zeilen sind Unterraster. Damit misst die
   Namensspalte in allen vier Zeilen gleich viel — so breit wie der längste Name —, und die
   Knöpfe stehen untereinander, aber dicht hinter den Namen.

   Nicht „margin-left: auto“ am ersten Knopf: Das schiebt die Gruppe an den rechten Rand der
   Zeile. In einer schmalen Liste sieht das bündig aus, bei 1206 Bildpunkten Zeilenbreite
   stünde „entfernen“ neunhundert Bildpunkte hinter dem Namen. Bündig ist nicht dasselbe wie
   zusammengehörig. */
.seat-list {
    display: grid;
    grid-template-columns: auto 3.5rem minmax(0, max-content) 1fr;
    gap: 0.4rem;
}

.seat-row {
    grid-column: 1 / -1;
    align-items: center;
    padding: 0.5rem 0.6rem;
    border-radius: var(--radius-sm);
    background: rgba(0, 0, 0, 0.18);

    /* Der Rückfall zuerst, das Unterraster danach: Ein Browser ohne subgrid überliest die
       zweite Zeile und behält die erste. Dann sind die Spalten je Zeile eigen, aber die
       feste Namensbreite hält die Knöpfe trotzdem in einer Linie. */
    display: grid;
    grid-template-columns: auto 3.5rem minmax(0, 12rem) 1fr;
    grid-template-columns: subgrid;

    column-gap: 0.55rem;
}

.seat-name { font-weight: 600; font-size: 0.85rem; color: var(--text-dim); }

/* Die Knopfgruppe sitzt am linken Rand ihrer Spalte — die Spalte selbst nimmt den ganzen
   Rest der Zeile ein, damit rechts nichts weiter aufgefüllt wird. */
.seat-actions { display: flex; align-items: center; gap: 0.4rem; justify-self: start; }

/* Der Zusatz "(Bot)" klebte am Namen: Razor lässt zwischen zwei Ausdrücken keinen
   Leerraum stehen, und ohne Abstand wird aus Karl-Heinz und dem Zusatz ein Wort. */
.occupant { display: inline-flex; align-items: baseline; gap: 0.35rem; font-size: 0.9rem; }
.occupant.bot { font-style: italic; }
.occupant.away { opacity: 0.6; }
.seat-row .spacer { flex: 1; }

/* ================================================================
   Der Tisch
   ================================================================ */

.table {
    position: relative;
    flex: 1;
    min-height: 0;
    display: grid;
    /* 18 statt 16 rem: Die Kachel bleibt bei 15 rem, daneben braucht die Ordensspange
       ihre 2,15 rem. Ohne den Zuschlag würde die Ablage in der Mitte schmaler. */
    grid-template-columns: 18rem minmax(0, 1fr) 18rem;
    grid-template-rows: auto minmax(0, 1fr) auto;
    grid-template-areas:
        ".    top    ."
        "left trick right"
        "hand hand   hand";
    gap: 1rem;
    padding: 1.25rem;
    border-radius: var(--radius-lg);
    border: 1px solid var(--felt-line);
    background:
        radial-gradient(ellipse 70% 60% at 50% 42%, var(--felt-center), var(--felt-edge) 78%);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05), var(--shadow);
}

.opponent.top { grid-area: top; justify-self: center; }
.opponent.left { grid-area: left; align-self: center; }
.opponent.right { grid-area: right; align-self: center; }
/* min-height: 0 ist hier keine Kosmetik, sondern die Erlaubnis zu schrumpfen: Ohne sie
   nimmt ein Flex-Element mindestens seine Inhaltsgröße ein, wächst über seine Rasterzelle
   hinaus und quillt bei justify-content: center oben und unten gleichmäßig heraus. Genau so
   landete die Statuszeile auf der Handlungsleiste — gemessen 250 Pixel Inhalt in einer
   184 Pixel hohen Zelle. */
.trick-area {
    grid-area: trick;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.8rem;
    min-height: 0;
}
.my-area { grid-area: hand; }

/* ---- Mitspieler ---- */

/* Eine Kachel je Mitspieler, in fester Größe.

   Sie wuchs und schrumpfte einmal mit ihrem Inhalt: ein längerer Name, ein Statuswort mehr,
   eine Ansage — und vor allem das Videobild, das als eigener Block darüber saß und mit der
   Kamera erschien oder verschwand. Damit hatte dieselbe Kachel je nach Lage drei
   verschiedene Höhen, und ein zugeschaltetes Bild schob den halben Tisch.

   Jetzt steht jede Zeile an ihrem Platz, auch wenn sie leer ist. Am Rand ist reichlich
   Raum — die Kachel darf ihn nehmen und behalten. */
.seat-tile {
    width: 15rem;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    padding: 0.45rem;
    border-radius: var(--radius);
    background: rgba(0, 0, 0, 0.18);
    border: 1px solid transparent;
    transition: border-color 0.15s, background 0.15s;
}

/* Wer am Zug ist, bekommt den einzigen farbigen Rahmen am Tisch. */
.seat-tile.active {
    border-color: var(--accent-line);
    background: rgba(224, 166, 60, 0.1);
}

/* Die Bildfläche ist immer vorhanden — mit Kamera das Videobild, ohne Kamera das
   Sitzkennzeichen. Genau daran hängt, dass nichts springt, wenn jemand mitten im Spiel
   seine Kamera an- oder abschaltet: Der Platz war schon vorher da. */
/* 16:9 und nicht 4:3. Kameras liefern 16:9 — im hochformatigeren Rahmen wurde das Bild
   beschnitten oder bekam Balken, und die Kachel war 40 Pixel höher als nötig. Mal drei
   Plätze ist das der Unterschied zwischen "passt" und "wird eng" auf einem flachen Fenster. */
.seat-face {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-sm);
    overflow: hidden;
    background: var(--bg-bar);
}

.seat-face video { width: 100%; height: 100%; object-fit: cover; display: none; }
.seat-face.live video { display: block; }

/* Hier standen das Buchstabenzeichen für Menschen ohne Kamera und die gezeichnete
   Bot-Silhouette — zwei Bilder für dieselbe Stelle. Ersetzt durch das gemeinsame Sitzbild
   in SeatPortrait.razor, das sein CSS selbst mitbringt.

   Nicht betroffen: .bot-tag, die Marke hinter dem Namen. */

/* Name und Zustand in <b>einer</b> Zeile fester Höhe.

   Vorher standen sie übereinander, und der Zustand wurde weggelassen, wenn es nichts zu
   sagen gab. Damit wuchs und schrumpfte die Kachel bei jedem "denkt nach" — und weil die
   Kacheln die Höhe des Tisches bestimmen, wanderte die halbe Ansicht mit. Genau das war das
   Hüpfen.

   Die feste Höhe ist die eigentliche Zusicherung: Was auch immer in dieser Zeile steht oder
   fehlt, die Kachel misst immer gleich viel. Alles andere in der Kachel liegt absolut im
   Bild und kann ohnehin nichts verschieben. */
.seat-ident {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    min-width: 0;
    height: 1.6rem;
}

/* Der Name gibt nach, der Zustand nicht: Ein langer Name wird gekürzt, "wird vertreten"
   bleibt lesbar. Umgekehrt bräche die Zeile um, und wir wären wieder beim Hüpfen. */
.seat-ident .opponent-name { flex: 1 1 auto; min-width: 0; }
.seat-ident .opponent-status { flex: none; }

/* Am Tisch liest man Namen im Vorbeischauen. Neben dem Kartenbild waren 0,88 rem dafür
   zu wenig — Platz ist an den Seiten reichlich. */
.opponent-name { font-size: 1.1rem; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.opponent-status {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.84rem;
    color: var(--text-faint);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.opponent-status .dot { width: 0.4rem; height: 0.4rem; border-radius: 50%; background: var(--text-faint); flex: none; }
.seat-tile.active .opponent-status .dot { background: var(--accent); }

/* Hier stand .hand-count, die Zahl der Handkarten in der Ecke des Bildes. Sie ist ersatzlos
   weg — alle vier haben immer gleich viele Karten, und wie viele es sind, sagen das eigene
   Blatt und der abgeräumte Stapel. Die obere rechte Ecke der Kachel ist damit frei. */

/* Der letzte Stich, angeschrieben an dem, der ihn geholt hat.
   Gegenüber der Kartenzahl, also links oben — die beiden Ecken tragen damit je eine
   Angabe und die Kachel bleibt ausgewogen. Volle Bernsteinfläche statt eines Rahmens:
   Das Zeichen liegt je nach Lage auf einem Videobild, einem Sitzkennzeichen oder dem
   Filz, und nur eine deckende Fläche liest sich auf allen dreien gleich gut. */
.trick-badge {
    position: absolute;
    top: 0.3rem;
    left: 0.3rem;
    padding: 0.1rem 0.4rem;
    border-radius: var(--radius-sm);
    background: var(--accent);
    color: var(--accent-ink);
    font-size: 0.72rem;
    font-weight: 700;
    white-space: nowrap;
    box-shadow: var(--shadow-soft);
    animation: trick-badge-in 0.5s cubic-bezier(0.2, 0.9, 0.3, 1.4);
}

/* Einmal kurz aufziehen, dann stehen bleiben. Es geht darum, den Blick zu holen, wenn das
   Zeichen den Platz wechselt — nicht darum, dauerhaft zu zappeln. */
@keyframes trick-badge-in {
    from { opacity: 0; transform: scale(0.6); }
    to { opacity: 1; transform: scale(1); }
}

/* ---- Partei und Ansagen je Platz ---- */

/* Feste Mindesthöhe: Sagt jemand mitten im Spiel an, sollen die Kacheln nicht springen. */
/* Kachel und Ordensspange nebeneinander.

   Die Spange steht immer da, auch leer — sonst rückte die Kachel beim ersten Vorbehalt zur
   Seite. "Außen" heißt vom Tisch weg: Am linken Platz liegt sie links der Kachel, sonst
   rechts, wo sie keine Höhe kostet.

   Hier stand vorher die Ansagezeile, die im Bild lag. Sie ist ersatzlos weg: Marken auf
   einem Gesicht sind im Weg, sobald dort ein Videobild steht. */
.seat-slot { display: flex; align-items: flex-start; }
.seat-slot.west { flex-direction: row-reverse; }

/* "Bot" gehört an den Namen, nicht in eine eigene Zeile — wie ein Nachname, nur leiser. */
.bot-tag {
    margin-left: 0.35rem;
    padding: 0 0.3rem;
    border-radius: 3px;
    background: rgba(255, 255, 255, 0.1);
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-dim);
    vertical-align: 0.1em;
}

/* ---- Die Tafel oben links ----

   Sie liegt über dem Tisch statt darin: absolut gesetzt, also ohne jede Wirkung auf die
   Höhenverteilung. Die Zeile, die sie ersetzt, stand quer unter der Ablage und nahm den
   Karten Platz weg. */
.table-board {
    position: absolute;
    top: 0.75rem;
    left: 0.9rem;
    z-index: 4;

    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.4rem;

    /* Nichts hier ist anklickbar — der Tisch darunter schon. */
    pointer-events: none;
}

.board-main { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }

/* Die Spielart ist die eine Auskunft, die immer gilt — entsprechend groß. */
.board-game {
    font-size: 1.15rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text);
    text-shadow: 0 1px 8px rgba(0, 0, 0, 0.6);
}

/* Die Bockrunde ruft. Sie verdoppelt jeden Punkt des Abends, und wer sie übersieht,
   verspielt sie — deshalb hier laut statt als eine Marke unter vielen. */
.board-bock {
    padding: 0.2rem 0.6rem;
    border-radius: var(--radius-sm);
    background: var(--danger) padding-box;
    border: 1px solid var(--danger);
    color: #fff;
    font-size: 0.95rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
}

.board-round {
    padding: 0.15rem 0.5rem;
    border-radius: var(--radius-sm);
    background: var(--accent-soft) padding-box;
    border: 1px solid var(--accent-line);
    color: var(--accent);
    font-size: 0.8rem;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

/* Was nur mich betrifft: kleiner, darunter, und nicht da, wenn es nichts gibt. */
.board-mine { display: flex; align-items: center; gap: 0.35rem; flex-wrap: wrap; }
.board-mine:empty { display: none; }

/* Die eigene Partei in der eigenen Zeile des Punktestands. */
.mini-chip.party.re { background: var(--re-soft); color: var(--re-ink); }
.mini-chip.party.kontra { background: var(--kontra-soft); color: var(--kontra-ink); }
.mini-chip.party.re.loud { background: var(--re); color: #fff; }
.mini-chip.party.kontra.loud { background: var(--kontra); color: #fff; }

/* Der Kachelrahmen in der Zustandsfarbe — statisch, nie animiert.

   :not(.active), damit der Rahmen des Spielers am Zug gewinnt: Wer gerade dran ist, ist die
   dringendere Auskunft, und zwei Rahmenfarben an einer Kachel gibt es nicht. */
.seat-tile.ann-solo:not(.active) { border-color: #c0483c; }
.seat-tile.ann-vorbehalt:not(.active) { border-color: #e0a83a; }

/* Dasselbe Kennzeichen klein in der Punktestand-Zeile. */
.mini-chip {
    margin-left: 0.4rem;
    padding: 1px 5px;
    border-radius: 4px;
    font-family: ui-monospace, "Cascadia Mono", "SFMono-Regular", Menlo, Consolas, monospace;
    font-size: 8.5px;
    font-weight: 700;
    letter-spacing: 0.06em;
    vertical-align: middle;
    white-space: nowrap;
}

.mini-chip.solo { background: #c0483c; color: #f5b3aa; }
.mini-chip.vorbehalt { background: #e0a83a; color: #14281c; }

/* Die zusammengeklappte Bestätigung anstelle der Knopfreihe. */
.prompt.settled strong {
    font-family: ui-monospace, "Cascadia Mono", "SFMono-Regular", Menlo, Consolas, monospace;
    letter-spacing: 0.08em;
    color: var(--accent);
}

/* Die Rolle aus der Vorbehaltsrunde — Solo, Armut, "nimmt an", Hochzeit.

   Bewusst ruhiger als die Partei- und Absage-Marken daneben: Die sagen, wie es gerade
   steht, und ändern sich mitten im Spiel. Die Rolle steht von der ersten Karte an fest und
   soll nur da sein, wenn man hinsieht — nicht rufen. */
.badge.absage { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); font-weight: 600; }

.badge.schwein { padding: 0 0.35rem; font-size: 0.85rem; line-height: 1; letter-spacing: 0; }

/* Der eigene Stich in der Statuszeile — dieselbe Fläche wie an den Kacheln der anderen,
   damit dieselbe Auskunft nicht zweierlei aussieht.

   Der Zusatz heißt "trick-won" und nicht "trick": ".trick" ist bereits vergeben — das ist
   das Vierer-Raster der Karten in der Tischmitte. Die Marke erbte dessen
   "grid-template-columns: repeat(4, …)" und war damit 483 statt 60 Bildpunkte breit. */
.badge.trick-won {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-ink);
    font-weight: 700;
    animation: trick-badge-in 0.5s cubic-bezier(0.2, 0.9, 0.3, 1.4);
}

/* ---- Stich und Ablegezone ---- */

.game-status {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.3rem 0.75rem;
    border-radius: var(--radius-pill);
    background: rgba(0, 0, 0, 0.2);
    border: 1px solid var(--accent-line);
    font-size: 0.72rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-dim);
}

.game-status .dot { width: 0.4rem; height: 0.4rem; border-radius: 50%; background: var(--accent); }
.game-status strong { color: var(--text); font-weight: 600; }
.game-status .sub { text-transform: none; letter-spacing: 0; color: var(--text-faint); }

/* Steht unter der Statuszeile, solange eine Hochzeit läuft: worauf gewartet wird, und
   ab wann es entschieden ist. */
.hochzeit-note {
    font-size: 0.78rem;
    color: var(--text-dim);
    background: rgba(0, 0, 0, 0.17);
    border-left: 2px solid var(--accent);
    border-radius: var(--radius-sm);
    padding: 0.3rem 0.65rem;
    max-width: 30rem;
}

/* Die Zielfläche ist absichtlich größer als die vier Karten, die darin liegen. Sie war
   einmal knapp bemessen und während eines laufenden Stichs randlos — wer eine Karte
   dorthin zog, zielte auf etwas Unsichtbares und ließ sie oft daneben los. Zusammen mit
   der Toleranz im Zeigermodul trifft man jetzt auch ungenau.

   Der Rahmen sieht immer gleich aus. Er wechselte einmal mit jeder Lage: heller, sobald man
   am Zug war, unsichtbar, sobald ein fertiger Stich dalag, und wieder zurück, sobald die
   nächste Karte fiel. Das sind mehrere Wechsel je Stich an einer Fläche, die ihre Größe nie
   ändert — aus zwei Metern Abstand sah es aus, als flackere der Tisch. Die Fläche ist
   ohnehin reserviert, also darf der Rahmen einfach stehen bleiben. */
/* Die Ablage nimmt, was nach der Statuszeile übrig bleibt — und gibt ab, wenn es eng wird.
   Vorher stand hier eine Mindesthöhe, die auf einem flachen Fenster nicht mehr passte; die
   Zeile darunter rutschte dann aus der Mitte heraus. */
.drop-zone {
    /* Bezugsgröße für die Karten des Stichs — sie messen sich über cqh an dieser Fläche.
       container-type: size heißt auch, dass die Fläche ihre Größe nicht mehr vom Inhalt
       nimmt; hier ist das richtig, denn sie bekommt sie ohnehin von flex: 1. */
    container-type: size;

    flex: 1;
    min-height: 0;
    max-height: 17rem;
    display: flex;
    align-items: center;
    justify-content: center;
    width: min(42rem, 100%);
    padding: 1rem;

    /* Bezugspunkt für die Augenzahl, die unten in der Kante sitzt. */
    position: relative;

    border: 1.5px dashed rgba(255, 255, 255, 0.13);
    border-radius: var(--radius-lg);
    transition: border-color 0.12s, background 0.12s;
}

/* Die einzige erlaubte Ausnahme: Solange wirklich eine Karte am Zeiger hängt, zeigt der
   Tisch, wohin sie darf. Das ist kein Flackern, sondern die Antwort auf etwas, das man
   selbst gerade tut — und es endet mit dem Loslassen. */
.drop-zone.drop-armed { border-color: var(--accent-line); background: rgba(224, 166, 60, 0.05); }

.drop-zone.drop-active {
    border-color: var(--accent);
    border-style: solid;
    background: var(--accent-soft);
}

.drop-hint { display: flex; flex-direction: column; align-items: center; gap: 0.4rem; color: var(--text-faint); font-size: 0.9rem; }
.drop-hint::before { content: "+"; font-size: 1.3rem; opacity: 0.7; }

/* Vier feste Plätze in einer Reihe — aber jeder Platz gehört einem Spieler.
 *
 * Vorher wurde von links nach rechts in der Reihenfolge des Legens belegt. Die Reihe
 * verriet damit nur, wer wann gelegt hatte, und das musste man mitzählen: Wer herausgekommen
 * war, stand nirgends.
 *
 * Jetzt hat jede Himmelsrichtung ihre feste Spalte, in der Reihenfolge West – Nord – Süd –
 * Ost. Die beiden mittleren tragen zusätzlich einen kleinen Versatz: Nord ein Stück nach
 * oben, Süd ein Stück nach unten. Das ist das Kreuz, nur flachgedrückt — man sieht die
 * Richtung, ohne dass zwei volle Kartenhöhen dafür draufgehen.
 *
 * Ein echtes Kreuz war der erste Versuch. Es las sich gut, kostete aber die doppelte Höhe
 * (gemessen 324 statt 164 Bildpunkte) und zwang die Karten von 176 auf 146 Bildpunkte
 * herunter. Der Versatz bringt dieselbe Auskunft für 26 Bildpunkte.
 *
 * Die Fläche steht dabei weiter von Anfang an fest und rührt sich nicht, wenn eine Karte
 * dazukommt. Leere Plätze bleiben leer. */
.trick {
    /* Die Kartenhöhe folgt der Ablegefläche und nicht mehr allein der Fensterhöhe.
     *
     * 16vh allein ging schief: Im Fenster 1440x800 blieben der Ablage nur 111 Bildpunkte,
     * die Reihe brauchte aber 175 und stand über den Rand. Der Fehler war älter als das
     * Kreuz — schon die alte Reihe brauchte dort 149 Bildpunkte. Mit cqh misst sich die
     * Karte an dem Kasten, in dem sie wirklich liegt; die Fensterhöhe bleibt als zweite
     * Grenze daneben stehen, damit die Karte auf einem breiten, flachen Schirm nicht
     * unnötig wächst.
     *
     * Die Untergrenze von 5rem ist kein gerundeter Wunschwert, sondern eine Grenze der
     * Lesbarkeit: Darunter wird die Karte schmaler als 53 Bildpunkte, und bei 34 war eine
     * Dame von einem König nicht mehr zu unterscheiden. Im Fenster 1440x800 bleiben der
     * Ablage nur 111 Bildpunkte — dort greift die Untergrenze, und die Karten ragen ein
     * Stück über die gestrichelte Fläche hinaus. Das ist die bessere der beiden schlechten
     * Möglichkeiten; das eigentliche Problem ist der Höhenhaushalt des Tisches auf flachen
     * Fenstern, und der gehört woandershin. */
    --trick-card-h: clamp(5rem, min(16vh, 100cqh - 4.6rem), 11rem);

    /* Der Versatz. Groß genug, um ihn im Vorbeischauen zu sehen, klein genug, dass die
       Reihe eine Reihe bleibt. Oben und unten steht er als Polster noch einmal, sonst
       schöbe die versetzte Karte die Fläche auf oder liefe aus ihr heraus. */
    --trick-lift: 0.85rem;

    display: grid;
    grid-template-columns: repeat(4, calc(var(--trick-card-h) * 0.658));
    gap: 0.8rem;
    align-items: center;
    padding-block: var(--trick-lift);
}

/* Feste Höhe aus Karte, Abstand und Namenszeile. Nötig, weil in derselben Reihe gelegte
   Karten und leere Plätze nebeneinanderstehen: Ohne die feste Höhe wäre ein leerer Platz
   niedriger und säße dadurch anders in der Zeile als seine Nachbarn. */
.trick-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.35rem;
    min-width: 0;
    height: calc(var(--trick-card-h) + 0.35rem + 1.1rem);
}

.trick-card .muted { height: 1.1rem; line-height: 1.1rem; }

/* Der angedeutete Platz. Gestrichelt wie die Ablegefläche selbst — dieselbe Sprache für
   dasselbe: hier gehört etwas hin, das noch nicht da ist. Sehr blass, weil vier davon
   nebeneinanderstehen und sie zusammen ruhiger sein müssen als eine einzelne Karte. */
.trick-slot {
    width: 100%;
    height: var(--trick-card-h);
    border: 1.5px dashed rgba(255, 255, 255, 0.11);
    border-radius: 6px;
}

/* Die Spalte hängt am Platz und nicht an der Reihenfolge des Legens.

   Der Versatz steht als --lift und nicht direkt im transform: Beim Einsammeln übernimmt
   eine Bewegung dieselbe Eigenschaft, und sie muss den Versatz mitführen — sonst rutschte
   die Karte im ersten Bild der Bewegung erst einmal auf die Grundlinie zurück. */
.trick-card { transform: translateY(var(--lift, 0)); }

.trick-card.west { grid-column: 1; }
.trick-card.north { grid-column: 2; --lift: calc(var(--trick-lift) * -1); }
.trick-card.south { grid-column: 3; --lift: var(--trick-lift); }
.trick-card.east { grid-column: 4; }

/* Der Name darf seinen Platz nicht sprengen — sonst schöbe ein langer Name die Spalte
   auseinander und der feste Platz wäre wieder keiner. */
.trick-card .muted {
    color: var(--text-faint);
    font-size: 0.82rem;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Wer den Stich geholt hat, steht am Namen — nicht in einem zweiten Rahmen um die Karte.
   Der lag als drittes Rechteck um eine Karte, die schon ihren gedruckten Rand und den
   Kasten der Ablegefläche hat, tauchte am Ende jedes Stichs auf und verschwand mit dem
   Abräumen wieder. Dieselbe Auskunft trägt der Name darunter, ohne dass sich etwas rührt. */
.trick-card.winner .muted { color: var(--accent); font-weight: 600; }
/* Höhe aus derselben Marke wie die Spaltenbreite des Rasters. Stünde hier noch einmal ein
   eigenes clamp, könnten Platz und Karte auseinanderlaufen, ohne dass es jemandem auffällt. */
.trick-card .card { height: var(--trick-card-h); width: 100%; aspect-ratio: 0.658; }

/* Feste Höhe: Die Zeile erscheint erst, wenn Karten liegen — ohne festen Platz schöbe sie
   beim Auftauchen die Statuszeile nach unten. */
/* Die Augenzahl liegt im Rahmen, unten an der Kante — nicht mehr als eigene Zeile darunter.

   Absolut gesetzt, damit sie keine Höhe kostet: Die 1,2 rem, die sie als Zeile immer
   reserviert hielt, gehen an die Kartenreihe. Der Rahmen bringt seinen eigenen Platz mit;
   unter den Karten steht ohnehin nichts. */
.trick-line {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0.35rem;
    text-align: center;
    color: var(--text-faint);
    font-size: 0.85rem;
    pointer-events: none;
}

/* ---- Der Weg der Karte ----

   Hinflug: Die Karte kommt von dem, der sie gelegt hat. Das gab es schon, war mit 0,22 s
   und 90 bis 120 Bildpunkten aber eher ein Aufblitzen als ein Weg — man sah die Karte
   liegen, nicht kommen. Jetzt segelt sie: weiter, länger, und mit einer Drehung, die sich
   unterwegs auflöst, als würde sie über den Filz geschoben.

   Die Strecken sind größer als der Abstand zur Kachel. Das ist Absicht: Die Karte tritt am
   Bildrand der Ablage ein und nicht mittendrin aus dem Nichts. */
.card.dealt { animation: fly-in 0.44s cubic-bezier(0.16, 0.72, 0.24, 1) both; }
.card.from-south { --from-x: 0; --from-y: 230px; --from-turn: -7deg; }
.card.from-west { --from-x: -300px; --from-y: 40px; --from-turn: 11deg; }
.card.from-north { --from-x: 0; --from-y: -230px; --from-turn: 7deg; }
.card.from-east { --from-x: 300px; --from-y: 40px; --from-turn: -11deg; }

@keyframes fly-in {
    from {
        opacity: 0;
        transform: translate(var(--from-x), var(--from-y)) rotate(var(--from-turn, 0deg)) scale(0.86);
    }
    30% { opacity: 1; }
    to { opacity: 1; transform: translate(0, 0) rotate(0deg) scale(1); }
}

/* ---- Und wieder weg: der Stich geht zu dem, der ihn geholt hat ----

   Vorher verschwand der fertige Stich einfach. Wer holt ihn? Das stand als Wort in der
   Zeile darunter, und wer die nicht las, sah nur, dass vier Karten weg waren.

   Die Richtung setzt der Tisch als --to-x/--to-y; der Versatz der mittleren Spalten steckt
   in --lift und muss in den Bildpunkten mitgeführt werden, sonst spränge die Karte beim
   Anlaufen der Bewegung erst einmal in die Mitte zurück. */
.trick.collecting .trick-card {
    animation: trick-collect 0.5s cubic-bezier(0.5, 0, 0.75, 0) both;
}

/* Gestaffelt nach Spalte, damit der Stapel sich zieht statt als Block zu springen. */
.trick.collecting .trick-card.west { animation-delay: 0.00s; }
.trick.collecting .trick-card.north { animation-delay: 0.04s; }
.trick.collecting .trick-card.south { animation-delay: 0.08s; }
.trick.collecting .trick-card.east { animation-delay: 0.12s; }

/* Der leere Platz bleibt, wo er ist — er gehört zur Fläche und nicht zum Stich. */
.trick.collecting .trick-card:not(:has(.card)) { animation: none; }

/* Wohin gesammelt wird. Dieselben Richtungen wie beim Hinflug, nur andersherum — und ein
   Stück weiter, weil die Karten aus dem Bild heraus sollen. */
.trick.to-south { --to-x: 0; --to-y: 260px; --to-turn: 6deg; }
.trick.to-west { --to-x: -340px; --to-y: 30px; --to-turn: -12deg; }
.trick.to-north { --to-x: 0; --to-y: -260px; --to-turn: -6deg; }
.trick.to-east { --to-x: 340px; --to-y: 30px; --to-turn: 12deg; }

@keyframes trick-collect {
    from { opacity: 1; transform: translate(0, var(--lift, 0)) scale(1); }
    to {
        opacity: 0;
        transform:
            translate(var(--to-x, 0), calc(var(--lift, 0) + var(--to-y, 0)))
            rotate(var(--to-turn, 0deg))
            scale(0.5);
    }
}

.status { display: flex; flex-direction: column; align-items: center; gap: 0.45rem; text-align: center; }
.status .muted { color: var(--text-faint); }

/* ---- Zuruf bei Doppelkopf und gefangenem Fuchs ---- */

/* Schwebt über der Tischmitte, nimmt keinen Platz im Raster ein und lässt sich nicht
   anklicken — es soll auffallen, aber nichts verstellen. */
.cheer {
    position: absolute;
    left: 50%;
    top: 22%;
    z-index: 6;
    padding: 0.55rem 1.2rem;
    border-radius: var(--radius-pill);
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    white-space: nowrap;
    pointer-events: none;
    box-shadow: var(--shadow-lift);
    animation: cheer-in 2.8s cubic-bezier(0.2, 0.9, 0.3, 1) both;
}

.cheer.doppelkopf { background: var(--accent); color: var(--accent-ink); }
.cheer.fuchs { background: var(--card-red); color: #fff; }

/* Kurz größer werden, stehen bleiben, dann nach oben verschwinden. Die Prozentwerte
   verteilen sich auf die 2,8 Sekunden, die der Zuruf im Markup steht — beides muss
   zusammenpassen, sonst springt er am Ende. */
@keyframes cheer-in {
    0% { opacity: 0; transform: translate(-50%, 0.6rem) scale(0.85); }
    8% { opacity: 1; transform: translate(-50%, 0) scale(1.06); }
    14% { transform: translate(-50%, 0) scale(1); }
    82% { opacity: 1; transform: translate(-50%, 0) scale(1); }
    100% { opacity: 0; transform: translate(-50%, -1.4rem) scale(0.97); }
}

/* Wer Bewegung im Betriebssystem abbestellt hat, bekommt den Zuruf ohne Flug. */
@media (prefers-reduced-motion: reduce) {
    .cheer { animation: cheer-fade 2.8s linear both; }

    @keyframes cheer-fade {
        0%, 90% { opacity: 1; transform: translate(-50%, 0); }
        100% { opacity: 0; transform: translate(-50%, 0); }
    }
}

.sound-toggle { font-size: 1rem; line-height: 1; padding: 0.45rem 0.6rem; }
.sound-toggle.off { opacity: 0.55; }

/* ---- Der Autopilot ----

   Oben rechts auf dem Filz: derselbe Abstand nach oben und zur Kante wie bei der Tafel oben
   links, nur gespiegelt. In derselben Spalte liegt weiter unten der abgeräumte Stapel — die
   rechte Kante trägt damit beides, und die Mitte bleibt den Karten.

   Ausgeschaltet sieht er aus wie der Stapel darunter: derselbe grüne Absatz auf dem Filz.
   Er soll nicht um Aufmerksamkeit bitten, solange man selbst spielt.

   Eingeschaltet trägt er die volle Akzentfarbe. Das ist der Zustand, in dem man ihn
   wiederfinden muss: Wer nach einer halben Stunde zurückkommt und sieht, dass seine Karten
   von allein fallen, soll die Antwort dort finden, wo er sie abgegeben hat — und nicht
   raten, welcher Knopf ihn wieder ins Spiel bringt. */
.autoplay-toggle {
    position: absolute;
    top: 0.75rem;
    right: 0.9rem;
    z-index: 4;

    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.4rem 0.75rem;
    border-radius: var(--radius);
    border: 1px solid var(--felt-line);
    background: rgba(30, 77, 54, 0.6) padding-box;
    text-align: left;
}

/* Die drei Zustandsregeln stehen bewusst mit :not(:disabled) da: Die allgemeine Regel für
   Schaltflächen ist "button:hover:not(:disabled)" und wiegt (0,2,1). Ohne den Zusatz wöge
   ".autoplay-toggle.on" nur (0,2,0) und verlöre — der eingeschaltete Knopf würde beim
   Überfahren wieder grau. Denselben Fehler hatte einmal die Ansagenleiste. */
.autoplay-toggle:hover:not(:disabled) {
    border-color: var(--accent-line);
    background: rgba(61, 133, 96, 0.4) padding-box;
}

.autoplay-toggle.on {
    background: var(--accent) padding-box;
    border-color: var(--accent);
    color: var(--accent-ink);
}

.autoplay-toggle.on:hover:not(:disabled) {
    background: var(--accent-bright) padding-box;
    border-color: var(--accent-bright);
    color: var(--accent-ink);
}

.autoplay-glyph { font-size: 1.25rem; line-height: 1; }

.autoplay-text { display: flex; flex-direction: column; line-height: 1.25; }
.autoplay-text strong { font-size: 0.85rem; font-weight: 600; }
.autoplay-text .muted { font-size: 0.72rem; }
.autoplay-toggle.on .muted { color: var(--accent-ink); opacity: 0.7; }

/* Der Autopilot an der Kachel eines anderen. Dieselbe Form wie die Bot-Marke daneben — über
   das Blatt sagen beide dasselbe —, aber in Bernstein statt in Grau: Hier sitzt ein Mensch,
   der abgegeben hat, und kein Bot. */
.bot-tag.auto {
    background: var(--accent-soft);
    color: var(--accent);
    letter-spacing: 0.03em;
}

.badge.auto {
    background: var(--accent-soft) padding-box;
    border-color: var(--accent-line);
    color: var(--accent);
}

/* ---- Der abgeräumte Stich ---- */

/* Liegt auf dem Filz statt im Raster: ein kleiner Stapel in der Ecke, wie am echten Tisch.
   Anklicken zeigt ihn groß. */
.last-trick {
    position: absolute;
    right: 1.25rem;
    bottom: 1.25rem;
    z-index: 3;
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.5rem 0.8rem 0.5rem 0.6rem;
    border-radius: var(--radius);
    border: 1px solid var(--felt-line);
    /* Ein grüner Absatz auf dem Filz, kein schwarzer Schleier: Schwarz auf dem helleren Filz
       sah aus wie ein Fleck. Und beim Zeigen wird die Fläche heller, nicht dunkler — die
       Schaltfläche hebt sich ja an, und was sich anhebt, fängt mehr Licht. */
    background: rgba(30, 77, 54, 0.6) padding-box;
    text-align: left;
    cursor: pointer;
    transition: border-color 0.15s, background 0.15s, transform 0.15s;
}

.last-trick:hover { border-color: var(--accent-line); background: rgba(61, 133, 96, 0.4) padding-box; transform: translateY(-2px); }

.pile { display: flex; }
.pile .card { margin-left: -1.55rem; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.6)); }
.pile .card:first-child { margin-left: 0; }

.card.mini { height: 3.1rem; width: calc(3.1rem * 0.658); border-radius: 3px; }

.pile-text { display: flex; flex-direction: column; line-height: 1.3; }
.pile-text strong { font-size: 0.85rem; font-weight: 600; }
.pile-text .muted { font-size: 0.76rem; }

/* Die Abrechnung im Ergebnisfenster. Sie stand einmal als Kasten in der Statuszeile am
   Tisch — dort wuchs die Zeile beim Ende jeder Partie von 27 auf 158 Bildpunkte und schob
   die Ablegefläche zusammen. */
.result-headline { margin: 0; font-size: 1rem; }
.lines { margin: 0.5rem 0 0; padding-left: 1.1rem; color: var(--text-dim); font-size: 0.85rem; }
.lines li { margin-bottom: 0.15rem; }

/* Die Bockrunden-Zeile ist keine weitere Position, sondern die Rechnung über allen anderen —
   deshalb ein Strich davor, wie unter einer Spalte auf dem Block. */
.lines li.bock {
    margin-top: 0.35rem;
    padding-top: 0.35rem;
    border-top: 1px solid var(--line);
    color: var(--text);
    list-style: none;
    margin-left: -1.1rem;
}

/* ---- Regelbuch ---- */

/* In der Lobby steht das Buch dort, wo sonst die Tischliste steht, und die Lobby ist auf
   Fensterhöhe festgesetzt (main.lobby, overflow: hidden). Ohne eigene Bildlaufleiste ragten
   die 51 Regeln einfach aus ihrem Abschnitt heraus — die Fußzeile lag dann quer über der
   Tabelle. Dieselben drei Angaben wie bei .table-list lösen das. */
.rule-book { flex: 1; min-height: 0; overflow-y: auto; padding-right: 0.45rem; }

/* Im Regelfenster am Tisch gibt es keinen Flex-Verbund — dort ist das Fenster selbst
   begrenzt, und das Buch soll seine volle Höhe behalten dürfen. */
.modal .rule-book { flex: none; overflow-y: visible; padding-right: 0; }

/* Und beim Lesen in der Lobby rollt die Lobby als Ganzes — Aufmacher inbegriffen, damit
   die Regeln die volle Fensterhöhe bekommen statt der gut 200 Bildpunkte, die von der
   Tischliste übrig bleiben.

   Nicht "overflow: visible": Die Seite darüber ist auf Fensterhöhe festgesetzt und rollt
   nicht mit. Der Text stünde dann zwar da, wäre aber unerreichbar — nachgemessen mit
   scrollHeight 3091 bei clientHeight 900. */
.lobby.reading { overflow-y: auto; display: block; padding-bottom: 4rem; }
.lobby.reading .open-tables,
.lobby.reading .rule-book { display: block; overflow: visible; min-height: 0; padding-right: 0; }

.rule-book-lead { margin: 0 0 0.9rem; color: var(--text-dim); }

.rule-book-group { margin-bottom: 1.1rem; }

.rule-book-group h4 {
    margin: 0 0 0.35rem;
    font-size: 0.8rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-dim);
}

/* Die Tabelle scrollt in ihrem eigenen Kasten. Sonst schöbe eine lange Regelzeile die
   ganze Seite seitwärts, und das trifft jede andere Ansicht mit. */
.rule-book-group { overflow-x: auto; }

.rule-book-table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }

.rule-book-table th,
.rule-book-table td {
    padding: 0.3rem 0.6rem 0.3rem 0;
    text-align: left;
    vertical-align: top;
    border-bottom: 1px solid var(--line-soft);
}

.rule-book-table thead th {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-dim);
}

.rule-book-table tbody th { font-weight: 500; }

.rule-book-table td { white-space: nowrap; }

/* Nur die Abweichungen sollen ins Auge fallen — der Rest ist Bestätigung, dass alles
   beim Üblichen bleibt. */
.rule-book-table tr.differs th,
.rule-book-table tr.differs td:not(.muted) { color: var(--accent); }

.rule-book-hint {
    display: block;
    font-size: 0.78rem;
    font-weight: 400;
    color: var(--text-dim);
    white-space: normal;
}

/* ---- Gesten ----

   Die Leiste liegt rechts neben dem eigenen Blatt, auf dem Filz. Fünf Spalten, neun Knöpfe:
   Die letzte Reihe bleibt halb leer, und das ist besser als drei mal drei — so steht
   "bin kurz weg" allein und wird nicht versehentlich gedrückt. */
/* Eingeklappt ein einziger Griff, ausgefahren das Raster.

   Vorher stand die Leiste immer da und lag über dem abgeräumten Stapel. Ein Fach, das nur
   aufgeht, wenn man hinzeigt, nimmt keinen Platz weg und macht die Ansicht nicht unruhig. */
/* Der Griff hängt unter der Vorbehaltsleiste, an ihrer linken Kante.

   Er steht im Fluss und nicht absolut auf dem Filz: So gehört er sichtbar zu der Leiste
   darüber, statt frei in einer Ecke zu schweben. Das Fach fährt nach unten heraus — dorthin
   also, wo man nach dem Öffnen ohnehin hinsieht. */
.gesture-flyout {
    position: relative;
    z-index: 5;

    /* Nur so breit wie der Griff. Ohne das nähme die Zeile die volle Breite ein und
       schöbe das Handfach nach unten. */
    width: max-content;
    margin-bottom: 0.5rem;
}

.gesture-handle {
    width: 2.75rem;
    height: 2.75rem;
    min-width: 44px;
    min-height: 44px;
    padding: 0;
    border-radius: var(--radius);
    background: rgba(0, 0, 0, 0.3);
    font-size: 1.35rem;
    line-height: 1;
}

/* Das Fach fährt nach unten heraus und liegt dabei über dem Blatt — es soll die Hand
   verdecken, nicht verschieben.

   Eingeklappt ist es unerreichbar: Ohne visibility bliebe es für Maus und Tastatur da, nur
   unsichtbar, und man klickte versehentlich Gesten. */
.gestures {
    position: absolute;
    top: calc(100% + 0.35rem);
    left: 0;

    display: grid;
    grid-template-columns: repeat(5, 2.75rem);
    gap: 0.3rem;

    padding: 0.6rem 0.75rem;
    border-radius: var(--radius);
    background: rgba(0, 0, 0, 0.45);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);

    transform-origin: left top;
    transform: translateY(-0.35rem);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.16s ease-out, transform 0.16s ease-out, visibility 0s linear 0.16s;
}

/* :focus-within ist kein Beiwerk: Wer mit der Tastatur unterwegs ist, käme sonst nie an die
   Knöpfe — und wer eine Geste angeklickt hat, soll das Fach nicht verlieren, während der
   Zeiger noch darauf steht. */
.gesture-flyout:hover .gestures,
.gesture-flyout:focus-within .gestures {
    transform: translateY(0);
    opacity: 1;
    visibility: visible;
    transition-delay: 0s;
}

@media (prefers-reduced-motion: reduce) {
    .gestures { transition: opacity 0.1s linear, visibility 0s linear 0.1s; transform: none; }
    .gesture-flyout:hover .gestures,
    .gesture-flyout:focus-within .gestures { transform: none; }
}

/* Mindestens 44 × 44 Bildpunkte: Darunter trifft man auf einem Tablet nicht mehr
   zuverlässig, und die Leiste soll man im Vorbeigehen bedienen können. */
.gesture {
    position: relative;
    width: 2.75rem;
    height: 2.75rem;
    min-width: 44px;
    min-height: 44px;

    display: flex;
    align-items: center;
    justify-content: center;

    padding: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    font-size: 1.35rem;
    line-height: 1;
}

/* "Loser" trägt einen Buchstaben statt eines Sinnbilds — es gibt keins, das die Geste
   trifft, und ein gezeichnetes Handzeichen wird an dieser Größe zu Matsch. */
.gesture.letter .glyph { font-weight: 800; font-size: 1.25rem; letter-spacing: 0.02em; }

/* Die Ziffer ist zugleich die Taste. Klein und blass: Wer sie einmal gelesen hat, braucht
   sie nicht mehr, und wer sie nie liest, soll nicht gestört werden. */
.gesture .key {
    position: absolute;
    right: 0.2rem;
    bottom: 0.1rem;
    font-size: 0.6rem;
    color: var(--text-faint);
    pointer-events: none;
}

/* ---- Die gezeigte Geste am Sitzbild ---- */

.gesture-shown {
    position: absolute;
    top: 50%;
    left: 50%;
    z-index: 4;

    display: flex;
    align-items: center;
    justify-content: center;
    width: 3.4rem;
    height: 3.4rem;

    border-radius: 50%;
    background: rgba(8, 20, 14, 0.82);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
    font-size: 1.8rem;
    line-height: 1;
    pointer-events: none;

    animation: gesture-in 0.4s cubic-bezier(0.2, 0.9, 0.3, 1.5) both;
}

@keyframes gesture-in {
    from { transform: translate(-50%, -50%) scale(0.4); opacity: 0; }
    to   { transform: translate(-50%, -50%) scale(1); opacity: 1; }
}

.gesture-shown.letter { font-weight: 800; font-size: 1.5rem; }

/* Mit Kamera klein in die Ecke: Ein Mensch, der gerade spricht, soll nicht hinter einem
   Sinnbild verschwinden. */
.gesture-shown.corner {
    top: auto;
    left: auto;
    right: 0.4rem;
    bottom: 0.4rem;
    width: 2rem;
    height: 2rem;
    font-size: 1.05rem;
    animation-name: gesture-in-corner;
}

@keyframes gesture-in-corner {
    from { transform: scale(0.4); opacity: 0; }
    to   { transform: scale(1); opacity: 1; }
}

/* Wer kurz weg ist, ist am Tisch anwesend, aber nicht dabei — und genau so sieht es aus. */
.portrait-wrap { position: absolute; inset: 0; }
.portrait-wrap.away { opacity: 0.5; }

/* Das kleine Menü am Platz. Ein Eintrag, ein Abbruch — mehr gibt es hier nicht zu
   entscheiden. */
.seat-menu {
    position: absolute;
    top: 0.4rem;
    left: 0.4rem;
    right: 0.4rem;
    z-index: 6;

    display: flex;
    flex-direction: column;
    gap: 0.25rem;

    padding: 0.4rem;
    border-radius: var(--radius-sm);
    background: var(--panel-raised);
    border: 1px solid var(--line);
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.5);
}

.seat-menu button { font-size: 0.78rem; padding: 0.3rem 0.4rem; text-align: left; }

/* ---- Handlungsleiste ---- */

.actions {
    display: flex;
    align-items: center;
    gap: 0.4rem;

    /* Eine Zeile, notfalls zum Schieben.

       Sie brach vorher um, sobald es eng wurde — neun Schalter von „Kein Solo“ bis
       „Fleischloser“ messen zusammen 1198 Bildpunkte. Zwei Zeilen sind nicht nur unschön:
       Auf einem flachen Fenster kostet die zweite 39 Bildpunkte Höhe, und die fehlen dem
       Tisch darüber. Bei 1194 × 744 legte sich die Leiste dadurch über die Kacheln von
       West und Ost.

       Waagerecht schieben statt umbrechen löst beides. Auf dem Laptop ändert sich dadurch
       nichts: Dort steht die Zeile ohnehin in einer Reihe, und wo nichts überläuft,
       erscheint auch keine Rollleiste. */
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: thin;

    padding: 0.6rem 0.75rem;
    margin-bottom: 0.5rem;
    border-radius: var(--radius);
    background: rgba(0, 0, 0, 0.17);

    /* 3,5 rem und nicht 3: Die Leiste ist mit Knöpfen 52 Bildpunkte hoch — ein Knopf misst
       34, dazu zweimal 9 Polsterung. Die alten 3 rem ergaben leer nur 45, und die fehlenden
       sieben kamen jedes Mal dazu, wenn Knöpfe erschienen. Das war der Hüpfer.

       Der Wert ist gemessen, nicht geschätzt; ändert sich die Höhe der Knöpfe, gehört er
       nachgemessen. */
    min-height: 3.5rem;
}

/* Nichts in der Zeile darf schrumpfen oder umbrechen. Ohne das quetscht Flexbox lieber die
   Beschriftungen zusammen, als die Zeile überlaufen zu lassen — aus „Damen-Buben-Solo“
   würde ein dreizeiliger Klumpen, und die Leiste wäre wieder hoch. */
.actions > * { flex: none; }
.actions button { white-space: nowrap; }

.actions .prompt {
    font-size: 0.68rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--text-faint);
    margin-right: 0.3rem;
    white-space: nowrap;
}

/* Die Schweinchen-Ansage steht abgesetzt hinter den Vorbehalten — sie gehört nicht in die
   Reihe, aus der man eines wählt, sondern daneben: Sie schließt nichts aus und verbraucht
   den Zug nicht. */
.actions .schwein-call { margin-left: 0.6rem; border-color: var(--line); }
.actions .schwein-call:first-of-type { margin-left: auto; }

.actions button { border-radius: var(--radius-pill); background: transparent; }

/* :not(.primary) ist hier keine Feinheit, sondern die Reparatur.
 *
 * "\.actions button:hover" war spezifischer als "\.actions button.primary" und überschrieb
 * beim Überfahren den Bernstein mit einer Flächenfarbe. Die Schrift blieb dabei
 * --accent-ink — absichtlich dunkel, weil sie auf Bernstein liegen soll. Dunkel auf dunkel:
 * Der Knopf war unter dem Mauszeiger nicht mehr zu lesen.
 *
 * Der Bernstein selbst war also nie falsch. Falsch war, ihn wegzunehmen und die dafür
 * gewählte Schriftfarbe stehen zu lassen. */
.actions button:not(.primary):hover:not(:disabled) { background: var(--panel-raised); }

.actions button.primary { background: var(--accent); }
.actions button.primary:hover:not(:disabled) { background: var(--accent-bright); border-color: var(--accent-bright); }

/* Die Hochzeit bekommt eine eigene Zeile. Sie ist kein weiterer Vorbehalt unter vielen,
   sondern legt fest, wodurch sich mitten in der Partie die Partei bestimmt — das gehört
   ausgeschrieben und nicht auf einen Knopf mit einem Wort darauf. */
.hochzeit-choice {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.hochzeit-choice .prompt {
    flex-basis: 100%;
    margin: 0 0 0.1rem;
    text-transform: none;
    letter-spacing: 0;
    font-size: 0.78rem;
    color: var(--text-dim);
}

.hochzeit-choice button {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.1rem;
    text-align: left;
    padding: 0.4rem 0.8rem;
    border-radius: var(--radius-sm);
    border: 1px solid var(--line);
    font-weight: 600;
}

.hochzeit-choice button small { font-weight: 400; font-size: 0.72rem; color: var(--text-faint); }
.hochzeit-choice button:hover:not(:disabled) small { color: var(--text-dim); }

/* ================================================================
   Handfächer und Karten
   ================================================================ */

/* Die Kartengröße hängt an der Bildschirmhöhe: Auf einem großen Schirm werden die Karten
   größer, auf einem Laptop passen sie trotzdem unter den Tisch. Das Seitenverhältnis
   entspricht dem viewBox der Motivdatei (1635 : 2486 = 0,658) — nicht dem gerundeten
   100 : 140, das hier einmal stand: Unter object-fit: contain lässt jede Abweichung einen
   weißen Streifen neben dem gedruckten Rahmen stehen. */
/* Der untere Innenabstand ist kein Zierrat: Eine um 19° gedrehte Karte ist rund 25 px
   höher als ihr Kasten, und der Bogen senkt die Randkarten zusätzlich ab. Ohne diesen
   Platz ragen sie unter den Tisch und die Seite bekommt eine Bildlaufleiste.
   Oben steht Platz für das Anheben beim Überfahren. */
/* Der Platz bleibt reserviert, auch wenn keine Karte mehr da ist.
 *
 * Sonst fällt der eigene Bereich am Ende jeder Partie in sich zusammen — gemessen von 261
 * auf 111 Bildpunkte —, und weil die mittlere Rasterzeile den frei werdenden Platz nimmt,
 * wächst der Filz um genau diesen Betrag. Der ganze Tisch springt damit bei jeder letzten
 * Karte einmal auf und beim nächsten Geben wieder zu.
 *
 * Die Höhe hängt nicht an der Zahl der Karten, sondern nur daran, ob überhaupt eine da
 * ist: Der Fächer überlappt waagerecht, zehn Karten sind so hoch wie eine. */
.hand {
    --card-h: clamp(6.5rem, 17vh, 10rem);
    display: flex;
    justify-content: center;
    align-items: flex-end;
    padding: 2.2rem 0 1.7rem;
    min-height: calc(var(--card-h) + 3.9rem);
    touch-action: none;
}

/* 0.658 ist das Verhältnis der Motivdatei (viewBox 1635 : 2486). Bewusst nicht das des
   gedruckten Rahmens — der weiße Kartenrand dazwischen gehört zur Karte. */
.hand .card { height: var(--card-h); width: calc(var(--card-h) * 0.658); }

.card-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

.card {
    --rot: 0deg;
    --lift: 0px;
    width: 3.6rem;
    height: 5rem;
    flex: none;
    /* Der Drehpunkt liegt unterhalb der Karte — dadurch fächert die Hand auf einem Bogen
       auf, statt nur an Ort und Stelle zu kippen. */
    transform-origin: 50% 145%;
    transform: rotate(var(--rot)) translateY(var(--lift)) translate(var(--dx, 0), var(--dy, 0));
    transition: transform 0.13s ease-out, filter 0.13s ease-out;
    filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.5));
    user-select: none;
    -webkit-user-select: none;
}

/* Die Motive zeichnen einen gedruckten Rahmen, der 4,49 % vom Rand der Datei eingerückt
   ist. Dieser Abstand ist kein Fehler, sondern der weiße Kartenrand — so sieht eine echte
   Spielkarte aus, und ohne ihn kleben die Eckzeichen an der Kante.

   Die Box behält deshalb das Verhältnis der Datei, und das Bild wird nicht beschnitten.
   Was einmal zu viel war, war ein *eigener* Rahmen ganz außen: Der stand dann als dritter
   Strich um den weißen Rand herum. Den gibt es nicht mehr.

   Trumpf und Auswahl markieren von außen, damit sie nicht mit dem gedruckten Rahmen
   verschmelzen. Innen liegen die Motive durchsichtig auf der weißen Fläche. */
.card {
    position: relative;
    background: var(--card-face);
    border-radius: 6px;
    overflow: hidden;
}

.card img { display: block; width: 100%; height: 100%; object-fit: contain; pointer-events: none; }

.card.trump { box-shadow: 0 0 0 2px var(--accent); }

/*
   Ausgewählt beim Armutstausch.

   Ein dickerer Ring reichte hier nicht — und zwar aus einem Grund, der erst am Tisch
   auffällt: Trümpfe tragen bereits einen bernsteinfarbenen Ring, und getauscht werden
   gerade Trümpfe. Die Auswahl unterschied sich also von der Nachbarkarte um genau ein Pixel
   Ringstärke in derselben Farbe.

   Jetzt kommen drei Zeichen zusammen, von denen jedes für sich trägt: ein Haken in der Ecke,
   ein dunkler Absatz um den Ring und das Anheben aus der Reihe.
*/
.card.selected {
    box-shadow:
        0 0 0 3px var(--accent),
        0 0 0 7px rgba(23, 58, 41, 0.92),
        0 10px 20px rgba(0, 0, 0, 0.55);
}

.card.selected::after {
    content: "✓";
    position: absolute;
    top: 0.15rem;
    right: 0.15rem;
    width: 1.1rem;
    height: 1.1rem;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--accent);
    color: var(--accent-ink);
    font-size: 0.75rem;
    font-weight: 700;
    line-height: 1;
}

/* Während des Tauschs treten die übrigen Karten zurück. Ohne das sucht man drei
   Ausgewählte in einem Fächer aus zehn gleich hellen Karten. */
.hand.choosing .card:not(.selected) { opacity: 0.45; }

/* Ausgegraut wird nur, wenn es überhaupt eine Wahl gibt.
   Während der Vorbehaltsphase oder wenn ein anderer am Zug ist, ist keine einzige Karte
   spielbar — die ganze Hand grau zu färben sieht dann aus, als sei etwas kaputt. Man
   schaut sich sein Blatt in dem Moment ja gerade an. */
.hand:not(.inspecting) .card:not(.playable) {
    filter: grayscale(0.5) brightness(0.82) drop-shadow(0 1px 2px rgba(0, 0, 0, 0.4));
}

.hand .card.playable { cursor: grab; }

/* Das :not(.dragging) ist kein Zierrat, sondern der Kern des Ziehens.

   Beim Zeigerfang behält die gefangene Karte den Hover-Zustand über den ganzen Zug hinweg —
   der Zeiger gilt weiter als über ihr, auch wenn er längst über der Tischmitte steht. Diese
   Regel hat mit vier Klassen eine höhere Spezifität als .hand .card.dragging mit dreien und
   gewann deshalb jedes Mal, unabhängig von der Reihenfolge im Blatt. Die Karte hob sich
   also brav um 1,6 rem an und blieb dann liegen: Ziehen sah aus, als sei es nicht gebaut.
   Wer hier den Ausschluss entfernt, schaltet Ziehen und Ablegen wieder ab. */
.hand .card.playable:hover:not(.dragging),
.hand .card.playable:focus-visible:not(.dragging) {
    transform: rotate(var(--rot)) translateY(calc(var(--lift) - 1.6rem)) scale(1.05);
    filter: drop-shadow(0 10px 16px rgba(0, 0, 0, 0.6));
    z-index: 5;
    outline: none;
}

/* Gezogen wird die Karte selbst, in voller Deckkraft und mit klarer Kante — kein
   halbdurchsichtiges Schattenbild. Der Drehpunkt wandert in die Mitte, damit sie
   ungekippt und unverzerrt am Zeiger hängt. */
.hand .card.dragging {
    cursor: grabbing;
    transition: none;
    z-index: 40;
    opacity: 1;
    transform-origin: 50% 50%;
    transform: translate(var(--dx, 0), var(--dy, 0)) scale(1.05) rotate(0deg);
    box-shadow: 0 0 0 2px var(--accent);
    filter: drop-shadow(0 18px 26px rgba(0, 0, 0, 0.7));
}

.card.selected { transform: rotate(var(--rot)) translateY(calc(var(--lift) - 1.3rem)); z-index: 5; }

@media (prefers-reduced-motion: reduce) {
    .card, .drop-zone, .seat-tile { transition: none; }
    .card.dealt { animation: none; }

    /* Der Stich verschwindet dann ohne Flug. Die Auskunft, wer ihn geholt hat, geht dabei
       nicht verloren — sie steht am Stichmacher als Marke und unten am Stapel. */
    .trick.collecting .trick-card { animation: none; opacity: 0; }

    /* Das Sitzbild bringt seine eigene Rücksichtnahme mit (SeatPortrait.razor.css) — hier
       standen die Regeln der abgelösten Bot-Silhouette. */
}

/* ================================================================
   Sprache und Video
   ================================================================ */

.media-controls { display: inline-flex; gap: 0.35rem; align-items: center; }

/* Schwebt am linken Rand statt im Fluss zu stehen. Vorher war sie eine Zeile über dem
   Tisch — wer mitten in einer Partie die Kamera einschaltete, verschob damit das Blatt
   aller anderen nach unten. */
.media-strip {
    position: fixed;
    left: 1rem;
    bottom: 1rem;
    z-index: 55;
    max-width: min(22rem, 30vw);
    display: flex;
    gap: 0.5rem;
    align-items: center;
    flex-wrap: wrap;
    padding: 0.5rem;
    border-radius: var(--radius);
    background: var(--panel-raised);
    border: 1px solid var(--line);
    box-shadow: var(--shadow);
}

.video-tile {
    position: relative;
    width: 100%;
    aspect-ratio: 4 / 3;
    background: var(--bg-bar);
    border-radius: var(--radius-sm);
    overflow: hidden;
    margin-bottom: 0.35rem;
}

.media-strip .video-tile { width: 9rem; margin-bottom: 0; }
.video-tile video { width: 100%; height: 100%; object-fit: cover; display: block; }
.video-tile.audio-only video { display: none; }

.video-placeholder {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.5rem;
    font-weight: 700;
    color: var(--text-faint);
    background: rgba(255, 255, 255, 0.03);
}

.mic-off, .link-state, .tile-name {
    position: absolute;
    padding: 0.05rem 0.3rem;
    border-radius: 3px;
    background: rgba(0, 0, 0, 0.6);
    font-size: 0.68rem;
    color: var(--text);
}

.mic-off { bottom: 0.2rem; right: 0.2rem; }
.link-state { bottom: 0.2rem; left: 0.2rem; color: var(--accent); }
.tile-name { top: 0.2rem; left: 0.2rem; }

.self-view {
    display: inline-block;
    width: 4.5rem;
    aspect-ratio: 4 / 3;
    border-radius: var(--radius-sm);
    overflow: hidden;
    background: var(--bg-bar);
    vertical-align: middle;
}

.self-view.hidden { display: none; }
.self-view video { width: 100%; height: 100%; object-fit: cover; transform: scaleX(-1); }

.spectator-note { color: var(--text-dim); font-size: 0.88rem; text-align: center; margin-bottom: 0.5rem; }
.opponent.bottom { display: flex; justify-content: center; }

/* ================================================================
   Seitenleiste
   ================================================================ */

.sidebar .panel { margin-bottom: 0.85rem; }
.sidebar .panel h3 { display: flex; justify-content: space-between; align-items: center; }

.totals { display: flex; flex-direction: column; gap: 0.25rem; }

.total-row {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.4rem 0.5rem;
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    font-size: 0.9rem;
}

/* Die eigene Zeile ist hervorgehoben — bei vier ähnlichen Namen findet man sich sonst nicht. */
.total-row.mine { background: var(--accent-soft) padding-box; border-color: var(--accent-line); }
.total-row .name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.total-row .name .you { color: var(--text-faint); font-size: 0.78rem; }
.total-row .score { font-variant-numeric: tabular-nums; font-weight: 600; }
.total-row .score.negative { color: var(--danger); }

.sheet { margin: 0; padding-left: 1.1rem; font-size: 0.82rem; color: var(--text-dim); }
.sheet li { margin-bottom: 0.2rem; }

.sheet a { color: inherit; text-decoration: none; display: block; border-radius: var(--radius-sm); }
.sheet a:hover { color: var(--text); background: var(--panel-raised); }

/* Reiter über der Liste: Tische oder Anwesende. Sie sitzen in der Überschrift, damit die
   Zeile nicht doppelt so hoch wird wie nötig. */
/* Fußzeile der Lobby: Version und Bauzeitpunkt. Leise — sie wird gesucht, nicht gelesen. */
.lobby-foot {
    flex: none;
    padding: 0.6rem 0 0.2rem;
    text-align: center;
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    color: var(--text-faint);
}

/* Nebeneinander, nicht auseinandergezogen: Die Überschrift trug vorher Titel und Zähler an
   den beiden Enden und verteilt deshalb mit space-between — zwei Reiter an
   gegenüberliegenden Rändern läsen sich nicht als Paar.
   Der Elementname muss mit in den Selektor, sonst gewinnt ".open-tables h3". */
.open-tables h3.tabs { display: flex; gap: 1rem; align-items: baseline; justify-content: flex-start; }

.tabs .tab {
    background: transparent;
    border: none;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    padding: 0.2rem 0.1rem;
    font-size: inherit;
    font-weight: inherit;
    color: var(--text-dim);
    letter-spacing: inherit;
    text-transform: inherit;
}

.tabs .tab:hover { color: var(--text); background: transparent; }
.tabs .tab.active { color: var(--text); border-bottom-color: var(--accent); }

/* Die Zahl am Reiter als eigene kleine Fläche.
 *
 * Vorher stand sie als bloße Ziffer hinter dem Wort und klebte daran — die Reiter tragen
 * Großbuchstaben mit 0,16 em Sperrung, und der Abstand danach ist genau ein Buchstabenraum.
 * Zwischen "TISCHE" und "4" sah das aus wie ein Tippfehler.
 *
 * Die Sperrung geht hier auf null zurück: Sie steht auch hinter dem letzten Zeichen, und
 * bei einer einzelnen Ziffer säße sie sichtbar als zusätzlicher Rand rechts. Dieselbe
 * Falle wie beim Schweinchen am Tisch.
 *
 * Feste Breite über tabular-nums und ein Mindestmaß: Aus 9 wird 10, und der Reiter soll
 * dabei nicht springen. */
.tabs .tab-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.5rem;
    margin-left: 0.5rem;
    padding: 0.1rem 0.35rem;
    border-radius: var(--radius-pill);
    background: var(--panel-raised) padding-box;
    border: 1px solid var(--line);
    color: var(--text-dim);
    font-size: 0.78rem;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0;
    vertical-align: 0.05em;
}

/* Am gewählten Reiter trägt die Zahl dessen Farbe mit — sonst bliebe sie als einziges
   Stück grau zurück, während daneben alles hell wird. */
.tabs .tab.active .tab-count {
    border-color: var(--accent-line);
    background: var(--accent-soft) padding-box;
    color: var(--accent);
}

/* ================================================================
   Wiederholungs-Betrachter

   Eine ruhige Leseseite, kein zweiter Spieltisch: Alle vier Blätter liegen offen, die
   Partie ist vorbei. Gespielte Karten bleiben stehen und verblassen nur — so sieht man
   auf einen Blick, was jemand noch hatte, als er sich entscheiden musste.
   ================================================================ */

.replay { display: flex; flex-direction: column; gap: 1rem; max-width: 78rem; margin: 0 auto; }

.replay-head { display: flex; align-items: flex-start; gap: 1rem; }
.replay-head h1 { margin: 0 0 0.15rem; font-size: 1.35rem; }
.replay-head p { margin: 0; }
.replay-head button { margin-left: auto; }

.replay-hands {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 0.7rem;
}

/* Die Kästen liegen auf dem Seitenhintergrund und sind heller als er — vorher war es
   umgekehrt: ein schwarzer Schleier machte sie dunkler als die Seite, und vier vertiefte
   Löcher nebeneinander sind kein Blatt, das man liest. */
.replay-hand {
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: 0.6rem;
}

.replay-hand.to-act { border-color: var(--accent-line); background: var(--accent-soft) padding-box; }

.replay-hand-head { display: flex; align-items: center; gap: 0.4rem; margin-bottom: 0.5rem; }
.replay-hand-head .name { font-weight: 600; }
.replay-hand-head .count { margin-left: auto; font-variant-numeric: tabular-nums; }

.replay-cards { display: flex; flex-wrap: wrap; gap: 0.15rem; }

/* Kleine Karten: Der Betrachter zeigt vierzig auf einmal — aber nicht so klein, dass eine
   Dame von einem König nicht mehr zu unterscheiden ist. Bei 34 Pixeln war genau das der
   Fall; jetzt passen fünf je Zeile statt sieben, dafür sind sie lesbar. */
.replay .card.small { width: 46px; }
.replay .card.spent { opacity: 0.22; filter: grayscale(1); }

.replay-trick {
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: 0.8rem;
    text-align: center;
}

.replay-trick-cards { display: flex; justify-content: center; gap: 1.1rem; margin-bottom: 0.5rem; }
.replay-play { display: flex; flex-direction: column; align-items: center; gap: 0.25rem; font-size: 0.8rem; }
.replay-play.winner { color: var(--accent); }
.replay-play.winner .card { box-shadow: 0 0 0 2px var(--accent); }

.replay-controls { display: flex; align-items: center; gap: 0.5rem; outline: none; }
.replay-controls input[type=range] { flex: 1; accent-color: var(--accent); }
.replay-controls .step { font-variant-numeric: tabular-nums; white-space: nowrap; }

.replay-log { display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); gap: 1rem; }

.trick-table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
.trick-table th { text-align: left; color: var(--text-dim); font-weight: 500; padding: 0.25rem 0.4rem; }
.trick-table td { padding: 0.2rem 0.4rem; border-top: 1px solid var(--line); }
.trick-table tbody tr { cursor: pointer; }
.trick-table tbody tr:hover { background: var(--panel); }
.trick-table tbody tr.current { background: var(--accent-soft); }
.trick-table td.won { color: var(--accent); font-weight: 600; }

.mini { font-variant-numeric: tabular-nums; }
.mini.trump { color: var(--accent); }

.replay-events h3 { margin: 0 0 0.4rem; font-size: 0.9rem; }
.replay-events ol { margin: 0 0 0.8rem; padding: 0; list-style: none; font-size: 0.82rem; }
.replay-events li.later { opacity: 0.4; }
.replay-events li button { padding: 0.1rem 0.3rem; font-size: inherit; text-align: left; }
.replay-events ul { margin: 0; padding-left: 1.1rem; font-size: 0.8rem; }

@media (max-width: 74rem) {
    .replay-log { grid-template-columns: minmax(0, 1fr); }
}

.chat .messages {
    display: flex;
    flex-direction: column-reverse;
    max-height: 15rem;
    overflow-y: auto;
    gap: 0.3rem;
    margin-bottom: 0.6rem;
    font-size: 0.85rem;
}

.message { display: flex; gap: 0.4rem; align-items: baseline; }
.message .who {
    flex: none;
    font-size: 0.62rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-faint);
    background: var(--panel-raised);
    border-radius: 3px;
    padding: 0.05rem 0.3rem;
}
.message.system .body { color: var(--text-dim); }
.chat form { display: flex; gap: 0.4rem; }
.chat .text { flex: 1; min-width: 0; }

/* ================================================================
   Regel-Editor
   ================================================================ */

.rule-presets { display: flex; gap: 0.4rem; align-items: center; flex-wrap: wrap; margin-bottom: 0.7rem; }

.rule-summary {
    background: var(--accent-soft);
    border: 1px solid var(--accent-line);
    border-radius: var(--radius-sm);
    padding: 0.5rem 0.75rem;
    margin-bottom: 0.8rem;
    font-size: 0.88rem;
}

.rule-summary summary { cursor: pointer; font-weight: 600; }
.rule-summary ul, .rule-diff { margin: 0.4rem 0 0; padding-left: 1.2rem; font-size: 0.83rem; color: var(--text-dim); }

.rule-group { border: 1px solid var(--line-soft); border-radius: var(--radius-sm); padding: 0.6rem 0.8rem 0.8rem; margin-bottom: 0.8rem; }
.rule-group legend { font-size: 0.68rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-faint); padding: 0 0.3rem; font-weight: 600; }

.rule-row { padding: 0.3rem 0.4rem; border-radius: 4px; }
.rule-row label { display: flex; align-items: center; gap: 0.5rem; font-size: 0.88rem; }
.rule-row label > span { flex: 1; }
.rule-row.changed { background: var(--accent-soft); box-shadow: inset 2px 0 0 var(--accent); }

.hint { margin: 0.1rem 0 0.3rem 1.6rem; font-size: 0.78rem; }
.rule-actions { margin-top: 0.5rem; }

/* ================================================================
   Ladeanzeige
   ================================================================ */

/* Der Ladebildschirm trägt schon den Hintergrund der Lobby. Er ist das Erste, was man vom
   Spiel sieht, und der Übergang soll kein Farbwechsel sein. */
.boot {
    height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1.1rem;
    background:
        radial-gradient(ellipse 90% 55% at 50% -8%, #1e5c3d 0%, #164a32 30%, #0d2c1d 60%, #070d0a 100%);
}

/* Der ganze Aufbau, so breit wie das Fenster es hergibt — gedeckelt, damit er auf einem
   großen Bildschirm nicht zum Plakat wird. Die Maße stehen zusätzlich im img-Tag: Ohne sie
   springt der Ladebildschirm, sobald das Bild eintrifft. */
.boot-logo {
    width: min(24rem, 68vw);
    height: auto;
}

/* Der Anspruch unter dem Namen. Der negative Zuschlag zum Zeilenabstand holt ihn dicht an
   den Schriftzug heran — die 1,1 rem Lücke des Ladebildschirms trennen sonst zwei Dinge,
   die zusammengehören. */
.boot-claim {
    margin-top: -0.7rem;
    font-size: 0.9rem;
    color: var(--text-dim);
}

/* Quadratisch und mit ausgeschriebener Höhe. Beides fehlte: Ohne Höhe behielt das svg die
   Vorgabe von 150 px, und der in Prozent angegebene Radius rechnete sich aus der Diagonale
   dieses ungleichen Kastens — der Kreis wurde breiter als sein Rahmen. */
.loading-progress {
    display: block;
    width: 5.5rem;
    height: 5.5rem;
    animation: boot-spin 1.4s linear infinite;
}

.loading-progress circle { fill: none; stroke-width: 6; }
.loading-progress .track { stroke: rgba(255, 255, 255, 0.1); }

/* Ein Viertel des Umfangs (2π × 42 ≈ 264), rund abgeschlossen. Bewusst eine feste Länge
   statt des Ladefortschritts: Der stand vorher bei 0 %, solange Blazor noch nichts gemeldet
   hatte — und ein Bogen der Länge null ist unsichtbar. Es sah aus, als sei nichts los.
   Wie weit es ist, sagt die Zeile darunter. */
.loading-progress .arc {
    stroke: var(--accent);
    stroke-linecap: round;
    stroke-dasharray: 66 264;
}

@keyframes boot-spin {
    to { transform: rotate(360deg); }
}

/* Wer Bewegung abbestellt hat, bekommt den Ring stehend. Der Fortschritt steht ohnehin
   als Zahl darunter. */
@media (prefers-reduced-motion: reduce) {
    .loading-progress { animation: none; }
}

.loading-progress-text {
    text-align: center;
    color: var(--text-dim);
    font-size: 0.9rem;
    font-variant-numeric: tabular-nums;
}

.loading-progress-text:after { content: var(--blazor-load-percentage-text, "Lade …"); }

#blazor-error-ui {
    background: var(--danger-soft);
    border-top: 1px solid var(--danger-line);
    color: var(--danger);
    display: none;
    position: fixed;
    bottom: 0;
    width: 100%;
    padding: 0.6rem 1.2rem;
    z-index: 1000;
}

#blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: 0.75rem; top: 0.6rem; }

/* ================================================================
   Der Türsteher

   Sichtbar nur, wenn das Fenster zu klein oder hochkant ist. Bewusst nicht knapp gehalten:
   Wer hier landet, will wissen, woran es liegt und was er tun kann.
   ================================================================ */

.screen-guard {
    flex: 1;
    min-height: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    overflow: auto;
}

.screen-guard-panel {
    max-width: 34rem;
    text-align: center;
    background: rgba(0, 0, 0, 0.17);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: 2rem 1.75rem;
}

.screen-guard-panel h1 {
    margin: 0 0 0.75rem;
    font-size: 1.5rem;
    letter-spacing: -0.01em;
}

.screen-guard-panel .lead { max-width: none; }

/* Vier angedeutete Karten wie in der Lobby — dieselbe Handschrift, damit die Meldung
   aussieht wie ein Teil des Spiels und nicht wie eine Fehlerseite des Browsers. */
.screen-guard-mark {
    display: flex;
    justify-content: center;
    gap: 0.35rem;
    margin-bottom: 1.25rem;
    font-size: 1.4rem;
    color: var(--text-dim);
}

.screen-guard-mark span:nth-child(1) { color: var(--card-red); transform: rotate(-9deg); }
.screen-guard-mark span:nth-child(3) { color: var(--card-red); transform: rotate(3deg); }
.screen-guard-mark span:nth-child(2) { transform: rotate(-3deg); }
.screen-guard-mark span:nth-child(4) { transform: rotate(9deg); }

.screen-guard-tips {
    margin: 1.5rem 0 0;
    padding: 0;
    list-style: none;
    text-align: left;
    display: grid;
    gap: 0.6rem;
}

.screen-guard-tips li {
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    padding: 0.6rem 0.8rem;
    color: var(--text-dim);
    line-height: 1.5;
}

.screen-guard-tips strong { color: var(--text); }

.screen-guard kbd {
    display: inline-block;
    min-width: 1.6rem;
    padding: 0.05rem 0.35rem;
    border: 1px solid var(--line);
    border-bottom-width: 2px;
    border-radius: var(--radius-sm);
    background: var(--panel-raised);
    color: var(--text);
    font-family: var(--font);
    font-size: 0.85em;
    text-align: center;
}

.screen-guard .footnote { margin: 1.25rem 0 0; font-size: 0.85rem; line-height: 1.5; }

/* Eine Tastenkombination bricht nicht um: "Strg +" am Zeilenende und ein einsames Minus
   darunter liest sich wie zwei Angaben. */
.screen-guard .keys { white-space: nowrap; }


/* ================================================================
   Enge Fenster — Tablet im Querformat
   ================================================================

   Ein iPad Pro meldet quer 1366 × 930 (13 Zoll) oder 1194 × 744 (11 Zoll) CSS-Pixel. Beides
   ist genug Fläche für den Tisch, aber weniger, als das Grundlayout großzügig verteilt.

   Was hier steht, ist deshalb kein eigenes Layout, sondern dasselbe enger gestellt: knappere
   Polsterung, schmalere Seitenspalten. Vier Plätze um eine Mitte, Blatt unten, Punktestand
   rechts — daran ändert sich nichts.

   <b>Auf dem Laptop greift davon nichts.</b> Die Schwellen liegen bei 1439 Bildpunkten
   Breite und 860 Bildpunkten Höhe; ein Fenster mit 1600 × 900 oder 1920 × 1080 fällt durch
   keine der beiden Bedingungen. Nachgemessen an beiden Größen, vorher und nachher.

   Gemessen wurde in der Vorbehaltsphase: Dort steht die breiteste Leiste, und dort ist die
   Mitte am knappsten. Ohne diese Regeln lag die Leiste bei 1194 × 744 sieben Bildpunkte
   über den Kacheln von West und Ost. */

/* ---- Flach: die Lobby enger stellen, damit die Tischliste etwas sieht ----

   Die Lobby verteilt ihre Höhe fest: Kopfzeile, Aufmacher, die beiden Kacheln, die
   Reiterzeile, die Liste, die Fußzeile. Alles außer der Liste hat eine feste Höhe — was oben
   verbraucht wird, fehlt genau dort.

   Gemessen bei 1194 × 702, dem iPad Pro 11" im Querformat: Aufmacher 136, Kacheln 205, und
   für die Liste blieben 117 Bildpunkte. Bei 69 Bildpunkten je Zeile sind das 1,7 Tische —
   eine Liste, die mehr Bildlaufleiste ist als Inhalt.

   <b>Eigene Schwelle, tiefer als die 860 darunter.</b> Die gilt schon für ein Laptopfenster
   mit 1512 × 860, und dort ist alles in Ordnung. 780 liegt über jedem Tablet-Querformat
   (744 in Safari, 702 in Chrome) und unter jedem Laptop. */
@media (max-height: 780px) {
    /* Alles hier gilt nur dem quergelegten Aufmacher der Lobby, nicht dem hochkant
       gestellten (.stacked) von Anmeldung und neuem Passwort. Dort gibt es keine Tischliste,
       für die Platz zu schaffen wäre — und die Anleitung darunter braucht man, um
       überhaupt hineinzukommen.

       Erst die Zierde: die vier Farbzeichen neben der Überschrift. Das allein bringt
       allerdings fast nichts — der Aufmacher liegt quer, seine Höhe kommt vom Text daneben.
       Gemessen: 136 auf 119 Bildpunkte, davon 17 aus der Polsterung und null aus den
       Zeichen. Sie gehen trotzdem, weil sie neben einer einzelnen Zeile ohnehin verloren
       stünden. */
    .lobby .hero:not(.stacked) .hero-cards { display: none; }

    /* Das hier bringt es: zwei Zeilen Einführungsprosa. „Vier Spieler, ein Blatt, mit Mikro
       oder Video …" liest, wer zum ersten Mal da ist — und der kommt selten mit einem
       Tablet quer. Überschrift und Anspruch bleiben stehen. */
    .lobby .hero:not(.stacked) .lead { display: none; }
    .lobby .hero:not(.stacked) h1 { font-size: 1.4rem; }

    .hero { padding: 0.5rem 0 0.6rem; gap: 0.6rem; }

    .lobby { padding-bottom: 0.5rem; }
    .lobby-cards { margin-bottom: 0.7rem; }
    .lobby-card { padding: 0.85rem; }
}

/* ---- Flach: Höhe zurückgewinnen ---- */

@media (max-height: 860px) {
    /* Der Rand um den Filz und die Fugen zwischen den Feldern. Auf einem hohen Fenster
       geben sie dem Tisch Luft; auf einem flachen nehmen sie dem Blatt die Karten weg. */
    .table {
        padding: 0.75rem;
        gap: 0.6rem;
    }

    main { padding: 0.5rem 0.7rem 0.6rem; }

    /* Die Kachel etwas schmaler — und damit von selbst niedriger: Die Bildfläche darin
       folgt der Breite über aspect-ratio 16/9. 13,5 statt 15 rem nimmt der Kachel 12
       Bildpunkte Höhe, und zwar zweimal: einmal in der oberen Reihe, einmal in der
       Anforderung der mittleren. Aus zehn Bildpunkten Luft zur Vorbehaltsleiste werden so
       vierunddreißig — genug, dass auch ein Safari mit sichtbarer Adressleiste nicht
       plötzlich überlappt. */
    .seat-tile { width: 13.5rem; }

    /* Die Vorbehaltsleiste und die Fugen darunter.

       Die 3,5 rem Mindesthöhe sind am Knopf gemessen: 34 Bildpunkte plus zweimal neun
       Polsterung. Mit knapperer Polsterung trägt dieselbe eine Zeile in 3 rem — und weil
       Polsterung und Mindesthöhe zusammen gerechnet sind (12,8 + 34 = 47 gegen 48), hüpft
       dabei nichts, wenn Knöpfe erscheinen oder verschwinden. */
    .actions {
        min-height: 3rem;
        padding: 0.4rem 0.6rem;
        margin-bottom: 0.35rem;
    }

    .gesture-flyout { margin-bottom: 0.35rem; }

    /* Die Kopfleiste: 0,5 statt 0,85 rem Polsterung, also elf Bildpunkte weniger. Die
       Knöpfe darin bleiben 34 hoch und auf dem Tastfeld sicher zu treffen — weg fällt nur
       der Rand um sie herum, und der Filz bekommt ihn. */
    .top-bar { padding: 0.5rem 1.25rem; }
}

/* ---- Schmal: Breite für die Mitte ---- */

@media (max-width: 1439px) {
    /* 16,5 statt 18 rem. Weiter geht nicht: Die Kachel misst 14 rem, die Ordensspange
       daneben 2,15 rem — darunter würde die Spange abgeschnitten. Die gewonnenen 48
       Bildpunkte gehen an die Ablage in der Mitte, und dort liegen die Karten. */
    .table { grid-template-columns: 16.5rem minmax(0, 1fr) 16.5rem; }

    /* Punktestand und Chat rücken zusammen: 17,5 statt 21 rem, also 56 Bildpunkte mehr für
       den Tisch. Der Punktestand trägt vier Zeilen aus Zeichen, Name und Zahl — der Name
       hat auch bei 280 Bildpunkten reichlich Platz —, und der Chat ist ohnehin eine schmale
       Spalte. Beides verliert dabei nichts, was man liest. */
    .room-body { grid-template-columns: minmax(0, 1fr) 17.5rem; }
}


/* ================================================================
   Der Hinweis auf eine neue Fassung
   ================================================================ */

/* Fest über der Seite und nicht in ihrem Fluss. Am Tisch ist die Höhe knapp — ein Balken,
   der alles um sechzig Bildpunkte schiebt, wäre schlimmer als der alte Stand.

   Unten rechts, weil dort am wenigsten steht: Oben links liegt die Tafel, oben rechts der
   Autopilot. Unten rechts liegt am Tisch der abgeräumte Stapel, und der ist zwischen zwei
   Partien weg — genau dann, wenn ein Neuladen am wenigsten weh tut. */
.update-banner {
    position: fixed;
    right: 1rem;
    bottom: 1rem;
    z-index: 60;

    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.7rem 0.8rem;
    border-radius: var(--radius);
    background: var(--panel-raised);
    border: 1px solid var(--accent-line);
    box-shadow: var(--shadow);

    animation: update-in 0.25s cubic-bezier(0.2, 0.9, 0.3, 1.2);
}

@keyframes update-in {
    from { transform: translateY(0.6rem); opacity: 0; }
    to { transform: translateY(0); opacity: 1; }
}

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

.update-text { display: flex; flex-direction: column; line-height: 1.3; }
.update-text strong { font-size: 0.9rem; }
.update-text .muted { font-size: 0.76rem; }

/* Der Schließer ist quadratisch und schmal — er soll nicht so aussehen wie der Knopf, auf
   den es ankommt. */
.update-banner button.ghost { padding: 0.3rem 0.5rem; font-size: 0.85rem; line-height: 1; }

/* Der Knopf in der Fußzeile. Er steht in einer Zeile mit der Version, also klein und ohne
   Fläche — gesucht wird er nur, wenn man ihn braucht. */
.lobby-foot { display: flex; align-items: center; justify-content: center; gap: 0.6rem; flex-wrap: wrap; }
.lobby-foot button.check-update { padding: 0.15rem 0.5rem; font-size: 0.72rem; }
.lobby-foot .update-hint { color: var(--accent); }

/* Auf schmalen Fenstern bricht der Balken um und wird gedeckelt.

   Er liegt fest über der Seite und weiß nichts von der Spalte darunter. Bei 1600 Bildpunkten
   passt er mit 318 in die 315 breite Seitenspalte und deckt dort nur leere Fläche zu; bei
   1194 ist die Spalte nur noch 263 breit, und dieselben 318 ragten über den Filz — gemessen
   quer über den abgeräumten Stapel und das Handfach.

   Fünfzehn rem sind 240 Bildpunkte und passen auch dort. Der Text nimmt dafür eine eigene
   Zeile, die Knöpfe rücken darunter. */
@media (max-width: 1439px) {
    .update-banner {
        flex-wrap: wrap;
        max-width: 15rem;
    }

    .update-banner .update-text { flex-basis: 100%; }
}


/* ---- Die Gästeliste ----

   Vier Spalten: Name, Zustand, Versand, Streichen. Die Liste ist das Raster, die Zeilen sind
   Unterraster — damit misst jede Spalte in allen Zeilen gleich viel und die Knöpfe stehen
   untereinander. Dieselbe Bauart wie die Platzliste am Tisch.

   Die zweite Spalte steht auf max-content und nicht auf 1fr. Das ist der Unterschied
   zwischen lesbar und unbrauchbar: Dort steht der Einladungscode, und als der Versand
   dazukam, drückte das Adressfeld die Spalte zusammen — aus "3NRVTM" wurde "3N…". Ein Code,
   von dem man die Hälfte sieht, ist kein Code. Nachgeben darf die Versandspalte, denn ein
   abgeschnittenes Eingabefeld kann man immer noch benutzen. */

.guest-list {
    list-style: none;
    margin: 0.9rem 0 0;
    padding: 0;
    display: grid;
    grid-template-columns: max-content max-content minmax(0, 1fr) auto;
    gap: 0.35rem;
}

.guest-list li {
    grid-column: 1 / -1;

    /* Rückfall zuerst, Unterraster danach — ein Browser ohne subgrid überliest die zweite
       Zeile und behält die erste. */
    display: grid;
    grid-template-columns: max-content max-content minmax(0, 1fr) auto;
    grid-template-columns: subgrid;

    align-items: center;
    column-gap: 0.6rem;
    padding: 0.45rem 0.6rem;
    border-radius: var(--radius-sm);
    background: rgba(0, 0, 0, 0.18);
}

.guest-name { font-weight: 600; font-size: 0.9rem; }
.guest-state { font-size: 0.78rem; color: var(--text-faint); }

/* Wer schon spielt, tritt zurück: Die Liste wird durchgesehen, um zu erkennen, wer noch
   fehlt. Deshalb stehen die Offenen oben und die Erledigten blass. */
.guest-list li.redeemed { opacity: 0.6; }
.guest-list li.redeemed .guest-state { color: var(--accent); }

.guest-list button.ghost { padding: 0.2rem 0.5rem; font-size: 0.75rem; white-space: nowrap; }

/* Der Einladungscode steht anstelle der Zustandsangabe — er ist die Auskunft, die man an
   einem noch offenen Namen braucht. Schreibmaschinenschrift und gesperrt, weil er abgetippt
   oder am Telefon vorgelesen wird. */
.guest-code {
    font-family: var(--font-mono);
    font-size: 0.82rem;
    letter-spacing: 0.14em;
    color: var(--accent);
    white-space: nowrap;
}

/* Ein Name ohne Code ist kein Zustand, sondern ein offener Punkt — er wird angestrichen,
   nicht beiläufig erwähnt. */
.guest-open { color: var(--danger); }
.guest-list button.ghost.warn { border-color: var(--danger-line); color: var(--danger); }

/* Adressfeld und Versandknopf in der dritten Spalte. Das Feld darf schrumpfen, die Knöpfe
   nicht — eine halb sichtbare Beschriftung liest niemand, ein halb sichtbares Eingabefeld
   benutzt man trotzdem. */
.guest-send { display: flex; align-items: center; gap: 0.4rem; min-width: 0; }
.guest-send button { flex: none; }

.guest-mail {
    flex: 1;
    min-width: 6rem;
    max-width: 13rem;
    padding: 0.2rem 0.45rem;
    font-size: 0.78rem;
}

/* ---- Kontofenster: Adresse und Passwort ---- */

.konto-adresse .row { display: flex; gap: 0.4rem; align-items: center; }
.konto-adresse .row .text { flex: 1; min-width: 0; }

/* Ein Strich statt einer Überschrift: Adresse und Passwort sind zwei Dinge im selben
   Fenster, aber keins von beiden braucht eine Ansage. */
.konto-trenner { margin: 1.1rem 0; border: 0; border-top: 1px solid var(--line-soft); }

/* Der Vergessen-Knopf steht unter dem Wechsel zwischen Anmelden und Anlegen — leiser als
   beide, weil man ihn selten braucht und dann gezielt sucht. */
.sign-in button.ghost.forgot {
    display: block;
    width: 100%;
    margin-top: 0.35rem;
    padding: 0.3rem;
    font-size: 0.78rem;
    color: var(--text-faint);
    border-color: transparent;
}

.sign-in button.ghost.forgot:hover:not(:disabled) { color: var(--text); }
