/* ==========================================================================
 * INFERNUS Blackjack — game.css
 * Die Huelle: Token, Kopfzeile, Buehne, Schublade, Bediendeck, Dialoge.
 *
 * ABGRENZUNG — verbindlich.
 * Dieses Blatt gestaltet NICHTS im Tisch selbst. Alles, was mit .card, .seat-,
 * .hand-, .shoe, .chip- oder .fx- beginnt, gehoert table.css. Genau an dieser
 * Naht ist das Projekt dreimal gebrochen: Markup ohne Blatt. Deshalb gilt hier
 * die Umkehrung — wer Markup erzeugt, gestaltet es auch, und niemand sonst.
 *
 * Was dieses Blatt liefert und alleine verantwortet:
 *   .app .topbar .stage .table-wrap .side .deck  und alles darin,
 *   die Token in :root (gemeinsamer Vertrag, table.css darf sie lesen),
 *   die Zustandsregeln fuer [hidden] und [aria-disabled].
 *
 * Zwei Ausnahmen, bewusst und eng gefasst — game.js baut diese Knoepfe selbst
 * und vergibt Klassennamen, die der Renderer im Tisch ebenfalls benutzt.
 * Beide sind deshalb hart auf ihren Behaelter geschnitten:
 *   #chips .chip       (Einsatzchips in der Leiste, nicht auf dem Filz)
 *   #emote-bar .emote  (Emoteknoepfe, nicht die Sprechblase am Sitz)
 *
 * MOBIL IST DER HAUPTFALL. Entworfen fuer 412 x 915 CSS-Pixel im Hochformat.
 * Alle Grundregeln gelten fuer das Telefon; die Media Queries bauen nach oben.
 * ========================================================================== */

/* ==========================================================================
   0 — [hidden] durchsetzen
   hidden setzt display:none nur im Standardblatt des Browsers. Jede Autorregel
   mit display (etwa .actions { display: grid }) gewinnt dagegen und macht das
   Attribut lautlos wirkungslos. Ohne diese Regel stehen Einsatzleiste,
   Versicherungsfrage und Zuschauerhinweis dauerhaft offen.
   ========================================================================== */
[hidden] {
	display: none !important;
}

/* ==========================================================================
   1 — TOKEN
   Hier wird ausgewaehlt, nicht erfunden. table.css darf mitlesen.
   ========================================================================== */

:root {
	/* --- Marke: Grundflaechen -------------------------------------------- */
	--bg: #141419;
	--bg-sunk: #0e0e12;
	--bg-raised: #1c1c23;
	--bg-panel: #21212a;
	--bg-panel-2: #2a2a35;
	--line: #34343f;
	--line-soft: #2a2a33;

	/* --- Marke: Text -----------------------------------------------------
	   --text-3 war #85858f. Auf --bg-panel (Dialogflaeche) ergab das 4,37:1
	   und auf --bg-panel-2 nur 3,88:1 — beide unter der Schwelle von 4,5:1,
	   die fuer 11-Pixel-Text gilt. Betroffen waren .dlg__body h3,
	   .dlg__field span und .dlg__table th. #94949e traegt 5,31:1 auf
	   --bg-panel und 4,72:1 auf --bg-panel-2 und bleibt deutlich leiser
	   als --text-2 (8,21:1). */
	--text: #f7f7f7;
	--text-2: #b9b9c4;
	--text-3: #94949e;
	--text-ink: #17171d;

	/* --- Marke: Glut ----------------------------------------------------- */
	--accent: #ff7a00;
	--accent-lit: #ffb45b;
	--accent-deep: #b34e00;
	--accent-ink: #2a1400;
	--accent-glow: rgb(255 122 0 / 0.45);
	--accent-veil: rgb(255 122 0 / 0.12);

	/* --- Tisch (Vertrag mit table.css) ----------------------------------- */
	--felt: #123a2c;
	--felt-lit: #1a5040;
	--felt-deep: #0a2419;
	--felt-line: rgb(255 255 255 / 0.09);
	--rail: #241a14;
	--rail-lit: #3a2a1f;

	/* --- Karten (Vertrag mit table.css) ---------------------------------- */
	--card-paper: #f8f6f1;
	--card-paper-2: #eae6dd;
	--card-red: #c8342b;
	--card-black: #1a1a22;
	--card-edge: rgb(0 0 0 / 0.35);

	/* --- Semantik -------------------------------------------------------- */
	--win: #35c07a;
	--win-deep: #135f3c;
	--lose: #e2483c;
	--lose-deep: #6d1e18;
	/* --lose als Schrift reicht nicht: auf --bg-panel sind es 3,97:1. Fuer
	   Raender und Flaechen bleibt --lose, fuer Text gilt dieser Ton (6,29:1
	   auf --bg-panel). Betrifft .dlg__result[data-tone='bad']. */
	--lose-text: #ff7a70;
	--push: #8c8ca0;
	--info: #4aa3ff;
	--warn: #ffb020;
	--warn-deep: #7a5200;

	/* --- Turnier ----------------------------------------------------------
	   Eine eigene Familie, die sonst NIRGENDS am Tisch vorkommt. Das ist der
	   Zweck: Wer im Turnier sitzt, soll es an der Farbe erkennen, bevor er ein
	   Wort gelesen hat. Geprueft auf --tour-bg: --text 15,4:1, --text-2 8,5:1,
	   --text-3 5,5:1, --win 7,1:1, --warn 9,0:1, --push 5,0:1. Auf
	   --tour-bg-alarm: --text 14,4:1, --text-2 7,9:1, --text-3 5,1:1. */
	--tour-bg: #241a33;
	--tour-bg-alarm: #3a1c1c;
	--tour-edge: #8b6ad9;
	--tour-line: #5b46a0;
	--tour-veil: rgb(139 106 217 / 0.22);

	/* --- Farben der fuenf Spielzuege --------------------------------------
	   Jeder Zug traegt dieselbe Farbe an jedem Tag. Die Farbe ist Wiedererkennung,
	   nie die einzige Information: Beschriftung und Position tragen den Sinn. */
	--act-hit: #ff7a00;
	--act-stand: #4a90d9;
	--act-double: #d9a01f;
	--act-split: #8b6ad9;
	--act-surrender: #9a5a55;

	/* --- Chipfarben (Einsatzleiste) -------------------------------------- */
	--chip1-body: #f0efea;
	--chip1-ink: #17171d;
	--chip5-body: #c8342b;
	--chip5-ink: #fff6f4;
	--chip25-body: #1c7a4d;
	--chip25-ink: #f2fff8;
	--chip100-body: #1a1a22;
	--chip100-ink: #f7f7f7;
	--chip500-body: #6d3bbf;
	--chip500-ink: #f6f1ff;
	--chip1000-body: #ff7a00;
	--chip1000-ink: #2a1400;
	--chip-ring: rgb(255 255 255 / 0.9);

	/* --- Abstaende (4er-Raster) ------------------------------------------ */
	--sp-1: 4px;
	--sp-2: 8px;
	--sp-3: 12px;
	--sp-4: 16px;
	--sp-5: 24px;
	--sp-6: 32px;
	--sp-7: 48px;

	/* --- Radien ----------------------------------------------------------- */
	--r-1: 4px;
	--r-2: 8px;
	--r-3: 12px;
	--r-4: 18px;
	--r-5: 26px;
	--r-pill: 999px;

	/* --- Schatten --------------------------------------------------------- */
	--sh-1: 0 1px 2px rgb(0 0 0 / 0.4);
	--sh-2: 0 2px 6px rgb(0 0 0 / 0.45);
	--sh-3: 0 6px 18px rgb(0 0 0 / 0.5);
	--sh-4: 0 14px 40px rgb(0 0 0 / 0.55);
	--sh-inset: inset 0 1px 0 rgb(255 255 255 / 0.07);
	--glow-ember: 0 0 0 1px rgb(255 122 0 / 0.35), 0 0 24px -4px var(--accent-glow);

	/* --- Bewegung --------------------------------------------------------- */
	--dur-1: 90ms;
	--dur-2: 160ms;
	--dur-3: 260ms;
	--dur-4: 380ms;
	--dur-5: 520ms;
	--dur-6: 760ms;
	--dur-7: 1200ms;
	--ease-out: cubic-bezier(0.22, 1, 0.36, 1);
	--ease-in: cubic-bezier(0.55, 0, 1, 0.45);
	--ease-both: cubic-bezier(0.65, 0, 0.35, 1);
	--ease-snap: cubic-bezier(0.34, 1.56, 0.64, 1);

	/* --- Groessen ---------------------------------------------------------- */
	--tap: 48px; /* Mindestmass fuer ein Daumenziel */
	--chip-d: 54px; /* Durchmesser der Einsatzchips */

	/* Hoehe der Kopfzeile. Sie ist kein Selbstzweck: Meldungen setzen sich
	   darunter ab, statt sich auf das Guthaben zu legen. Wer die Polsterung
	   der Kopfzeile aendert, aendert diesen Wert mit.
	   62 statt 58, seit der Rueckweg und der Schubladenknopf 44 statt 40
	   Pixel hoch sind: 8 + 44 + 8 + 1 Rahmen = 61. */
	--top-h: 62px;

	/* Hoehe des Verbindungsbandes, solange es steht. Es liegt im Fluss unter
	   der Kopfzeile, die Meldungen schweben — ohne diesen Zuschlag legte sich
	   die erste Meldung genau auf die Verbindungswarnung. Zwei Zeilen bei
	   13 Pixeln plus Polsterung sind der Maximalfall (siehe Regel unten). */
	--conn-h: 0px;

	/* Dasselbe fuer das Turnierband: Es liegt im Fluss unter dem
	   Verbindungsband, die Meldungen schweben. Ohne diesen Zuschlag legte sich
	   die erste Meldung genau auf die Zahlen der Bahn — und im Fehlgriff-Fall
	   sogar auf die Warnung samt ihrer beiden Knoepfe, die dann unerreichbar
	   waeren. Wie bei --conn-h ist der Wert der Maximalfall statt einer
	   Messung: zu gross setzt die Meldung nur etwas tiefer, zu klein legte sie
	   wieder auf das Band. 96 deckt Kopfzeile, zwei Zahlenreihen, Balken und
	   eine Zeile Hinweis. */
	--tour-h: 0px;

	/* Hoehe der Wechselzone im Deck. Einsatzleiste und Spielzuege liegen in
	   derselben Zelle; diese Hoehe ist die Zusage, dass beim Wechsel nichts
	   springt. Sie ist so bemessen, dass die vollstaendige Einsatzleiste
	   hineinpasst — die Spielzuege fuellen sie mit zwei Reihen aus.
	   124 statt 118: Chipreihe 54 + 4 + Steuerzeile 44 (Daumenmass, vorher 38)
	   + 4 + Hinweiszeile 15 = 121. */
	--zone-h: 124px;

	/* --- Schrift ---------------------------------------------------------- */
	--font: 'Segoe UI', system-ui, -apple-system, 'Helvetica Neue', sans-serif;
	--fs-micro: 10px;
	--fs-xs: 11px;
	--fs-sm: 13px;
	--fs-md: 15px;
	--fs-lg: 18px;
	--fs-xl: 24px;
	--ls-wide: 0.09em;

	/* --- Z-Ordnung --------------------------------------------------------
	   Der Renderer legt seine Flugschicht auf 80. Deck und Kopfzeile liegen
	   darueber: eine fliegende Karte darf die Bedienung nie verdecken. */
	--z-deck: 82;
	--z-top: 84;
	--z-scrim: 93;
	--z-side: 94;
	--z-loading: 95;
	--z-toast: 97;

	color-scheme: dark;
}

/* ==========================================================================
   2 — GRUNDLAGEN
   ========================================================================== */

*,
*::before,
*::after {
	box-sizing: border-box;
}

html {
	height: 100%;
}

