/* Game Review – zweite Ansicht, gleiche Sprache.
 *
 * Diese Datei definiert bewusst nur, was es auf der Galaxie-Seite nicht gibt
 * (Brett, Bewertungsbalken, Zugpaare). Alles Gemeinsame – Panels, Karten,
 * .platToggle, Eingabefelder – kommt aus style.css und wird hier NICHT
 * dupliziert: Zwei Quellen für denselben Knopf laufen unweigerlich
 * auseinander. Maße und Farben sind die der Sidebar bzw. der Setup-Karte.
 *
 * Einzige Abweichung in der Bedeutung: Gold heißt hier nicht "dein Zug",
 * sondern "Ungenauigkeit". Ohne Galaxie gibt es keine Besitzfarbe, und
 * Gelb→Rot ist die Skala, die Schachspieler von überall kennen.
 *
 * Seit dem 17. August 2026 lädt auch board.html diese Datei. Alles, was dort
 * gleich aussieht, ist deshalb wirklich dasselbe: .panel, .ghostBtn,
 * .solidBtn, .boardWrap, #evalBar, .boardBar/.navBtn und die Engine-Zeilen.
 * Wer hier etwas ändert, ändert zwei Ansichten – und das ist der Zweck. Was
 * nur das Analysebrett hat, steht in css/board.css.
 */

/* Die Galaxie-Seite sperrt das Scrollen (sie ist eine Leinwand). Hier wird
   gescrollt – die Klasse sitzt deshalb auf html *und* body.
   `visible` und nicht `auto`: `auto` macht aus html und body je einen eigenen
   Rollbereich, und ein Rollbereich, der selbst nie rollt, nimmt jedem
   `position: sticky` darin seinen Spielraum. Gemessen war das Brett auf dem
   Telefon trotz `position: sticky` bei Scrollstand 900 vollständig aus dem
   Bild (Oberkante −804). `visible` gibt das Rollen wieder dem Dokument, und
   das ist ohnehin, was hier gemeint war: nur das `hidden` aus style.css
   zurücknehmen. */
.reviewPage,
.reviewPage body { height: auto; overflow: visible; }

/* Der Himmel steht in shell.css – er gilt für alle Seiten ohne Leinwand. */

/* Ghost-Elemente exakt wie #demoBtn / #feedbackLink der Topbar. */
.ghostBtn {
  display: inline-flex;
  align-items: center;
  background: transparent;
  border: 1px solid var(--panel-border);
  color: var(--text-dim);
  gap: var(--sp-3);
  min-height: var(--ctl-h);
  padding: 10px 16px;
  border-radius: var(--r-md);
  font-size: var(--fs-base);
  font-family: inherit;
  font-weight: 400;
  text-decoration: none;
  cursor: pointer;
}
.ghostBtn:hover { color: var(--text); border-color: var(--accent); }
.ghostBtn:disabled { opacity: 0.45; cursor: default; }

/* Der einzige gefüllte Knopf der Ansicht – Maße von #suGo. */
.solidBtn {
  min-height: var(--ctl-h-lg);
  padding: 14px 20px;
  border: none;
  border-radius: 12px;
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-size: var(--fs-lead);
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
}
.solidBtn:hover { background: var(--btn-bg-hover); }
.solidBtn:disabled { opacity: 0.5; cursor: wait; }

/* Ohne Topbar trägt der Inhalt selbst den oberen Abstand. */
/* Ohne Topbar trägt der Inhalt selbst den oberen Abstand. Die Analyse darf
   die Fläche nehmen, die sie hat – ein Brett von 400 px auf einem 1600er
   Bildschirm ist verschenkter Platz. */
/* Genau einen Bildschirm hoch, und das Raster darin nimmt, was die Kopfzeile
   uebrig laesst. Vorher rechnete das Raster mit einer festen Zahl und uebersah
   die Kopfzeile: Gemessen war #reviewMain 1132 px hoch auf einem 1080er
   Schirm, und die 52 px Differenz hat der Zuschnitt der Seite verschluckt.
   Was dort lag, war unerreichbar – der untere Rand des Bewertungsverlaufs. */
/* Der Deckel waechst mit, sobald die Engine-Spalte da ist – sonst holt sie
   sich ihren Platz doch wieder vom Brett. Gemessen: ohne diese Zeile blieb das
   Raster bei 1512 px und das Brett fiel von 690 auf 658. */
#reviewMain:has(#linesCard:not(.hidden)) { max-width: 1840px; }
#reviewMain {
  max-width: 1560px;
  margin: 0 auto;
  padding: 26px 24px 60px;
  height: 100vh;
  display: flex;
  flex-direction: column;
}
/* Der Einstieg bleibt schmal: Ein Namensfeld über die halbe Wand ist keine
   Hilfe, sondern Größe ohne Grund.

   Und er ist so hoch, wie er ist. Die 100vh oben gelten der Analyse: Dort
   muss das Brett auf einen Bildschirm passen, und alles daneben rechnet mit
   dieser Höhe. Auf dem Einstieg war sie ein Deckel — die Fragen unter der
   Einstiegskarte wurden am unteren Rand des Bildschirms abgeschnitten, und
   zwar genau der Text, für den diese Hälfte der Seite da ist. */