body.bj {
	margin: 0;
	height: 100%;
	background: var(--bg);
	color: var(--text);
	font-family: var(--font);
	font-size: var(--fs-md);
	line-height: 1.4;
	-webkit-font-smoothing: antialiased;
	overscroll-behavior: none;
	touch-action: manipulation;
	/* Kalter Kellerclub mit Glut in der Ferne. */
	background-image:
		radial-gradient(120% 80% at 50% -10%, rgb(255 122 0 / 0.1) 0%, transparent 55%),
		radial-gradient(90% 60% at 50% 118%, rgb(255 122 0 / 0.07) 0%, transparent 60%),
		linear-gradient(180deg, #17171d 0%, #141419 42%, #0f0f13 100%);
	background-attachment: fixed;
}

button,
input,
select,
textarea {
	font: inherit;
	color: inherit;
}

:where(button) {
	cursor: pointer;
}

/* Fokus ist immer sichtbar — auf Filz, auf Glut, auf Papier. */
:focus-visible {
	outline: 2px solid var(--accent-lit);
	outline-offset: 3px;
	border-radius: var(--r-2);
}

/* Ziffern stehen still, damit ein zaehlender Wert nicht zappelt. */
.tabnum,
.balance__val,
.fuse__val,
.betbar__value,
.seatrow__meta {
	font-variant-numeric: tabular-nums;
	font-feature-settings: 'tnum' 1;
}

.sr-only {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* --------------------------------------------------------------- Gesperrt --
   game.js sperrt ueber aria-disabled, nie ueber disabled: der Knopf bleibt
   erreichbar und vorlesbar, wird aber eindeutig als gerade nicht moeglich
   lesbar. Unmoegliche Spielzuege verschwinden nie — sie treten zurueck. */
[aria-disabled='true'],
.is-disabled {
	opacity: 0.38;
	filter: saturate(0.45);
	cursor: not-allowed;
}

[aria-disabled='true']:hover,
.is-disabled:hover {
	transform: none;
}

/* -------------------------------------------------------------- Sprungmarken */
.skiplinks a {
	position: absolute;
	left: -9999px;
	top: 0;
	z-index: 120;
	padding: var(--sp-2) var(--sp-4);
	border-radius: 0 0 var(--r-2) 0;
	background: var(--accent);
	color: var(--accent-ink);
	font-weight: 700;
	text-decoration: none;
	white-space: nowrap;
}

.skiplinks a:focus-visible {
	left: 0;
}

/* ==========================================================================
   3 — GERUEST
   Drei feste Baender: Kopfzeile, Buehne, Deck. Nur die Buehne ist elastisch.
   dvh statt vh — die Browserleiste des Telefons frisst Hoehe, und ein vh-Wert
   schiebt das Deck dann unter den sichtbaren Rand.
   ========================================================================== */

.app {
	display: grid;
	/* SECHS Zeilen: gemeinsame Leiste, Tischzeile, Verbindungsband, Turnierband,
	   Buehne, Bedienung.

	   Die ERSTE ist neu und traegt web/kopf.js. Sie kostet, was sie kostet — 44 px
	   plus den oberen Sicherheitsabstand des Geraets — und das ist der Preis
	   dafuer, dass der Tisch dieselbe Marke und denselben Kontoknopf fuehrt wie
	   die uebrigen sieben Seiten. Die gedraengte Fassung ({kompakt: true}) haelt
	   ihn so klein wie das Daumenmass es zulaesst; unter 44 px darf ein Knopf in
	   dieser Anwendung nicht.

	   Zeile 3 und 4 (Stoerungs- und Turnierband) kosten im Normalfall NICHTS:
	   ihre Elemente tragen dann [hidden], display:none nimmt sie aus dem
	   Rasterfluss, und eine auto-Zeile ohne Inhalt faellt auf 0 zusammen. Genau
	   deshalb muessen die Zuweisungen darunter fest sein. */
	grid-template-rows: auto auto auto auto minmax(0, 1fr) auto;
	/* EINE Spalte, ausdruecklich hingeschrieben statt der stillen impliziten.
	   Im Querformat werden daraus ZWEI (siehe Abschnitt 16), und dann muss
	   jedes Kind wissen, ob es eine Spalte belegt oder beide ueberspannt.
	   Ohne eine benannte Spalte gaebe es die Linie -1 nicht, gegen die die
	   Regel darunter spannt. */
	grid-template-columns: minmax(0, 1fr);
	height: 100dvh;
	max-height: 100dvh;
	overflow: hidden;
}

/* Zeilen FEST zuweisen statt sie fliessen zu lassen.
 *
 * #connection traegt normalerweise [hidden], und display:none nimmt ein Element
 * aus dem Rasterfluss. Ohne feste Zuweisung ruecken dann alle nachfolgenden
 * Kinder eine Zeile hoch: der Tisch landet in einer auto-Zeile und bleibt auf
 * seiner Mindesthoehe, waehrend die Aktionszone das minmax(0,1fr) erbt und sich
 * mit leerem Raum aufblaeht. Genau das war der Grund fuer das grosse Loch unter
 * dem Timer und den zu flachen Tisch.
 *
 * WER WELCHE NUMMER HAT UND WARUM — die Reihenfolge ist die Lesereihenfolge von
 * oben nach unten, und jede Nummer ist nach dem Einzug der gemeinsamen Leiste um
 * eins nachgezogen worden:
 *   1  #ifb-kopf   Marke und Konto. Ganz oben, weil eine Kopfzeile unter dem
 *                  Inhalt keine ist — und weil an dieser Kante der obere
 *                  Sicherheitsabstand des Geraets sitzt.
 *   2  .topbar     Was nur am Tisch gilt: zurueck, Tischname, Chips, Schublade.
 *                  Direkt unter der Marke, nicht darueber: das Allgemeine steht
 *                  oben, das Besondere darunter, auf jeder Seite dieser Anwendung.
 *   3  .conn       Stoerungsband. Ueber der Buehne, damit es niemand uebersieht,
 *                  aber UNTER dem Kopf, damit es sich nicht auf das Guthaben
 *                  legt — die fruehere schwebende Fassung tat genau das.
 *   4  .tour       Turnierband. Unmittelbar ueber der Buehne, weil es sagt, mit
 *                  wessen Chips hier gespielt wird.
 *   5  .stage      Das Spielfeld. Die EINZIGE elastische Zeile (minmax(0,1fr));
 *                  alles, was die anderen fuenf nicht brauchen, faellt hierher.
 *   6  .deck       Die Bedienung. Unten, wo der Daumen ist.
 *
 * WER HIER EINE ZEILE EINFUEGT, ZIEHT ALLE FOLGENDEN NUMMERN NACH — und die
 * Zahl in grid-template-rows gleich mit. tools/kopf-dom.js rechnet seit dieser
 * Schicht genau das nach: Spurenzahl gegen Zuweisungen, und .stage auf der
 * einzigen minmax(0,1fr)-Spur. */

/*
 * UND DIE SPALTE DAZU. Grundzustand ist eine einzige, jedes Kind ueberspannt
 * sie ganz — das ist genau das, was ohne diese Regel automatisch geschaehe,
 * steht aber jetzt da, weil es im Querformat NICHT mehr automatisch stimmt:
 * dort bekommt .app eine zweite Spalte, und ein Kind ohne Zuweisung landete
 * dann in Spalte 1 (der schmalen Markenspalte) statt ueber der ganzen Breite.
 * Der Schaden waere derselbe wie bei den Zeilen, nur quer: Buehne und Deck
 * saessen auf 280 Pixeln neben der Leiste statt unter ihr.
 */
.app > * {
	grid-column: 1 / -1;
}

/*
 * Dazu der seitliche Rand: --kpf-pad-x ist der dafuer vorgesehene Stellknopf
 * von web/kopf.js (dort dokumentiert). Gesetzt wird eine VARIABLE und keine
 * Regel — das Markup der Leiste und seine Gestaltung bleiben in ihrer Hand.
 * Ohne das stuende die Marke auf 12 px, waehrend der Zurueck-Pfeil unmittelbar
 * darunter ab 900 px auf 24 px sitzt: zwei Kanten, die uebereinander gehoeren
 * und es um 12 px verfehlen.
 */
.app > #ifb-kopf {
	grid-row: 1;
	--kpf-pad-x: var(--sp-3);
}

/*
 * Zeile 1 ohne kopf.js: laedt das Modul nicht (Netzstoerung, alter
 * Zwischenspeicher), gibt es #ifb-kopf gar nicht — dann steht die Tischzeile
 * selbst ganz oben und rueckt in Zeile 1 nach. Beide Zuweisungen zugleich sind
 * unmoeglich: sobald die Leiste haengt, traegt der Koerper .has-ifb-kopf.
 * Ohne diese Regel klebte die Tischzeile im Stoerungsfall in Zeile 2 und liesse
 * eine leere Zeile ueber sich stehen.
 */
.app > .topbar {
	grid-row: 1;
}

body.has-ifb-kopf .app > .topbar {
	grid-row: 2;
}

.app > .conn {
	grid-row: 3;
}

.app > .tour {
	grid-row: 4;
}

.app > .stage {
	grid-row: 5;
}

.app > .deck {
	grid-row: 6;
}

/* ==========================================================================
   4 — TISCHZEILE (.topbar)
   Zeile 2 des Kopfes: was NUR am Tisch gilt. Die Marke und der Kontoknopf
   stehen eine Zeile hoeher in der gemeinsamen Leiste aus web/kopf.js.
   Schlank und vollstaendig in der Textflusslage: kein Element schwebt, also
   kann auch nichts mehr ueber dem Guthaben liegen.

   DER OBERE SICHERHEITSABSTAND GEHOERT DEM OBERSTEN ELEMENT, und das ist seit
   dem Einzug der Leiste nicht mehr diese Zeile. env(safe-area-inset-top) misst
   das GERAET, nicht die Lage im Dokument — es liefert auch dann noch 44 px,
   wenn ueber dieser Zeile laengst eine andere sitzt. Ohne diese Umschaltung
   haette ein Telefon mit Hauskante den Abstand zweimal bezahlt: einmal in der
   Leiste, einmal hier, und dem Spielfeld fehlten 44 px, die niemand sieht.
   ========================================================================== */

/* Am <html> und nicht an .app: #toasts liegt als Geschwister von #app im
   <body> und rechnet ebenfalls mit diesem Abstand. body.has-ifb-kopf setzt
   web/kopf.js, solange die Leiste haengt — sie zahlt den Abstand dann selbst. */
:root {
	--sicher-oben: env(safe-area-inset-top, 0px);

	/* WAS DIE GEMEINSAME LEISTE ALS EIGENE ZEILE KOSTET — und nur das.
	   --ifb-kopf-h ist ihre GEMESSENE Hoehe (kopf.js schreibt sie ans <html>).
	   Was die Meldungen darunter aber brauchen, ist nicht ihre Hoehe, sondern
	   der Platz, den sie ueber der Tischzeile wegnimmt. Beides ist dasselbe,
	   solange sie eine eigene Zeile hat — im Querformat teilt sie sich die
	   Zeile mit der Tischzeile (Abschnitt 16), und dann ist es 0. Ohne diese
	   Unterscheidung saessen die Meldungen dort 44 px zu tief, also mitten auf
	   dem Filz statt unmittelbar unter dem Kopf. */
	--kopf-eigen-h: var(--ifb-kopf-h, 0px);
}

body.has-ifb-kopf {
	--sicher-oben: 0px;
}

.topbar {
	position: relative;
	z-index: var(--z-top);
	display: flex;
	align-items: center;
	gap: var(--sp-2);
	min-width: 0;
	padding: var(--sp-2) var(--sp-3);
	padding-top: max(var(--sp-2), var(--sicher-oben));
	padding-left: max(var(--sp-3), env(safe-area-inset-left));
	padding-right: max(var(--sp-3), env(safe-area-inset-right));
	border-bottom: 1px solid var(--line-soft);
	background: linear-gradient(180deg, rgb(28 28 35 / 0.94), rgb(20 20 25 / 0.9));
	backdrop-filter: blur(8px);
}

/* Der Weg zurueck. Als echter Link funktioniert er auch ohne Skript.
   44 statt 40 Pixel: darunter liegt das Ziel unter dem Daumenmass. */
.topbar__back {
	display: inline-grid;
	place-items: center;
	flex: none;
	width: 44px;
	height: 44px;
	border-radius: var(--r-2);
	border: 1px solid var(--line);
	background: rgb(255 255 255 / 0.04);
	color: var(--text-2);
	font-size: var(--fs-lg);
	line-height: 1;
	text-decoration: none;
	transition:
		background var(--dur-2) var(--ease-out),
		color var(--dur-2) var(--ease-out);
}

.topbar__back:hover {
	background: var(--accent-veil);
	color: var(--accent-lit);
}

/* min-width:0 laesst diesen Block unter seine Inhaltsbreite schrumpfen — das
   ist gewollt, damit der Schubladenknopf rechts nie aus der Zeile gedraengt
   wird. Ohne overflow:hidden malte der Modus-Chip dann aber ueber das
   Guthaben: bei 360 und 390 Pixeln fehlten 37 beziehungsweise 7 Pixel. Genau
   diese Ueberlagerung hat der Nutzer gemeldet. Jetzt wird abgeschnitten
   statt uebergemalt — und weiter unten wird der Platz gleich mit frei. */
.tableid {
	display: flex;
	align-items: center;
	gap: var(--sp-2);
	flex: 1 1 auto;
	min-width: 0;
	overflow: hidden;
	margin: 0;
	font-size: var(--fs-sm);
	font-weight: 600;
}

.tableid__mark {
	width: 10px;
	height: 10px;
	flex: none;
	border-radius: 50%;
	background: radial-gradient(circle at 32% 30%, var(--accent-lit), var(--accent-deep));
	box-shadow: 0 0 10px -1px var(--accent-glow);
}

.tableid__name {
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* ---------------------------------------------------------------- Plaketten */
.pill {
	display: inline-flex;
	align-items: center;
	/* Nie schrumpfen: eine gestauchte Plakette schneidet ihr eigenes Wort ab. */
	flex: none;
	gap: var(--sp-1);
	padding: 3px var(--sp-3);
	border-radius: var(--r-pill);
	border: 1px solid var(--line);
	background: rgb(0 0 0 / 0.3);
	font-size: var(--fs-xs);
	font-weight: 700;
	letter-spacing: var(--ls-wide);
	text-transform: uppercase;
	color: var(--text-2);
	white-space: nowrap;
}

/* Auf dem Telefon kostet die weite Polsterung des Modus-Chips 8 Pixel, die in
   der Kopfzeile fehlen. Ab 560 kommt sie zurueck. */
.pill--mode {
	flex: none;
	padding-inline: var(--sp-2);
}

.pill--phase {
	border-color: rgb(255 122 0 / 0.45);
	background: var(--accent-veil);
	color: var(--accent-lit);
}

/* ---------------------------------------------------------------- Guthaben --
   Frueher lag das Guthaben in einem .status-Block, der darunter noch eine
   .status__row mit Stufe und Rang stapelte. Beide sind weg (sie stehen jetzt am
   Avatar in der gemeinsamen Leiste), und damit auch die zwei Huellen: eine
   Hoehle fuer ein einziges Kind ist keine Ordnung, sondern eine Stelle mehr,
   an der beim naechsten Umbau jemand raten muss, wozu sie da war. */
.balance {
	flex: none;
	display: flex;
	align-items: baseline;
	gap: var(--sp-2);
	line-height: 1.1;
}

/* Das Wort GUTHABEN kostet auf dem Telefon rund 67 Pixel in einer Zeile, in
   der bei 360 Pixeln bereits 37 fehlen. Der Wert daneben liest sich als
   "12.450 Chips" und traegt die Bedeutung allein; fuer Screenreader bleibt
   das Wort erhalten, deshalb wird es verborgen und nicht entfernt.
   Ab 560 Pixeln steht es wieder da. */
.balance__label {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
	font-size: var(--fs-micro);
	letter-spacing: var(--ls-wide);
	text-transform: uppercase;
	color: var(--text-3);
}

.balance__val {
	font-size: var(--fs-md);
	font-weight: 800;
	color: var(--accent-lit);
	white-space: nowrap;
}

/* HIER STANDEN .status__row, .metric*, .meter*, .rank__* UND #level-text /
   #rating-tier — rund 110 Zeilen fuer Stufe und Wertung in der Tischzeile.
   Alles ersatzlos entfernt, zusammen mit #level-block und #rating-block im
   Markup, den vier Kennungen in der dom-Karte von game.js, dem Stufenzweig in
   renderSidebar() und der kompletten Rangberechnung in ui.js (TIERS, tierFor,
   showRating) samt ihrer eigenen Abfrage auf /api/game/me.

   WARUM: Beides sind Fortschrittsanzeigen, die sich waehrend einer Hand nicht
   aendern, und beides steht jetzt in der gemeinsamen Leiste eine Zeile hoeher —
   die Stufe als Plakette am Avatar, die Wertung als Rangring um denselben
   Avatar, mit den vollen Zahlen im Profilmodal dahinter. #rating-block war
   ausserdem seit dem Turnierumbau totes Markup: es trug [hidden], und die
   einzige Stelle, die es je sichtbar gemacht hat, war jenes showRating() in
   ui.js — ein zweiter Abruf desselben Endpunkts, den die Leiste ohnehin holt.

   Geprueft, bevor gestrichen wurde: .metric, .meter, .rank__badge und
   .rank__name kamen NUR hier vor (table.css, render.js, ui.js und game.js: null
   Treffer). Beim Ausraeumen der Lobby ist an dieser Stelle schon einmal
   gemeinsam genutztes Vokabular mitgegangen; deshalb steht der Nachweis hier
   und nicht nur im Auftrag. Nicht zu verwechseln mit .seat-balance in
   table.css und dem privaten #metricsGen in render.js — beide heissen aehnlich
   und haben mit dieser Zeile nichts zu tun. */

/* ==========================================================================
   5 — VERBINDUNGSZUSTAND
   Fruehere Fassung schwebte fest ueber der Kopfzeile und legte sich auf das
   Guthaben. Jetzt ein eigenes Band im Fluss: sichtbar nur bei Stoerung,
   ansonsten per [hidden] ohne jeden Platzbedarf.
   ========================================================================== */

/* Solange das Band steht, muss die Meldungsspalte darunter ansetzen. game.js
   haelt data-connection am Wurzelelement und [hidden] am Band im Gleichschritt:
   sichtbar ist es genau dann, wenn der Zustand nicht 'online' ist. 56 Pixel
   decken den Maximalfall (zwei Zeilen a 13 Pixeln plus 16 Polsterung plus
   Rahmen) — ein zu grosser Wert setzt die Meldung nur etwas tiefer, ein zu
   kleiner legte sie wieder auf die Warnung. */
html[data-connection]:not([data-connection='online']) {
	--conn-h: 56px;
}

.conn {
	display: flex;
	align-items: center;
	gap: var(--sp-2);
	padding: var(--sp-2) var(--sp-4);
	border-bottom: 1px solid var(--line-soft);
	background: rgb(40 32 18 / 0.92);
	font-size: var(--fs-sm);
	color: var(--text-2);
}

.conn__dot {
	width: 9px;
	height: 9px;
	flex: none;
	border-radius: 50%;
	background: var(--warn);
	box-shadow: 0 0 10px -1px var(--warn);
	animation: dot-breathe 1.6s var(--ease-both) infinite;
}

.conn[data-state='offline'],
.conn[data-state='denied'] {
	background: rgb(52 20 18 / 0.92);
}

.conn[data-state='offline'] .conn__dot,
.conn[data-state='denied'] .conn__dot {
	background: var(--lose);
	box-shadow: 0 0 10px -1px var(--lose);
}

@keyframes dot-breathe {
	0%,
	100% {
		opacity: 1;
	}
	50% {
		opacity: 0.35;
	}
}

/* ==========================================================================
   5b — TURNIERBAND
   Der Tisch sieht im Turnier aus wie immer — derselbe Filz, dieselben Knoepfe.
   Genau daran ist ein Spieler haengengeblieben: Er kam ueber tisch/?turnier=…,
   landete auf einem freien Tisch und setzte ECHTE Chips, waehrend die
   Turnierseite bei 0 von 50 Haenden stehen blieb. Dieses Band ist die Antwort
   darauf. Es traegt eine eigene Farbfamilie (Violett, sonst nirgends am Tisch)
   und liegt ueber der Buehne — man kann es nicht uebersehen, ohne den Tisch
   selbst zu verdecken.

   Die Zahlen darin stehen bewusst gross und in Tabellenziffern: „Hand 3 / 50“
   ist die Zahl, wegen der der Spieler ueberhaupt hier ist.
   ========================================================================== */

/* game.js haelt data-turnier am Wurzelelement und [hidden] am Band im
   Gleichschritt — gesetzt wird es genau dann, wenn das Band steht. */
html[data-turnier='on'] {
	--tour-h: 96px;
}

.tour {
	display: grid;
	gap: var(--sp-2);
	padding: var(--sp-2) var(--sp-4);
	border-bottom: 1px solid var(--tour-line);
	/* Der Streifen links ist die eigentliche Marke: er bleibt sichtbar, auch
	   wenn die Flaeche in einem hellen Umfeld anders wirkt. */
	border-left: 3px solid var(--tour-edge);
	background: var(--tour-bg);
	color: var(--text);
}

.tour__head {
	display: flex;
	align-items: center;
	gap: var(--sp-2);
	min-width: 0;
}

/* Die Plakette sagt in einem Wort, wo man sitzt. Gold auf Violett — die
   Kombination kommt am freien Tisch nirgends vor. */
.tour__mark {
	flex: none;
	padding: 2px var(--sp-2);
	border-radius: var(--r-1);
	background: var(--accent-lit);
	color: var(--accent-ink);
	font-size: var(--fs-micro);
	font-weight: 900;
	letter-spacing: var(--ls-wide);
	text-transform: uppercase;
}

.tour__name {
	flex: 1 1 auto;
	min-width: 0;
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
	font-size: var(--fs-sm);
	font-weight: 700;
}

.tour__lane {
	flex: none;
	font-size: var(--fs-xs);
	font-weight: 700;
	color: var(--text-2);
}

/* Zustand der BAHN, nicht des Turniers. Die Farbe wiederholt nur, was das Wort
   schon sagt — sie ist nie die einzige Information. */
.tour[data-lane='playing'] .tour__lane {
	color: var(--win);
}

.tour[data-lane='parked'] .tour__lane {
	color: var(--warn);
}

.tour[data-lane='done'] .tour__lane {
	color: var(--push);
}

/* Zwei Spalten auf dem Telefon, vier ab 560. Vier nebeneinander waeren auf
   412 Pixeln je 90 Pixel breit — die Zahl „5.000“ braucht mehr. */
.tour__facts {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--sp-1) var(--sp-3);
	margin: 0;
}

.tour__fact {
	display: flex;
	align-items: baseline;
	gap: var(--sp-2);
	min-width: 0;
}

.tour__key {
	flex: none;
	font-size: var(--fs-xs);
	color: var(--text-3);
}

/* dd traegt im Standardblatt margin-inline-start: 40px. */
.tour__val {
	margin: 0;
	min-width: 0;
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
	font-size: var(--fs-sm);
	font-weight: 700;
	color: var(--text);
}

.tour__bar {
	display: block;
	height: 4px;
	border-radius: var(--r-pill);
	background: rgb(0 0 0 / 0.35);
	overflow: hidden;
}

.tour__fill {
	display: block;
	height: 100%;
	width: 0;
	border-radius: inherit;
	background: linear-gradient(90deg, var(--tour-edge), var(--accent-lit));
	transition: width var(--dur-4) var(--ease-out);
}

.tour__note {
	margin: 0;
	font-size: var(--fs-xs);
	line-height: 1.45;
	color: var(--text-2);
}

.tour__note:empty {
	display: none;
}

/* ---------------------------------------------------------------- Fehlgriff
   ?turnier= stand in der Adresse, der Zustand kennt aber kein Turnier: dann
   sitzt der Spieler an einem freien Tisch und wuerde mit ECHTEN Chips setzen.
   Die Zahlen des Bandes waeren hier gelogen, also verschwinden sie; die
   Warnung nimmt ihren Platz ein. game.js sperrt zusaetzlich die Einsatzleiste. */
.tour[data-lane='mismatch'] {
	border-left-color: var(--lose);
	border-bottom-color: var(--lose-deep);
	background: var(--tour-bg-alarm);
}

.tour[data-lane='mismatch'] .tour__mark {
	background: var(--lose);
	color: var(--text);
}

.tour[data-lane='mismatch'] .tour__facts,
.tour[data-lane='mismatch'] .tour__bar {
	display: none;
}

.tour__alarm {
	display: grid;
	gap: var(--sp-2);
}

.tour__actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--sp-2);
}

/* ------------------------------------------------------------ Zwischenstand
   In der Schublade, nicht im Band: er ist Beiwerk, kein Bedienelement. Die
   Zahlen stehen unter Embargo (siehe #tour-rank-note) — der Client rechnet
   nichts dazu, er zeigt genau das, was der Server freigegeben hat. */
.tourrank {
	display: grid;
	gap: 2px;
	margin: 0 0 var(--sp-2);
	padding: 0;
	list-style: none;
}

.tourrank__row {
	display: flex;
	align-items: baseline;
	gap: var(--sp-2);
	padding: 3px var(--sp-2);
	border-radius: var(--r-1);
	font-size: var(--fs-xs);
	color: var(--text-2);
}

.tourrank__row--me {
	background: var(--tour-veil);
	color: var(--text);
	font-weight: 700;
}

.tourrank__pos {
	flex: none;
	min-width: 2ch;
	color: var(--text-3);
	font-variant-numeric: tabular-nums;
}

.tourrank__row--me .tourrank__pos {
	color: var(--accent-lit);
}

.tourrank__name {
	flex: 1 1 auto;
	min-width: 0;
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
}

.tourrank__val {
	flex: none;
	font-variant-numeric: tabular-nums;
}

/* ==========================================================================
   6 — BUEHNE
   Links der Tisch, rechts die Schublade. Auf dem Telefon faehrt die Schublade
   ueber den Tisch; der Tisch behaelt die volle Flaeche.
   ========================================================================== */