.reviewPage:has(#pickPanel:not(.hidden)) #reviewMain,
.reviewPage:has(#listPanel:not(.hidden)) #reviewMain { max-width: 820px; height: auto; }
/* Beim Einstieg trägt die Karte den Abstand allein – sonst käme der des
   Hauptbereichs obendrauf und die Karte säße 26 px tiefer als die
   baugleiche auf der Galaxie-Seite. */
.reviewPage:has(#pickPanel:not(.hidden)) #reviewMain { padding-top: 0; }

/* Panels: die Sidebar der Galaxie, eins zu eins. */
.panel {
  background: var(--indigo-950);
  border: 1px solid var(--panel-border);
  border-radius: var(--r-xs);
  padding: var(--sp-7);
}
.panel + .panel { margin-top: 14px; }
.panelHead { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
.panel h2 { font-size: var(--fs-title); margin-bottom: 10px; }
.panelHead h2,
.panelHead h3 { margin: 0; }
.panel h3 {
  font-size: var(--fs-small);
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 1px;
  margin: 12px 0 6px;
}
.panel h3:first-child { margin-top: 0; }
.dim { color: var(--text-dim); font-size: var(--fs-base); }
.tiny { font-size: 11.5px; line-height: 1.5; margin-top: 8px; }

/* Auswahlfelder eines Aufbau-Formulars. Standen bis zum 22. August 2026 in
   board.css und sind hierher gewandert, als der Trainingsplan dieselben
   Felder brauchte — dies ist die Datei, die jede Werkzeugseite einbindet.
   Die Spaltenbreite kommt vom Brett: In einer 380 px breiten Seitenspalte
   stünden vier Auswahlfelder nebeneinander jeweils 85 px breit, und
   "All time controls" passt dort nicht hinein. */
.setupGrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--sp-3);
}
.setupSel {
  min-width: 0;
  min-height: var(--ctl-h);
  padding: 0 10px;
  background: var(--indigo-900);
  border: 1px solid var(--panel-border);
  border-radius: var(--r-md);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-base);
}
.setupSel:focus { border-color: var(--accent); }

/* ---------- Einstieg ----------
   Die Karte ist .entryCard aus style.css – dieselbe Klasse wie auf der
   Startseite. Hier steht nur noch, wo sie sitzt. Breite, Farbe, Radius,
   Schatten, Logo und Wortmarke werden bewusst NICHT wiederholt: Genau an
   der zweiten Abschrift liefen die beiden Einstiege auseinander (die
   Karte hier trug den helleren Panel-Ton und einen Weichzeichner). */
#pickPanel { margin: 8vh auto 0; }

/* Kein eigener Satz mehr: Der Winkelhaken bringt Groesse und Familie mit
   (style.css). Die Zentrierung war eine Abschrift aus der Zeit, als das Feld
   allein in einer mittigen Karte stand – im Kanal springt der Text damit
   gegenueber Hub und Galaxie. */
#revUser.err { border-color: var(--loss); }

.status { font-size: var(--fs-base); color: var(--text-dim); margin-top: 12px; }
.status.error { color: var(--loss); }

/* ---------- Partieliste: Zeilen wie .insEntry ---------- */
/* Die Zeile sagt jetzt, dass sie irgendwohin führt.
 *
 * Sie sagte es vorher nirgends: `border: 1px solid transparent` über
 * rgb(11, 9, 34) — gemessener Kontrast der Kante zur Fläche 1,000:1, also gar
 * keine Kante. Kein Zeichen, kein Pfeil, kein Wort; die einzige Zustandsregel
 * war :hover, und die gibt es auf einem Telefon nicht. In Plausible steht
 * dieselbe Stelle: 29 Besucher laden die Liste, 12 starten eine Analyse.
 *
 * Die Kante kam danach aus --panel-border. Das war die halbe Strecke: gemessen
 * 1,863:1 gegen die Zeilenfläche und 2,116:1 gegen das Fach dahinter — eine
 * Kante, die behauptet, ein Bedienelement abzugrenzen, und dafür nicht reicht.
 * Das Mindestmaß für einen Umriss, der ein Bedienelement kenntlich macht, ist
 * 3:1. Die Zeilenfläche selbst trägt noch weniger: 1,238:1 gegen das Fach.
 * Damit hing die ganze Auskunft "das hier ist ein Knopf" an einem 16 px
 * breiten Winkelhaken.
 *
 * Sie trägt jetzt: --accent gibt gemessen 3,127:1 gegen die Zeilenfläche und
 * 3,873:1 gegen das Fach. Der Ton ist nicht neu erfunden — er war schon die
 * Kante im Hover, er heißt auf dieser Seite "anfassbar" und bedeutet hier
 * dasselbe. Dass die Zeile damit eine deutlichere Kante hat als der
 * Ghost-Knopf daneben, ist kein Versehen: Der Knopf trägt sein Verb im Text,
 * die Zeile trägt keins.
 *
 * Der Charta-Vorbehalt "ruhig gewinnt" endet dort, wo es um die Daten geht,
 * und die zwölf Zeilen sind die Daten dieser Fläche. In Plausible steht die
 * Rechnung dazu: 29 Besucher laden die Liste, 12 starten eine Analyse.
 *
 * Weil der Ruhezustand jetzt --accent ist, geht der Druckzustand eine Stufe
 * höher — --indigo-300, gemessen 4,572:1. Er muss vom Ruhezustand
 * unterscheidbar bleiben, sonst wäre der Tipp wieder unbeantwortet. */
.gameRow {
  display: grid;
  grid-template-columns: 8px minmax(0, 1fr) auto auto 16px;
  gap: 12px;
  align-items: center;
  padding: 8px 10px;
  border: 1px solid var(--accent);
  border-radius: var(--r-md);
  cursor: pointer;
  background: rgba(140, 130, 205, 0.05);
  margin-bottom: 4px;
  text-align: left;
  width: 100%;
  font-family: inherit;
  color: var(--text);
}
.gameRow:hover { border-color: var(--indigo-300); background: rgba(140, 130, 205, 0.12); }
/* Auf einem Telefon gibt es kein Hover. Der Druckzustand ist dort die einzige
   Rückmeldung, dass der Tipp angekommen ist. */
.gameRow:active { border-color: var(--indigo-300); background: rgba(140, 130, 205, 0.12); }
.gameRow .rowGo {
  width: 16px;
  height: 16px;
  color: var(--text-dim);
}
.gameRow:hover .rowGo,
.gameRow:active .rowGo { color: var(--text); }
/* Der Punkt trägt Farbe *und* Form: gefüllt gewonnen, hohl remis, Balken
   verloren. Farbe allein war für rot-grün-blinde Besucher keine Auskunft. */
.gameRow .dot { width: 9px; height: 9px; border-radius: 50%; }
.gameRow .dot.w { background: var(--win); }
.gameRow .dot.d { background: transparent; border: 2px solid var(--draw); }
.gameRow .dot.l { background: var(--loss); border-radius: 1px; height: 3px; }
.gameRow .opp { font-size: var(--fs-base); font-weight: 600; }
.gameRow .meta { font-size: var(--fs-small); color: var(--text-dim); margin-top: 2px; display: block; }
.gameRow .side { font-size: var(--fs-small); color: var(--text-dim); }

/* Ein Satz über der Liste, weil zwölf Winkelhaken zwar zeigen, dass es
   weitergeht, aber nicht wohin. Das Wort „review" stand auf dieser Seite
   sonst genau einmal — in einer visually-hidden H1. */
#listHint { margin: -2px 0 var(--sp-5); }

/* Auf dem Telefon zwei Zeilen statt vier Spalten.
 *
 * Gemessen am 25. August 2026 bei 375 px: Von 313 px Zeilenbreite nahmen die
 * zwei rechten Spalten 170, für den Namen blieben 76 — er brach auf bis zu
 * fünf Zeilen um, die zwölf Zeilen waren 86 bis 151 px hoch. Bei 320 px blieben
 * dem Namen 22 px und der Eröffnungsname wurde um 21 px gekappt.
 *
 * Untereinander passt beides ungekappt, und alle zwölf Zeilen sind gleich
 * hoch — erst dann liest sich die Liste als Liste und nicht als Textblock. */
@media (max-width: 560px) {
  .gameRow {
    grid-template-columns: 8px minmax(0, 1fr) auto 16px;
    column-gap: 10px;
    row-gap: 4px;
  }
  .gameRow .dot { grid-column: 1; grid-row: 1; }
  .gameRow .rowWho { grid-column: 2 / 4; grid-row: 1; }
  .gameRow .side--result { grid-column: 2; grid-row: 2; }
  .gameRow .side--when { grid-column: 3; grid-row: 2; text-align: right; }
  .gameRow .rowGo { grid-column: 4; grid-row: 1 / 3; }
}

/* ---------- Analyse-Raster ---------- */
/* Brett links so groß wie möglich, rechts eine feste Spalte für Zahlen und
   Züge – die wächst mit der Fensterbreite nicht mit, weil Text ab einer
   gewissen Zeilenlänge schlechter zu lesen ist. */
/* Beide Spalten teilen sich die Fensterhöhe, statt dass eine unter der
   anderen ausläuft. Vorher endete das Brett auf halber Strecke und rechts
   lief die Zugliste 500 px weiter – zwei Spalten, die nichts miteinander
   zu tun hatten. Gescrollt wird jetzt innerhalb der rechten Spalte. */
.reviewGrid {
  display: grid;
  /* Breiter als vorher: Diese Spalte traegt seit dem 12. August 2026 nicht nur
     die Antwort, sondern auch die elf Urteile und die Zugliste. */
  /* NICHT anfassen: Hier stand am 12. August 2026 kurz
     "min(clamp(380px, 32%, 540px), 42%)" – ein Vorschlag aus der Abnahme, der
     die rechte Spalte auf vier Zehntel deckeln sollte. Er ist zweimal
     reproduzierbar mit einem Absturz des Renderers geendet: Die erste Spalte
     ist 1fr, ihr Inhalt haengt ueber --board an "100% - lane", und ein
     zweites Prozentmass in der Nachbarspalte schliesst den Kreis. Der Deckel
     wird ohnehin nicht gebraucht – der Boden von 380px greift auf schmalen
     Fenstern zuerst.

     Das Prozent bezieht sich auf die Inhaltsbox des Rasters, nicht auf das
     Fenster: Leiste und Innenabstand sind da schon abgezogen. */
  grid-template-columns: minmax(0, 1fr) clamp(380px, 32%, 540px);
  gap: 16px;
  align-items: stretch;
  /* Nimmt, was die Kopfzeile uebrig laesst – keine feste Zahl, die veraltet,
     sobald jemand der Kopfzeile eine Zeile hinzufuegt. */
  flex: 1 1 0;
  min-height: 0;
  /* Auf sehr breiten Schirmen wächst nicht das Brett mit, sondern nur der
     leere Rand daneben. Deshalb ein Deckel und Mitte statt voller Breite.

     Die volle Breite daneben ist nicht überflüssig: Als Flex-Kind mit
     "margin: 0 auto" schrumpft ein Element auf seine Inhaltsbreite, statt zu
     füllen. Gemessen war das Raster während der Analyse 359 px breit statt
     1500 – das Brett damit 285 px, und die halbe Ansicht klebte in der Mitte. */
  width: 100%;
  max-width: 1500px;
  margin: 0 auto;
}
/* Die rechte Spalte ist der *eine* Scrollbereich der Ansicht.
   Vorher scrollte zusätzlich die Zugliste in sich selbst – zwei ineinander
   liegende Scroller, von denen der innere gewann: Die Liste bekam 236 Pixel
   für 1400 Pixel Inhalt, während links 450 Pixel leer standen. */
.sideCol {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-height: 0;
  overflow-y: auto;
}
#movesCard { display: flex; flex-direction: column; }

/* ---------- Brettspalte: das Brett ist das Maß ----------
 *
 * Alles in dieser Spalte gehört zum Brett – die Namen darüber, der
 * Bewertungsbalken daneben, die Knöpfe, die Urteilszeile und der Verlauf
 * darunter. Also misst sich alles an der Kantenlänge des Bretts, nicht an der
 * Breite der Spalte.
 *
 * Vorher war es umgekehrt, und das ging so lange gut, wie das Brett die
 * Spaltenbreite füllte. Sobald es an der Höhe hing, hörte das auf: Gemessen
 * auf 1900 x 1000 zeichnete ein 780 px hohes Brett in einen 699 px hohen
 * Rahmen, stand darin mittig und lief oben wie unten 30 px heraus. Der Name
 * des Gegners wurde vom Brett überdeckt ("…esCoffee 1688"), die sechs
 * Navigationsknöpfe lagen in --text-dim auf Reihe 1, der Balken stand auf
 * 90 % der Bretthöhe, und der Verlauf war 192 px breiter als das Brett – auf
 * 1900 x 780 sogar 412 px. Der Verlauf verspricht in seinem eigenen
 * Kommentar, dass ein Punkt senkrecht unter seiner Stellung liegt; genau das
 * stimmte auf jedem breiten, flachen Schirm nicht.
 *
 * --board ist jetzt die eine Zahl, aus der alles folgt.
 */
.boardCol {
  /* 26 statt 24: Die Zahl im Balken war mit 9 px die kleinste Schrift der
     Fläche, gemessen 24 × 12 px auf dem Gerät mit dem kleinsten Bildschirm.
     Sie steht jetzt auf --fs-micro (10 px), dem Maß, das die Fußleiste für
     ihre Beschriftungen benutzt, und dafür braucht der Balken zwei Pixel mehr:
     gemessen 21,3 px für "+0.2" und 24,0 px für "-12.5", den längsten Wert,
     der in einer Partie vorkommt. Bei 24 px Balkenbreite wäre er auf beiden
     Seiten angeschnitten.

     Vorher stand hier "24 statt 20", mit derselben Rechnung bei 9 px (23 px
     für "+0.3"). Die zwei Pixel gehen wie damals vom Brett ab: gemessen fällt
     es auf 375 px Fensterbreite von 291 auf 289 px. */
  --bar-w: 26px;
  --bar-gap: 8px;
  /* Die Gasse links vom Brett, in der der Bewertungsbalken steht. */
  --lane: calc(var(--bar-w) + var(--bar-gap));

  /* Kantenlänge des Bretts: so breit wie die Spalte hergibt, aber nie höher
     als der Platz zwischen Kopf und Fuß. Kein Deckel darüber hinaus – das
     Brett ist der Gegenstand dieser Ansicht und darf den Platz haben.

     Die 390 px sind gemessen, nicht geschätzt: Namen (17) + Brettleiste (48)
     + Urteilszeile (20) + Verlauf (78) + fünf Abstände (50) = 213 innerhalb
     der Spalte, dazu 40 Innenabstand des Fachs, 86 Innenabstand des
     Hauptbereichs und 52 für die Kopfzeile samt Abstand.

     Am 12. August 2026 standen hier eine Zeit lang zwei beschriftete
     Analyse-Schalter mit in der Brettleiste. Sie machten die Leiste 136 statt
     48 px hoch, das Brett rechnete sich rund 100 px zu groß, die Spalte lief
     über – und die mittige Ausrichtung schob den Überlauf zur Hälfte nach
     OBEN aus der Spalte: Die Spielernamen lagen im Zurück-Knopf, der
     Bewertungsverlauf unter dem Fensterrand. Die Schalter stehen jetzt rechts
     unten im Analysefach, wo sie hingehören, und das Brett hat seine Größe
     zurück. */
  /* Der Boden bei 240 px ist kein Geschmack: Ohne ihn wird der Ausdruck
     negativ, sobald das Fenster niedriger als 390 px ist — auf einem quer
     gehaltenen Telefon (375 px hoch) blieb vom Brett nichts uebrig, gemessen
     110 px bei 500 px Fensterhoehe. Die Klammerung ist wichtig: Der Deckel
     steht aussen, damit die verfuegbare Breite immer gewinnt und nie
     waagerecht ueberlaufen wird. 240 statt 220, weil auf diesem Brett Figuren
     gezogen werden — 30 px je Feld ist die Untergrenze dafuer. */
  --board: min(100% - var(--lane), max(240px, calc(100vh - 390px)));

  display: flex;
  flex-direction: column;
  /* Mittig, damit der Platz, den das quadratische Brett übrig lässt, sich
     gleichmäßig verteilt statt als Loch unter dem Brett zu stehen.

     Das Schlüsselwort davor ist kein Beiwerk: Ohne "safe" schiebt ein zu hoher
     Inhalt seinen Überlauf zur Hälfte nach oben aus der Spalte, und was dort
     landet, ist unerreichbar – genau so lagen die Spielernamen im Knopf
     darüber. Mit "safe" kippt die Ausrichtung in diesem Fall auf den Anfang,
     und der Überlauf geht nach unten, wo man ihn erreichen kann. */
  justify-content: safe center;
  gap: 10px;
  min-height: 0;
}

.boardCol > * {
  width: calc(var(--board) + var(--lane));
  align-self: center;
}
.boardWrap {
  display: flex;
  gap: var(--bar-gap);
  /* Der Bewertungsbalken soll auf Bretthöhe wachsen – deshalb stretch. */
  align-items: stretch;
  /* Nicht schrumpfen: Genau das war der Fehler. Ein gestauchter Rahmen ließ
     das Brett herauslaufen, statt es kleiner zu machen. */
  flex: none;
  min-height: 0;
}
/* Das Brett bestimmt die Höhe, nicht umgekehrt.
   Vorher stand hier `height: 100%` bei gedeckelter Breite. In einer Spalte,
   die höher als breit ist, ergab das ein Brett von 681 × 1055 Pixeln: Die
   Reihen waren sichtbar in die Länge gezogen. Jetzt führt die Breite, die
   Höhe folgt aus dem Seitenverhältnis – ausgeschrieben, damit sie auch dann
   folgt, wenn die Leinwand noch keinen Puffer hat. */
#revBoard {
  width: var(--board);
  aspect-ratio: 1;
  height: auto;
  border-radius: 10px;
  display: block;
  cursor: grab;
  touch-action: none; /* Figuren ziehen statt die Seite zu scrollen */
}
#revBoard:active { cursor: grabbing; }