.stage {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	min-height: 0;
}

/* Der Behaelter, nicht der Inhalt: table.css gestaltet, was darin liegt.
   display:grid genuegt, damit #table die volle Flaeche einnimmt. */
.table-wrap {
	position: relative;
	display: grid;
	min-width: 0;
	min-height: 0;
}

/* ------------------------------------------------------ Versicherungsfrage --
   Sie liegt unmittelbar ueber dem Deck, also im Daumenbereich, und nicht in
   der Bildmitte: die Antwort faellt dort, wo die Hand ohnehin ist. */
/* Quer am Telefon liegt die Kerbe seitlich: ohne die sicheren Zonen schoebe
   sich die Versicherungsfrage darunter. */
.prompt {
	position: absolute;
	left: max(var(--sp-3), env(safe-area-inset-left));
	right: max(var(--sp-3), env(safe-area-inset-right));
	bottom: var(--sp-3);
	z-index: 70;
	display: grid;
	gap: var(--sp-2);
	padding: var(--sp-3) var(--sp-4);
	border-radius: var(--r-4);
	border: 1px solid rgb(255 122 0 / 0.5);
	background: linear-gradient(180deg, rgb(44 30 18 / 0.97), rgb(20 20 26 / 0.97));
	box-shadow: var(--sh-4);
	animation: prompt-in var(--dur-4) var(--ease-out) both;
}

.prompt__title {
	margin: 0;
	font-size: var(--fs-md);
	font-weight: 800;
	color: var(--accent-lit);
}

.prompt__text {
	margin: 0;
	font-size: var(--fs-xs);
	line-height: 1.5;
	color: var(--text-2);
}

.prompt__actions {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: var(--sp-2);
}

@keyframes prompt-in {
	from {
		opacity: 0;
		transform: translateY(14px);
	}
}

/* ==========================================================================
   7 — SCHUBLADE
   Sitzliste, Rundeninfo, Emotes, Tischtext und die Werkzeugknoepfe. Sie liegt
   bewusst nicht in der Kopfzeile: dort waere auf 412 Pixeln kein Platz, ohne
   dass sich etwas ueberlagert.
   ========================================================================== */

.side {
	position: fixed;
	top: 0;
	right: 0;
	bottom: 0;
	z-index: var(--z-side);
	width: min(88vw, 340px);
	display: flex;
	flex-direction: column;
	gap: var(--sp-4);
	padding: var(--sp-4);
	padding-top: max(var(--sp-4), env(safe-area-inset-top));
	padding-bottom: max(var(--sp-4), env(safe-area-inset-bottom));
	/* Sie faehrt von rechts ein und liegt quer genau unter der Kerbe. */
	padding-right: max(var(--sp-4), env(safe-area-inset-right));
	border-left: 1px solid var(--line);
	background: linear-gradient(180deg, rgb(26 26 32 / 0.99), rgb(14 14 18 / 0.99));
	box-shadow: var(--sh-4);
	overflow-y: auto;
	overscroll-behavior: contain;
	/* Geschlossen ist die Schublade nicht nur weggeschoben, sondern auch aus
	   der Tabreihenfolge genommen — sonst wandert der Fokus ins Unsichtbare. */
	transform: translateX(101%);
	visibility: hidden;
	transition:
		transform var(--dur-4) var(--ease-out),
		visibility 0s linear var(--dur-4);
}

html[data-side='open'] .side {
	transform: none;
	visibility: visible;
	transition:
		transform var(--dur-4) var(--ease-out),
		visibility 0s;
}

.scrim {
	position: fixed;
	inset: 0;
	z-index: var(--z-scrim);
	appearance: none;
	border: 0;
	padding: 0;
	background: rgb(8 8 11 / 0.6);
	backdrop-filter: blur(2px);
	animation: fade-in var(--dur-3) var(--ease-out);
}

.side__close {
	align-self: flex-end;
}

.side__section {
	display: grid;
	gap: var(--sp-2);
	min-width: 0;
}

.side__title {
	margin: 0;
	font-size: var(--fs-xs);
	font-weight: 700;
	letter-spacing: var(--ls-wide);
	text-transform: uppercase;
	color: var(--text-3);
}

.side__tools {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: var(--sp-2);
	margin-top: auto;
	padding-top: var(--sp-3);
	border-top: 1px solid var(--line-soft);
}

.side__tools .btn {
	padding-inline: var(--sp-2);
}

.side__tools .btn--leave {
	grid-column: 1 / -1;
}

/* -------------------------------------------------------------- Rundeninfo */
.factlist {
	display: flex;
	flex-wrap: wrap;
	gap: var(--sp-2);
}

/* ---------------------------------------------------------------- Sitzliste
   Nur Namen und Stand, keine Tischgrafik. Die Zeilen heissen .seatrow und
   nicht .seat-row: der Praefix .seat- ist fuer table.css reserviert. */
.seatlist {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: var(--sp-2);
}

.seatrow {
	display: flex;
	align-items: center;
	gap: var(--sp-3);
	padding: var(--sp-2);
	border-radius: var(--r-2);
	border: 1px solid var(--line-soft);
	background: rgb(255 255 255 / 0.03);
}

.seatrow--me {
	border-color: rgb(255 122 0 / 0.5);
	background: var(--accent-veil);
}

.seatrow--active {
	border-color: var(--accent);
	box-shadow: var(--glow-ember);
}

.seatrow__index {
	display: inline-grid;
	place-items: center;
	width: 24px;
	height: 24px;
	flex: none;
	border-radius: 50%;
	background: rgb(0 0 0 / 0.45);
	border: 1px solid var(--line);
	font-size: var(--fs-xs);
	font-weight: 700;
	font-variant-numeric: tabular-nums;
}

.seatrow__body {
	display: grid;
	gap: 1px;
	min-width: 0;
	flex: 1 1 auto;
}

.seatrow__name {
	font-size: var(--fs-sm);
	font-weight: 600;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.seatrow__meta {
	font-size: var(--fs-xs);
	color: var(--text-3);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.seatrow__take {
	appearance: none;
	width: 100%;
	/* Daumenmass: 40 Pixel waren zu flach. */
	min-height: 44px;
	padding: 0 var(--sp-3);
	border-radius: var(--r-2);
	border: 1px dashed rgb(255 255 255 / 0.24);
	background: rgb(0 0 0 / 0.28);
	color: var(--text-2);
	font-size: var(--fs-xs);
	font-weight: 700;
	letter-spacing: var(--ls-wide);
	text-transform: uppercase;
	transition:
		border-color var(--dur-2) var(--ease-out),
		background var(--dur-2) var(--ease-out),
		color var(--dur-2) var(--ease-out);
}

.seatrow__take:hover:not(.is-disabled) {
	border-color: var(--accent);
	border-style: solid;
	background: var(--accent-veil);
	color: var(--accent-lit);
}

/* ------------------------------------------------------------------ Emotes
   Eng auf #emote-bar geschnitten: .emote heisst am Sitz die schwebende
   Sprechblase und gehoert dort table.css. Hier ist es ein stiller Knopf. */
#emote-bar {
	display: flex;
	flex-wrap: wrap;
	gap: var(--sp-2);
}

#emote-bar .emote {
	appearance: none;
	position: static;
	transform: none;
	animation: none;
	pointer-events: auto;
	/* Daumenmass. Acht Emotes zu 44 Pixeln passen bei 412 in zwei Reihen der
	   308 Pixel breiten Schublade (6 x 44 + 5 x 8 = 304). */
	width: 44px;
	height: 44px;
	padding: 0;
	display: grid;
	place-items: center;
	border-radius: var(--r-2);
	border: 1px solid var(--line);
	background: rgb(255 255 255 / 0.04);
	box-shadow: none;
	font-size: var(--fs-lg);
	line-height: 1;
	transition:
		background var(--dur-2) var(--ease-out),
		transform var(--dur-2) var(--ease-snap);
}

#emote-bar .emote:hover {
	background: var(--accent-veil);
	transform: translateY(-2px);
}

/* --------------------------------------------------------- Tisch in Worten */
.tabletext {
	margin: 0;
	max-height: 14em;
	overflow-y: auto;
	font-size: var(--fs-xs);
	line-height: 1.6;
	color: var(--text-2);
}

/* ==========================================================================
   8 — DAS DECK
   Eine einzige Bedienzone am unteren Rand, im Daumenbereich.
   Aufbau von oben nach unten, immer gleich:
     Zuendschnur (Timer)  —  Lage (Phase + Hinweis)  —  Wechselzone.
   Die Wechselzone hat eine feste Mindesthoehe. Einsatzleiste, Spielzuege und
   Zuschauerhinweis liegen alle in DERSELBEN Rasterzelle. Damit verschiebt der
   Wechsel von Setzen zu Spielen kein einziges Pixel.
   ========================================================================== */

.deck {
	position: relative;
	z-index: var(--z-deck);
	display: grid;
	gap: var(--sp-2);
	padding: var(--sp-2) var(--sp-3);
	padding-bottom: max(var(--sp-3), env(safe-area-inset-bottom));
	padding-left: max(var(--sp-3), env(safe-area-inset-left));
	padding-right: max(var(--sp-3), env(safe-area-inset-right));
	border-top: 1px solid var(--line);
	background: linear-gradient(180deg, rgb(26 26 33 / 0.96), rgb(15 15 19 / 0.99));
	box-shadow: 0 -12px 32px -18px rgb(0 0 0 / 0.9);
	transition: border-color var(--dur-3) var(--ease-out);
}

/* ---------------------------------------------------------- Zuendschnur ----
   Ein Balken, kein Blinken. Er sitzt unmittelbar ueber den Knoepfen, damit er
   im Blick bleibt, waehrend entschieden wird. Der Platz bleibt auch dann
   reserviert, wenn gerade keine Frist laeuft — sonst huepft das ganze Deck. */
.deck__fuse {
	display: flex;
	align-items: center;
	height: 16px;
}

#hud-timer {
	display: flex;
	align-items: center;
	gap: var(--sp-3);
	width: 100%;
}

.fuse__track {
	position: relative;
	flex: 1 1 auto;
	height: 6px;
	border-radius: var(--r-pill);
	background: rgb(0 0 0 / 0.5);
	border: 1px solid var(--line-soft);
	overflow: hidden;
}

.fuse__fill {
	display: block;
	height: 100%;
	width: 100%;
	border-radius: inherit;
	background: linear-gradient(90deg, var(--accent-deep), var(--accent), var(--accent-lit));
	/* Der Ticker in game.js schreibt viermal pro Sekunde; die lineare Blende
	   glaettet das zu einer ruhig laufenden Schnur. */
	transition:
		width var(--dur-3) linear,
		background var(--dur-4) var(--ease-out);
}

.fuse__val {
	flex: none;
	min-width: 4ch;
	text-align: right;
	font-size: var(--fs-sm);
	font-weight: 800;
	color: var(--text-2);
	transition: color var(--dur-3) var(--ease-out);
}

/* Letzte zehn Sekunden: waermer. */
#hud-timer[data-level='low'] .fuse__fill {
	background: linear-gradient(90deg, var(--warn-deep), var(--warn));
}

#hud-timer[data-level='low'] .fuse__val {
	color: var(--warn);
}

/* Letzte fuenf Sekunden: deutlich, aber im Ruhepuls von einem Schlag pro
   Sekunde. Kein Zittern, kein schnelles Blinken — das stresst nur. */
#hud-timer[data-level='urgent'] .fuse__fill {
	background: linear-gradient(90deg, var(--lose-deep), var(--lose));
}

#hud-timer[data-level='urgent'] .fuse__val {
	color: var(--lose);
	animation: fuse-beat 1s var(--ease-both) infinite;
}

@keyframes fuse-beat {
	0%,
	100% {
		opacity: 1;
	}
	55% {
		opacity: 0.45;
	}
}

/* Das ganze Deck bekommt einen warmen Saum, damit die Dringlichkeit auch dann
   ankommt, wenn der Blick auf den Karten liegt. */
html[data-timer='urgent'] .deck {
	border-top-color: rgb(226 72 60 / 0.65);
	box-shadow:
		0 -1px 0 rgb(226 72 60 / 0.35),
		0 -14px 34px -20px rgb(226 72 60 / 0.5);
}

/* ------------------------------------------------------------------ Lage --
   Eine Zeile: was gerade laeuft, und was von mir erwartet wird. Feste
   Mindesthoehe, damit ein laengerer Hinweis nichts verschiebt. */
.deck__rail {
	display: flex;
	align-items: center;
	gap: var(--sp-2);
	min-height: 22px;
}

.deck__hint {
	flex: 1 1 auto;
	margin: 0;
	min-width: 0;
	font-size: var(--fs-sm);
	color: var(--text-2);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* ------------------------------------------------------------ Wechselzone --
   Drei Kinder, eine Zelle, feste Mindesthoehe. */
.deck__zone {
	display: grid;
	min-height: var(--zone-h);
}

.deck__zone > #spectate,
.deck__zone > #betbar,
.deck__zone > #actionbar {
	grid-area: 1 / 1;
	min-width: 0;
}

/* Standardlage: die Spielzuege liegen offen. */
#actionbar,
#betbar {
	opacity: 1;
	visibility: visible;
	transition:
		opacity var(--dur-3) var(--ease-out),
		visibility 0s;
}

/* Ausserhalb der Setzphase tritt die Einsatzleiste zurueck. game.js setzt
   data-active; sie bleibt im Raster stehen und haelt die Hoehe. */
#betbar[data-active='false'] {
	opacity: 0;
	visibility: hidden;
	pointer-events: none;
	transition:
		opacity var(--dur-2) var(--ease-in),
		visibility 0s linear var(--dur-2);
}

/* In der Setzphase treten die Spielzuege zurueck. Der Geschwisterselektor
   haengt an der Reihenfolge im Markup: #spectate, #betbar, #actionbar. */
#betbar[data-active='true'] ~ #actionbar {
	opacity: 0;
	visibility: hidden;
	pointer-events: none;
	transition:
		opacity var(--dur-2) var(--ease-in),
		visibility 0s linear var(--dur-2);
}

/* Zuschauen deckt beides zu — es ist ein eigener Zustand, keine Mischung. */
#spectate:not([hidden]) ~ #betbar,
#spectate:not([hidden]) ~ #actionbar {
	visibility: hidden;
	pointer-events: none;
}

/* ==========================================================================
   9 — SPIELZUEGE
   Fuenf Knoepfe, feste Reihenfolge, feste Position: Karte, Halten, Doppeln,
   Teilen, Aufgeben. Karte und Halten stehen oben und sind groesser — sie
   fallen in fast jedem Zug an. Ein unmoeglicher Zug wird gedimmt, niemals
   entfernt: die Hand findet den Knopf blind wieder.
   ========================================================================== */

#actionbar {
	display: grid;
	grid-template-columns: repeat(6, minmax(0, 1fr));
	grid-template-rows: 1.08fr 1fr;
	grid-template-areas:
		'hit hit hit stand stand stand'
		'double double split split surrender surrender';
	gap: var(--sp-2);
}

.btn--hit {
	grid-area: hit;
}
.btn--stand {
	grid-area: stand;
}
.btn--double {
	grid-area: double;
}
.btn--split {
	grid-area: split;
}
.btn--surrender {
	grid-area: surrender;
}

.act {
	appearance: none;
	position: relative;
	display: grid;
	place-content: center;
	justify-items: center;
	gap: 2px;
	min-width: 0;
	padding: 0 var(--sp-2);
	border-radius: var(--r-3);
	border: 1px solid var(--line);
	background: linear-gradient(180deg, var(--bg-panel-2), var(--bg-panel));
	box-shadow: var(--sh-inset);
	color: var(--text);
	font-size: var(--fs-sm);
	font-weight: 700;
	line-height: 1.15;
	text-align: center;
	overflow: hidden;
	transition:
		transform var(--dur-2) var(--ease-snap),
		border-color var(--dur-2) var(--ease-out),
		background var(--dur-2) var(--ease-out);
}

/* Farbkante unten: die Wiedererkennung sitzt an der Kante, nicht in der
   Flaeche — so bleibt die Beschriftung auf voller Deckkraft lesbar. */
.act::after {
	content: '';
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: 3px;
	background: var(--act, var(--accent));
}

.act:hover:not(.is-disabled) {
	transform: translateY(-2px);
	border-color: var(--act, var(--accent));
	background: linear-gradient(180deg, var(--bg-panel-2), color-mix(in srgb, var(--act) 14%, var(--bg-panel)));
}

.act:active:not(.is-disabled) {
	transform: translateY(1px) scale(0.985);
}

.act[aria-disabled='false'] {
	border-color: color-mix(in srgb, var(--act) 45%, var(--line));
}

.btn--hit {
	--act: var(--act-hit);
	font-size: var(--fs-md);
}
.btn--stand {
	--act: var(--act-stand);
	font-size: var(--fs-md);
}
.btn--double {
	--act: var(--act-double);
}
.btn--split {
	--act: var(--act-split);
}
.btn--surrender {
	--act: var(--act-surrender);
}

/* Der eigene Zug hebt die noch moeglichen Zuege sanft an. */
html[data-turn='mine'] .act[aria-disabled='false'] {
	background: linear-gradient(180deg, color-mix(in srgb, var(--act) 22%, var(--bg-panel-2)), var(--bg-panel));
}

/* Tastenmarken. Auf reinen Tippgeraeten kosten sie nur Platz. */
.btn__key {
	display: inline-grid;
	place-items: center;
	min-width: 18px;
	padding: 0 4px;
	border-radius: var(--r-1);
	border: 1px solid rgb(255 255 255 / 0.18);
	background: rgb(0 0 0 / 0.4);
	font-family: inherit;
	font-size: 9px;
	font-weight: 700;
	color: var(--text-3);
}

@media (pointer: coarse) {
	.btn__key {
		display: none;
	}
}

/* ==========================================================================
   10 — EINSATZLEISTE
   Belegt dieselbe Zone wie die Spielzuege. Reihe eins traegt die Chips (oder
   bei festem Einsatz den einen Knopf), Reihe zwei Summe und Korrektur,
   Reihe drei den Hinweis.
   ========================================================================== */

#betbar {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto auto;
	grid-template-rows: minmax(0, 1fr) auto auto;
	gap: var(--sp-1) var(--sp-2);
	align-content: stretch;
}

#chips,
#bet-fixed {
	grid-column: 1 / -1;
	grid-row: 1;
	align-self: center;
}

#chips {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--sp-1);
	min-width: 0;
}

/* --------------------------------------------------------- Einsatzchips ----
   Eng auf #chips geschnitten: der Renderer benutzt .chip fuer die fliegenden
   Chips auf dem Filz. Diese hier sind Knoepfe in der Leiste. */
#chips .chip {
	appearance: none;
	position: relative;
	flex: 0 1 var(--chip-d);
	width: var(--chip-d);
	max-width: var(--chip-d);
	aspect-ratio: 1;
	padding: 0;
	border: 0;
	border-radius: 50%;
	display: grid;
	place-items: center;
	color: var(--chip-ink, var(--text-ink));
	background:
		radial-gradient(circle at 50% 50%, var(--chip-body) 0 58%, transparent 58.5%),
		repeating-conic-gradient(
			from 0deg,
			var(--chip-ring) 0deg 12deg,
			var(--chip-body) 12deg 30deg
		);
	box-shadow:
		0 1px 2px rgb(0 0 0 / 0.5),
		0 4px 10px -4px rgb(0 0 0 / 0.7),
		inset 0 0 0 2px rgb(0 0 0 / 0.16);
	transition:
		transform var(--dur-2) var(--ease-snap),
		filter var(--dur-2) var(--ease-out);
}

#chips .chip__value {
	font-size: calc(var(--chip-d) * 0.28);
	font-weight: 900;
	line-height: 1;
	letter-spacing: -0.04em;
	text-shadow: 0 1px 0 rgb(0 0 0 / 0.2);
}

#chips .chip:hover:not(.is-disabled) {
	transform: translateY(-4px);
}

#chips .chip:active:not(.is-disabled) {
	transform: translateY(-1px) scale(0.95);
}

/* Der gewaehlte Chip steht sichtbar vor den anderen. */
#chips .chip[aria-pressed='true'] {
	transform: translateY(-5px);
	filter: drop-shadow(0 0 9px var(--accent-glow));
}

#chips .chip[data-denom='1'] {
	--chip-body: var(--chip1-body);
	--chip-ink: var(--chip1-ink);
}
#chips .chip[data-denom='5'] {
	--chip-body: var(--chip5-body);
	--chip-ink: var(--chip5-ink);
}
#chips .chip[data-denom='25'] {
	--chip-body: var(--chip25-body);
	--chip-ink: var(--chip25-ink);
}
#chips .chip[data-denom='100'] {
	--chip-body: var(--chip100-body);
	--chip-ink: var(--chip100-ink);
}
#chips .chip[data-denom='500'] {
	--chip-body: var(--chip500-body);
	--chip-ink: var(--chip500-ink);
}
#chips .chip[data-denom='1000'] {
	--chip-body: var(--chip1000-body);
	--chip-ink: var(--chip1000-ink);
}