/* Alles unter dem Brett rückt um die Breite des Bewertungsbalkens ein,
   damit es mittig zum Brett steht und nicht zur ganzen Spalte. */
.players,
.moveNote,
.exploreBar { padding-left: var(--lane); }
.players { display: flex; justify-content: space-between; font-size: var(--fs-base); color: var(--text-dim); }
.players b { color: var(--text); font-weight: 600; }

/* Der Verlauf steht unter den Zügen: Man liest erst, was gespielt wurde,
   dann wohin es führte. */
/* Wie .moveNote um die Balkenbreite eingerückt: Dadurch steht der Graph
   exakt unter dem Brett und ein Punkt liegt senkrecht unter seiner Stellung. */

/* Der Verlauf ist ein Fach in der rechten Spalte, keine Beschriftung des
   Bretts: Er sagt etwas ueber die ganze Partie, so wie alles andere dort. */
#evalGraphWrap { display: flex; flex-direction: column; gap: var(--sp-4); }
#evalGraphWrap.hidden { display: none; }
#evalGraph { width: 100%; height: 96px; display: block; border-radius: var(--r-md); background: var(--space-deep); cursor: pointer; }

/* Bewertungsbalken: füllt sich von unten mit Weiß, wie überall im Schach. */
#evalBar {
  position: relative;
  width: var(--bar-w);
  flex: none;
  border-radius: var(--r-sm);
  overflow: hidden;
  background: var(--indigo-800);
}
#evalBarFill {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 50%;
  background: var(--indigo-100);
  transition: height .25s ease;
}
/* Aus Schwarz-Sicht sitzt Weiß oben – der helle Teil wächst dann von oben. */
#evalBar.whiteTop #evalBarFill { bottom: auto; top: 0; }
/* Die einzige Stelle, an der die Bewertung als Zahl steht — der Verlaufsgraph
   zeigt Kurven, die Urteilszeile nennt Genauigkeit, nicht Bauern. Eine Zahl,
   die es nur einmal gibt, ist keine Beschriftung zweiter Klasse und bekommt
   deshalb --fs-micro statt einer eigenen 9. Tabellenziffern, damit die Zahl
   beim Blättern nicht unter dem Finger wandert. */
#evalBarText {
  position: absolute;
  left: 0; right: 0; bottom: 3px;
  text-align: center;
  font-size: var(--fs-micro);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--indigo-100);
}
#evalBarText.onLight { color: var(--indigo-950); }

/* Umbruch erlaubt: Sechs Knöpfe zu je 44 px passen auf 390 px gerade so –
   auf einem schmaleren Gerät sollen sie in die zweite Zeile rutschen statt
   aus dem Brett zu laufen. */

.moveNote { font-size: var(--fs-base); min-height: 20px; text-align: center; color: var(--text-dim); }
.moveNote b { color: var(--text); }
/* Warum die Analyse hier aufmacht – steht nur an der Ankunft und verschwindet
   beim ersten Weiterblättern. Dieselbe Auszeichnung wie das Urteil daneben,
   damit die Zeile eine Zeile bleibt und keine zweite Ebene aufmacht. */
.moveNote .why { color: var(--text); font-weight: 600; }

/* Eigene Variante: sichtbar abgesetzt, damit niemand sie für die Partie hält. */
.exploreBar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  flex-wrap: wrap;
  font-size: 12.5px;
  color: var(--gold);
}
.exploreBar .ghostBtn { padding: 4px 10px; font-size: var(--fs-small); }

/* ---------- Fortschritt ---------- */
.bar { height: 7px; background: var(--indigo-800); border-radius: var(--r-xs); overflow: hidden; margin-bottom: 8px; }
#progressFill,
#brilliantFill { height: 100%; width: 0; background: var(--accent); border-radius: var(--r-xs); transition: width .2s linear; }

/* ---------- Brillante Züge ---------- */
#brilliantCard .ghostBtn { margin: 10px 0 8px; }
/* ---------- Engine-Zeilen ----------
   Dieselbe Aussage wie die drei Pfeile auf dem Brett, nur lesbar. Die Farben
   sind deshalb genau die aus board.js: Rang 1 mint, die beiden Alternativen
   neutral und blasser. Wer die Zeile liest, sucht den Pfeil in derselben
   Farbe – ein zweiter Farbschlüssel wäre eine zweite Sprache. */
#linesDepth { margin: 0; white-space: nowrap; }
.lines { list-style: none; display: flex; flex-direction: column; gap: 5px; }
.lineRow {
  display: flex;
  align-items: baseline;
  gap: 9px;
  padding: 5px 8px;
  border-radius: var(--r-sm);
  background: rgba(140, 130, 205, 0.06);
  font-size: var(--fs-base);
}
.lineScore {
  flex: none;
  min-width: 46px;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--move-neutral);
}
.lineRow[data-rank="0"] .lineScore { color: var(--node-pulsar); }
/* Die Züge selbst sind der Inhalt dieser Karte und stehen deshalb hell.
   Vorher trugen sie --text-dim und die Ränge 2 und 3 zusätzlich eine
   Deckkraft von 0,78. Nachgemessen ergab das 4,49:1 für Rang 1 und 3,29:1
   für die anderen beiden – der lesenswerteste Text der Karte war ihr
   schlechtest lesbarer. Den Rang trägt jetzt allein die Helligkeit, und die
   Farbe der Zahl daneben sagt ihn ein zweites Mal. */