/* --------------------------------------------------------- Summe und Rest */
.betbar__amount {
	grid-row: 2;
	grid-column: 1;
	display: flex;
	align-items: baseline;
	gap: var(--sp-2);
	min-width: 0;
	padding-left: var(--sp-1);
}

.betbar__label {
	font-size: var(--fs-micro);
	letter-spacing: var(--ls-wide);
	text-transform: uppercase;
	color: var(--text-3);
}

.betbar__value {
	font-size: var(--fs-md);
	font-weight: 800;
	color: var(--accent-lit);
	white-space: nowrap;
}

#bet-clear {
	grid-row: 2;
	grid-column: 2;
}

#bet-repeat {
	grid-row: 2;
	grid-column: 3;
}

#bet-hint {
	grid-row: 3;
	grid-column: 1 / -1;
	margin: 0;
	min-height: 15px;
	font-size: var(--fs-xs);
	color: var(--text-3);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* ==========================================================================
   11 — ZUSCHAUEN
   Kein Randzustand und keine Sackgasse: der Ausweg steht gross in derselben
   Zone, in der sonst gespielt wird. Zwei Wege, beide sichtbar.
   ========================================================================== */

#spectate {
	z-index: 2;
	display: grid;
	align-content: center;
	gap: var(--sp-2);
	padding: var(--sp-3);
	border-radius: var(--r-3);
	border: 1px solid rgb(255 122 0 / 0.45);
	background: linear-gradient(180deg, rgb(42 30 18 / 0.97), rgb(22 22 28 / 0.97));
	text-align: center;
}

.spectate__title {
	margin: 0;
	font-size: var(--fs-md);
	font-weight: 800;
	color: var(--accent-lit);
}

.spectate__text {
	margin: 0;
	font-size: var(--fs-xs);
	line-height: 1.45;
	color: var(--text-2);
}

.spectate__actions {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: var(--sp-2);
}

/* ==========================================================================
   12 — KNOEPFE
   ========================================================================== */

.btn {
	appearance: none;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--sp-2);
	min-height: var(--tap);
	padding: 0 var(--sp-4);
	border-radius: var(--r-2);
	border: 1px solid var(--line);
	background: linear-gradient(180deg, var(--bg-panel-2), var(--bg-panel));
	box-shadow: var(--sh-inset);
	color: var(--text);
	font-size: var(--fs-sm);
	font-weight: 700;
	line-height: 1.1;
	text-align: center;
	text-decoration: none;
	white-space: nowrap;
	transition:
		transform var(--dur-2) var(--ease-snap),
		border-color var(--dur-2) var(--ease-out),
		background var(--dur-2) var(--ease-out);
}

.btn:hover:not(.is-disabled):not([aria-disabled='true']) {
	border-color: var(--accent);
	background: linear-gradient(180deg, var(--bg-panel-2), rgb(58 38 18));
}

.btn:active:not(.is-disabled):not([aria-disabled='true']) {
	transform: translateY(1px);
}

/* 44 statt 38. Diese Groesse traegt den Schubladenknopf in der Kopfzeile,
   Loeschen und Wiederholen in der Einsatzleiste, die vier Werkzeuge in der
   Schublade und jedes Schliessen im Dialog — alles Daumenziele, alle bisher
   sechs Pixel zu flach. --zone-h ist um denselben Betrag mitgewachsen. */
.btn--sm {
	min-height: 44px;
	padding: 0 var(--sp-3);
	font-size: var(--fs-xs);
}

/* Der Schubladenknopf traegt nur ein Zeichen und waere mit 12 Pixeln
   Polsterung 38 Pixel breit — hoch genug, aber zu schmal. Er ist auf dem
   Telefon der einzige Weg zu Mitspielern, Emotes und Werkzeugen. */
.side-toggle {
	min-width: 44px;
	flex: none;
}

.btn--primary {
	border-color: transparent;
	background: linear-gradient(180deg, var(--accent-lit), var(--accent) 55%, var(--accent-deep));
	color: var(--accent-ink);
	box-shadow: var(--sh-2);
}

.btn--primary:hover:not(.is-disabled):not([aria-disabled='true']) {
	background: linear-gradient(180deg, #ffc57e, var(--accent-lit) 55%, var(--accent));
	border-color: transparent;
}

.btn--ghost {
	background: rgb(255 255 255 / 0.03);
}

.btn--danger {
	border-color: rgb(226 72 60 / 0.45);
	color: #ffb7b0;
}

.btn--danger:hover:not(.is-disabled):not([aria-disabled='true']) {
	border-color: var(--lose);
	background: rgb(90 26 22 / 0.6);
}

/* ==========================================================================
   13 — MELDUNGEN
   ========================================================================== */

/* Meldungen setzen unterhalb der Kopfzeile an. Die fruehere Fassung begann am
   oberen Rand und legte sich damit genau auf Guthaben und Rang — dieselbe
   Ueberlagerung, die den Nutzer schon einmal gestoert hat.
   --conn-h kommt dazu: bei gestoerter Verbindung steht zwischen Kopfzeile und
   Buehne ein zweites Band, und ohne den Zuschlag deckte die erste Meldung
   genau die Verbindungswarnung zu.

   --kopf-eigen-h ist seit dem Einzug der gemeinsamen Leiste der erste Summand:
   die Leiste MISST sich selbst und veroeffentlicht ihre Hoehe am <html>. Fest
   eintragen liesse sie sich nicht — sie haengt an der Schriftgroesse des
   Nutzers und am Sicherheitsabstand des Geraets. Ohne diesen Summanden legte
   sich die erste Meldung genau auf die Tischzeile, also wieder auf das
   Guthaben. Er ist 0, solange die Leiste nicht haengt — und ebenso im
   Querformat, wo sie sich die Zeile mit der Tischzeile teilt und deshalb
   keine eigene Hoehe kostet (siehe die Erklaerung an :root ganz oben). Dann
   traegt --sicher-oben den Geraeteabstand. */
.toasts {
	position: fixed;
	left: 50%;
	transform: translateX(-50%);
	top: calc(
		var(--kopf-eigen-h) + var(--sicher-oben) + var(--top-h) + var(--conn-h) + var(--tour-h) + var(--sp-2)
	);
	z-index: var(--z-toast);
	display: grid;
	gap: var(--sp-2);
	width: min(92vw, 420px);
	pointer-events: none;
}

.toast {
	display: grid;
	gap: 2px;
	padding: var(--sp-3) var(--sp-4);
	border-radius: var(--r-3);
	border: 1px solid var(--line);
	border-left-width: 4px;
	background: rgb(24 24 30 / 0.97);
	box-shadow: var(--sh-3);
	animation: toast-in var(--dur-4) var(--ease-out) both;
}

.toast--success {
	border-left-color: var(--win);
}
.toast--info {
	border-left-color: var(--info);
}
.toast--warning {
	border-left-color: var(--warn);
}

.toast__title {
	font-size: var(--fs-sm);
	font-weight: 800;
}

.toast__msg {
	font-size: var(--fs-xs);
	color: var(--text-2);
}

.toast--leaving {
	animation: toast-out var(--dur-3) var(--ease-in) forwards;
}

@keyframes toast-in {
	from {
		opacity: 0;
		transform: translateY(-14px);
	}
}

@keyframes toast-out {
	to {
		opacity: 0;
		transform: translateY(-10px);
	}
}

@keyframes fade-in {
	from {
		opacity: 0;
	}
}

/* ==========================================================================
   14 — DIALOGE
   ========================================================================== */

dialog {
	width: min(92vw, 560px);
	max-height: min(86dvh, 720px);
	padding: 0;
	border: 1px solid var(--line);
	border-radius: var(--r-4);
	background: linear-gradient(180deg, var(--bg-panel), var(--bg-sunk));
	color: var(--text);
	box-shadow: var(--sh-4);
	overflow: hidden;
}

dialog::backdrop {
	background: rgb(8 8 11 / 0.68);
	backdrop-filter: blur(3px);
}

.dlg__head {
	display: flex;
	align-items: center;
	gap: var(--sp-3);
	padding: var(--sp-3) var(--sp-4);
	border-bottom: 1px solid var(--line-soft);
	background: rgb(255 255 255 / 0.03);
}

.dlg__head h2 {
	margin: 0;
	flex: 1 1 auto;
	font-size: var(--fs-lg);
}

.dlg__body {
	padding: var(--sp-4);
	display: grid;
	gap: var(--sp-4);
	max-height: min(66dvh, 560px);
	overflow-y: auto;
}

.dlg__body h3 {
	margin: 0 0 var(--sp-1);
	font-size: var(--fs-xs);
	letter-spacing: var(--ls-wide);
	text-transform: uppercase;
	color: var(--text-3);
}

.dlg__body p {
	margin: 0;
	font-size: var(--fs-sm);
	color: var(--text-2);
}

/* Die Beschriftung ist der Schalter: das <label> umschliesst das Kaestchen.
   Mit 11-Pixel-Text und 8 Pixeln Polsterung war die Trefferflaeche nur rund
   31 Pixel hoch. */
.dlg__field {
	display: grid;
	grid-template-columns: 1fr auto;
	align-items: center;
	gap: var(--sp-3);
	min-height: 44px;
	padding: var(--sp-2) 0;
	border-bottom: 1px solid var(--line-soft);
}

.dlg__field:last-child {
	border-bottom: 0;
}

.dlg__field span {
	display: block;
	font-size: var(--fs-xs);
	color: var(--text-3);
}

.dlg__field input[type='checkbox'] {
	width: 22px;
	height: 22px;
	accent-color: var(--accent);
}

.dlg__field input[type='range'] {
	width: 150px;
	accent-color: var(--accent);
}

.dlg__row {
	display: flex;
	align-items: center;
	gap: var(--sp-3);
	flex-wrap: wrap;
}

.mono {
	flex: 1 1 220px;
	min-width: 0;
	padding: var(--sp-2);
	border-radius: var(--r-2);
	border: 1px solid var(--line);
	background: var(--bg-sunk);
	font-family: ui-monospace, 'Cascadia Mono', Consolas, monospace;
	font-size: var(--fs-xs);
	overflow-wrap: anywhere;
}

.dlg__body input[type='text'] {
	flex: 1 1 200px;
	min-width: 0;
	min-height: 44px;
	padding: 0 var(--sp-3);
	border-radius: var(--r-2);
	border: 1px solid var(--line);
	background: var(--bg-sunk);
	color: var(--text);
	font-family: ui-monospace, 'Cascadia Mono', Consolas, monospace;
	font-size: var(--fs-sm);
}

.dlg__result {
	min-height: 1.4em;
	font-size: var(--fs-sm);
}

.dlg__result[data-tone='ok'] {
	color: var(--win);
}

.dlg__result[data-tone='bad'] {
	color: var(--lose-text);
}

/* Der Weg zur vollstaendigen Pruefseite. Leise gehalten: wer hier steht, hat
   seine Antwort meist schon im Dialog bekommen. */
.dlg__more {
	margin-top: var(--sp-3);
	font-size: var(--fs-sm);
}

.dlg__table {
	width: 100%;
	border-collapse: collapse;
	font-size: var(--fs-sm);
}

.dlg__table th,
.dlg__table td {
	text-align: left;
	padding: var(--sp-2);
	border-bottom: 1px solid var(--line-soft);
}

.dlg__table th {
	font-size: var(--fs-xs);
	letter-spacing: var(--ls-wide);
	text-transform: uppercase;
	color: var(--text-3);
}

.dlg__table td:last-child {
	text-align: right;
	font-variant-numeric: tabular-nums;
}

.dlg__list {
	margin: 0;
	padding-left: var(--sp-5);
	display: grid;
	gap: var(--sp-1);
	font-size: var(--fs-sm);
	color: var(--text-2);
}

kbd {
	display: inline-grid;
	place-items: center;
	min-width: 22px;
	padding: 1px 6px;
	border-radius: var(--r-1);
	border: 1px solid rgb(255 255 255 / 0.18);
	background: rgb(0 0 0 / 0.35);
	font-family: inherit;
	font-size: var(--fs-xs);
	font-weight: 700;
}

/* ==========================================================================
   15 — LADEZUSTAND
   ========================================================================== */

.loading {
	position: fixed;
	inset: 0;
	z-index: var(--z-loading);
	display: grid;
	place-items: center;
	padding: var(--sp-5);
	background:
		radial-gradient(70% 50% at 50% 40%, rgb(255 122 0 / 0.1), transparent 70%),
		var(--bg);
}

/* Einmal verbunden, bleibt der Vorhang unten. Spaetere Abbrueche meldet das
   Band unter der Kopfzeile — ein erneuter Vorhang waere nur im Weg. */
html[data-ready] .loading,
html[data-connection='online'] .loading {
	display: none;
}

.loading__box {
	display: grid;
	justify-items: center;
	gap: var(--sp-3);
	max-width: 34ch;
	text-align: center;
}

.loading__mark {
	width: 46px;
	height: 46px;
	border-radius: var(--r-3);
	background:
		radial-gradient(circle at 30% 25%, var(--accent-lit), transparent 60%),
		linear-gradient(145deg, var(--accent), var(--accent-deep));
	box-shadow: var(--glow-ember);
	animation: mark-breathe var(--dur-7) var(--ease-both) infinite alternate;
}

@keyframes mark-breathe {
	from {
		transform: scale(0.94);
		opacity: 0.75;
	}
	to {
		transform: scale(1.04);
		opacity: 1;
	}
}

.loading__title {
	margin: 0;
	font-size: var(--fs-lg);
	font-weight: 800;
	letter-spacing: var(--ls-wide);
	text-transform: uppercase;
}

.loading__text {
	margin: 0;
	font-size: var(--fs-sm);
	color: var(--text-2);
}

.loading__actions {
	display: flex;
	gap: var(--sp-3);
	flex-wrap: wrap;
	justify-content: center;
}

/* ==========================================================================
   16 — GROESSERE GERAETE
   Das Telefon ist der Ausgangspunkt. Von hier an wird nur ergaenzt.
   ========================================================================== */

/* ---- Ab 560: mehr Text in der Kopfzeile, mehr Luft im Deck --------------- */
@media (min-width: 560px) {
	:root {
		--chip-d: 58px;
		--zone-h: 128px;
		--top-h: 70px;
	}

	/* Ab hier tragen vier Spalten die vier Zahlen in EINER Zeile — das Band
	   kostet den Tisch dann nur noch eine Zeile Hoehe. */
	.tour__facts {
		grid-template-columns: repeat(4, minmax(0, 1fr));
	}

	.topbar {
		gap: var(--sp-3);
		padding-block: var(--sp-3);
	}

	.tableid {
		font-size: var(--fs-md);
	}

	.pill--mode {
		padding-inline: var(--sp-3);
	}

	/* Ab hier ist die Zeile weit genug: das Wort steht wieder sichtbar da. */
	.balance__label {
		position: static;
		width: auto;
		height: auto;
		margin: 0;
		overflow: visible;
		clip-path: none;
	}

	.balance__val {
		font-size: var(--fs-lg);
	}

	.deck {
		gap: var(--sp-3);
		padding-inline: var(--sp-4);
	}

	.deck__hint {
		font-size: var(--fs-md);
	}

	.prompt__actions,
	.spectate__actions {
		grid-template-columns: repeat(2, minmax(0, 200px));
		justify-content: center;
	}
}

/* ---- Ab 900: die fuenf Spielzuege stehen in einer Reihe ------------------
   Die Hoehe der Zone bleibt, wie sie ist — sie wird von der Einsatzleiste
   bestimmt, nicht von den Spielzuegen. Die Knoepfe bekommen ein festes Mass
   und stehen mittig darin, statt sich auf gut hundert Pixel zu strecken. */
@media (min-width: 900px) {
	#actionbar {
		grid-template-columns: repeat(5, minmax(0, 1fr));
		grid-template-rows: 68px;
		grid-template-areas: 'hit stand double split surrender';
		align-content: center;
	}

	.btn--double,
	.btn--split,
	.btn--surrender {
		font-size: var(--fs-md);
	}

	.deck,
	.topbar {
		padding-left: max(var(--sp-5), env(safe-area-inset-left));
		padding-right: max(var(--sp-5), env(safe-area-inset-right));
	}

	/* Die Leiste zieht mit — siehe die Begruendung an der Zuweisung oben. */
	.app > #ifb-kopf {
		--kpf-pad-x: var(--sp-5);
	}
}