.linePv {
  color: var(--text);
  /* Eine Variante, eine Zeile: Umgebrochen sähen drei Vorschläge aus wie
     sechs. Was nicht passt, ist ohnehin der unsicherste Teil.

     min-width: 0 ist hier nicht Feinschliff, sondern die Bedingung dafür,
     dass die Zeile überhaupt kürzt: Ein Flex-Kind hat von sich aus die
     Mindestbreite seines Inhalts, und bei white-space: nowrap ist das die
     ganze Variante. Gemessen auf 360 px: nötige Breite 223, gezeigte Breite
     223 – das Auslassungszeichen kam nie zum Einsatz, stattdessen wuchs die
     Zeile und schob die ganze Spalte auseinander. */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lineRow[data-rank="1"] .linePv,
.lineRow[data-rank="2"] .linePv { color: var(--indigo-200); }

#brilliantList { display: flex; flex-direction: column; gap: 4px; }
.bRow {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 6px 8px;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: rgba(140, 130, 205, 0.06);
  font: inherit;
  font-size: var(--fs-base);
  color: var(--text);
  text-align: left;
  cursor: pointer;
}
.bRow:hover { border-color: var(--accent); }
.bMove { font-weight: 600; }
.bWho { color: var(--text-dim); font-size: var(--fs-small); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bTag {
  margin-left: auto;
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 1px;
  flex: none;
}
.bTag.found { color: var(--mint-300); }
.bTag.missed { color: var(--gold-400); }

/* ---------- Zusammenfassung ---------- */
.verdict {
  font-size: var(--fs-base);
  line-height: 1.5;
  color: var(--text-dim);
  background: rgba(140, 130, 205, 0.05);
  border-left: 1px solid var(--accent);
  border-radius: 0 8px 8px 0;
  padding: 8px 11px;
  margin-bottom: 12px;
}

/* minmax(0,…) statt 1fr: 1fr hat auto als Minimum, also drückte
   "DARKHORSE210291" seine Spalte breiter als die des kürzeren Gegners – die
   beiden Kästen standen sichtbar ungleich. Derselbe Fehler wie einmal in
   .nextRow. `stretch` zieht sie zusätzlich auf gleiche Höhe. */
.accRow { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); gap: 10px; align-items: stretch; }
.accCell {
  text-align: center;
  background: rgba(3, 4, 10, 0.22);
  border-radius: var(--r-md);
  padding: 9px 6px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.accLabel { display: block; font-size: 11.5px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.accValue { display: block; font-size: 26px; font-weight: 700; margin-top: 2px; line-height: 1.15; font-variant-numeric: tabular-nums; }
.accCaption, .ratingRow .dim {
  font-size: var(--fs-micro);
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 1px;
  font-weight: 400;
}


/* Kopfzeile der Zähltabelle: dieselbe Zurückhaltung wie die Beschriftungen,
   aber vorhanden – vorher standen hier zwei Zahlenspalten ohne Zuordnung. */
#countTable th {
  font-size: var(--fs-micro);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .6px;
  color: var(--text-dim);
  padding-bottom: 6px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Die eigenen Zahlen sind Sprungknöpfe. Sie sehen aus wie die Zahl daneben,
   bis man sie mit Maus oder Tastatur berührt – die Tabelle soll nicht nach
   Bedienoberfläche aussehen, sondern nach Auskunft, die auch etwas tut. */
/* Die eigenen Zahlen sind Sprungknöpfe – und das muss man ihnen ansehen, ohne
   sie zu berühren. Bis zum 12. August waren sie von einer gewöhnlichen Zahl
   nicht zu unterscheiden; der kürzeste Weg von "du hattest drei Fehler" zu
   "hier war der erste" war damit unsichtbar. Jetzt tragen sie einen Rahmen. */
#countTable button.jump {
  font: inherit;
  font-size: var(--fs-lead);
  font-weight: 600;
  color: inherit;
  background: none;
  border: 1px solid transparent;
  border-radius: var(--r-xs);
  padding: 2px 6px;
  margin: -2px -6px;
  cursor: pointer;
  font-variant-numeric: tabular-nums;
}
#countTable button.jump:hover { border-color: var(--accent); }

/* ---------- Das Fach der Urteile ----------
   Elf Zeilen, zwei Seiten, eine Spalte Namen dazwischen. Es ist die einzige
   Stelle der Ansicht, an der die ganze Partie auf einmal steht – deshalb
   bekommt sie Zeilenhöhe und Haarlinien statt vier Pixel Polsterung: Eine
   Tabelle, die wie ein Absatz gesetzt ist, liest sich nicht wie eine Tabelle. */
#countTable { width: 100%; border-collapse: collapse; font-size: var(--fs-base); }
#countTable td { padding: 7px 0; border-top: 1px solid var(--panel-border); }
#countTable tr:first-child td { border-top: none; }
#countTable td.lbl { color: var(--text-dim); }
#countTable tbody tr:hover td { background: var(--indigo-900); }
/* Kategorien ohne Vorkommen bleiben stehen, treten aber zurück. */
/* Nicht ueber Deckkraft: 0,38 auf --text-dim ergab gemessen 1,77:1, und die
   Zahlenspalten 2,99:1 — beides unter den zugesagten 4,5:1. Zurueckgenommen
   wird jetzt ueber die Farbe, und --text-dim liegt bei 4,87:1. */
#countTable tr.zero td, #countTable tr.zero td.num { color: var(--text-dim); }
#countTable tr.zero:hover td { background: none; }
/* Feste Breite, damit die Farbfelder untereinander stehen. Ohne sie zentriert
   jede Zeile ihren eigenen Text und die Marken stehen treppenförmig. */
.lblInner {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 118px;
  margin: 0 auto;
  text-align: left;
}
#countTable td.num {
  width: 5ch;
  font-size: var(--fs-lead);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
#countTable td.num.l { text-align: left; }
#countTable td.num.r { text-align: right; }

/* Der Abstand zur echten Wertungszahl steht klein daneben: über dem eigenen
   Niveau gespielt oder darunter – das ist die Frage dahinter. */
.delta { font-size: 11.5px; font-weight: 600; }
.delta.up { color: var(--win); }
.delta.down { color: var(--loss); }

.ratingRow {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: 10px;
  align-items: center;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--panel-border);
  font-size: var(--fs-title);
  font-weight: 700;
  text-align: center;
}

/* ---------- Zugpaare: Nummer | Weiß | Schwarz ---------- */
/* Die Liste zeigt alle Züge; gescrollt wird die Spalte, nicht die Liste. */
#moveList { overflow-x: hidden; }
.mvPair {
  display: grid;
  grid-template-columns: 3.4ch minmax(0, 1fr) minmax(0, 1fr);
  gap: 4px;
  align-items: stretch;
}
.mvPair:nth-child(odd) { background: rgba(140, 130, 205, 0.04); }
.mvNo {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding-right: 6px;
  color: var(--text-dim);
  font-size: var(--fs-small);
  font-variant-numeric: tabular-nums;
}
.mvCell {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: transparent;
  font-family: inherit;
  font-size: var(--fs-base);
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  color: inherit;
}
.mvCell.empty { cursor: default; }
.mvCell:hover:not(.empty) { background: rgba(140, 130, 205, 0.12); }
.mvCell.current { border-color: var(--accent); background: rgba(140, 130, 205, 0.16); }
.mvSan { color: var(--text); }
/* Die Bedenkzeit steht am rechten Rand der Zelle und tritt zurück – sie ist
   Zusatz, nicht Urteil. Grundlage für die spätere Frage: Wie gut war der
   Zug im Verhältnis zur Zeit, die er gekostet hat? */
.mvTime {
  margin-left: auto;
  padding-left: 6px;
  font-size: 10.5px;
  font-weight: 400;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
/* Nur auffällige Urteile färben den Zugtext – sonst wird die Liste bunt
   und die eigentlichen Ausschläge gehen unter. */
.mvCell.c-brilliant .mvSan { color: var(--mint-300); }
.mvCell.c-great .mvSan { color: var(--mint-400); }
.mvCell.c-inaccuracy .mvSan { color: var(--gold-400); }
.mvCell.c-mistake .mvSan { color: var(--coral-300); }
.mvCell.c-blunder .mvSan { color: var(--coral-500); }
.fig { font-size: 1.15em; line-height: 1; margin-right: 1px; }

/* Abzeichen in Tabelle und Zugliste – dieselbe Marke, dieselbe Größe. */
.swatch, .mvMark {
  display: inline-grid;
  place-items: center;
  width: 15px; height: 15px;
  border-radius: var(--r-xs);
  font-size: 9px;
  font-weight: 800;
  flex: none;
  color: var(--indigo-950);
}
/* Alltägliche Urteile tragen dieselbe Marke, nur leiser – sie sollen die
   drei Züge, auf die es ankommt, nicht überstrahlen. */


/* Klassifikationsfarben: eine Skala von "stark" nach "schwach". */
.c-brilliant { color: var(--mint-300); }
.c-great     { color: var(--mint-400); }
.c-best      { color: var(--mint-500); }
.c-good      { color: var(--indigo-200); }
.c-book      { color: var(--indigo-300); }
.c-inaccuracy{ color: var(--gold-400); }
.c-mistake   { color: var(--coral-300); }
.c-blunder   { color: var(--coral-500); }

.bg-brilliant { background: var(--mint-300); }
.bg-great     { background: var(--mint-400); }
.bg-best      { background: var(--mint-500); }
.bg-good      { background: var(--indigo-200); }
.bg-book      { background: var(--indigo-300); }
.bg-inaccuracy{ background: var(--gold-400); }
.bg-mistake   { background: var(--coral-300); }
.bg-blunder   { background: var(--coral-500); }
/* Die zwei dunkelsten Marken tragen helle Schrift – dieselbe Regel, die das
   Brett per Helligkeitsrechnung anwendet, nur hier von Hand. */
.bg-book, .bg-blunder { color: var(--indigo-100); }

@media (max-width: 900px) {
  /* Auf dem Handy stehen die Spalten untereinander, gescrollt wird die
     Seite – die Höhenbegrenzung des Rasters muss dafür weichen. */
  /* minmax(0, 1fr) statt 1fr: Ein blankes `1fr` hat als Untergrenze die
     Mindestbreite seines Inhalts. Ein einziges breites Kind – gemessen die
     Engine-Zeilen – zog die Spalte damit auf 346 px in einem 282 px breiten
     Bereich, und weil html/body auf overflow: hidden stehen, gab es keinen
     Rollbalken, um das Herausragende zu erreichen: Das Brett stand zu 54 px
     außerhalb des Bildschirms und war dort nicht mehr zu holen. */
  #reviewMain { height: auto; display: block; }
  .reviewGrid { grid-template-columns: minmax(0, 1fr); height: auto; flex: none; }
  .sideCol { overflow-y: visible; }

  /* Das Brett bleibt stehen, alles andere rollt darunter durch.
   *
   * Das war der schwerste Mangel der Ansicht auf dem Telefon. Wer sich mit
   * den Pfeilen durch eine Partie arbeitete, sah nach dem ersten Klick nur
   * noch die Zugliste — die Seite rollte bei jedem Halbzug dorthin (Ursache
   * lag in js/review/page.js, siehe haltInSicht) und das Brett war weg. Ein
   * Werkzeug, dessen Gegenstand beim Benutzen aus dem Bild verschwindet, ist
   * keins.
   *
   * Dafür muss das Raster hier ein Flexcontainer werden. `position: sticky`
   * misst seinen Spielraum am umgebenden Kasten, und ein Rasterkind bekommt
   * als Kasten nur sein eigenes Rasterfeld — es bliebe also genau so lange
   * stehen, wie seine eigene Zeile hoch ist, und das ist gar nicht. Als
   * Flexkind ist der Kasten der ganze Container, und damit hält es über die
   * volle Länge der Seite.
   *
   * Nebenwirkung, die zugleich der Gewinn ist: Mit `order` statt benannten
   * Rasterfeldern lässt sich die Reihenfolge wieder frei setzen. */
  .reviewGrid,
  .reviewGrid:has(> #linesCard:not(.hidden)) {
    display: flex;
    flex-direction: column;
    grid-template-areas: none;
  }
  /* Der Innenabstand der Hauptbereiche bekommt einen Namen, weil das Deck
     gleich mit einem negativen Außenabstand dagegenrechnet. Wer die eine Zahl
     ändert und die andere vergisst, bekommt die zwei 10-px-Ritzen zurück,
     durch die der Text vorher am Deck vorbeilief. */
  #reviewMain,
  #boardMain { --main-pad-x: 10px; }

  /* Was auf dem Telefon überhaupt zu verteilen ist: die Fensterhöhe ohne die
     Fußleiste. Deck und Zugliste rechnen beide dagegen, deshalb steht die Zahl
     einmal am gemeinsamen Vorfahren.

     svh und nicht dvh: dvh ändert sich, während die Adressleiste von iOS ein-
     und ausfährt — das Brett rechnete sich dann mitten im Rollen neu, und die
     Charta sagt, das Chrome ist statisch. svh ist der ungünstigste Fall,
     einmal genommen und danach still. Die 100vh-Zeile darüber ist die
     Rücklage: Ein Browser ohne svh bekäme sonst gar kein --board, und die
     Leinwand fiele auf ihre 640 px Eigenbreite zurück. */
  html.reviewPage { --frei: calc(100vh - var(--nav-h) - env(safe-area-inset-bottom)); }
  @supports (height: 100svh) {
    html.reviewPage { --frei: calc(100svh - var(--nav-h) - env(safe-area-inset-bottom)); }
  }

  /* Was das Deck außer dem Brett trägt. Nachgerechnet aus der gemessenen
     Deckhöhe minus der gemessenen Kantenlänge, jeweils bei 375 x 812:
     /review 415 - 291 = 124 (Namenszeile, Blätterleiste, Urteilszeile, drei
     Abstände, Innenabstand, Kante), /board ohne Partien 414 - 315 = 99, mit
     geladenen Partien 470 - 280 = 190 — das Praxisfach steht dort als eigene
     Zeile unter dem Brett.

     Die Zahl steht am Wurzelelement und nicht an .boardCol, weil sie zweimal
     gebraucht wird: einmal für die Kantenlänge des Bretts und einmal für den
     Rollrand weiter unten. Der Rollrand hängt am Rollbehälter, und das ist
     html. */
  html.reviewPage { --deck-rest: 124px; }
  html.reviewPage:has(#boardMain) { --deck-rest: 100px; }
  html.reviewPage:has(#boardMain .practiceDash:not([hidden])) { --deck-rest: 190px; }

  /* Der Browser rollt selbst — und weiß von dem Deck nichts.
   *
   * Gemessen am 25. August 2026 auf /board bei 375 x 812: Nach einem
   * scrollIntoView lag das Auswahlfeld "Colour" bei y 386, die Deckkante bei
   * 414 — hinter dem Brett. Das Namensfeld blieb bei y 768 stehen, also hinter
   * der Fußleiste, weil "nearest" es im 812 px hohen Fenster für sichtbar
   * hielt. Beides passiert auch ohne Zutun: iOS rollt ein Feld ins Bild,
   * sobald es den Fokus bekommt.
   *
   * Der Wert ist die Obergrenze der Deckhöhe, dieselbe Rechnung wie unten:
   * Sobald die Breite das Brett begrenzt, ist das Deck kleiner als diese
   * Zahl, und ein zu großer Rollrand schiebt das Ziel nur etwas tiefer in den
   * Lesestreifen. Gemessen nach der Änderung: die vier Bedienelemente der
   * rechten Spalte auf /board landeten alle bei y 565-572 statt bei 386
   * (hinter dem Deck) und 768 (hinter der Fußleiste), und alle vier waren
   * anzutippen.
   *
   * Auf /review nur, solange die Analyse offen ist — auf Einstieg und
   * Partieliste gibt es kein Deck. /trust bindet dieselbe Datei ein und hat
   * gar keins, deshalb keine Regel für html.reviewPage allein.
   *
   * Der untere Rand gehört der Fußleiste und gilt für jede Fläche mit ihr;
   * hier steht er nur für diese beiden. Siehe Bericht. */
  /* Der Rollrand gehört dem Ziel, nicht dem Roller.
   *
   * Hier stand bis zum 25. August 2026 `scroll-padding-top` auf dem
   * Dokumentroller, mit derselben Rechnung wie die Deckhöhe. Der Gedanke war
   * richtig und die Wirkung gemessen: Die vier Bedienelemente der rechten
   * Spalte auf /board landeten danach bei y 565-572 statt hinter dem Deck.
   *
   * Nur gilt `scroll-padding` für **jedes** Ziel des Rollers — auch für die,
   * die im Deck stehen und dort festkleben. Ein Blätterknopf bei y 0-415 kann
   * eine Polsterung von 415 px nie verlassen: Der Browser rollt, das Deck
   * bleibt stehen, der Knopf bewegt sich nicht, also rollt der Browser
   * weiter — bis zum Anschlag. Gemessen bei 375 x 812 mit einer Leseposition
   * an der Zugliste: drei Tipper auf "Next move" warfen scrollY von 517 über
   * 286 und 55 auf 0. Damit war genau die Handlung kaputt, für die es diese
   * Ansicht gibt: durch die Partie blättern und dabei die Liste lesen. Im
   * Querformat war die Polsterung mit 404 px größer als das 375 px hohe
   * Fenster, und der Knopf kam gar nicht mehr an.
   *
   * `scroll-margin` hängt dagegen am Ziel. Es bekommt, was unter dem Deck
   * steht; das Deck selbst bekommt nichts und wird damit nie zum Rollanlass —
   * es ist ja immer im Bild. Aufgezählt werden die zwei Behälter außerhalb
   * des Decks statt "alles außer dem Deck": `:not(.boardCol *)` verlangt
   * einen zusammengesetzten Selektor in `:not()`, und wo den ein Browser
   * nicht kennt, fällt die ganze Regel aus.
   *
   * Der untere Rand steht nicht mehr hier. Er gehört der Fußleiste, die
   * gibt es auf jeder Fläche, und deshalb steht er in css/shell.css. */
  html.reviewPage {
    --deck-h: max(calc(var(--frei) * 0.55), calc(280px + var(--deck-rest)));
  }
  .sideCol :is(a[href], button, input, select, textarea, summary),
  #linesCard :is(a[href], button, input, select, textarea, summary),
  .gameRow {
    scroll-margin-top: calc(var(--deck-h) + 8px);
  }

  /* Das Brett bleibt beim Blättern stehen — als Deck, nicht als Folie.
   *
   * Die Anforderung vom 22. August war richtig und hält auch danach: 48
   * Halbzüge weitergeblättert, das Brett 48 Mal ganz im Bild. Falsch war die
   * Bauart. Auf /board trägt <section class="boardCol"> kein .panel, die
   * Klebespalte hatte also background rgba(0,0,0,0) — und eine durchsichtige
   * Fläche mit z-index verdeckt nichts und fängt trotzdem alles.
   *
   * Gemessen am 25. August 2026 auf /board bei 375 x 812 mit geladenen
   * Partien: Die Praxiskarte — die Antwort, für die es diese Ansicht gibt —
   * stand an 1 von 28 Rollpositionen ganz lesbar da und lief an 22 hinter dem
   * Brett durch; die Zugkarte an 0 gegen 12. Und wo man etwas sah, traf man es
   * nicht: an 54 von 128 Stellen landete der Finger im Brett statt auf dem
   * Ziel, kalt an 33 von 203. Ein echter Klick auf "Read my games" kam
   * überhaupt nicht an — die Prüfung lief in ihren Zeitgeber, weil das Deck
   * den Zeiger abfing.
   *
   * Drei Dinge machen aus der Folie ein Deck, und alle drei werden gebraucht:
   * undurchsichtig, bis an den Bildschirmrand, mit einer Kante. Die Spalte war
   * 355 von 375 px breit — durch die zwei Ritzen des Innenabstands kam zurück,
   * was sie gerade verdeckt hatte. Und weil Deck und Karten denselben Ton
   * tragen, liest sich das Abschneiden ohne Linie als Darstellungsfehler statt
   * als Kante.
   *
   * Auf /review gab es das Fangen nie: Dort trägt die Spalte .panel und damit
   * --indigo-950. Gemessen 0 sichtbar-aber-tote Stellen unter 1487 Proben,
   * vorher wie nachher. Wer dort etwas anderes berichtet, hat "im Fenster"
   * gezählt, ohne zu prüfen, ob etwas Undurchsichtiges davorliegt.
   *
   * z-index aus der Leiter statt aus der Zahl 3: über allem, was darunter
   * durchrollt, unter der Fußleiste, die das Oberste bleibt. */
  .boardCol {
    order: 1;
    position: sticky;
    top: 0;
    z-index: var(--z-sticky);

    background: var(--indigo-950);
    margin-inline: calc(-1 * var(--main-pad-x));
    padding: 6px var(--main-pad-x) 8px;
    border: 0;
    border-bottom: 1px solid var(--panel-border);
    border-radius: 0;
    gap: 8px;
    justify-content: flex-start;

    /* Das Brett gibt nach, nicht das Lesen.
     *
     * Vorher hing die Kantenlänge allein an der Breite. Auf dem Telefon heißt
     * das: Das Deck nimmt sich, was es braucht, und was übrig bleibt, ist
     * Rest. Gemessen wurde dieser Rest — der Streifen zwischen der Kante des
     * festgeklebten Decks und der Fußleiste — bei 375 x 812 mit 252 px auf
     * /board mit geladenen Partien und 316 px auf /review. Bei 252 px stand
     * die Praxiskarte an 1 von 28 Rollpositionen ganz im Bild.
     *
     * Also bekommt das Deck einen Deckel: höchstens 55 % dessen, was zwischen
     * Fensterkante und Fußleiste liegt. Danach sind es 284 und 339 px. Der
     * Deckel soll das Lesen retten, nicht das Brett bestrafen — bei 50 % fiele
     * das Brett auf /review auf 253 px und damit unter die 281, die vorher
     * schon dastanden.
     *
     * Die Klammerung ist die alte: Der Deckel steht außen, damit die
     * verfügbare Breite immer gewinnt und nie waagerecht überlaufen wird. Der
     * Boden von 280 px steht innen und ist genau die Größe, mit der /review
     * heute läuft — 35 px je Feld. Auf einem Brett, auf dem Figuren mit dem
     * Finger gezogen werden, ist das die Untergrenze, die schon jemand
     * ausgehalten hat. */
    --board: min(
      100% - var(--lane),
      max(280px, calc(var(--frei) * 0.55 - var(--deck-rest)))
    );
  }

  /* Muss über die Kennung gehen und nicht über .boardPage: board.css wird nach
     review.css geladen und setzt --board dort selbst — eine gleich spezifische
     Regel davor verliert. #boardMain .boardCol schlägt .boardPage .boardCol,
     und damit bleibt alles zur Sache in einer Datei.

     Das Praxisfach bleibt im Deck: Es ist die zweite Hälfte der Frage dieser
     Ansicht ("und was spiele ich tatsächlich?") und ändert sich mit jedem Zug.
     Bezahlt wird es aus der Kantenlänge — gemessen fällt sie dann auf ihren
     Boden von 280 px, und der Lesestreifen steigt von 252 auf 284. */
  #boardMain .boardCol {
    --dash-lane: 0px;
    --board: min(
      100% - var(--lane),
      max(280px, calc(var(--frei) * 0.55 - var(--deck-rest)))
    );
  }

  /* Die Engine-Karte rückt ans Ende, hinter ihren eigenen Schalter.
   *
   * Sie stand an zweiter Stelle, direkt unter dem Brett — der Schalter, der
   * sie öffnet, steht als Letztes auf der Seite. Gemessen am 25. August 2026
   * bei 375 x 812: Ein Tipp auf "Engine lines" schob 343 px Karte oberhalb der
   * eigenen Leseposition ein. Die Karte öffnete bei y -1881, also 1554 px über
   * dem Bildrand, und der Schalter wanderte von y 567-629 auf 768-830 — hinter
   * die Fußleiste, die bei 754 beginnt. Die Rückmeldung "On" war danach nicht
   * mehr zu lesen und der Schalter nicht mehr zu treffen.
   *
   * Hinter der Seitenspalte (order 3) fügt sie sich unter ihren Schalter ein.
   * Damit bewegt sich oberhalb nichts: Der Schalter blieb gemessen bei y
   * 567-629 stehen, "On" ist lesbar, und die Karte öffnet bei y 730 statt
   * 1554 px über dem Bildrand. Was er bewirkt hat, steht damit an zwei
   * Stellen im Bild — die Pfeile auf dem Brett, das als Deck ohnehin oben
   * stehen bleibt, und der Kopf der Karte unter dem Schalter.
   *
   * Auf /board greift diese Zeile nicht: Dort ist #linesCard ein Kind von
   * .sideCol, und board.css:420-422 ordnet diese Spalte selbst. */
  #linesCard { order: 4; }
  /* Und sie wird geholt, statt unter der Fußleiste aufzugehen.
   *
   * Die Umsortierung darüber hat den Schalter gerettet, nicht die Karte:
   * gemessen am 25. August 2026 bei 375 x 812 öffnete sie bei y 730 und ist
   * 327 px hoch, die Fußleiste beginnt bei 754 — 25 von 327 px im Bild. Wer
   * den Schalter umlegt, sieht einen Fingerbreit Karte.
   *
   * Den Rollbefehl gibt js/review/page.js, und zwar nur, wenn die Karte
   * wirklich nicht dasteht. Diese Zeile sagt, wo sie landen soll: unter der
   * Kante des klebenden Bretts, nicht darunter. Ohne sie läge ihr Kopf hinter
   * dem Deck — dieselbe Rechnung wie für die Bedienelemente der rechten
   * Spalte, nur dass die Karte selbst das Ziel ist und nicht ihr Inhalt. */
  #linesCard { scroll-margin-top: calc(var(--deck-h) + 8px); }
  .sideCol { display: flex; flex-direction: column; order: 3; }

  /* Reihenfolge unter dem Brett: erst das Urteil, dann der Verlauf, dann die
     Züge, zuletzt die Aufschlüsselung.
     Hier stand `#movesCard { order: -1 }` mit der Begründung, die Zugliste
     müsse direkt unter das Brett, weil ein Zug antippen und die Stellung
     sehen sonst zweimal einen ganzen Bildschirm koste. Das stimmte — solange
     das Brett wegrollte. Es rollt nicht mehr weg, und damit fällt der Grund
     weg. Übrig bleibt die Frage, was jemand nach 37 Sekunden Rechnen zuerst
     lesen soll, und das ist nicht die Notation, sondern das Ergebnis. */
  #progressCard { order: 0; }
  #summaryCard { order: 1; }
  #evalGraphWrap { order: 3; }
  #movesCard { order: 3; }
  /* Die Zugliste rückt vor den Verlauf.
     Der Absatz darüber gilt weiter für den einen Augenblick, in dem nach
     37 Sekunden Rechnen das Ergebnis ankommt — deshalb bleibt #summaryCard
     vorn. Danach wird die Liste vierzig- bis achtzigmal benutzt und das Urteil
     einmal. Gemessen bei 375 x 812 fällt der Rollweg vom Urteil bis zur Liste
     von 574 auf 384 px. Der Tausch mit dem Verlaufsgraphen ist der
     kleinstmögliche Eingriff — beide sind Auskünfte über die ganze Partie,
     aber nur eine davon ist anfassbar.
     :has(#moveList) grenzt es auf die Game Review ein: Auf dem Analysebrett
     heißt dasselbe Fach auch #movesCard, trägt aber den Zugstreifen und das
     FEN-Feld. Dort greift die Regel gar nicht — es gibt kein #moveList —, und
     board.css:420-422 setzt die Reihenfolge der rechten Spalte ohnehin selbst.
     Die Zeile darüber ist die Rückfallreihenfolge, falls :has() nicht greift. */
  #movesCard:has(#moveList) { order: 2; }
  #gradesCard { order: 4; }
  /* Auf dem Telefon zuletzt: Wer dort rollt, will erst das Ergebnis und die
     Züge sehen, nicht zwei Schalter. Am Rechner stehen sie direkt unter dem
     Urteil (siehe .sideCol-Reihenfolge weiter unten). */
  .modeGroup { order: 5; }

  /* Solange gerechnet wird, steht der Fortschritt über dem Brett.
   *
   * Gemessen am 17. August 2026 auf 360×740: „Analysing…" lag bei y 716–738,
   * die Fußleiste beginnt bei 688 — verdeckt. Der Balken lag bei y 748–755,
   * also **außerhalb** eines 740 px hohen Fensters. 37,5 Sekunden lang sah ein
   * Besucher eine unbewegte Grundstellung, vier tote Knöpfe und sonst nichts;
   * er konnte nicht einmal wissen, dass er warten soll. Genau dort leckt
   * `review_finished / review_started`, die eine Zahl, an der sich dieses
   * Produkt misst.
   *
   * Die Umkehr gilt nur, solange die Fortschrittskarte sichtbar ist: Danach
   * greift `:has()` nicht mehr und die Spalten stehen wieder in ihrer
   * Reihenfolge. Kein Zustand in JavaScript, nichts, was hängen bleiben kann.
   *
   * Was hier ausdrücklich NICHT passiert: das Brett mitlaufen lassen. Das gab
   * es schon einmal, und der Kommentar in js/review/page.js:270 hält fest,
   * warum es wieder herausgeflogen ist — die Ankunft beim Ergebnis las sich
   * danach als Absturz. Der Fortschritt sagt, dass gerechnet wird; das Brett
   * bleibt still. */
  /* Und die zwei Analyse-Schalter treten ab, statt ausgegraut die
     auffälligste Fläche des Wartebildschirms zu belegen. */
  #reviewPanel:has(#progressCard:not(.hidden)) .modeGroup { display: none; }
  #pickPanel { margin-top: 2vh; }
  #reviewMain { padding: 14px var(--main-pad-x) 40px; }

  /* Der Deckel ist die Rechnung, die Brett und Liste auf einen Schirm bringt.
     Das Deck nimmt 55 % von --frei, der Liste bleiben 45 % davon minus dem,
     was das Fach selbst wiegt: Überschrift und Innenabstand, gemessen 74 px,
     plus 42 px Luft, damit es nicht an die Fußleiste stößt. Der Boden von
     160 px greift im Querformat.

     Ohne Deckel steht die Liste 1056 px hoch in der Seite, und der aktuelle
     Zug läuft beim Blättern aus dem Bild: gemessen an 14 von 48 Halbzügen noch
     sichtbar, im Querformat an 0 von 48. Mit Deckel und dem berichtigten
     haltInSicht (js/review/page.js) bleibt er im Bild, ohne dass die Seite
     springt.

     scroll-padding-block hält eine Zeile Zusammenhang über und unter dem
     aktuellen Zug frei; 48 px sind eine Zeile auf Touch (.mvCell trägt dort
     min-height: 44px, dazu der Abstand des Paares).

     Kein overscroll-behavior: contain. Wer am Ende der Liste weiterwischt,
     soll die Seite weiterrollen — eine Fläche, die den Wisch verschluckt, ist
     genau das, was hier gerade abgestellt wird. */
  #moveList {
    overflow-y: auto;
    max-height: max(160px, calc(var(--frei) * 0.45 - 116px));
    scroll-padding-block: 48px;
  }
  /* Der Einzug richtet die Zeile am Brett aus. Auf dem Handy kostet er
     Platz, den die sechs Knöpfe brauchen: Sechs mal 44 px sind 264, und mit
     dem alten Abstand brach der letzte in eine zweite Zeile um. Volle
     Breite, und der Rest verteilt sich zwischen den Knöpfen. */
  .boardNav {
    padding-left: 0;
    gap: 2px;
    justify-content: space-between;
    flex-wrap: nowrap;
  }
}


/* Querformat: Es gibt nichts zu bekleben.
 *
 * Gemessen am 25. August 2026 bei 812 x 375: Auf /review klebte die Spalte
 * 397 px hoch in einem Fenster, dessen Fußleiste bei 331 beginnt — der
 * Streifen unter der Deckkante war minus 150 px. Auf /board waren es 848 px
 * Deck gegen minus 644 px Streifen, und 172 von 176 Tippern gingen daneben.
 * Die Seite ließ sich nicht mehr lesen, nur noch bewegen.
 *
 * Ein Deck lohnt sich erst, wenn darunter etwas übrig bleibt; hier bleibt
 * nichts. Also steht es still, und die Seite rollt wieder als Ganzes. Die
 * Zugliste behält ihren Deckel, dort auf dem Boden von 160 px — sie ist damit
 * 160 statt 1056 px hoch, und der aktuelle Zug bleibt beim Blättern im Bild.
 *
 * Auf (max-width: 900px) eingegrenzt, weil nur dort ein Deck entsteht. Ein
 * flaches Fenster am Rechner ist breiter als 900 px und trägt die zweispaltige
 * Ansicht — dort gäbe es nichts aufzuheben, wohl aber ein Panel zu zerlegen. */
@media (max-width: 900px) and (max-height: 560px) {
  .boardCol {
    position: static;
    z-index: auto;
    background: none;
    border-bottom: 0;
    margin-inline: 0;
    padding-inline: 0;
  }
  /* Auf /review ist die Spalte auch ein Panel. Ohne Deck bekommt sie es
     zurück, statt als randlose Fläche dazustehen. */
  .boardCol.panel {
    background: var(--indigo-950);
    border: 1px solid var(--panel-border);
    border-radius: var(--r-xs);
    padding: var(--sp-7);
  }
}


/* ---------- Kopfzeile der Analyse ---------- */
/* Der Weg zurück zur Partieliste. Ohne ihn führte der einzige Rückweg über
   "Change player" und ein erneutes Laden derselben zwölf Partien. */
.reviewHead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-5);
  margin-bottom: var(--sp-5);
  min-height: var(--ctl-h);
}
#reviewFor {
  font-size: var(--fs-small);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Der gedrückte Schalter trägt Gold – dieselbe Rolle wie in der Leiste:
   "das ist gerade an". */
.ghostBtn[aria-pressed="true"] {
  color: var(--gold);
  border-color: var(--gold);
  background: rgba(255, 209, 102, 0.09);
}

/* ---------- Bewegung an den Ladebalken ----------
   Die einzige Animation im Chrome, und sie hat einen Grund: Ein Balken, der
   sich nur in Sprüngen füllt, sieht bei einer langsamen Stellung aus wie ein
   Absturz. Der Schimmer sagt "es läuft weiter", ohne eine Zahl zu behaupten.
   Bei reduzierter Bewegung greift die Regel aus shell.css und der Balken
   steht still – die Breite sagt es dann allein. */
#progressFill, #brilliantFill {
  position: relative;
  overflow: hidden;
  transition: width var(--mo-calm) var(--mo-ease);
}
#progressFill::after, #brilliantFill::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg,
    transparent 0%,
    rgba(230, 226, 248, 0.45) 50%,
    transparent 100%);
  animation: barSheen 1.6s var(--mo-ease) infinite;
}
@keyframes barSheen {
  from { transform: translateX(-100%); }
  to   { transform: translateX(100%); }
}
@media (prefers-reduced-motion: reduce) {
  #progressFill::after, #brilliantFill::after { animation: none; opacity: 0; }
}


/* Zugklassen in der Oberfläche: Zugliste, Zusammenfassung, Legende.
 *
 * Ersetzt die Textkürzel in .mvMark / .swatch durch die Marken aus
 * marks-sprite.svg bzw. orbs-sprite.svg. Das Sprite wird EINMAL ins HTML
 * eingebettet (siehe README) – externes <use href="…svg#id"> scheitert an
 * der Content-Security-Policy 'self' und wird von Browsern ohnehin geblockt.
 *
 * Diese Datei ergänzt css/review.css; die Klassen .c-* / .bg-* bleiben, wo
 * sie sind – Textfarben in der Zugliste kommen weiter von dort.
 */