/* ---- Ab 1024: die Schublade wird zur festen Spalte ----------------------- */
@media (min-width: 1024px) {
	.stage {
		grid-template-columns: minmax(0, 1fr) clamp(260px, 22vw, 340px);
	}

	.side {
		position: static;
		z-index: auto;
		width: auto;
		transform: none;
		visibility: visible;
		box-shadow: none;
		border-left: 1px solid var(--line-soft);
		background: linear-gradient(180deg, rgb(20 20 25 / 0.72), rgb(14 14 18 / 0.92));
		transition: none;
	}

	.side__close,
	.side-toggle,
	.scrim {
		display: none;
	}

	/* Das Deck bekommt eine Mitte, statt sich ueber zwei Meter zu strecken. */
	.deck__fuse,
	.deck__rail,
	.deck__zone {
		width: min(100%, 860px);
		margin-inline: auto;
	}
}

/* ---- Querformat auf dem Telefon ----------------------------------------
   Hoehe ist knapp: die Zuendschnur wandert in die Lagezeile, die Wechselzone
   schrumpft auf eine Reihe. Der Tisch behaelt so den groesseren Teil. */
@media (orientation: landscape) and (max-height: 560px) {
	:root {
		--chip-d: 42px;
		--zone-h: 62px;
		--tap: 42px;
		/* 4 + 44 + 4 = 52 (der Rahmen entfaellt, siehe die Zusammenlegung
		   weiter unten); der Wert muss darueber liegen, sonst setzt sich die
		   erste Meldung auf die Kopfzeile. */
		--top-h: 54px;
	}

	.topbar {
		padding-block: var(--sp-1);
	}

	/* Hier stand eine Regel, die den .status-Block quer in eine Zeile legte,
	   damit Guthaben, Stufe und Rang nebeneinander statt uebereinander standen.
	   Sie ist mit dem Block entfallen: uebrig ist das Guthaben allein, und das
	   war schon immer eine Zeile. */

	.deck {
		gap: var(--sp-1);
		padding-block: var(--sp-1);
		padding-bottom: max(var(--sp-1), env(safe-area-inset-bottom));
	}

	.deck__fuse {
		height: 10px;
	}

	.deck__rail {
		min-height: 18px;
	}

	#actionbar {
		grid-template-columns: repeat(5, minmax(0, 1fr));
		grid-template-rows: minmax(0, 1fr);
		grid-template-areas: 'hit stand double split surrender';
		gap: var(--sp-1);
	}

	/* Alles in eine Reihe. Kein Bedienelement faellt weg — nur der Hinweistext
	   weicht, denn dieselbe Aussage steht bereits in der Lagezeile darueber. */
	#betbar {
		grid-template-columns: minmax(0, 1fr) auto auto auto;
		grid-template-rows: minmax(0, 1fr);
		gap: var(--sp-2);
		align-items: center;
	}

	#chips,
	#bet-fixed {
		grid-column: 1;
		grid-row: 1;
	}

	.betbar__amount {
		grid-column: 2;
		grid-row: 1;
	}

	#bet-clear {
		grid-column: 3;
		grid-row: 1;
	}

	#bet-repeat {
		grid-column: 4;
		grid-row: 1;
	}

	#bet-hint {
		display: none;
	}

	#spectate {
		grid-auto-flow: column;
		align-items: center;
		gap: var(--sp-3);
		padding: var(--sp-2) var(--sp-3);
		text-align: left;
	}

	.spectate__actions {
		grid-auto-flow: column;
	}
}

/* ---- Querformat: die zwei Kopfzeilen teilen sich EINE Zeile --------------
 *
 * DER SCHADEN, DEN DAS ABSTELLT — gemessen auf 915x412 (Telefon gedreht):
 * die gemeinsame Leiste hat als eigene Rasterzeile 44 px gekostet, die
 * Tischzeile 53. Zusammen 97 von 412 px, also 23,5 % der Schirmhoehe fuer
 * Kopfzeilen. Der Buehne blieben 208 statt 252 px — sie hat durch den Einzug
 * der Leiste 17,5 % ihrer Hoehe verloren. Der Block darueber spart mit
 * kleineren Chips, kuerzerer Zuendschnur und flacherer Wechselzone rund 25 px
 * ein; eine zweite Kopfzeile nimmt 44. Das ist der Punkt, an dem Sparen an
 * der Bedienung sinnlos wird, solange oben zwei Baender uebereinander stehen.
 *
 * WARUM NEBENEINANDER UND NICHT KLEINER: quer ist Hoehe knapp und Breite im
 * Ueberfluss. Kleiner geht nicht — der Kontoknopf der Leiste misst 44 px und
 * der Zurueck-Pfeil der Tischzeile ebenso; unter 44 px darf in dieser
 * Anwendung nichts liegen, was ein Daumen trifft. Also wandert die Leiste
 * neben die Tischzeile statt ueber sie. Die Zeile ist danach 52 px hoch (4 +
 * 44 + 4, das Mass der Tischzeile), die Leiste kostet 0. Aus 97 px Kopf werden
 * 52 — 45 px zurueck an die Buehne, 44 fuer die Leiste und eines fuer die
 * Trennlinie, die jetzt IN der Zeile liegt statt unter ihr. Auf 915x412 heisst
 * das: 253 px Buehne statt 208, und wieder mehr als die 252 von vorher.
 *
 * DIE BREITE, GERECHNET UND NICHT GESCHAETZT (deshalb der Deckel min-width).
 * Die gedraengte Leiste zeigt KEIN Markenwort — es ist per Regel aus, uebrig
 * sind Bild und Kontoknopf; tools/kopf-dom.js druckt genau diese Rechnung als
 * "gedraengtes Breitenbudget" aus und prueft sie gegen web/kopf.js nach:
 *   Leiste  24 Polster + 107,3 Logo + 12 + 44 Konto                 = 187,3
 *   Tisch   24 Polster + 44 Pfeil + 8 + ~93 Guthaben + 8 + 44 Knopf = 221
 *   zusammen rund 408 px. Beim engsten Telefon quer (480 px) bleiben 72 px
 *   uebrig, bei 568 sind es 160.
 * Ab 560 px werden Wort und Zahl des Guthabens groesser (Abschnitt weiter
 * oben), die Summe steigt auf rund 509 — bei 600 px bleiben 91 px Luft.
 * Unter 480 px bleibt es bei zwei Zeilen: lieber 44 px weniger Buehne als
 * eine Zeile, in der sich Marke und Guthaben gegenseitig abschneiden.
 *
 * Der Tischname steht in dieser Rechnung mit 0: er ist auf dem Telefon
 * Beiwerk und weicht hier genauso wie im Hochformat (dort seit jeher, mit
 * derselben Begruendung — die Kennung des Tisches steht in der Lobby). Ab
 * 900 px, wo der Platz da ist, kommt er zurueck; die Grenze ist dieselbe, an
 * der die Tischzeile ohnehin ihr breiteres Polster bekommt.
 *
 * Nur mit haengender Leiste (body.has-ifb-kopf). Laedt kopf.js nicht, gibt es
 * #ifb-kopf nicht, und die Tischzeile steht wie eh und je allein in Zeile 1.
 */