/* Chip in Zugliste und Tabelle: dieselbe Größe wie bisher (15 px), damit
   sich die Zeilenhöhe nicht ändert. */
.mvMark,
.swatch {
  width: 15px;
  height: 15px;
  flex: none;
  display: block;
}

/* Alltägliche Urteile tragen dieselbe Marke, nur leiser – sie sollen die
   drei Züge, auf die es ankommt, nicht überstrahlen. */
.mvMark.quiet { opacity: 0.42; }
.mvCell.current .mvMark.quiet,
.mvCell:hover .mvMark.quiet { opacity: 0.85; }

/* Körper statt Chip: überall dort, wo Platz ist – Legende, Zusammenfassung,
   Kopf der Analyse. Unter 15 px trägt kein Körper mehr, dort bleibt der Chip. */
.orb {
  width: 28px;
  height: 28px;
  flex: none;
  display: block;
}
.orb.lg { width: 44px; height: 44px; }

/* Legendenzeile: Marke, Name, Anzahl. */
.classRow {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 5px 0;
  font-size: 13px;
}
.classRow .name { font-weight: 600; }
.classRow .count {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
}

/* ---------- Die Leiste unter dem Brett ----------
   Zwei Gruppen, weil es zwei verschiedene Fragen sind: „Wo bin ich in der
   Partie" und „Was zeigt mir die Engine". Bis zum 12. August 2026 lagen beide
   in einer Reihe aus sechs gleichen 26-px-Symbolen — der Engine-Schalter war
   der fünfte davon und von „Brett drehen" nicht zu unterscheiden. Die
   Navigation bleibt Symbol, weil sie tausendmal benutzt wird und man sie
   kennt; die Analyse bekommt ihren Namen, ihren Nutzen und ihren Zustand,
   weil man sie einmal findet oder nie. */
.boardBar {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  /* Mittig unter dem Brett. Linksbündig standen die Symbolknöpfe in der linken
     Ecke einer Zeile, die so breit ist wie das Brett – die Leiste sah dadurch
     aus, als fehlte rechts etwas. Am Rechner sitzt die Gruppe damit gemessen
     exakt auf der Brettmitte (Versatz 0), weil der Einzug um die Balkengasse
     erhalten bleibt.

     Auf dem Handy fällt der Einzug weg (siehe unten), also zentriert sich die
     Gruppe unter Brett *und* Bewertungsbalken zusammen – gemessen 16 px links
     der Brettmitte. Das ist der bewusst kleinere Fehler: Mit Einzug bräuchten
     die sechs Knöpfe des Analysebretts 318 px in 308 verfügbaren, und der
     letzte bräche bei 360 px Fensterbreite in eine zweite Zeile um. Eine
     zweite Zeile sieht man, 16 px nicht. */
  justify-content: center;
  gap: var(--sp-5);
  padding-left: var(--lane);
  /* Die Reihe darf nie breiter werden als das Brett darueber: Bei 320 px
     scrollte die Review sonst waagerecht, weil fuenf Symbolknoepfe zu 48 px
     plus Abstaende mehr brauchen als das Brett breit ist. */
  max-width: 100%;
  min-width: 0;
}
.navGroup { flex-wrap: wrap; }
.navBtn { flex: 0 1 auto; min-width: 40px; }

/* `0 1 auto` und nicht `0 0 auto`: Die Gruppe trägt schon `flex-wrap: wrap`,
   aber solange sie selbst nicht nachgeben darf, kommt ihre Umbruchregel nie
   zum Zug — sie behält ihre volle Breite und läuft aus der Leiste heraus.
   Gemessen am 25. August 2026 auf /board bei 1200 x 500, wo das Brett auf
   seinem Boden steht: `.boardBar` 39 px Innenüberlauf, der letzte Knopf
   angeschnitten. Mit `0 1 auto` bricht stattdessen "Undo" in eine zweite
   Zeile — sichtbar, aber vollständig. In jedem Fenster mit genug Platz ändert
   sich nichts, weil dort nie geschrumpft wird. */
.navGroup { display: flex; gap: var(--sp-2); flex: 0 1 auto; min-width: 0; }
.navBtn {
  width: var(--ctl-h-lg);
  height: var(--ctl-h-lg);
  display: grid;
  place-items: center;
  background: transparent;
  border: 1px solid var(--panel-border);
  border-radius: var(--r-md);
  color: var(--text-dim);
  cursor: pointer;
}
.navBtn .ico { width: 22px; height: 22px; }
.navBtn:hover { color: var(--text); border-color: var(--accent); }
.navBtn:disabled { opacity: .4; cursor: default; }
/* Am Zeiger sagt der title, wie der Knopf heisst. Am Finger gibt es keinen —
   deshalb steht das Wort nur schmal da (weiter unten), genau wie in der
   Fussleiste. Ausgeblendet wird es hier und nicht dort, damit die Regel, die
   es zeigt, hinter den Grundregeln dieser Datei steht: Der Block fuer schmale
   Fenster weiter oben kaeme sonst vor .navBtn und verloere. */
.navBtnLabel { display: none; }

/* Eigene Zeile unter der Navigation. In derselben Zeile brach die Gruppe je
   nach Brettbreite mal so, mal so um; zwei Zeilen, die immer zwei Zeilen sind,
   lesen sich besser als eine, die manchmal eine ist. */
/* Die zwei Analyse-Schalter sitzen seit dem 17. August 2026 in der rechten
   Spalte statt in der Brettleiste — sonst hätten sie 152 der 440 Pixel belegt,
   die auf dem Telefon am Brett festgenagelt sind.
   Am Rechner rollt diese Spalte gemessen 2700 bis 2900 px weit, und am Ende
   davon hätte sie niemand je gesehen. Sie kleben deshalb am unteren Rand ihrer
   Spalte — wörtlich das, was der Kommentar in review.html seit jeher
   behauptet: „rechts unten, wo die Analyse ohnehin steht". Kein Verlauf, keine
   Fläche außer dem Grundton: Der Streifen soll tragen, nicht auffallen. */
/* Untereinander, nicht nebeneinander: In der 380 px schmalen Spalte teilten
   sich zwei Schalter 185 px, und „Every sound sacrifice, including the ones
   you missed" brach auf sechs Zeilen um — der Knopf wurde 165 px hoch.
   Ein kurzer Versuch, sie am Fuß der Spalte kleben zu lassen, ist daran
   gescheitert: 165 px dauerhaft belegte Fläche für zwei Schalter, die man
   einmal drückt. Sie stehen jetzt direkt unter dem Urteil — sichtbar, ohne zu
   rollen, und ohne etwas zu verdecken. */
.modeGroup {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  flex: none;
  min-width: 0;
  margin-top: 14px;
}

/* Am Rechner stehen sie direkt unter dem Urteil: Die rechte Spalte rollt
   gemessen 2700 bis 2900 px weit, und in der Reihenfolge des Markups lägen
   sie ganz am Ende — gesehen hätte sie dort niemand. Das Urteil bleibt
   trotzdem das Erste; ein Schalter über dem Ergebnis wäre genau der Fehler,
   den diese Ansicht auf dem Telefon schon hatte.
   Als eigene Abfrage, damit die Reihenfolge des Telefons weiter unten nicht
   überschrieben wird — sie steht früher in der Datei. */
@media (min-width: 901px) {
  #progressCard { order: 0; }
  #summaryCard { order: 1; }
  .modeGroup { order: 2; }
  #evalGraphWrap { order: 3; }
  #movesCard { order: 4; }
  #gradesCard { order: 5; }
}

/* Der Schalter, der bisher ein Symbol war. Er trägt jetzt seinen Namen, den
   Satz, wofür er gut ist, und die Marke, ob er an ist — in dieser Reihenfolge
   von links nach rechts, weil man in dieser Reihenfolge fragt. */
.modeBtn {
  flex: 1 1 0;
  min-width: 0;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--sp-4);
  min-height: 62px;
  padding: var(--sp-4) var(--sp-5);
  background: var(--indigo-900);
  border: 1px solid var(--panel-border);
  border-radius: var(--r-xs);
  color: var(--text-dim);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}
.modeBtn .ico { width: 26px; height: 26px; }
.modeBtn:hover:not(:disabled) { border-color: var(--accent); color: var(--text); }
.modeBtn:disabled { opacity: .42; cursor: default; }
.modeText { min-width: 0; }
.modeName {
  display: block;
  font-size: var(--fs-lead);
  font-weight: 600;
  color: var(--text);
}
.modeHint {
  display: block;
  font-size: var(--fs-small);
  color: var(--text-dim);
  line-height: 1.35;
  margin-top: 2px;
}
.modeBtn:disabled .modeName { color: var(--text-dim); }

/* Der Zustand steht als Marke am Knopf, nicht in seiner Beschriftung: Ein
   Knopf, dessen Name sich ändert, ist für den Leser zwei Knöpfe. */
.modeState {
  flex: none;
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-dim);
  border: 1px solid var(--panel-border);
  border-radius: var(--r-xs);
  padding: 4px 8px;
  white-space: nowrap;
}
/* An heißt: Rahmen und Marke kippen um. Kein Gold — Gold heißt „dein Zug",
   und ein Schalter ist kein Zug. */
.modeBtn[aria-pressed="true"] {
  border-color: var(--accent);
  background: var(--indigo-800);
  color: var(--text);
}
.modeBtn[aria-pressed="true"] .modeState {
  background: var(--btn-bg);
  color: var(--btn-fg);
  border-color: var(--btn-bg);
}

@media (max-width: 900px) {
  /* Auf dem Telefon untereinander: Zwei 62-px-Schalter nebeneinander sind
     zwei Schalter, die beide nicht lesbar sind. */
  /* `flex: 1 1 0` meint in einer Reihe die Breite und in einer Spalte die
     Höhe — und `flex-basis: 0` heißt dort: rechne den Inhalt nicht mit. Übrig
     blieb `min-height: 62px` als einzige Höhenangabe, während der Inhalt bei
     375 px auf 74 px wuchs, weil der Hinweissatz auf zwei Zeilen umbrach. Mit
     `align-items: center` verteilte sich der Überschuss nach oben und unten:
     Gemessen stand die zweite Zeile („board") 4 px unter der Innenkante des
     Knopfes, also mitten auf seinem Rahmen. In der Spalte darf der Knopf
     deshalb seine eigene Höhe behalten. */
  .modeBtn { flex: 0 0 auto; }
  .boardBar { padding-left: 0; }

  /* Das Wort unter dem Zeichen — und es kostet keine Bretthöhe.
   *
   * .navBtn ist ein Raster mit place-items: center; ein zweites Kind macht
   * daraus zwei Reihen. Gerechnet in den 46 px, die der 48-px-Knopf innerhalb
   * seiner Kante hat: Zeichen 22 + Abstand 2 + Zeile 10 = 34, also 6 px Luft
   * oben und unten. Damit bleibt die Brettleiste 48 px hoch, --deck-rest
   * stimmt weiter, und das Brett behält seine Kantenlänge — gemessen 289 px
   * vorher wie nachher.
   *
   * Versalien und --fs-micro sind nicht neu erfunden: Es ist dasselbe Maß und
   * dieselbe Auszeichnung, mit der die Fußleiste ihre sechs Ziele beschriftet
   * (css/shell.css). Der Zwischenraum ist mit 0,3 px kleiner als dort — bei
   * 42 px nutzbarer Breite entscheidet jedes Zehntel, ob "FIRST" umbricht.
   *
   * Eigene Klasse statt .navLabel: Die Fußleiste blendet ihre Beschriftung
   * global ein und aus, und zwei Leisten an einem Schalter wären eine Falle
   * für den, der eine davon ändert. */
  .navBtn { gap: 2px; }

  /* Sechs Knöpfe passen anders als fünf.
   *
   * /review blättert durch eine gespielte Partie und braucht fünf; /board
   * spielt selbst und hat einen sechsten, "Undo". Bei sechs Zielen zu 48 px
   * und dem Zwischenraum von --sp-2 misst die Gruppe 318 px — auf einem
   * 320-px-Telefon stehen nach dem Innenabstand 300 zur Verfügung. Gemessen
   * lief die Leiste um 9 px aus sich heraus, sichtbar am angeschnittenen
   * "Undo".
   *
   * Der Zwischenraum gibt nach, nicht das Ziel: 2 px zwischen den Knöpfen
   * statt 6, und die Untergrenze bleibt bei 44 px. Sechs mal 44 plus fünf mal
   * 2 sind 274 und passen mit 26 px Luft. Die Knöpfe berühren sich dabei
   * nicht — 2 px sind genug, damit der Finger nicht zwischen zwei Zielen
   * landet. */
  .navGroup { gap: 2px; }
  .navBtn { min-width: 44px; }

  .navBtnLabel {
    display: block;
    font-size: var(--fs-micro);
    line-height: 1;
    letter-spacing: 0.3px;
    text-transform: uppercase;
    font-weight: 700;
  }
}