@media (orientation: landscape) and (max-height: 560px) and (min-width: 480px) {
	body.has-ifb-kopf .app {
		/* FUENF Zeilen statt sechs — die Kopfzeile ist jetzt eine, nicht zwei.
		   Die sechste einfach leer stehen zu lassen waere bequem (eine
		   auto-Spur ohne Inhalt faellt auf 0 zusammen und kostet nichts), aber
		   es ist genau die Nachlaessigkeit, aus der hier schon einmal ein
		   flaches Spielfeld geworden ist: ab dann stimmt die Zahl der Spuren
		   nicht mehr mit den Nummern darunter ueberein, und der Naechste zieht
		   beim Einfuegen die falschen nach. tools/kopf-dom.js rechnet beide
		   Zustaende dieser Datei durch und laesst eine leere Spur nicht durch.

		   Spalte 1 nimmt genau die Breite der Leiste; alles Weitere gehoert der
		   Tischzeile. Alle uebrigen Kinder ueberspannen weiter beide Spalten —
		   das steht als .app > * { grid-column: 1 / -1 } weiter oben und ist der
		   Grund, warum es dort ueberhaupt steht. */
		grid-template-rows: auto auto auto minmax(0, 1fr) auto;
		grid-template-columns: auto minmax(0, 1fr);
	}

	/* Und die Nummern dazu, alle fuenf, auch die unveraenderten:
	     1  #ifb-kopf + .topbar   nebeneinander, eine Zeile
	     2  .conn                 Stoerungsband
	     3  .tour                 Turnierband
	     4  .stage                die einzige elastische Spur
	     5  .deck                 die Bedienung
	   Wer hier eine Nummer auslaesst, weil sie "ohnehin stimmt", verlaesst sich
	   auf die Zuweisung aus dem Grundzustand — und die zeigt auf ein Raster mit
	   sechs Spuren. */
	body.has-ifb-kopf .app > #ifb-kopf {
		grid-row: 1;
		grid-column: 1;
	}

	/* EINE ZEILE, EIN GRUND, EINE TRENNLINIE.
	   #16161d ist die Farbe, die die gedraengte Leiste sich selbst gibt
	   (web/kopf.js, [data-kompakt='1']). Behielte die Tischzeile ihren Verlauf,
	   liefe mitten durch die Zeile eine senkrechte Kante zwischen zwei
	   Grautoenen — das saehe aus wie ein Fehler, nicht wie zwei Baender.
	   Gerechnet wird die Lesbarkeit im Projekt ohnehin gegen #16161d
	   (tools/kopf-dom.js), die Kontrasttabelle gilt hier also unveraendert. */
	body.has-ifb-kopf .app > .topbar {
		/* Zieht aus Zeile 2 hoch. Ohne diese Zeile bliebe die Tischzeile unten
		   und Spalte 2 der ersten Zeile stuende leer — 52 px Loch neben der
		   Marke, und gewonnen waere nichts. */
		grid-row: 1;
		grid-column: 2;
		/* Der Geraeteabstand gehoert wieder hierher — siehe --sicher-oben
		   unten. Die Querformatregel weiter oben setzt padding-block pauschal
		   auf 4 px und haette ihn stillschweigend wieder abgeraeumt; sie war
		   richtig, solange die Leiste ueber dieser Zeile stand und ihn allein
		   bezahlte. */
		padding-top: max(var(--sp-1), var(--sicher-oben));
		background: #16161d;
		backdrop-filter: none;
		/* Die Linie zieht das ::after weiter unten ueber die GANZE Breite.
		   Bliebe sie hier, endete sie 280 px vom linken Rand entfernt — dort,
		   wo die Markenspalte aufhoert. Ein Strich, der auf halber Strecke
		   anfaengt, ist schlimmer als keiner. */
		border-bottom: 0;
	}

	body.has-ifb-kopf .app > .conn {
		grid-row: 2;
	}

	body.has-ifb-kopf .app > .tour {
		grid-row: 3;
	}

	body.has-ifb-kopf .app > .stage {
		grid-row: 4;
	}

	body.has-ifb-kopf .app > .deck {
		grid-row: 5;
	}

	body.has-ifb-kopf {
		/* Die Tischzeile ist jetzt SELBST ein oberstes Element und traegt den
		   Geraeteabstand wieder mit. Ausgeschaltet war er, solange die Leiste
		   ueber ihr stand und ihn allein bezahlte (Abschnitt 4). Ohne diese
		   Rueckschaltung liefe der Zurueck-Pfeil auf einem Telefon mit
		   seitlicher Hauskante 44 px hoeher als die Marke daneben. */
		--sicher-oben: env(safe-area-inset-top, 0px);

		/* Und die Leiste kostet keine eigene Zeile mehr — siehe .toasts. */
		--kopf-eigen-h: 0px;
	}

	/*
	 * DIE TRENNLINIE ALS EIGENES RASTERKIND, und zwar aus einem Grund, der beim
	 * naechsten Umbau sonst niemandem einfaellt: die Linie muss ueber beide
	 * Spalten laufen, aber die eine davon gehoert web/kopf.js. In fremdes
	 * Markup eine Regel zu schreiben ist in diesem Projekt der Riss, an dem
	 * schon dreimal etwas weggebrochen ist — .app gibt der Leiste eine
	 * Variable (--kpf-pad-x) und sonst nichts. Also zieht .app die Linie
	 * selbst, ueber seine eigene ::after-Flaeche.
	 *
	 * Sie ist FEST platziert (Zeile 1, beide Spalten) und damit kein Fall fuer
	 * die automatische Platzierung — dieselbe Falle wie bei den Zeilen.
	 * align-self:end laesst sie unten in der Zeile sitzen, 1 px hoch traegt sie
	 * nichts zur Zeilenhoehe bei. Der z-index liegt eins ueber der Tischzeile
	 * (--z-top), sonst deckte deren eigener Grund sie zu; unter dem Schirm der
	 * Schublade (--z-scrim: 93) bleibt sie damit weiterhin.
	 */
	body.has-ifb-kopf .app::after {
		content: '';
		grid-row: 1;
		grid-column: 1 / -1;
		align-self: end;
		z-index: calc(var(--z-top) + 1);
		height: 1px;
		background: var(--line-soft);
	}
}

/* Der Tischname weicht, solange die Zeile schmal ist — die Rechnung oben steht
   und faellt damit. Ohne diese Regel bliebe bei 600 px kein Platz fuer die
   Modus-Plakette: sie ist flex:none, schrumpft also nicht, und .tableid
   schneidet ab statt umzubrechen — eine halb abgeschnittene Plakette sieht aus
   wie ein Defekt. Dieselbe Entscheidung wie im Hochformat, dieselbe Grenze wie
   die, ab der die Tischzeile ihr breiteres Polster bekommt (900 px). */
@media (orientation: landscape) and (max-height: 560px) and (min-width: 480px) and (max-width: 899px) {
	body.has-ifb-kopf .topbar .tableid {
		display: none;
	}
}

/* ==========================================================================
   17 — RUHIGE BEWEGUNG
   Die Einstellung im Dialog setzt data-motion auf dem Wurzelelement. Sie muss
   auch dann greifen, wenn das System nichts vorgibt — deshalb beide Wege.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}
}

html[data-motion='reduced'] *,
html[data-motion='reduced'] *::before,
html[data-motion='reduced'] *::after {
	animation-duration: 0.01ms !important;
	animation-iteration-count: 1 !important;
	transition-duration: 0.01ms !important;
}

/* Die Zuendschnur bleibt auch dann ablesbar: sie springt in Stufen statt zu
   gleiten, und die Farbe traegt die Dringlichkeit allein. */
html[data-motion='reduced'] .fuse__fill {
	transition: none !important;
}


/* ==========================================================================
   HOCHFORMAT AUF DEM TELEFON — dem Tisch Hoehe verschaffen
   ==========================================================================
   .app ist ein Raster aus Kopfzeile (auto), Tisch (1fr) und Aktionszone (auto)
   bei fester Hoehe 100dvh. Der Tisch kann also nur wachsen, wenn Kopfzeile und
   Aktionszone schrumpfen — eine groessere Mindesthoehe am Tisch selbst wuerde
   nur ueberlaufen und abgeschnitten. Genau das passiert hier: das Beiwerk wird
   kompakt, der Gewinn landet vollstaendig beim Spielfeld.
   Alles Bedienbare bleibt dabei ueber 44 px hoch. */
@media (orientation: portrait) and (max-width: 820px) {
	.topbar {
		padding-top: max(4px, var(--sicher-oben));
		padding-bottom: 4px;
		gap: var(--sp-1, 4px);
	}

	/* Auch das Turnierband zahlt hier ein. Es bleibt vollstaendig lesbar —
	   gekuerzt wird die Polsterung, nicht die Information. Der Hinweistext
	   entfaellt NICHT: er nennt den Grund, warum eine Bahn steht. */
	.tour {
		gap: var(--sp-1);
		padding: var(--sp-1) var(--sp-3);
	}

	/* Der Tischname ist auf dem Telefon Beiwerk — die Kennung steht in der Lobby. */
	.topbar .tableid {
		display: none;
	}

	.deck {
		gap: var(--sp-1, 4px);
		padding-top: var(--sp-1, 4px);
		padding-bottom: max(var(--sp-2, 8px), env(safe-area-inset-bottom));
	}

	/* Der Tisch nimmt, was frei wird, und darf die Zeile voll ausfuellen. */
	.stage,
	.table-wrap {
		min-height: 0;
		height: 100%;
	}
}

/* Sehr flache Geraete: noch einmal enger, sonst bleibt vom Filz nichts. */
@media (orientation: portrait) and (max-height: 700px) {
	.topbar {
		padding-top: max(2px, var(--sicher-oben));
		padding-bottom: 2px;
	}

	.deck {
		padding-top: 2px;
	}
}


/* ==========================================================================
   EINSATZGRENZE IN DER LEISTE
   Eine Schranke ohne sichtbares Ziel wirkt wie Willkuer. Deshalb steht neben
   dem Hoechsteinsatz immer, welche Stufe die naechste Grenze freischaltet.
   ========================================================================== */
.betrail__cap {
	align-self: center;
	padding: 2px 10px;
	border: 1px solid var(--line);
	border-radius: 999px;
	font-size: 0.72rem;
	letter-spacing: 0.02em;
	color: var(--text-2);
	white-space: nowrap;
}

/* Chips oberhalb der eigenen Grenze: sichtbar, aber erkennbar gesperrt.
   Entfernen waere schlechter — dann waere unklar, dass es sie ueberhaupt gibt. */
#chips .chip.is-disabled {
	opacity: 0.34;
	filter: saturate(0.5);
	cursor: not-allowed;
}

/* ==========================================================================
   TISCHZEILE AUF DEM TELEFON
   ==========================================================================
   Waehrend des Spiels zaehlt genau eine Zahl: das Guthaben. Stufe und Wertung
   waren Fortschrittsanzeigen und rangen hier mit ihm um Platz — sie stehen
   jetzt am Avatar in der gemeinsamen Leiste, und diese Zeile hat nur noch
   Zurueck, Tischname, Guthaben und Menue zu tragen.
   Zurueck-Knopf und Menue bleiben bei vollen 44 px Trefferflaeche. */
@media (max-width: 560px) {
	.topbar {
		align-items: center;
		gap: var(--sp-2, 8px);
	}

	.balance__val {
		font-size: clamp(1.05rem, 5.2vw, 1.4rem);
		line-height: 1.1;
		white-space: nowrap;
	}

	.balance__label {
		font-size: 0.66rem;
		letter-spacing: 0.08em;
	}

	.topbar__back,
	.side-toggle {
		min-width: 44px;
		min-height: 44px;
		display: inline-flex;
		align-items: center;
		justify-content: center;
	}
}

/* Hier stand eine letzte Regel fuer sehr schmale Geraete: unter 380 px wich
   auch noch die Stufenspur, damit das Guthaben allen Platz bekam. Sie ist
   gegenstandslos — die Stufe steht nicht mehr in dieser Zeile, das Guthaben hat
   sie ohnehin fuer sich. */