/* ---------- Das Fach unten: Urteile und die Brillanzsuche ----------
   Beides gehört zusammen: Was die Suche findet, ist die erste Zeile dieser
   Tabelle. Bis zum 12. August 2026 standen sie in zwei Fächern übereinander,
   und nach dem Umbau war das obere davon leer. */
#gradesCard { display: flex; flex-direction: column; gap: var(--sp-5); }
#gradesCard .slotLabel { margin: 0; }
.gradesHint { margin: 0; }
.brilliantSlot:empty { display: none; }
.brilliantSlot { display: flex; flex-direction: column; gap: var(--sp-4); }
/* Nur eine Trennlinie, wenn darunter wirklich etwas steht. */
.brilliantSlot:has(#brilliantList:not(:empty)),
.brilliantSlot:has(.bar:not(.hidden)) {
  border-top: 1px solid var(--panel-border);
  padding-top: var(--sp-5);
}


/* ---------- Das Analysefach, ganz unten rechts ----------
   Erst die zwei Schalter, dann was sie hervorbringen: die drei Engine-Zuege
   und der Fund der Brillanzsuche. Beides zeigt dasselbe wie die Pfeile auf dem
   Brett, einmal gezeichnet und einmal lesbar. */



/* Die Ergebnisansicht liegt in einem eigenen Abschnitt zwischen Hauptbereich
   und Raster. Ohne diese Zeilen war er ein gewoehnlicher Block: Das Raster
   darin bekam seine Hoehe vom Inhalt statt vom Bildschirm, wuchs auf 3438 px
   und wurde vom Zuschnitt der Seite abgeschnitten. Gemessen, nicht vermutet. */
#reviewPanel {
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
#reviewPanel.hidden { display: none; }

@media (max-width: 900px) {
  /* Schmal scrollt die Seite als Ganzes – dort darf nichts festgenagelt sein. */
  #reviewPanel { display: block; flex: none; }
  #reviewPanel.hidden { display: none; }
}


/* ---------- Die Engine-Spalte, links neben dem Brett ----------

   Sie darf dem Brett keine Breite nehmen. Gemessen tat sie das zuerst: Das
   Brett fiel von 690 auf 650 px, sobald der Schalter anging – und ein Brett,
   das beim Einschalten einer Nebenansicht springt, ist ein Defekt, kein
   Layout. Die Spalte holt ihren Platz deshalb vom Seitenrand: Das Raster darf
   von 1500 auf 1780 px wachsen, sobald sie da ist.

   Ab 1600 px Fensterbreite geht das auf. Gerechnet bei 1920: 1682 nutzbar,
   davon 264 Engine, 480 Antwortspalte, 32 Abstand – 906 fuer die Brettspalte,
   also 832 moeglich. Das Brett bleibt bei seinen 690, weil die Fensterhoehe
   die engere Grenze ist. Darunter geht es nicht auf: Bei 1440 blieben der
   Brettspalte 426 px, das Brett muesste auf 352 schrumpfen. Deshalb rueckt die
   Engine dort unter das Brett, statt daneben zu stehen.

   Die Spalte gibt es nur, wenn der Schalter an ist: Ein leeres Fach neben dem
   Brett waere ein dauerhaftes Loch fuer eine Funktion, die meistens aus ist. */
.boardCol { grid-area: board; }
.sideCol { grid-area: side; }
#linesCard { grid-area: lines; }
.reviewGrid { grid-template-areas: "board side"; }

/* Unter 1600: Die Engine steht unter dem Brett, in derselben Spalte. */
/* Unter 1600 ist neben dem Brett kein Platz. Dann steht die Engine OBEN in der
   rechten Spalte – nicht unter dem Brett, denn dort naehme sie ihm Hoehe, und
   genau das soll sie nirgends. Das Brett behaelt Breite und Hoehe. */
.reviewGrid:has(> #linesCard:not(.hidden)) {
  grid-template-areas: "board lines" "board side";
  grid-template-rows: auto minmax(0, 1fr);
}

@media (min-width: 1600px) {
  .reviewGrid:has(> #linesCard:not(.hidden)) {
    grid-template-areas: "lines board side";
    grid-template-columns: 264px minmax(0, 1fr) clamp(380px, 32%, 540px);
    grid-template-rows: minmax(0, 1fr);
    max-width: 1780px;
  }
  /* Hier nimmt sie dem Brett nichts – die Grenze ist wieder die Fensterhoehe. */
  .reviewGrid:has(> #linesCard:not(.hidden)) .boardCol {
  }
}

#linesCard.hidden { display: none; }
#linesCard {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  min-height: 0;
  overflow-y: auto;
}
.linesHint { margin-top: auto; }

@media (max-width: 900px) {
  /* Eine Spalte, und die Engine direkt unter das Brett: Sie redet ueber die
     Stellung, die darueber steht. */
  .reviewGrid,
  .reviewGrid:has(> #linesCard:not(.hidden)) {
    grid-template-areas: "board" "lines" "side";
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto;
  }
  /* Solange gerechnet wird, kommt die rechte Spalte nach oben – sie enthält
     dann nur die Fortschrittskarte, alles andere ist versteckt.
     `order` reicht dafür nicht: Die Kinder sitzen in benannten Rasterfeldern,
     und wer per Feld platziert wird, lässt sich per Reihenfolge nicht
     verschieben. Also werden die Felder getauscht. */
  /* Solange gerechnet wird, kommt die rechte Spalte nach oben – sie enthaelt
     dann nur die Fortschrittskarte. Seit das Raster hier eine Flexspalte ist,
     genuegt dafuer ; vorher mussten die Rasterfelder getauscht werden. */
  #reviewPanel:has(#progressCard:not(.hidden)) .sideCol { order: 0; }
  #linesCard { overflow-y: visible; }
}


/* ---------- Die Zeilen der Engine ----------
   Eine Variante anzusehen und sie zu spielen ist derselbe Wunsch, also ist die
   Zeile der Knopf. Sie sieht trotzdem nicht nach Bedienoberflaeche aus: Rahmen
   und Grund kommen erst bei Maus oder Tastatur. */
.lineRow {
  display: flex;
  align-items: baseline;
  gap: var(--sp-4);
  width: 100%;
  text-align: left;
  padding: var(--sp-3) var(--sp-4);
  background: none;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  color: inherit;
  font: inherit;
  cursor: pointer;
}
button.lineRow:hover:not(:disabled) { border-color: var(--accent); background: var(--indigo-900); }
button.lineRow:disabled { cursor: default; }
.lineRow--note { cursor: default; }
.linePv { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Eine Meldung ist keine Variante: Sie darf umbrechen. Einzeilig gesetzt wurde
   aus "Paused while you play your own line" ein "Paused while you play your
   own l…" – der halbe Satz, und ausgerechnet der erklaerende. */
.lineRow--note .linePv { white-space: normal; overflow: visible; }

/* Die Legende der Pfeile. Drei Bedeutungen, drei Striche – dieselben Farben,
   die board.js auf das Brett zeichnet, aus denselben Tokens. */
.arrowKey { list-style: none; display: flex; flex-direction: column; gap: var(--sp-3); }
.arrowKey li {
  display: flex; align-items: center; gap: var(--sp-3);
  font-size: var(--fs-small); color: var(--text-dim);
}
.key { width: 22px; height: 4px; border-radius: 2px; flex: none; }
.key--played { background: var(--move-own); }
.key--best { background: var(--node-pulsar); }
.key--alt { background: var(--move-neutral); opacity: .6; }

/* ---------- Finger statt Zeiger ----------
   Vier Bedienelemente blieben mobil unter 44 px, und alle vier tauchen erst
   nach der Analyse auf – der statische Durchlauf sah sie deshalb nie. Die
   Sprungzahlen wachsen ueber Polsterung nach aussen statt ueber eine Hoehe,
   damit die Tabelle darunter nicht auseinanderfaellt. */
@media (pointer: coarse) {
  button.lineRow { min-height: 44px; }
  .mvCell { min-height: 44px; }
  .ghostBtn { min-height: 44px; }
  .exploreBar .ghostBtn { padding: 10px 14px; }
  /* Eine einzelne Ziffer traegt die Polsterung nicht in die Breite. */
  #countTable button.jump { padding: 12px 10px; margin: -12px -10px; min-width: 44px; }

  /* Der senkrechte Wisch gehört der Seite, das Ziehen gehört der Figur.
   *
   * Zeile 350 bleibt, wie sie ist: Mit der Maus gibt es keinen Wisch, dort ist
   * `none` richtig. Diese Regel gilt dem Finger. Gemessen am 25. August 2026
   * mit einer echten Geste über 200 px auf dem leeren Feld e4: scrollY blieb
   * 0. Vom Brett aus rollte die Seite überhaupt nicht — auf /review belegt es
   * 281 von 375 px Breite.
   *
   * `pan-y` allein wäre ein Tausch und keine Lösung: Dann fängt der Browser
   * jede Bewegung mit senkrechtem Übergewicht ab, und das sind die meisten
   * Schachzüge. Die Wache in js/review/touch-scroll.js gehört zu dieser Regel
   * und ist nicht optional — sie bestellt das Rollen ab, sobald der Finger auf
   * einer eigenen Figur aufsetzt.
   *
   * `pinch-zoom` kommt dazu, weil das Brett bisher auch das Aufziehen
   * verschluckt hat.
   *
   * #anBoard fehlt hier mit Absicht: Seine Regel steht in css/board.css, die
   * nach dieser Datei geladen wird — eine gleich spezifische Zeile hier
   * verlöre. Siehe Bericht. */
  #revBoard { touch-action: pan-y pinch-zoom; }
}
